diff --git a/apps/web-ele/.env.development b/apps/web-ele/.env.development index 77c13d398..cf25c6dd5 100644 --- a/apps/web-ele/.env.development +++ b/apps/web-ele/.env.development @@ -6,7 +6,7 @@ VITE_BASE=/ # 请求路径 VITE_BASE_URL=http://127.0.0.1:48080 # 接口地址 -VITE_GLOB_API_URL=/admin-api +VITE_GLOB_API_URL=http://47.103.66.220:48080/admin-api # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 VITE_UPLOAD_TYPE=server # 是否打开 devtools,true 为打开,false 为关闭 diff --git a/apps/web-ele/package.json b/apps/web-ele/package.json index aa96e8564..9ded8eee4 100644 --- a/apps/web-ele/package.json +++ b/apps/web-ele/package.json @@ -45,6 +45,7 @@ "@vben/utils": "workspace:*", "@vueuse/core": "catalog:", "@vueuse/integrations": "catalog:", + "benz-amr-recorder": "^1.1.5", "cropperjs": "catalog:", "dayjs": "catalog:", "element-plus": "catalog:", diff --git a/apps/web-ele/src/store/index.ts b/apps/web-ele/src/store/index.ts index 269586ee8..af0c4ab6f 100644 --- a/apps/web-ele/src/store/index.ts +++ b/apps/web-ele/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-ele/src/store/tagsView.ts b/apps/web-ele/src/store/tagsView.ts new file mode 100644 index 000000000..396f581e8 --- /dev/null +++ b/apps/web-ele/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-ele/src/utils/index.ts b/apps/web-ele/src/utils/index.ts index 3a066fd4a..e64084b6c 100644 --- a/apps/web-ele/src/utils/index.ts +++ b/apps/web-ele/src/utils/index.ts @@ -1,2 +1,30 @@ +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-ele/src/utils/routerHelper.ts b/apps/web-ele/src/utils/routerHelper.ts index 477a640ce..31164d7ba 100644 --- a/apps/web-ele/src/utils/routerHelper.ts +++ b/apps/web-ele/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}'); @@ -13,3 +18,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-ele/src/utils/tree.ts b/apps/web-ele/src/utils/tree.ts new file mode 100644 index 000000000..172ed2ba1 --- /dev/null +++ b/apps/web-ele/src/utils/tree.ts @@ -0,0 +1,442 @@ +interface TreeHelperConfig { + id: string; + children: string; + pid: string; +} + +const DEFAULT_CONFIG: TreeHelperConfig = { + id: 'id', + children: 'children', + pid: 'pid', +}; +export const defaultProps = { + children: 'children', + label: 'name', + value: 'id', + isLeaf: 'leaf', + emitPath: false, // 用于 cascader 组件:在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值 +}; + +const getConfig = (config: Partial) => + Object.assign({}, DEFAULT_CONFIG, config); + +// tree from list +export const listToTree = ( + list: any[], + config: Partial = {}, +): T[] => { + const conf = getConfig(config) as TreeHelperConfig; + const nodeMap = new Map(); + const result: T[] = []; + const { id, children, pid } = conf; + + for (const node of list) { + node[children] = node[children] || []; + nodeMap.set(node[id], node); + } + for (const node of list) { + const parent = nodeMap.get(node[pid]); + (parent ? parent.children : result).push(node); + } + return result; +}; + +export const treeToList = ( + tree: any, + config: Partial = {}, +): T => { + config = getConfig(config); + const { children } = config; + const result: any = [...tree]; + for (let i = 0; i < result.length; i++) { + const childNodes = result[i][children]; + if (!childNodes) continue; + result.splice(i + 1, 0, ...childNodes); + } + return result; +}; + +export const findNode = ( + tree: any, + func: Fn, + config: Partial = {}, +): null | T => { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + for (const node of list) { + if (func(node)) return node; + const childNodes = node[children]; + if (childNodes) { + list.push(...childNodes); + } + } + return null; +}; + +export const findNodeAll = ( + tree: any, + func: Fn, + config: Partial = {}, +): T[] => { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + const result: T[] = []; + for (const node of list) { + func(node) && result.push(node); + const childNodes = node[children]; + if (childNodes) { + list.push(...childNodes); + } + } + return result; +}; + +export const findPath = ( + tree: any, + func: Fn, + config: Partial = {}, +): null | T | T[] => { + config = getConfig(config); + const path: T[] = []; + const list = [...tree]; + const visitedSet = new Set(); + const { children } = config; + while (list.length > 0) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + const childNodes = node[children]; + if (childNodes) { + list.unshift(...childNodes); + } + path.push(node); + if (func(node)) { + return path; + } + } + } + return null; +}; + +export const findPathAll = ( + tree: any, + func: Fn, + config: Partial = {}, +) => { + config = getConfig(config); + const path: any[] = []; + const list = [...tree]; + const result: any[] = []; + const visitedSet = new Set(); + const { children } = config; + while (list.length > 0) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + const childNodes = node[children]; + if (childNodes) { + list.unshift(...childNodes); + } + path.push(node); + func(node) && result.push([...path]); + } + } + return result; +}; + +export const filter = ( + tree: T[], + func: (n: T) => boolean, + config: Partial = {}, +): T[] => { + config = getConfig(config); + const children = config.children as string; + + function listFilter(list: T[]) { + return list + .map((node: any) => ({ ...node })) + .filter((node) => { + node[children] = node[children] && listFilter(node[children]); + return func(node) || node[children]?.length > 0; + }); + } + + return listFilter(tree); +}; + +export const forEach = ( + tree: T[], + func: (n: T) => any, + config: Partial = {}, +): void => { + config = getConfig(config); + const list: any[] = [...tree]; + const { children } = config; + for (let i = 0; i < list.length; i++) { + // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿 + if (func(list[i])) { + return; + } + children && + list[i][children] && + list.splice(i + 1, 0, ...list[i][children]); + } +}; + +/** + * @description: Extract tree specified structure + */ +export const treeMap = ( + treeData: T[], + opt: { children?: string; conversion: Fn }, +): T[] => { + return treeData.map((item) => treeMapEach(item, opt)); +}; + +/** + * @description: Extract tree specified structure + */ +export const treeMapEach = ( + data: any, + { children = 'children', conversion }: { children?: string; conversion: Fn }, +) => { + const haveChildren = + Array.isArray(data[children]) && data[children].length > 0; + const conversionData = conversion(data) || {}; + return haveChildren + ? { + ...conversionData, + [children]: data[children].map((i: number) => + treeMapEach(i, { + children, + conversion, + }), + ), + } + : { + ...conversionData, + }; +}; + +/** + * 递归遍历树结构 + * @param treeDatas 树 + * @param callBack 回调 + * @param parentNode 父节点 + */ +export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => { + treeDatas.forEach((element) => { + const newNode = callBack(element, parentNode) || element; + if (element.children) { + eachTree(element.children, callBack, newNode); + } + }); +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export const handleTree = ( + data: any[], + id?: string, + parentId?: string, + children?: string, +) => { + if (!Array.isArray(data)) { + console.warn('data must be an array'); + return []; + } + const config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children', + }; + + const childrenListMap = {}; + const nodeIds = {}; + const tree: any[] = []; + + for (const d of data) { + const parentId = d[config.parentId]; + if ( + childrenListMap[parentId] === null || + childrenListMap[parentId] === undefined + ) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } + + for (const d of data) { + const parentId = d[config.parentId]; + if (nodeIds[parentId] === null || nodeIds[parentId] === undefined) { + tree.push(d); + } + } + + for (const t of tree) { + adaptToChildrenList(t); + } + + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (const c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + } + + return tree; +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + * @param {*} rootId 根Id 默认 0 + */ +// @ts-ignore: 遗留函数,保持原有逻辑不变 +export const handleTree2 = (data, id, parentId, children, rootId) => { + id = id || 'id'; + parentId = parentId || 'parentId'; + // children = children || 'children' + rootId = + rootId || + Math.min( + ...data.map((item) => { + return item[parentId]; + }), + ) || + 0; + // 对源数据深度克隆 + const cloneData = structuredClone(data); + // 循环所有项 + const treeData = cloneData.filter((father) => { + const branchArr = cloneData.filter((child) => { + // 返回每一项的子级数组 + return father[id] === child[parentId]; + }); + branchArr.length > 0 ? (father.children = branchArr) : ''; + // 返回第一层 + return father[parentId] === rootId; + }); + return treeData === '' ? data : treeData; +}; + +/** + * 校验选中的节点,是否为指定 level + * + * @param tree 要操作的树结构数据 + * @param nodeId 需要判断在什么层级的数据 + * @param level 检查的级别, 默认检查到二级 + * @return true 是;false 否 + */ +export const checkSelectedNode = ( + tree: any[], + nodeId: any, + level = 2, +): boolean => { + if (tree === undefined || !Array.isArray(tree) || tree.length === 0) { + console.warn('tree must be an array'); + return false; + } + + // 校验是否是一级节点 + if (tree.some((item) => item.id === nodeId)) { + return false; + } + + // 递归计数 + let count = 1; + + // 深层次校验 + function performAThoroughValidation(arr: any[]): boolean { + count += 1; + for (const item of arr) { + if (item.id === nodeId) { + return true; + } else if ( + item.children !== undefined && + item.children.length > 0 && + performAThoroughValidation(item.children) + ) { + return true; + } + } + return false; + } + + for (const item of tree) { + count = 1; + if ( + performAThoroughValidation(item.children) && // 找到后对比是否是期望的层级 + count >= level + ) { + return true; + } + } + + return false; +}; + +/** + * 获取节点的完整结构 + * @param tree 树数据 + * @param nodeId 节点 id + */ +export const treeToString = (tree: any[], nodeId) => { + if (tree === undefined || !Array.isArray(tree) || tree.length === 0) { + console.warn('tree must be an array'); + return ''; + } + // 校验是否是一级节点 + const node = tree.find((item) => item.id === nodeId); + if (node !== undefined) { + return node.name; + } + let str = ''; + + function performAThoroughValidation(arr) { + if (arr === undefined || !Array.isArray(arr) || arr.length === 0) { + return false; + } + for (const item of arr) { + if (item.id === nodeId) { + str += ` / ${item.name}`; + return true; + } else if (item.children !== undefined && item.children.length > 0) { + str += ` / ${item.name}`; + if (performAThoroughValidation(item.children)) { + return true; + } + } + } + return false; + } + + for (const item of tree) { + str = `${item.name}`; + if (performAThoroughValidation(item.children)) { + break; + } + } + return str; +}; diff --git a/apps/web-ele/src/views/mp/components/wx-account-select/index.ts b/apps/web-ele/src/views/mp/components/wx-account-select/index.ts new file mode 100644 index 000000000..374d993fe --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-account-select/index.ts @@ -0,0 +1 @@ +export { default } from './main.vue'; diff --git a/apps/web-ele/src/views/mp/components/wx-account-select/main.vue b/apps/web-ele/src/views/mp/components/wx-account-select/main.vue new file mode 100644 index 000000000..e2c1b552e --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-account-select/main.vue @@ -0,0 +1,83 @@ + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-location/index.ts b/apps/web-ele/src/views/mp/components/wx-location/index.ts new file mode 100644 index 000000000..374d993fe --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-location/index.ts @@ -0,0 +1 @@ +export { default } from './main.vue'; diff --git a/apps/web-ele/src/views/mp/components/wx-location/main.vue b/apps/web-ele/src/views/mp/components/wx-location/main.vue new file mode 100644 index 000000000..784b45553 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-location/main.vue @@ -0,0 +1,61 @@ + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-material-select/index.ts b/apps/web-ele/src/views/mp/components/wx-material-select/index.ts new file mode 100644 index 000000000..f4fcea0ba --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-material-select/index.ts @@ -0,0 +1,3 @@ +export { default } from './main.vue'; + +export { MaterialType, NewsType } from './types'; diff --git a/apps/web-ele/src/views/mp/components/wx-material-select/main.vue b/apps/web-ele/src/views/mp/components/wx-material-select/main.vue new file mode 100644 index 000000000..467636110 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-material-select/main.vue @@ -0,0 +1,295 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-material-select/types.ts b/apps/web-ele/src/views/mp/components/wx-material-select/types.ts new file mode 100644 index 000000000..3d44c5db7 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-material-select/types.ts @@ -0,0 +1,11 @@ +export enum NewsType { + Draft = '2', + Published = '1', +} + +export enum MaterialType { + Image = 'image', + News = 'news', + Video = 'video', + Voice = 'voice', +} diff --git a/apps/web-ele/src/views/mp/components/wx-msg/card.scss b/apps/web-ele/src/views/mp/components/wx-msg/card.scss new file mode 100644 index 000000000..58fde79fd --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/card.scss @@ -0,0 +1,116 @@ +.avue-card { + &__item { + box-sizing: border-box; + height: 200px; + margin-bottom: 16px; + font-size: 14px; + font-feature-settings: 'tnum'; + font-variant: tabular-nums; + line-height: 1.5; + color: rgb(0 0 0 / 65%); + cursor: pointer; + list-style: none; + background-color: #fff; + border: 1px solid #e8e8e8; + + &:hover { + border-color: rgb(0 0 0 / 9%); + box-shadow: 0 2px 8px rgb(0 0 0 / 9%); + } + + &--add { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + font-size: 16px; + color: rgb(0 0 0 / 45%); + background-color: #fff; + border: 1px dashed #000; + border-color: #d9d9d9; + border-radius: 2px; + + i { + margin-right: 10px; + } + + &:hover { + color: #40a9ff; + background-color: #fff; + border-color: #40a9ff; + } + } + } + + &__body { + display: flex; + padding: 24px; + } + + &__detail { + flex: 1; + } + + &__avatar { + width: 48px; + height: 48px; + margin-right: 12px; + overflow: hidden; + border-radius: 48px; + + img { + width: 100%; + height: 100%; + } + } + + &__title { + margin-bottom: 12px; + font-size: 16px; + color: rgb(0 0 0 / 85%); + + &:hover { + color: #1890ff; + } + } + + &__info { + display: -webkit-box; + height: 64px; + overflow: hidden; + -webkit-line-clamp: 3; + color: rgb(0 0 0 / 45%); + -webkit-box-orient: vertical; + } + + &__menu { + display: flex; + justify-content: space-around; + height: 50px; + line-height: 50px; + color: rgb(0 0 0 / 45%); + text-align: center; + background: #f7f9fa; + + &:hover { + color: #1890ff; + } + } +} + +/** joolun 额外加的 */ +.avue-comment__main { + flex: unset !important; + margin: 0 8px !important; + border-radius: 5px !important; +} + +.avue-comment__header { + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +.avue-comment__body { + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} diff --git a/apps/web-ele/src/views/mp/components/wx-msg/comment.scss b/apps/web-ele/src/views/mp/components/wx-msg/comment.scss new file mode 100644 index 000000000..219e2e5d4 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/comment.scss @@ -0,0 +1,109 @@ +/* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss */ +.avue-comment { + display: flex; + align-items: flex-start; + margin-bottom: 30px; + + &--reverse { + flex-direction: row-reverse; + + .avue-comment__main { + &::before, + &::after { + right: -8px; + left: auto; + border-width: 8px 0 8px 8px; + } + + &::before { + border-left-color: #dedede; + } + + &::after { + margin-right: 1px; + margin-left: auto; + border-left-color: #f8f8f8; + } + } + } + + &__avatar { + box-sizing: border-box; + width: 48px; + height: 48px; + vertical-align: middle; + border: 1px solid transparent; + border-radius: 50%; + } + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 5px 15px; + background: #f8f8f8; + border-bottom: 1px solid #eee; + } + + &__author { + font-size: 14px; + font-weight: 700; + color: #999; + } + + &__main { + position: relative; + flex: 1; + margin: 0 20px; + border: 1px solid #dedede; + border-radius: 2px; + + &::before, + &::after { + position: absolute; + top: 10px; + right: 100%; + left: -8px; + display: block; + width: 0; + height: 0; + pointer-events: none; + content: ' '; + border-color: transparent; + border-style: solid solid outset; + border-width: 8px 8px 8px 0; + } + + &::before { + z-index: 1; + border-right-color: #dedede; + } + + &::after { + z-index: 2; + margin-left: 1px; + border-right-color: #f8f8f8; + } + } + + &__body { + padding: 15px; + overflow: hidden; + font-family: + 'Segoe UI', 'Lucida Grande', Helvetica, Arial, 'Microsoft YaHei', + FreeSans, Arimo, 'Droid Sans', 'wenquanyi micro hei', 'Hiragino Sans GB', + 'Hiragino Sans GB W3', FontAwesome, sans-serif; + font-size: 14px; + color: #333; + background: #fff; + } + + blockquote { + padding: 1px 0 1px 15px; + margin: 0; + font-family: + Georgia, 'Times New Roman', Times, Kai, 'Kaiti SC', KaiTi, BiauKai, + FontAwesome, serif; + border-left: 4px solid #ddd; + } +} diff --git a/apps/web-ele/src/views/mp/components/wx-msg/components/Msg.vue b/apps/web-ele/src/views/mp/components/wx-msg/components/Msg.vue new file mode 100644 index 000000000..8daa0fd51 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/components/Msg.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-msg/components/MsgEvent.vue b/apps/web-ele/src/views/mp/components/wx-msg/components/MsgEvent.vue new file mode 100644 index 000000000..70c193082 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/components/MsgEvent.vue @@ -0,0 +1,54 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-msg/components/MsgList.vue b/apps/web-ele/src/views/mp/components/wx-msg/components/MsgList.vue new file mode 100644 index 000000000..36b574d83 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/components/MsgList.vue @@ -0,0 +1,77 @@ + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-msg/index.ts b/apps/web-ele/src/views/mp/components/wx-msg/index.ts new file mode 100644 index 000000000..048afc95f --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/index.ts @@ -0,0 +1,3 @@ +export { default } from './main.vue'; + +export { MsgType } from './types'; diff --git a/apps/web-ele/src/views/mp/components/wx-msg/main.vue b/apps/web-ele/src/views/mp/components/wx-msg/main.vue new file mode 100644 index 000000000..4e2bb6469 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/main.vue @@ -0,0 +1,210 @@ + + + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-msg/types.ts b/apps/web-ele/src/views/mp/components/wx-msg/types.ts new file mode 100644 index 000000000..e988954ad --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/types.ts @@ -0,0 +1,17 @@ +export enum MsgType { + Event = 'event', + Image = 'image', + Link = 'link', + Location = 'location', + Music = 'music', + News = 'news', + Text = 'text', + Video = 'video', + Voice = 'voice', +} + +export interface User { + nickname: string; + avatar: string; + accountId: number; +} diff --git a/apps/web-ele/src/views/mp/components/wx-music/index.ts b/apps/web-ele/src/views/mp/components/wx-music/index.ts new file mode 100644 index 000000000..374d993fe --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-music/index.ts @@ -0,0 +1 @@ +export { default } from './main.vue'; diff --git a/apps/web-ele/src/views/mp/components/wx-music/main.vue b/apps/web-ele/src/views/mp/components/wx-music/main.vue new file mode 100644 index 000000000..e097e1dfc --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-music/main.vue @@ -0,0 +1,70 @@ + + + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-news/index.ts b/apps/web-ele/src/views/mp/components/wx-news/index.ts new file mode 100644 index 000000000..374d993fe --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-news/index.ts @@ -0,0 +1 @@ +export { default } from './main.vue'; diff --git a/apps/web-ele/src/views/mp/components/wx-news/main.vue b/apps/web-ele/src/views/mp/components/wx-news/main.vue new file mode 100644 index 000000000..f93893c92 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-news/main.vue @@ -0,0 +1,123 @@ + + + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/components/TabImage.vue b/apps/web-ele/src/views/mp/components/wx-reply/components/TabImage.vue new file mode 100644 index 000000000..073934088 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/components/TabImage.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/components/TabMusic.vue b/apps/web-ele/src/views/mp/components/wx-reply/components/TabMusic.vue new file mode 100644 index 000000000..fe74ea779 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/components/TabMusic.vue @@ -0,0 +1,137 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/components/TabNews.vue b/apps/web-ele/src/views/mp/components/wx-reply/components/TabNews.vue new file mode 100644 index 000000000..85ede8813 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/components/TabNews.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/components/TabText.vue b/apps/web-ele/src/views/mp/components/wx-reply/components/TabText.vue new file mode 100644 index 000000000..a7e389db0 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/components/TabText.vue @@ -0,0 +1,29 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/components/TabVideo.vue b/apps/web-ele/src/views/mp/components/wx-reply/components/TabVideo.vue new file mode 100644 index 000000000..3ddcb2cd9 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/components/TabVideo.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/components/TabVoice.vue b/apps/web-ele/src/views/mp/components/wx-reply/components/TabVoice.vue new file mode 100644 index 000000000..9467120c6 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/components/TabVoice.vue @@ -0,0 +1,174 @@ + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/components/types.ts b/apps/web-ele/src/views/mp/components/wx-reply/components/types.ts new file mode 100644 index 000000000..67571ef59 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/components/types.ts @@ -0,0 +1,58 @@ +import type { Ref } from 'vue'; + +import { unref } from 'vue'; + +enum ReplyType { + Image = 'image', + Music = 'music', + News = 'news', + Text = 'text', + Video = 'video', + Voice = 'voice', +} + +interface _Reply { + accountId: number; + type: ReplyType; + name?: null | string; + content?: null | string; + mediaId?: null | string; + url?: null | string; + title?: null | string; + description?: null | string; + thumbMediaId?: null | string; + thumbMediaUrl?: null | string; + musicUrl?: null | string; + hqMusicUrl?: null | string; + introduction?: null | string; + articles?: any[]; +} + +type Reply = _Reply; // Partial<_Reply> + +enum NewsType { + Draft = '2', + Published = '1', +} + +/** 利用旧的reply[accountId, type]初始化新的Reply */ +const createEmptyReply = (old: Ref | Reply): Reply => { + return { + accountId: unref(old).accountId, + type: unref(old).type, + name: null, + content: null, + mediaId: null, + url: null, + title: null, + description: null, + thumbMediaId: null, + thumbMediaUrl: null, + musicUrl: null, + hqMusicUrl: null, + introduction: null, + articles: [], + }; +}; + +export { createEmptyReply, NewsType, type Reply, ReplyType }; diff --git a/apps/web-ele/src/views/mp/components/wx-reply/index.ts b/apps/web-ele/src/views/mp/components/wx-reply/index.ts new file mode 100644 index 000000000..a3f675bb1 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/index.ts @@ -0,0 +1,8 @@ +export { + createEmptyReply, + NewsType, + type Reply, + ReplyType, +} from './components/types'; + +export { default } from './main.vue'; diff --git a/apps/web-ele/src/views/mp/components/wx-reply/main.vue b/apps/web-ele/src/views/mp/components/wx-reply/main.vue new file mode 100644 index 000000000..d3be2557a --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/main.vue @@ -0,0 +1,215 @@ + + + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-video-play/index.ts b/apps/web-ele/src/views/mp/components/wx-video-play/index.ts new file mode 100644 index 000000000..374d993fe --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-video-play/index.ts @@ -0,0 +1 @@ +export { default } from './main.vue'; diff --git a/apps/web-ele/src/views/mp/components/wx-video-play/main.vue b/apps/web-ele/src/views/mp/components/wx-video-play/main.vue new file mode 100644 index 000000000..364b05890 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-video-play/main.vue @@ -0,0 +1,76 @@ + + + + diff --git a/apps/web-ele/src/views/mp/components/wx-voice-play/index.ts b/apps/web-ele/src/views/mp/components/wx-voice-play/index.ts new file mode 100644 index 000000000..374d993fe --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-voice-play/index.ts @@ -0,0 +1 @@ +export { default } from './main.vue'; diff --git a/apps/web-ele/src/views/mp/components/wx-voice-play/main.vue b/apps/web-ele/src/views/mp/components/wx-voice-play/main.vue new file mode 100644 index 000000000..23b721380 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-voice-play/main.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/hooks/useUpload.ts b/apps/web-ele/src/views/mp/hooks/useUpload.ts new file mode 100644 index 000000000..05da55f58 --- /dev/null +++ b/apps/web-ele/src/views/mp/hooks/useUpload.ts @@ -0,0 +1,67 @@ +import type { UploadRawFile } from 'element-plus'; + +import { ElMessage } from 'element-plus'; + +const message = ElMessage; // 消息 + +enum UploadType { + Image = 'image', + Video = 'video', + Voice = 'voice', +} + +const useBeforeUpload = (type: UploadType, maxSizeMB: number) => { + const fn = (rawFile: UploadRawFile): boolean => { + let allowTypes: string[] = []; + let name = ''; + + switch (type) { + case UploadType.Image: { + allowTypes = [ + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/bmp', + 'image/jpg', + ]; + maxSizeMB = 2; + name = '图片'; + break; + } + case UploadType.Video: { + allowTypes = ['video/mp4']; + maxSizeMB = 10; + name = '视频'; + break; + } + case UploadType.Voice: { + allowTypes = [ + 'audio/mp3', + 'audio/mpeg', + 'audio/wma', + 'audio/wav', + 'audio/amr', + ]; + maxSizeMB = 2; + name = '语音'; + break; + } + } + // 格式不正确 + if (!allowTypes.includes(rawFile.type)) { + message.error(`上传${name}格式不对!`); + return false; + } + // 大小不正确 + if (rawFile.size / 1024 / 1024 > maxSizeMB) { + message.error(`上传${name}大小不能超过${maxSizeMB}M!`); + return false; + } + + return true; + }; + + return fn; +}; + +export { UploadType, useBeforeUpload }; diff --git a/apps/web-ele/src/views/mp/menu/assets/iphone_backImg.png b/apps/web-ele/src/views/mp/menu/assets/iphone_backImg.png new file mode 100644 index 000000000..bb09591a7 Binary files /dev/null and b/apps/web-ele/src/views/mp/menu/assets/iphone_backImg.png differ diff --git a/apps/web-ele/src/views/mp/menu/assets/menu_foot.png b/apps/web-ele/src/views/mp/menu/assets/menu_foot.png new file mode 100644 index 000000000..4a89d4bd2 Binary files /dev/null and b/apps/web-ele/src/views/mp/menu/assets/menu_foot.png differ diff --git a/apps/web-ele/src/views/mp/menu/assets/menu_head.png b/apps/web-ele/src/views/mp/menu/assets/menu_head.png new file mode 100644 index 000000000..248cfb761 Binary files /dev/null and b/apps/web-ele/src/views/mp/menu/assets/menu_head.png differ diff --git a/apps/web-ele/src/views/mp/menu/components/menuOptions.ts b/apps/web-ele/src/views/mp/menu/components/menuOptions.ts new file mode 100644 index 000000000..9ba818fc4 --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/components/menuOptions.ts @@ -0,0 +1,42 @@ +export default [ + { + value: 'view', + label: '跳转网页', + }, + { + value: 'miniprogram', + label: '跳转小程序', + }, + { + value: 'click', + label: '点击回复', + }, + { + value: 'article_view_limited', + label: '跳转图文消息', + }, + { + value: 'scancode_push', + label: '扫码直接返回结果', + }, + { + value: 'scancode_waitmsg', + label: '扫码回复', + }, + { + value: 'pic_sysphoto', + label: '系统拍照发图', + }, + { + value: 'pic_photo_or_album', + label: '拍照或者相册', + }, + { + value: 'pic_weixin', + label: '微信相册', + }, + { + value: 'location_select', + label: '选择地理位置', + }, +]; diff --git a/apps/web-ele/src/views/mp/menu/components/types.ts b/apps/web-ele/src/views/mp/menu/components/types.ts new file mode 100644 index 000000000..928534ea9 --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/components/types.ts @@ -0,0 +1,73 @@ +export interface Replay { + title: string; + description: string; + picUrl: string; + url: string; +} + +export type MenuType = + | '' + | 'article_view_limited' + | 'click' + | 'location_select' + | 'pic_photo_or_album' + | 'pic_sysphoto' + | 'pic_weixin' + | 'scancode_push' + | 'scancode_waitmsg' + | 'view'; + +interface _RawMenu { + // db + id: number; + parentId: number; + accountId: number; + appId: string; + createTime: number; + + // mp-native + name: string; + menuKey: string; + type: MenuType; + url: string; + miniProgramAppId: string; + miniProgramPagePath: string; + articleId: string; + replyMessageType: string; + replyContent: string; + replyMediaId: string; + replyMediaUrl: string; + replyThumbMediaId: string; + replyThumbMediaUrl: string; + replyTitle: string; + replyDescription: string; + replyArticles: Replay; + replyMusicUrl: string; + replyHqMusicUrl: string; +} + +export type RawMenu = Partial<_RawMenu>; + +interface _Reply { + type: string; + accountId: number; + content: string; + mediaId: string; + url: string; + thumbMediaId: string; + thumbMediaUrl: string; + title: string; + description: string; + articles: null | Replay[]; + musicUrl: string; + hqMusicUrl: string; +} + +export type Reply = Partial<_Reply>; + +interface _Menu extends RawMenu { + children: _Menu[]; + reply: Reply; +} + +export type Menu = Partial<_Menu>; diff --git a/apps/web-ele/src/views/mp/menu/data.ts b/apps/web-ele/src/views/mp/menu/data.ts new file mode 100644 index 000000000..2a22837ba --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/data.ts @@ -0,0 +1,9 @@ +/** 菜单未选中标识 */ +export const MENU_NOT_SELECTED = '__MENU_NOT_SELECTED__'; + +/** 菜单级别枚举 */ +export enum Level { + Child = '2', + Parent = '1', + Undefined = '0', +} diff --git a/apps/web-ele/src/views/mp/menu/index.vue b/apps/web-ele/src/views/mp/menu/index.vue new file mode 100644 index 000000000..36425357f --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/index.vue @@ -0,0 +1,411 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/menu/modules/menu-editor.vue b/apps/web-ele/src/views/mp/menu/modules/menu-editor.vue new file mode 100644 index 000000000..45f259a68 --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/modules/menu-editor.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/menu/modules/menu-previewer.vue b/apps/web-ele/src/views/mp/menu/modules/menu-previewer.vue new file mode 100644 index 000000000..44ae9fc1d --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/modules/menu-previewer.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/playground/package.json b/playground/package.json index c8651563a..6c3045e62 100644 --- a/playground/package.json +++ b/playground/package.json @@ -54,6 +54,7 @@ "vue-router": "catalog:" }, "devDependencies": { - "@types/json-bigint": "catalog:" + "@types/json-bigint": "catalog:", + "vite": "catalog:" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d74811fa4..484162529 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -669,10 +669,10 @@ importers: version: link:scripts/vsh '@vitejs/plugin-vue': specifier: 'catalog:' - version: 6.0.1(vite@7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3)) + version: 6.0.1(vite@7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3)) '@vitejs/plugin-vue-jsx': specifier: 'catalog:' - version: 5.1.1(vite@7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3)) + version: 5.1.1(vite@7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3)) '@vue/test-utils': specifier: 'catalog:' version: 2.4.6 @@ -714,10 +714,10 @@ importers: version: 3.6.1(sass@1.93.2)(typescript@5.9.3)(vue-tsc@2.2.10(typescript@5.9.3))(vue@3.5.22(typescript@5.9.3)) vite: specifier: 'catalog:' - version: 7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + version: 7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) vitest: specifier: 'catalog:' - version: 3.2.4(@types/node@22.18.11)(happy-dom@17.6.3)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + version: 3.2.4(@types/node@22.18.11)(happy-dom@17.6.3)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) vue: specifier: ^3.5.17 version: 3.5.22(typescript@5.9.3) @@ -854,6 +854,9 @@ importers: vue3-signature: specifier: 'catalog:' version: 0.2.4(vue@3.5.22(typescript@5.9.3)) + vuedraggable: + specifier: 'catalog:' + version: 4.1.0(vue@3.5.22(typescript@5.9.3)) apps/web-ele: dependencies: @@ -914,6 +917,9 @@ importers: '@vueuse/integrations': specifier: 'catalog:' version: 13.9.0(async-validator@4.2.5)(axios@1.12.2)(focus-trap@7.6.5)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.6)(vue@3.5.22(typescript@5.9.3)) + benz-amr-recorder: + specifier: ^1.1.5 + version: 1.1.5 cropperjs: specifier: 'catalog:' version: 1.6.2 @@ -2113,6 +2119,9 @@ importers: '@types/json-bigint': specifier: 'catalog:' version: 1.0.4 + vite: + specifier: 'catalog:' + version: 7.1.11(@types/node@24.8.1)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) scripts/turbo-run: dependencies: @@ -5721,6 +5730,12 @@ packages: resolution: {integrity: sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==} hasBin: true + benz-amr-recorder@1.1.5: + resolution: {integrity: sha512-NepctcNTsZHK8NxBb5uKO5p8S+xkbm+vD6GLSkCYdJeEsriexvgumLHpDkanX4QJBcLRMVtg16buWMs+gUPB3g==} + + benz-recorderjs@1.0.5: + resolution: {integrity: sha512-EwedOQo9KLti7HxDi/eZY51PSRbAXnOdEZmLvJ6ro3QQSoF9Y3AXBt57MIllGvVz5vtFYMeikG+GD7qTm3+p9w==} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -15108,14 +15123,14 @@ snapshots: dependencies: vite-plugin-pwa: 1.1.0(vite@5.4.21(@types/node@24.8.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0))(workbox-build@7.3.0)(workbox-window@7.3.0) - '@vitejs/plugin-vue-jsx@5.1.1(vite@7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3))': + '@vitejs/plugin-vue-jsx@5.1.1(vite@7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.4) '@rolldown/pluginutils': 1.0.0-beta.44 '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.4) - vite: 7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) vue: 3.5.22(typescript@5.9.3) transitivePeerDependencies: - supports-color @@ -15137,10 +15152,10 @@ snapshots: vite: 5.4.21(@types/node@24.8.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0) vue: 3.5.22(typescript@5.9.3) - '@vitejs/plugin-vue@6.0.1(vite@7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.1(vite@7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 - vite: 7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) vue: 3.5.22(typescript@5.9.3) '@vitejs/plugin-vue@6.0.1(vite@7.1.11(@types/node@24.8.1)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3))': @@ -15157,13 +15172,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(vite@7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -15782,6 +15797,12 @@ snapshots: baseline-browser-mapping@2.8.18: {} + benz-amr-recorder@1.1.5: + dependencies: + benz-recorderjs: 1.0.5 + + benz-recorderjs@1.0.5: {} + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -21583,13 +21604,13 @@ snapshots: dependencies: vite: 7.1.11(@types/node@24.8.1)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) - vite-node@3.2.4(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): + vite-node@3.2.4(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -21758,7 +21779,7 @@ snapshots: sass: 1.93.2 terser: 5.44.0 - vite@7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): + vite@7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): dependencies: esbuild: 0.25.3 fdir: 6.5.0(picomatch@4.0.3) @@ -21769,7 +21790,7 @@ snapshots: optionalDependencies: '@types/node': 22.18.11 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 1.21.7 less: 4.4.2 sass: 1.93.2 terser: 5.44.0 @@ -21851,11 +21872,11 @@ snapshots: - typescript - universal-cookie - vitest@3.2.4(@types/node@22.18.11)(happy-dom@17.6.3)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): + vitest@3.2.4(@types/node@22.18.11)(happy-dom@17.6.3)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -21873,8 +21894,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.18.11 @@ -21897,7 +21918,7 @@ snapshots: dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@22.18.11)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@22.18.11)(jiti@1.21.7)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4