diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json index 5f315ad38..ef663925f 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -43,6 +43,7 @@ "@vben/styles": "workspace:*", "@vben/types": "workspace:*", "@vben/utils": "workspace:*", + "@videojs-player/vue": "catalog:", "@vueuse/core": "catalog:", "@vueuse/integrations": "catalog:", "ant-design-vue": "catalog:", @@ -59,6 +60,7 @@ "pinia": "catalog:", "steady-xml": "catalog:", "tinymce": "catalog:", + "video.js": "catalog:", "vue": "catalog:", "vue-dompurify-html": "catalog:", "vue-router": "catalog:", diff --git a/apps/web-antd/src/assets/imgs/wechat.png b/apps/web-antd/src/assets/imgs/wechat.png new file mode 100644 index 000000000..6afc5e41c Binary files /dev/null and b/apps/web-antd/src/assets/imgs/wechat.png differ diff --git a/apps/web-antd/src/store/index.ts b/apps/web-antd/src/store/index.ts index 269586ee8..af0c4ab6f 100644 --- a/apps/web-antd/src/store/index.ts +++ b/apps/web-antd/src/store/index.ts @@ -1 +1,12 @@ +import type { App } from 'vue'; + +import { createPinia } from 'pinia'; + export * from './auth'; +const store = createPinia(); + +export const setupStore = (app: App) => { + app.use(store); +}; + +export { store }; diff --git a/apps/web-antd/src/store/tagsView.ts b/apps/web-antd/src/store/tagsView.ts new file mode 100644 index 000000000..396f581e8 --- /dev/null +++ b/apps/web-antd/src/store/tagsView.ts @@ -0,0 +1,202 @@ +import type { RouteLocationNormalizedLoaded } from 'vue-router'; + +import { useUserStore } from '@vben/stores'; + +import { defineStore } from 'pinia'; + +import { router } from '#/router'; +import { findIndex } from '#/utils'; +import { getRawRoute } from '#/utils/routerHelper'; + +import { store } from './index'; + +const userStore = useUserStore(); + +export interface TagsViewState { + visitedViews: RouteLocationNormalizedLoaded[]; + cachedViews: Set; + selectedTag?: RouteLocationNormalizedLoaded; +} + +export const useTagsViewStore = defineStore('tagsView', { + state: (): TagsViewState => ({ + visitedViews: [], + cachedViews: new Set(), + selectedTag: undefined, + }), + getters: { + getVisitedViews(): RouteLocationNormalizedLoaded[] { + return this.visitedViews; + }, + getCachedViews(): string[] { + return [...this.cachedViews]; + }, + getSelectedTag(): RouteLocationNormalizedLoaded | undefined { + return this.selectedTag; + }, + }, + actions: { + // 新增缓存和tag + addView(view: RouteLocationNormalizedLoaded): void { + this.addVisitedView(view); + this.addCachedView(); + }, + // 新增tag + addVisitedView(view: RouteLocationNormalizedLoaded) { + if (this.visitedViews.some((v) => v.fullPath === view.fullPath)) return; + if (view.meta?.noTagsView) return; + const visitedView = Object.assign({}, view, { + title: view.meta?.title || 'no-name', + }); + + if (visitedView.meta) { + const titleSuffixList: string[] = []; + this.visitedViews.forEach((v) => { + if ( + v.path === visitedView.path && + v.meta?.title === visitedView.meta?.title + ) { + titleSuffixList.push((v.meta?.titleSuffix as string) || '1'); + } + }); + if (titleSuffixList.length > 0) { + let titleSuffix = 1; + while (titleSuffixList.includes(`${titleSuffix}`)) { + titleSuffix += 1; + } + visitedView.meta.titleSuffix = + titleSuffix === 1 ? undefined : `${titleSuffix}`; + } + } + + this.visitedViews.push(visitedView); + }, + // 新增缓存 + addCachedView() { + const cacheMap: Set = new Set(); + for (const v of this.visitedViews) { + const item = getRawRoute(v); + const needCache = !item.meta?.noCache; + if (!needCache) { + continue; + } + const name = item.name as string; + cacheMap.add(name); + } + if ( + [...this.cachedViews].sort().toString() === + [...cacheMap].sort().toString() + ) + return; + this.cachedViews = cacheMap; + }, + // 删除某个 + delView(view: RouteLocationNormalizedLoaded) { + this.delVisitedView(view); + this.delCachedView(); + }, + // 删除tag + delVisitedView(view: RouteLocationNormalizedLoaded) { + for (const [i, v] of this.visitedViews.entries()) { + if (v.fullPath === view.fullPath) { + this.visitedViews.splice(i, 1); + break; + } + } + }, + // 删除缓存 + delCachedView() { + const route = router.currentRoute.value; + const index = findIndex( + this.getCachedViews, + (v) => v === route.name, + ); + for (const v of this.visitedViews) { + if (v.name === route.name) { + return; + } + } + if (index > -1) { + this.cachedViews.delete( + this.getCachedViews[index] as unknown as string, + ); + } + }, + // 删除所有缓存和tag + delAllViews() { + this.delAllVisitedViews(); + this.delCachedView(); + }, + // 删除所有tag + delAllVisitedViews() { + // const userStore = useUserStoreWithOut(); + + // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix) + this.visitedViews = userStore.userInfo + ? this.visitedViews.filter((tag) => tag?.meta?.affix) + : []; + }, + // 删除其他 + delOthersViews(view: RouteLocationNormalizedLoaded) { + this.delOthersVisitedViews(view); + this.addCachedView(); + }, + // 删除其他tag + delOthersVisitedViews(view: RouteLocationNormalizedLoaded) { + this.visitedViews = this.visitedViews.filter((v) => { + return v?.meta?.affix || v.fullPath === view.fullPath; + }); + }, + // 删除左侧 + delLeftViews(view: RouteLocationNormalizedLoaded) { + const index = findIndex( + this.visitedViews, + (v) => v.fullPath === view.fullPath, + ); + if (index > -1) { + this.visitedViews = this.visitedViews.filter((v, i) => { + return v?.meta?.affix || v.fullPath === view.fullPath || i > index; + }); + this.addCachedView(); + } + }, + // 删除右侧 + delRightViews(view: RouteLocationNormalizedLoaded) { + const index = findIndex( + this.visitedViews, + (v) => v.fullPath === view.fullPath, + ); + if (index > -1) { + this.visitedViews = this.visitedViews.filter((v, i) => { + return v?.meta?.affix || v.fullPath === view.fullPath || i < index; + }); + this.addCachedView(); + } + }, + updateVisitedView(view: RouteLocationNormalizedLoaded) { + for (let v of this.visitedViews) { + if (v.fullPath === view.fullPath) { + v = Object.assign(v, view); + break; + } + } + }, + // 设置当前选中的 tag + setSelectedTag(tag: RouteLocationNormalizedLoaded) { + this.selectedTag = tag; + }, + setTitle(title: string, path?: string) { + for (const v of this.visitedViews) { + if (v.path === (path ?? this.selectedTag?.path)) { + v.meta.title = title; + break; + } + } + }, + }, + persist: false, +}); + +export const useTagsViewStoreWithOut = () => { + return useTagsViewStore(store); +}; diff --git a/apps/web-antd/src/utils/index.ts b/apps/web-antd/src/utils/index.ts index 3a066fd4a..5877010a3 100644 --- a/apps/web-antd/src/utils/index.ts +++ b/apps/web-antd/src/utils/index.ts @@ -1,2 +1,29 @@ +import type { Recordable } from '@vben/types'; + export * from './rangePickerProps'; export * from './routerHelper'; + +/** + * 查找数组对象的某个下标 + * @param {Array} ary 查找的数组 + * @param {Function} fn 判断的方法 + */ +type Fn = (item: T, index: number, array: Array) => boolean; +export const findIndex = >( + ary: Array, + fn: Fn, +): number => { + if (ary.findIndex) { + return ary.findIndex((item, index, array) => fn(item, index, array)); + } + let index = -1; + ary.some((item: T, i: number, ary: Array) => { + const ret: boolean = fn(item, i, ary); + if (ret) { + index = i; + return true; + } + return false; + }); + return index; +}; diff --git a/apps/web-antd/src/utils/routerHelper.ts b/apps/web-antd/src/utils/routerHelper.ts index 0f3c52096..a9e99f54b 100644 --- a/apps/web-antd/src/utils/routerHelper.ts +++ b/apps/web-antd/src/utils/routerHelper.ts @@ -1,3 +1,8 @@ +import type { + RouteLocationNormalized, + RouteRecordNormalized, +} from 'vue-router'; + import { defineAsyncComponent } from 'vue'; const modules = import.meta.glob('../views/**/*.{vue,tsx}'); @@ -14,3 +19,20 @@ export function registerComponent(componentPath: string) { } } } + +export const getRawRoute = ( + route: RouteLocationNormalized, +): RouteLocationNormalized => { + if (!route) return route; + const { matched, ...opt } = route; + return { + ...opt, + matched: (matched + ? matched.map((item) => ({ + meta: item.meta, + name: item.name, + path: item.path, + })) + : undefined) as RouteRecordNormalized[], + }; +}; diff --git a/apps/web-antd/src/views/mp/autoReply/data.ts b/apps/web-antd/src/views/mp/autoReply/data.ts new file mode 100644 index 000000000..31c0481b5 --- /dev/null +++ b/apps/web-antd/src/views/mp/autoReply/data.ts @@ -0,0 +1,88 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; + +import { markRaw } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; + +import WxAccountSelect from '#/views/mp/modules/wx-account-select/main.vue'; + +import { MsgType } from './modules/types'; + +/** 获取表格列配置 */ +export function useGridColumns(msgType: MsgType): VxeGridPropTypes.Columns { + const columns: VxeGridPropTypes.Columns = []; + // 请求消息类型列(仅消息回复显示) + if (msgType === MsgType.Message) { + columns.push({ + field: 'requestMessageType', + title: '请求消息类型', + minWidth: 120, + }); + } + + // 关键词列(仅关键词回复显示) + if (msgType === MsgType.Keyword) { + columns.push({ + field: 'requestKeyword', + title: '关键词', + minWidth: 150, + }); + } + + // 匹配类型列(仅关键词回复显示) + if (msgType === MsgType.Keyword) { + columns.push({ + field: 'requestMatch', + title: '匹配类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH }, + }, + }); + } + + // 回复消息类型列 + columns.push( + { + field: 'responseMessageType', + title: '回复消息类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.MP_MESSAGE_TYPE }, + }, + }, + { + field: 'responseContent', + title: '回复内容', + minWidth: 200, + slots: { default: 'replyContent' }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 140, + fixed: 'right', + slots: { default: 'actions' }, + }, + ); + return columns; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'accountId', + label: '公众号', + component: markRaw(WxAccountSelect), + }, + ]; +} diff --git a/apps/web-antd/src/views/mp/autoReply/index.vue b/apps/web-antd/src/views/mp/autoReply/index.vue index 06996f6d1..fb1b9ce3f 100644 --- a/apps/web-antd/src/views/mp/autoReply/index.vue +++ b/apps/web-antd/src/views/mp/autoReply/index.vue @@ -1,29 +1,259 @@ diff --git a/apps/web-antd/src/views/mp/autoReply/modules/ReplyForm.vue b/apps/web-antd/src/views/mp/autoReply/modules/ReplyForm.vue new file mode 100644 index 000000000..d8e137083 --- /dev/null +++ b/apps/web-antd/src/views/mp/autoReply/modules/ReplyForm.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/apps/web-antd/src/views/mp/autoReply/modules/ReplyTable.vue b/apps/web-antd/src/views/mp/autoReply/modules/ReplyTable.vue new file mode 100644 index 000000000..bdfb41ade --- /dev/null +++ b/apps/web-antd/src/views/mp/autoReply/modules/ReplyTable.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/web-antd/src/views/mp/autoReply/modules/form.vue b/apps/web-antd/src/views/mp/autoReply/modules/form.vue new file mode 100644 index 000000000..7f9b9ed38 --- /dev/null +++ b/apps/web-antd/src/views/mp/autoReply/modules/form.vue @@ -0,0 +1,142 @@ + + + diff --git a/apps/web-antd/src/views/mp/autoReply/modules/types.ts b/apps/web-antd/src/views/mp/autoReply/modules/types.ts new file mode 100644 index 000000000..f07a7d758 --- /dev/null +++ b/apps/web-antd/src/views/mp/autoReply/modules/types.ts @@ -0,0 +1,7 @@ +// 消息类型(Follow: 关注时回复;Message: 消息回复;Keyword: 关键词回复) +// 作为 tab.name,enum 的数字不能随意修改,与 api 参数相关 +export enum MsgType { + Follow = 1, + Keyword = 3, + Message = 2, +} diff --git a/apps/web-antd/src/views/mp/modules/wx-account-select/main.vue b/apps/web-antd/src/views/mp/modules/wx-account-select/main.vue index 75272e669..8388a6e9a 100644 --- a/apps/web-antd/src/views/mp/modules/wx-account-select/main.vue +++ b/apps/web-antd/src/views/mp/modules/wx-account-select/main.vue @@ -1,23 +1,26 @@ + + diff --git a/apps/web-ele/src/views/mp/autoReply/modules/ReplyForm.vue b/apps/web-ele/src/views/mp/autoReply/modules/ReplyForm.vue new file mode 100644 index 000000000..f55afc365 --- /dev/null +++ b/apps/web-ele/src/views/mp/autoReply/modules/ReplyForm.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/autoReply/modules/ReplyTable.vue b/apps/web-ele/src/views/mp/autoReply/modules/ReplyTable.vue new file mode 100644 index 000000000..bdfb41ade --- /dev/null +++ b/apps/web-ele/src/views/mp/autoReply/modules/ReplyTable.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/web-ele/src/views/mp/autoReply/modules/form.vue b/apps/web-ele/src/views/mp/autoReply/modules/form.vue new file mode 100644 index 000000000..a4a37438a --- /dev/null +++ b/apps/web-ele/src/views/mp/autoReply/modules/form.vue @@ -0,0 +1,142 @@ + + + diff --git a/apps/web-ele/src/views/mp/autoReply/modules/types.ts b/apps/web-ele/src/views/mp/autoReply/modules/types.ts new file mode 100644 index 000000000..f07a7d758 --- /dev/null +++ b/apps/web-ele/src/views/mp/autoReply/modules/types.ts @@ -0,0 +1,7 @@ +// 消息类型(Follow: 关注时回复;Message: 消息回复;Keyword: 关键词回复) +// 作为 tab.name,enum 的数字不能随意修改,与 api 参数相关 +export enum MsgType { + Follow = 1, + Keyword = 3, + Message = 2, +} diff --git a/apps/web-ele/src/views/mp/modules/wx-account-select/main.vue b/apps/web-ele/src/views/mp/modules/wx-account-select/main.vue index d154b892f..abad94cce 100644 --- a/apps/web-ele/src/views/mp/modules/wx-account-select/main.vue +++ b/apps/web-ele/src/views/mp/modules/wx-account-select/main.vue @@ -1,7 +1,7 @@