!268 Merge branch 'dev' of <a href="https://gitee.com/yudaocode/yudao-ui-admin-vben">https://gitee.com/yudaocode/yudao-ui-admin-vben</a> …
Merge pull request !268 from hw/reform-mp
This commit is contained in:
@@ -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: '关键词',
|
||||
|
||||
@@ -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" /> 关键词回复
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as WxAccountSelect } from './wx-account-select.vue';
|
||||
|
||||
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './types';
|
||||
export { default as WxLocation } from './wx-location.vue';
|
||||
|
||||
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||
@@ -1,4 +1,3 @@
|
||||
// TODO @hw:ele 没这个文件,是不是也要搞个?
|
||||
export interface WxLocationProps {
|
||||
label: string;
|
||||
locationX: number;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Col, Row } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'WxLocation' });
|
||||
|
||||
// TODO @dylan:@hw:apps/web-antd/src/views/mall/trade/delivery/pickUpStore/modules/form.vue 参考这个,从后端拿 key 哈
|
||||
// TODO @dylan:apps/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
|
||||
});
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as WxMaterialSelect } from './wx-material-select.vue';
|
||||
|
||||
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||
@@ -43,7 +43,7 @@ const queryParams = reactive({
|
||||
}); // 查询参数
|
||||
|
||||
const voiceGridColumns: VxeTableGridOptions<any>['columns'] = [
|
||||
// TODO @hw:@dylan:any 有 linter 告警;看看别的模块哈
|
||||
// TODO @dylan:any 有 linter 告警;看看别的模块哈
|
||||
{
|
||||
field: 'mediaId',
|
||||
title: '编号',
|
||||
@@ -78,7 +78,7 @@ const voiceGridColumns: VxeTableGridOptions<any>['columns'] = [
|
||||
];
|
||||
|
||||
const videoGridColumns: VxeTableGridOptions<any>['columns'] = [
|
||||
// TODO @hw:@dylan:any 有 linter 告警;看看别的模块哈
|
||||
// TODO @dylan:any 有 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;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export * from './types';
|
||||
|
||||
export { default as WxMsg } from './wx-msg.vue';
|
||||
|
||||
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||
@@ -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');
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { MpMsgType } from '@vben/constants';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
// TODO @hw:貌似这个 antd 才有?ele 需要有么?
|
||||
import {
|
||||
WxLocation,
|
||||
WxMusic,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// TODO @hw:用 MpUserApi 里的 user 可以么?
|
||||
|
||||
export interface User {
|
||||
accountId: number;
|
||||
avatar: string;
|
||||
nickname: string;
|
||||
}
|
||||
@@ -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: '用户', // 由于微信不再提供昵称,直接使用"用户"展示
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as WxMusic } from './wx-music.vue';
|
||||
|
||||
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||
@@ -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;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as WxNews } from './wx-news.vue';
|
||||
|
||||
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||
@@ -52,7 +52,7 @@ defineExpose({
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||
/** TODO @dylan:看看有没适合 tindwind 的哈。 */
|
||||
|
||||
.news-home {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export * from './types';
|
||||
|
||||
export { default as WxReply } from './wx-reply.vue';
|
||||
|
||||
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||
@@ -65,21 +65,20 @@ async function customRequest(options: any) {
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// TODO @hw:if 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -93,7 +93,7 @@ function onDelete() {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||
/** TODO @dylan:看看有没适合 tindwind 的哈。 */
|
||||
.select-item {
|
||||
width: 280px;
|
||||
padding: 10px;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -66,21 +66,21 @@ async function customRequest(options: any) {
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// TODO @hw:if 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;
|
||||
|
||||
@@ -26,7 +26,6 @@ const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: Reply): void;
|
||||
}>();
|
||||
|
||||
// TODO @hw:antd 和 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 @hw:antd 和 ele 风格不同,需要统一;
|
||||
// 监听 modelValue 变化,同步更新 currentTab 和缓存
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
@@ -64,7 +62,6 @@ watch(
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
// TODO @hw:antd 和 ele 风格不同,需要统一;
|
||||
watch(
|
||||
currentTab,
|
||||
(newTab, oldTab) => {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as WxVideoPlayer } from './wx-video-play.vue';
|
||||
|
||||
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as WxVoicePlayer } from './wx-voice-play.vue';
|
||||
|
||||
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||
@@ -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: '操作',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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 @hw:1)每个文章的选中框太粗了;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"
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Button } from 'ant-design-vue';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { WxVideoPlayer } from '#/views/mp/components';
|
||||
|
||||
// TODO @dylan:@hw:vue 组件名小写 + 中划线
|
||||
// TODO @dylan:vue 组件名小写 + 中划线
|
||||
|
||||
const props = defineProps<{
|
||||
list: any[];
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -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"
|
||||
|
||||
@@ -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; // 重建组件
|
||||
});
|
||||
});
|
||||
|
||||
// ======================== 菜单编辑(素材选择) ========================
|
||||
|
||||
/** 选择素材 */
|
||||
|
||||
@@ -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 里,直接用就完事啦!
|
||||
@@ -193,8 +193,6 @@ function onChildDragEnd({ newIndex }: { newIndex: number }) {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/** todo @hw:antd 和 ele 这里的写法,看看能不能统一; */
|
||||
|
||||
.draggable-ghost {
|
||||
background: #f7fafc;
|
||||
border: 1px solid #4299e1;
|
||||
|
||||
@@ -189,7 +189,7 @@ function showTotal(total: number) {
|
||||
:footer="null"
|
||||
destroy-on-close
|
||||
>
|
||||
<!-- TODO @hw,@dlayn:这里有告警; -->
|
||||
<!-- TODO @dlayn:这里有告警; -->
|
||||
<WxMsg :user-id="messageBoxUserId" />
|
||||
</Modal>
|
||||
</Page>
|
||||
|
||||
Reference in New Issue
Block a user