Merge pull request !268 from hw/reform-mp
This commit is contained in:
芋道源码
2025-11-23 10:53:33 +00:00
committed by Gitee
86 changed files with 963 additions and 1195 deletions

View File

@@ -3,27 +3,22 @@ import type { VxeGridPropTypes } from '#/adapter/vxe-table';
import { markRaw } from 'vue';
import { DICT_TYPE, AutoReplyMsgType as MsgType } from '@vben/constants';
import {
AutoReplyMsgType,
DICT_TYPE,
RequestMessageTypes,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { WxReply } from '#/views/mp/components';
// TODO @hw要不要使用统一枚举
const RequestMessageTypes = new Set([
'image',
'link',
'location',
'shortvideo',
'text',
'video',
'voice',
]); // 允许选择的请求消息类型
/** 获取表格列配置 */
export function useGridColumns(msgType: MsgType): VxeGridPropTypes.Columns {
export function useGridColumns(
msgType: AutoReplyMsgType,
): VxeGridPropTypes.Columns {
const columns: VxeGridPropTypes.Columns = [];
// 请求消息类型列(仅消息回复显示)
if (msgType === MsgType.Message) {
if (msgType === AutoReplyMsgType.Message) {
columns.push({
field: 'requestMessageType',
title: '请求消息类型',
@@ -32,7 +27,7 @@ export function useGridColumns(msgType: MsgType): VxeGridPropTypes.Columns {
}
// 关键词列(仅关键词回复显示)
if (msgType === MsgType.Keyword) {
if (msgType === AutoReplyMsgType.Keyword) {
columns.push({
field: 'requestKeyword',
title: '关键词',
@@ -41,7 +36,7 @@ export function useGridColumns(msgType: MsgType): VxeGridPropTypes.Columns {
}
// 匹配类型列(仅关键词回复显示)
if (msgType === MsgType.Keyword) {
if (msgType === AutoReplyMsgType.Keyword) {
columns.push({
field: 'requestMatch',
title: '匹配类型',
@@ -87,11 +82,11 @@ export function useGridColumns(msgType: MsgType): VxeGridPropTypes.Columns {
}
/** 新增/修改的表单 */
export function useFormSchema(msgType: MsgType): VbenFormSchema[] {
export function useFormSchema(msgType: AutoReplyMsgType): VbenFormSchema[] {
const schema: VbenFormSchema[] = [];
// 消息类型(仅消息回复显示)
if (msgType === MsgType.Message) {
if (msgType === AutoReplyMsgType.Message) {
schema.push({
fieldName: 'requestMessageType',
label: '消息类型',
@@ -106,7 +101,7 @@ export function useFormSchema(msgType: MsgType): VbenFormSchema[] {
}
// 匹配类型(仅关键词回复显示)
if (msgType === MsgType.Keyword) {
if (msgType === AutoReplyMsgType.Keyword) {
schema.push({
fieldName: 'requestMatch',
label: '匹配类型',
@@ -124,7 +119,7 @@ export function useFormSchema(msgType: MsgType): VbenFormSchema[] {
}
// 关键词(仅关键词回复显示)
if (msgType === MsgType.Keyword) {
if (msgType === AutoReplyMsgType.Keyword) {
schema.push({
fieldName: 'requestKeyword',
label: '关键词',

View File

@@ -5,8 +5,7 @@ import type { MpAutoReplyApi } from '#/api/mp/autoReply';
import { computed, nextTick, ref } from 'vue';
import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui';
// TODO @hw直接使用 AutoReplyMsgType不用 as
import { AutoReplyMsgType as MsgType } from '@vben/constants';
import { AutoReplyMsgType } from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
import { message, Row, Tabs } from 'ant-design-vue';
@@ -26,10 +25,10 @@ import Form from './modules/form.vue';
defineOptions({ name: 'MpAutoReply' });
const msgType = ref<string>(String(MsgType.Keyword)); // 消息类型
const msgType = ref<string>(String(AutoReplyMsgType.Keyword)); // 消息类型
const showCreateButton = computed(() => {
if (Number(msgType.value) !== MsgType.Follow) {
if (Number(msgType.value) !== AutoReplyMsgType.Follow) {
return true;
}
try {
@@ -56,7 +55,7 @@ async function onTabChange(tabName: string) {
msgType.value = tabName;
await nextTick();
// 更新 columns
const columns = useGridColumns(Number(msgType.value) as MsgType);
const columns = useGridColumns(Number(msgType.value) as AutoReplyMsgType);
if (columns) {
// 使用 setGridOptions 更新列配置
gridApi.setGridOptions({ columns });
@@ -72,7 +71,7 @@ async function handleCreate() {
const formValues = await gridApi.formApi.getValues();
formModalApi
.setData({
msgType: Number(msgType.value) as MsgType,
msgType: Number(msgType.value) as AutoReplyMsgType,
accountId: formValues.accountId,
})
.open();
@@ -83,7 +82,7 @@ async function handleEdit(row: any) {
const data = (await getAutoReply(row.id)) as any;
formModalApi
.setData({
msgType: Number(msgType.value) as MsgType,
msgType: Number(msgType.value) as AutoReplyMsgType,
accountId: row.accountId,
row: data,
})
@@ -116,7 +115,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(Number(msgType.value) as MsgType),
columns: useGridColumns(Number(msgType.value) as AutoReplyMsgType),
height: 'auto',
keepSource: true,
proxyConfig: {
@@ -125,7 +124,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
return await getAutoReplyPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
type: Number(msgType.value) as MsgType,
type: Number(msgType.value) as AutoReplyMsgType,
...formValues,
});
},
@@ -161,14 +160,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
class="w-full"
@change="(activeKey) => onTabChange(activeKey as string)"
>
<Tabs.TabPane :key="String(MsgType.Follow)">
<Tabs.TabPane :key="String(AutoReplyMsgType.Follow)">
<template #tab>
<Row align="middle">
<IconifyIcon icon="ep:star" class="mr-2px" /> 关注时回复
</Row>
</template>
</Tabs.TabPane>
<Tabs.TabPane :key="String(MsgType.Message)">
<Tabs.TabPane :key="String(AutoReplyMsgType.Message)">
<template #tab>
<Row align="middle">
<IconifyIcon icon="ep:chat-line-round" class="mr-2px" />
@@ -176,7 +175,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
</Row>
</template>
</Tabs.TabPane>
<Tabs.TabPane :key="String(MsgType.Keyword)">
<Tabs.TabPane :key="String(AutoReplyMsgType.Keyword)">
<template #tab>
<Row align="middle">
<IconifyIcon icon="fa:newspaper-o" class="mr-2px" /> 关键词回复

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup>
// TODO @hw这里 Reply 貌似不存在
import type { Reply } from '#/views/mp/components';
import type { Reply } from '#/views/mp/components/wx-reply/types';
import { computed, nextTick, ref } from 'vue';

View File

@@ -1,3 +0,0 @@
export { default as WxAccountSelect } from './wx-account-select.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -1,4 +0,0 @@
export * from './types';
export { default as WxLocation } from './wx-location.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -1,4 +1,3 @@
// TODO @hwele 没这个文件,是不是也要搞个?
export interface WxLocationProps {
label: string;
locationX: number;

View File

@@ -9,7 +9,7 @@ import { Col, Row } from 'ant-design-vue';
defineOptions({ name: 'WxLocation' });
// TODO @dylan@hwapps/web-antd/src/views/mall/trade/delivery/pickUpStore/modules/form.vue 参考这个,从后端拿 key 哈
// TODO @dylanapps/web-antd/src/views/mall/trade/delivery/pickUpStore/modules/form.vue 参考这个,从后端拿 key 哈
const props = withDefaults(defineProps<WxLocationProps>(), {
qqMapKey: 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E', // QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
});

View File

@@ -1,3 +0,0 @@
export { default as WxMaterialSelect } from './wx-material-select.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -43,7 +43,7 @@ const queryParams = reactive({
}); // 查询参数
const voiceGridColumns: VxeTableGridOptions<any>['columns'] = [
// TODO @hw@dylanany 有 linter 告警;看看别的模块哈
// TODO @dylanany 有 linter 告警;看看别的模块哈
{
field: 'mediaId',
title: '编号',
@@ -78,7 +78,7 @@ const voiceGridColumns: VxeTableGridOptions<any>['columns'] = [
];
const videoGridColumns: VxeTableGridOptions<any>['columns'] = [
// TODO @hw@dylanany 有 linter 告警;看看别的模块哈
// TODO @dylanany 有 linter 告警;看看别的模块哈
{
field: 'mediaId',
title: '编号',
@@ -382,7 +382,7 @@ watch(
</template>
<style lang="scss" scoped>
/** TODO @dylan@hw看看有没适合 tindwind 的哈。 */
/** TODO @dylan看看有没适合 tindwind 的哈。 */
@media (width >= 992px) and (width <= 1300px) {
.waterfall {
column-count: 3;

View File

@@ -1,5 +0,0 @@
export * from './types';
export { default as WxMsg } from './wx-msg.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { User } from './types';
import type { MpUserApi } from '#/api/mp/user/index';
import { preferences } from '@vben/preferences';
import { formatDateTime } from '@vben/utils';
@@ -11,7 +11,7 @@ defineOptions({ name: 'MsgList' });
const props = defineProps<{
accountId: number;
list: any[];
user: User;
user: Partial<MpUserApi.User>;
}>();
const SendFrom = {
@@ -63,7 +63,7 @@ function getNickname(sendFrom: number) {
<style lang="scss" scoped>
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
/** TODO @dylan@hw 看看有没适合 tindwind 的哈。 */
/** TODO @dylan 看看有没适合 tindwind 的哈。 */
@import url('./comment.scss');
@import url('./card.scss');

View File

@@ -2,7 +2,6 @@
import { MpMsgType } from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
// TODO @hw貌似这个 antd 才有ele 需要有么?
import {
WxLocation,
WxMusic,

View File

@@ -1,7 +0,0 @@
// TODO @hw用 MpUserApi 里的 user 可以么?
export interface User {
accountId: number;
avatar: string;
nickname: string;
}

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { User } from './types';
import type { MpUserApi } from '#/api/mp/user/index';
import { nextTick, onMounted, reactive, ref, unref } from 'vue';
@@ -29,7 +29,7 @@ const queryParams = reactive({
pageSize: 14, // 每页显示多少条
});
const user: User = reactive({
const user: Partial<MpUserApi.User> = reactive({
accountId, // 公众号账号编号
avatar: preferences.app.defaultAvatar,
nickname: '用户', // 由于微信不再提供昵称,直接使用"用户"展示

View File

@@ -1,3 +0,0 @@
export { default as WxMusic } from './wx-music.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -3,6 +3,8 @@ import type { WxMusicProps } from './types';
import { computed } from 'vue';
import { Typography } from 'ant-design-vue';
/** 微信消息 - 音乐 */
defineOptions({ name: 'WxMusic' });
@@ -14,6 +16,8 @@ const props = withDefaults(defineProps<WxMusicProps>(), {
thumbMediaUrl: '',
});
const { Link } = Typography;
const href = computed(() => props.hqMusicUrl || props.musicUrl);
defineExpose({
@@ -23,8 +27,7 @@ defineExpose({
<template>
<div>
<!-- TODO @hw是不是用 antd link 更好 -->
<a :href="href" target="_blank" class="text-success no-underline">
<Link :href="href" target="_blank" class="text-success no-underline">
<div class="music-card">
<div class="music-avatar">
<img :src="thumbMediaUrl" alt="音乐封面" />
@@ -34,12 +37,12 @@ defineExpose({
<div class="music-description">{{ description }}</div>
</div>
</div>
</a>
</Link>
</div>
</template>
<style lang="scss" scoped>
/** TODO @dylan@hw看看有没适合 tindwind 的哈。 */
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.music-card {
display: flex;

View File

@@ -1,3 +0,0 @@
export { default as WxNews } from './wx-news.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -52,7 +52,7 @@ defineExpose({
</template>
<style lang="scss" scoped>
/** TODO @dylan@hw看看有没适合 tindwind 的哈。 */
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.news-home {
width: 100%;

View File

@@ -1,5 +0,0 @@
export * from './types';
export { default as WxReply } from './wx-reply.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -65,21 +65,20 @@ async function customRequest(options: any) {
const result = await response.json();
// TODO @hwif return 风格简化掉。if (result.code !=== 0) { ... }
if (result.code === 0) {
// 清空上传时的各种数据
fileList.value = [];
uploadData.title = '';
uploadData.introduction = '';
// 上传好的文件,本质是个素材,所以可以进行选中
selectMaterial(result.data);
message.success('上传成功');
onSuccess(result, file);
} else {
if (result.code !== 0) {
message.error(result.msg || '上传出错');
onError(new Error(result.msg || '上传失败'));
return;
}
// 清空上传时的各种数据
fileList.value = [];
uploadData.title = '';
uploadData.introduction = '';
// 上传好的文件,本质是个素材,所以可以进行选中
selectMaterial(result.data);
message.success('上传成功');
onSuccess(result, file);
} catch (error) {
message.error('上传失败,请重试');
onError(error);

View File

@@ -21,8 +21,6 @@ import {
import { WxMaterialSelect } from '#/views/mp/components';
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
// TODO @hw类似 tab-image.vue 的建议
defineOptions({ name: 'TabMusic' });
const props = defineProps<{
@@ -76,20 +74,20 @@ async function customRequest(options: any) {
const result = await response.json();
if (result.code === 0) {
// 清空上传时的各种数据
fileList.value = [];
uploadData.title = '';
uploadData.introduction = '';
// 上传好的文件,本质是个素材,所以可以进行选中
selectMaterial(result.data);
message.success('上传成功');
onSuccess(result, file);
} else {
if (result.code !== 0) {
message.error(result.msg || '上传出错');
onError(new Error(result.msg || '上传失败'));
return;
}
// 清空上传时的各种数据
fileList.value = [];
uploadData.title = '';
uploadData.introduction = '';
// 上传好的文件,本质是个素材,所以可以进行选中
selectMaterial(result.data);
message.success('上传成功');
onSuccess(result, file);
} catch (error) {
message.error('上传失败,请重试');
onError(error);

View File

@@ -93,7 +93,7 @@ function onDelete() {
</template>
<style lang="scss" scoped>
/** TODO @dylan@hw看看有没适合 tindwind 的哈。 */
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.select-item {
width: 280px;
padding: 10px;

View File

@@ -74,21 +74,20 @@ async function customRequest(options: any) {
const result = await response.json();
// TODO @hw也采用类似 ele 的 if return(res.code !== 0) return 写法;
if (result.code === 0) {
// 清空上传时的各种数据
fileList.value = [];
uploadData.title = '';
uploadData.introduction = '';
// 选择素材
selectMaterial(result.data);
message.success('上传成功');
onSuccess(result, file);
} else {
if (result.code !== 0) {
message.error(result.msg || '上传出错');
onError(new Error(result.msg || '上传失败'));
return;
}
// 清空上传时的各种数据
fileList.value = [];
uploadData.title = '';
uploadData.introduction = '';
// 选择素材
selectMaterial(result.data);
message.success('上传成功');
onSuccess(result, file);
} catch (error) {
message.error('上传失败,请重试');
onError(error);

View File

@@ -66,21 +66,21 @@ async function customRequest(options: any) {
const result = await response.json();
// TODO @hwif result.code !== 0 return代码简洁一点。
if (result.code === 0) {
// 清空上传时的各种数据
fileList.value = [];
uploadData.title = '';
uploadData.introduction = '';
// 上传好的文件,本质是个素材,所以可以进行选中
selectMaterial(result.data);
message.success('上传成功');
onSuccess(result, file);
} else {
if (result.code !== 0) {
message.error(result.msg || '上传出错');
onError(new Error(result.msg || '上传失败'));
return;
}
// 清空上传时的各种数据
fileList.value = [];
uploadData.title = '';
uploadData.introduction = '';
// 上传好的文件,本质是个素材,所以可以进行选中
selectMaterial(result.data);
message.success('上传成功');
onSuccess(result, file);
} catch (error) {
message.error('上传失败,请重试');
onError(error);
@@ -169,7 +169,7 @@ function selectMaterial(item: Reply) {
</template>
<style lang="scss" scoped>
/** TODO @dylan@hw看看有没适合 tindwind 的哈。 */
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.select-item {
padding: 10px;
margin: 0 auto 10px;

View File

@@ -26,7 +26,6 @@ const emit = defineEmits<{
(e: 'update:modelValue', v: Reply): void;
}>();
// TODO @hwantd 和 ele 风格不同,需要统一;
interface Props {
modelValue: Reply | undefined;
newsType?: NewsType;
@@ -45,7 +44,6 @@ const reply = computed<Reply>({
const tabCache = new Map<ReplyType, Reply>(); // 作为多个标签保存各自 Reply 的缓存
const currentTab = ref<ReplyType>(props.modelValue?.type || ReplyType.Text); // 采用独立的 ref 来保存当前 tab避免在 watch 标签变化,对 reply 进行赋值会产生了循环调用
// TODO @hwantd 和 ele 风格不同,需要统一;
// 监听 modelValue 变化,同步更新 currentTab 和缓存
watch(
() => props.modelValue,
@@ -64,7 +62,6 @@ watch(
{ immediate: true, deep: true },
);
// TODO @hwantd 和 ele 风格不同,需要统一;
watch(
currentTab,
(newTab, oldTab) => {

View File

@@ -1,3 +0,0 @@
export { default as WxVideoPlayer } from './wx-video-play.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -1,3 +0,0 @@
export { default as WxVoicePlayer } from './wx-voice-play.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -1,14 +1,28 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { formatDateTime } from '@vben/utils';
/** 获取表格列配置 */
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'content',
title: '图文内容',
minWidth: 300,
slots: { default: 'content' },
field: 'cover',
title: '图',
width: 360,
slots: { default: 'cover' },
},
{
field: 'title',
title: '标题',
slots: { default: 'title' },
},
{
field: 'updateTime',
title: '修改时间',
formatter: ({ row }) => {
return formatDateTime(row.updateTime * 1000);
},
},
{
title: '操作',

View File

@@ -9,11 +9,11 @@ import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { createEmptyNewsItem, deleteDraft, getDraftPage } from '#/api/mp/draft';
// import { getDraftPage } from '#/api/mp/draft'; // 调试时注释掉
import { submitFreePublish } from '#/api/mp/freePublish';
import { WxAccountSelect } from '#/views/mp/components';
import { useGridColumns, useGridFormSchema } from './data';
import DraftTableCell from './modules/draft-table.vue';
import Form from './modules/form.vue';
defineOptions({ name: 'MpDraft' });
@@ -128,6 +128,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
// 调试用:跳过请求,直接返回模拟数据
const drafts = await getDraftPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
@@ -142,9 +143,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
}
});
return {
list: drafts.list as unknown as MpDraftApi.DraftArticle[],
total: drafts.total,
list: drafts.list,
total: drafts.total, // 模拟总数
};
},
},
@@ -187,10 +189,40 @@ const [Grid, gridApi] = useVbenVxeGrid({
]"
/>
</template>
<!-- TODO @hw按照微信群沟通的换下卡片的样式 -->
<template #content="{ row }">
<DraftTableCell :row="row" />
<!-- TODO @hw增加一列更新时间 -->
<template #cover="{ row }">
<div
v-if="row.content?.newsItem && row.content.newsItem.length > 0"
class="flex flex-col items-center justify-center gap-1"
>
<a
v-for="(item, index) in row.content.newsItem"
:key="index"
:href="(item as any).url"
target="_blank"
>
<img
:src="item.picUrl || item.thumbUrl"
class="h-36 w-[50px] rounded object-cover"
:alt="`文章${index + 1}封面图`"
/>
</a>
</div>
<span v-else class="text-gray-400">-</span>
</template>
<template #title="{ row }">
<div
v-if="row.content?.newsItem && row.content.newsItem.length > 0"
class="space-y-1"
>
<div
v-for="(item, index) in row.content.newsItem"
:key="index"
class="flex h-36 items-center justify-center"
>
{{ item.title }}
</div>
</div>
<span v-else class="text-gray-400">-</span>
</template>
<template #actions="{ row }">
<TableAction

View File

@@ -11,6 +11,7 @@ import { useAccessStore } from '@vben/stores';
import { Button, Image, message, Modal, Upload } from 'ant-design-vue';
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
import WxMaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
const props = defineProps<{
isFirst: boolean;
@@ -133,14 +134,13 @@ function onUploadError(err: Error) {
支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M
</div>
</div>
<!-- TODO @hw这个貌似不行ele 我试了下可以的 -->
<Modal
v-model:open="dialogVisible"
title="图片选择"
width="65%"
:footer="null"
>
<MaterialSelect
<WxMaterialSelect
type="image"
:account-id="accountId!"
@select-material="onMaterialSelected"

View File

@@ -1,20 +0,0 @@
<script lang="ts" setup>
import type { MpDraftApi } from '#/api/mp/draft';
import { WxNews } from '#/views/mp/components';
// TODO @hw按照微信里说的感觉这个可以干掉。少点组件哈。= = mp 模块,小组件可太多了。。。
defineOptions({ name: 'DraftTableCell' });
const props = defineProps<{
row: MpDraftApi.DraftArticle;
}>();
</script>
<template>
<div class="p-2.5">
<div v-if="props.row.content && props.row.content.newsItem">
<WxNews :articles="props.row.content.newsItem" />
</div>
</div>
</template>

View File

@@ -88,23 +88,25 @@ function plusNews() {
<div class="mx-auto mb-[10px] w-[60%] border border-[#eaeaea] p-[10px]">
<div v-for="(news, index) in newsList" :key="index">
<div
class="group relative mx-auto h-[120px] w-full cursor-pointer bg-white"
class="group relative mx-auto mb-[10px] w-full cursor-pointer border-[2px] bg-white"
v-if="index === 0"
:class="{
'border-[5px] border-[#2bb673]': activeNewsIndex === index,
}"
:class="
activeNewsIndex === index
? 'border-green-500'
: 'border-transparent'
"
@click="activeNewsIndex = index"
>
<div class="relative h-[120px] w-full bg-[#acadae]">
<div class="relative w-full bg-[#acadae]">
<img class="h-full w-full" :src="news.thumbUrl" />
<div
class="absolute bottom-0 left-0 inline-block h-[25px] w-[98%] overflow-hidden text-ellipsis whitespace-nowrap bg-black p-[1%] text-[15px] text-white opacity-65"
class="absolute bottom-0 left-0 inline-block h-[25px] w-[100%] overflow-hidden text-ellipsis whitespace-nowrap bg-black p-[1%] text-center text-[15px] text-white opacity-65"
>
{{ news.title }}
</div>
</div>
<div
class="relative -bottom-[25px] hidden text-center group-hover:block"
class="relative flex justify-center gap-[10px] py-[5px] text-center"
v-if="newsList.length > 1"
>
<Button
@@ -127,25 +129,28 @@ function plusNews() {
</Button>
</div>
</div>
<!-- TODO @hw1每个文章的选中框太粗了2没完全覆盖住文章最好首个文章和第个文章的情况都看看 -->
<div
class="group relative mx-auto w-full cursor-pointer border-t border-[#eaeaea] bg-white py-[5px]"
class="group relative mx-auto mb-[10px] cursor-pointer border-[2px] bg-white"
v-if="index > 0"
:class="{
'border-[5px] border-[#2bb673]': activeNewsIndex === index,
}"
:class="
activeNewsIndex === index
? 'border-green-500'
: 'border-transparent'
"
@click="activeNewsIndex = index"
>
<div class="relative -ml-[3px]">
<div class="inline-block w-[70%] text-xs">{{ news.title }}</div>
<div class="inline-block w-[25%] bg-[#acadae]">
<img class="h-full w-full" :src="news.thumbUrl" />
<div class="relative">
<div class="bg-[#acadae]">
<img class="block h-full w-full" :src="news.thumbUrl" />
<div
class="absolute bottom-0 left-0 inline-block h-[25px] w-[100%] overflow-hidden text-ellipsis whitespace-nowrap bg-black p-[1%] text-center text-[15px] text-white opacity-65"
>
{{ news.title }}
</div>
</div>
</div>
<!-- TODO @hw这里的按钮交互不太对应该在每个卡片的里面或者类似公众号现在的交互放到右侧复现本周如果有 2 个文章的时候 -->
<!-- TODO @hw当有 2 个文章的时候挪到第二个文章的时候卡片会变大期望不变大 -->
<div
class="relative -bottom-[25px] hidden text-center group-hover:block"
class="relative flex justify-center gap-[10px] py-[5px] text-center"
>
<Button
v-if="newsList.length > index + 1"

View File

@@ -12,7 +12,7 @@ import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { WxVideoPlayer } from '#/views/mp/components';
// TODO @dylan@hwvue 组件名小写 + 中划线
// TODO @dylanvue 组件名小写 + 中划线
const props = defineProps<{
list: any[];

View File

@@ -5,7 +5,7 @@ import { useAccess } from '@vben/access';
import { DocAlert, Page } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
// TODO @dlyan、@hw可以先 antd 迁移完,在搞 ele避免搞两遍
// TODO @dlyan、可以先 antd 迁移完,在搞 ele避免搞两遍
import {
Button,
Card,
@@ -117,8 +117,8 @@ async function handleDelete(id: number) {
<Card :bordered="false" class="mt-4 h-[88%]">
<Tabs v-model:active-key="type" @change="onTabChange">
<!-- tab 1图片 -->
<!-- TODO @hw要不这里也改成 grid 视图然后操作按钮都改成右上角 -->
<!-- TODO @hw图片展示时就编号文件名图片上传时间操作 -->
<!-- TODO @dylan要不这里也改成 grid 视图然后操作按钮都改成右上角 -->
<!-- TODO @dylan图片展示时就编号文件名图片上传时间操作 -->
<Tabs.TabPane :key="UploadType.Image">
<template #tab>
<span class="flex items-center">

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -16,11 +16,12 @@ import {
MENU_NOT_SELECTED,
useGridFormSchema,
} from '#/views/mp/menu/data';
import { MenuEditor, MenuPreviewer } from '#/views/mp/menu/modules';
import Editor from '#/views/mp/menu/modules/editor.vue';
import Previewer from '#/views/mp/menu/modules/previewer.vue';
import iphoneBackImg from './modules/assets/iphone_backImg.png';
import menuFootImg from './modules/assets/menu_foot.png';
import menuHeadImg from './modules/assets/menu_head.png';
import iphoneBackImg from './assets/iphone_backImg.png';
import menuFootImg from './assets/menu_foot.png';
import menuHeadImg from './assets/menu_head.png';
defineOptions({ name: 'MpMenu' });
@@ -64,8 +65,8 @@ const parentIndex = ref(-1);
// ======================== 菜单编辑 ========================
const showRightPanel = ref(false); // 右边配置显示默认详情还是配置详情
const isParent = ref<boolean>(true); // 是否一级菜单,控制MenuEditor中name字段长度
const activeMenu = ref<Menu>({}); // 选中菜单,MenuEditor的modelValue
const isParent = ref<boolean>(true); // 是否一级菜单控制Editor中name字段长度
const activeMenu = ref<Menu>({}); // 选中菜单Editor的modelValue
// 一些临时值放在这里进行判断,如果放在 activeMenu由于引用关系menu 也会多了多余的参数
const tempSelfObj = ref<{
@@ -327,7 +328,7 @@ function menuToBackend(menu: any) {
class="bg-[position:0_0] bg-no-repeat pl-[43px] text-xs after:clear-both after:table after:content-['']"
:style="{ backgroundImage: `url(${menuFootImg})` }"
>
<MenuPreviewer
<Previewer
v-model="menuList"
:account-id="accountId"
:active-index="activeIndex"
@@ -354,7 +355,7 @@ function menuToBackend(menu: any) {
class="float-left ml-5 box-border w-[63%] bg-[#e8e7e7] p-5"
v-if="showRightPanel"
>
<MenuEditor
<Editor
:account-id="accountId"
:is-parent="isParent"
v-model="activeMenu"

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, nextTick, ref, watch } from 'vue';
import { computed, ref } from 'vue';
import { IconifyIcon } from '@vben/icons';
@@ -37,17 +37,8 @@ const menu = computed({
},
});
const showNewsDialog = ref(false);
// TODO @hw这个 reset 还有用么?
const hackResetReplySelect = ref(false);
const isLeave = computed<boolean>(() => !(menu.value.children?.length > 0));
watch(menu, () => {
hackResetReplySelect.value = false; // 销毁组件
nextTick(() => {
hackResetReplySelect.value = true; // 重建组件
});
});
// ======================== 菜单编辑(素材选择) ========================
/** 选择素材 */

View File

@@ -1,5 +0,0 @@
export { default as MenuEditor } from './editor.vue';
export { default as MenuPreviewer } from './previewer.vue';
export type * from './types';
export * from './types';
// TODO @hw这个貌似没用可以考虑删除哈。modules 里,直接用就完事啦!

View File

@@ -193,8 +193,6 @@ function onChildDragEnd({ newIndex }: { newIndex: number }) {
</template>
<style lang="scss" scoped>
/** todo @hwantd 和 ele 这里的写法,看看能不能统一; */
.draggable-ghost {
background: #f7fafc;
border: 1px solid #4299e1;

View File

@@ -189,7 +189,7 @@ function showTotal(total: number) {
:footer="null"
destroy-on-close
>
<!-- TODO @hw@dlayn这里有告警 -->
<!-- TODO @dlayn这里有告警 -->
<WxMsg :user-id="messageBoxUserId" />
</Modal>
</Page>