From 721c5ca610be93810d3c0f95ec5cfe461332a889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Thu, 15 Jan 2026 13:09:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/SalespersonQrCodePreview.vue | 4 +- .../src/views/dashboard/workspace/index.vue | 38 +- .../src/views/system/menu/api/index.ts | 8 + .../src/views/system/notice/api/index.ts | 50 ++ .../views/system/notice/components/modal.vue | 61 ++ .../src/views/system/notice/config/form.ts | 93 +++ .../src/views/system/notice/config/search.ts | 45 ++ .../src/views/system/notice/config/table.ts | 84 +++ .../src/views/system/notice/index.vue | 135 ++++ .../views/system/role-quick-nav/api/index.ts | 58 ++ .../role-quick-nav/components/modal.vue | 61 ++ .../system/role-quick-nav/config/form.ts | 107 +++ .../system/role-quick-nav/config/search.ts | 37 + .../system/role-quick-nav/config/table.ts | 80 +++ .../src/views/system/role-quick-nav/index.vue | 133 ++++ .../system/role/components/auth-menu.vue | 10 +- .../role/components/menu-search-select.vue | 648 ++++++++++++++++++ .../system/role/components/quick-nav.vue | 342 +++++++++ apps/web-antd/src/views/system/role/index.vue | 21 + 19 files changed, 1986 insertions(+), 29 deletions(-) create mode 100644 apps/web-antd/src/views/system/notice/api/index.ts create mode 100644 apps/web-antd/src/views/system/notice/components/modal.vue create mode 100644 apps/web-antd/src/views/system/notice/config/form.ts create mode 100644 apps/web-antd/src/views/system/notice/config/search.ts create mode 100644 apps/web-antd/src/views/system/notice/config/table.ts create mode 100644 apps/web-antd/src/views/system/notice/index.vue create mode 100644 apps/web-antd/src/views/system/role-quick-nav/api/index.ts create mode 100644 apps/web-antd/src/views/system/role-quick-nav/components/modal.vue create mode 100644 apps/web-antd/src/views/system/role-quick-nav/config/form.ts create mode 100644 apps/web-antd/src/views/system/role-quick-nav/config/search.ts create mode 100644 apps/web-antd/src/views/system/role-quick-nav/config/table.ts create mode 100644 apps/web-antd/src/views/system/role-quick-nav/index.vue create mode 100644 apps/web-antd/src/views/system/role/components/menu-search-select.vue create mode 100644 apps/web-antd/src/views/system/role/components/quick-nav.vue diff --git a/apps/web-antd/src/views/business/store/settings/components/SalespersonQrCodePreview.vue b/apps/web-antd/src/views/business/store/settings/components/SalespersonQrCodePreview.vue index 3cb7e803..3006f79f 100644 --- a/apps/web-antd/src/views/business/store/settings/components/SalespersonQrCodePreview.vue +++ b/apps/web-antd/src/views/business/store/settings/components/SalespersonQrCodePreview.vue @@ -65,7 +65,9 @@ const downloadQRCode = async (name: string, isQr = false) => { // 创建a标签下载 const link = document.createElement('a'); // 创建a标签 link.href = canvas.toDataURL(); // 是canvas对象的一种方法,用于将canvas对象转换为base64位编码 - link.setAttribute('download', `${doctorInfo.value.doctor.name}${name}.png`); // 利用了a标签的download 来下载 canvas图片 + // 使用推广员名称或门店名称作为文件名 + const fileName = data.value?.nick_name || data.value?.qr_code?.store?.name || '推广二维码'; + link.setAttribute('download', `${fileName}${name}.png`); // 利用了a标签的download 来下载 canvas图片 link.style.display = 'none'; // 将图片隐藏起来 document.body.append(link); // 插入到其中 link.click(); diff --git a/apps/web-antd/src/views/dashboard/workspace/index.vue b/apps/web-antd/src/views/dashboard/workspace/index.vue index ab984bfa..e2da28ce 100644 --- a/apps/web-antd/src/views/dashboard/workspace/index.vue +++ b/apps/web-antd/src/views/dashboard/workspace/index.vue @@ -24,6 +24,7 @@ import { openWindow } from '@vben/utils'; import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue'; import {getQuickMenuApi} from "#/views/system/admin/api"; +import {getLatestNotice} from "#/views/system/notice/api"; const userStore = useUserStore(); @@ -95,32 +96,17 @@ const todoItems = ref([ title: '修复UI显示问题', }, ]); -const trendItems: WorkbenchTrendItem[] = [ - { - avatar: 'svg:logo', - content: `药师审方功能上线啦!`, - date: '2025-5-26', - title: '萧康云医', - }, - { - avatar: 'svg:logo', - content: `诊所、医生、药师基础功能已完成`, - date: '2025-4-30', - title: '萧康云医', - }, - { - avatar: 'svg:logo', - content: `医生开方功能上线啦!`, - date: '2025-3-11', - title: '萧康云医', - }, - { - avatar: 'svg:logo', - content: `萧康云医上线啦!`, - date: '2025-1-7', - title: '萧康云医', - }, -]; +const trendItems = ref([]); + +/** + * 获取最新公告 + */ +function getLatestNoticeList() { + getLatestNotice(10).then((res) => { + trendItems.value = res; + }); +} +getLatestNoticeList(); const router = useRouter(); diff --git a/apps/web-antd/src/views/system/menu/api/index.ts b/apps/web-antd/src/views/system/menu/api/index.ts index 116f61f1..eee8c38c 100644 --- a/apps/web-antd/src/views/system/menu/api/index.ts +++ b/apps/web-antd/src/views/system/menu/api/index.ts @@ -42,6 +42,14 @@ export async function getMenuTreeOptionSelect() { }); } +/** + * 搜索菜单(支持按 title 模糊搜索) + * @param data 搜索参数 { keyword: string, filter_by_user?: number } + */ +export async function searchMenu(data: { keyword: string; filter_by_user?: number }) { + return requestClient.get(`${prefix}search-menu`, { params: data }); +} + /** * 获取详情 * @param id diff --git a/apps/web-antd/src/views/system/notice/api/index.ts b/apps/web-antd/src/views/system/notice/api/index.ts new file mode 100644 index 00000000..4343f470 --- /dev/null +++ b/apps/web-antd/src/views/system/notice/api/index.ts @@ -0,0 +1,50 @@ +import { requestClient } from '#/api/request'; + +const prefix = 'announcement/'; +/** + * 分页查询公告列表 + * @param data + */ +export async function getNoticeList(data: any) { + return requestClient.get(`${prefix}list`, { params: data }); +} + +/** + * 获取最新公告(用于工作台) + * @param limit + */ +export async function getLatestNotice(limit: number = 10) { + return requestClient.get(`${prefix}latest`, { params: { limit } }); +} + +/** + * 获取详情 + * @param id + */ +export async function getNoticeInfo(id: number) { + return requestClient.get(`${prefix}detail`, { params: { id } }); +} + +/** + * 新增公告 + * @param data + */ +export async function createNotice(data: Record) { + return requestClient.post(`${prefix}create`, data); +} + +/** + * 编辑公告 + * @param data + */ +export async function updateNotice(data: Record) { + return requestClient.post(`${prefix}update`, data); +} + +/** + * 删除公告 + * @param data + */ +export async function deleteNotice(data: Record) { + return requestClient.post(`${prefix}delete`, data); +} diff --git a/apps/web-antd/src/views/system/notice/components/modal.vue b/apps/web-antd/src/views/system/notice/components/modal.vue new file mode 100644 index 00000000..ebc475ec --- /dev/null +++ b/apps/web-antd/src/views/system/notice/components/modal.vue @@ -0,0 +1,61 @@ + + diff --git a/apps/web-antd/src/views/system/notice/config/form.ts b/apps/web-antd/src/views/system/notice/config/form.ts new file mode 100644 index 00000000..972daedc --- /dev/null +++ b/apps/web-antd/src/views/system/notice/config/form.ts @@ -0,0 +1,93 @@ +import type { VbenFormProps } from '#/adapter/form'; + +export const modalFormProps: VbenFormProps = { + wrapperClass: 'grid-cols-12', // 24栅格, + commonConfig: { + formItemClass: 'col-span-6', + // 所有表单项 + componentProps: { + class: 'w-full', + }, + }, + layout: 'horizontal', + schema: [ + { + component: 'VbenInput', + fieldName: 'id', + label: 'ID', + dependencies: { + show: false, + triggerFields: ['id'], + }, + }, + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入标题', + }, + fieldName: 'title', + label: '标题', + rules: 'required', + }, + { + component: 'VbenTextarea', + componentProps: { + placeholder: '请输入内容', + rows: 4, + }, + fieldName: 'content', + label: '内容', + formItemClass: 'col-span-12', + rules: 'required', + }, + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入头像/图标(如:svg:logo)', + }, + fieldName: 'avatar', + label: '头像/图标', + defaultValue: 'svg:logo', + rules: 'required', + }, + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入日期(格式:2025-5-26)', + }, + fieldName: 'date', + label: '日期', + rules: 'required', + }, + { + component: 'InputNumber', + componentProps: { + placeholder: '请输入排序', + }, + defaultValue: 0, + fieldName: 'sort', + label: '排序', + rules: 'required', + }, + { + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '启用', + value: 1, + }, + { + label: '禁用', + value: 0, + }, + ], + }, + defaultValue: 1, + fieldName: 'status', + label: '状态', + rules: 'required', + }, + ], + showDefaultActions: false, +}; diff --git a/apps/web-antd/src/views/system/notice/config/search.ts b/apps/web-antd/src/views/system/notice/config/search.ts new file mode 100644 index 00000000..3890a82b --- /dev/null +++ b/apps/web-antd/src/views/system/notice/config/search.ts @@ -0,0 +1,45 @@ +import type { VbenFormProps } from '#/adapter/form'; + +export const formOptions: VbenFormProps = { + wrapperClass: 'grid-cols-12', + commonConfig: { + formItemClass: 'col-span-6', + componentProps: { + class: 'w-full', + }, + }, + layout: 'horizontal', + schema: [ + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入标题', + }, + fieldName: 'title', + label: '标题', + }, + { + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '全部', + value: '', + }, + { + label: '启用', + value: 1, + }, + { + label: '禁用', + value: 0, + }, + ], + }, + defaultValue: '', + fieldName: 'status', + label: '状态', + }, + ], + showDefaultActions: false, +}; diff --git a/apps/web-antd/src/views/system/notice/config/table.ts b/apps/web-antd/src/views/system/notice/config/table.ts new file mode 100644 index 00000000..78dcfd70 --- /dev/null +++ b/apps/web-antd/src/views/system/notice/config/table.ts @@ -0,0 +1,84 @@ +import type { VxeGridProps } from '#/adapter/vxe-table'; + +import { getNoticeList } from '../api'; + +interface RowType { + id: string; + title: string; + content: string; + avatar: string; + date: string; + sort: number; + status: number; + created_at: string; +} + +export const gridOptions: VxeGridProps = { + checkboxConfig: { + highlight: true, + labelField: '', + }, + columnConfig: { + useKey: true, + }, + rowConfig: { + useKey: true, + }, + columns: [ + { type: 'checkbox', width: 60 }, + { field: 'id', align: 'left', title: 'ID', width: 100 }, + { field: 'title', align: 'left', title: '标题' }, + { field: 'content', align: 'left', title: '内容', width: 300 }, + { field: 'avatar', title: '头像/图标' }, + { field: 'date', title: '日期', width: 150 }, + { field: 'sort', title: '排序', width: 100 }, + { + field: 'status', + title: '状态', + width: 100, + slots: { default: 'status' }, + }, + { field: 'created_at', title: '创建时间' }, + { + type: 'html', + align: 'right', + title: '操作', + slots: { default: 'action' }, + width: 200, + }, + ], + keepSource: true, + pagerConfig: {}, + proxyConfig: { + ajax: { + // 请求后端接口方法 + query: async ({ page }, formValues) => { + return await getNoticeList({ + page: page.currentPage, + pageSize: page.pageSize, + ...formValues, + }); + }, + }, + }, + height: 'auto', + border: false, + toolbarConfig: { + // 是否显示搜索表单控制按钮 + // @ts-ignore 正式环境时有完整的类型声明 + search: true, + refresh: true, // 刷新 + print: false, // 打印 + export: false, // 导出 + // custom: true, // 自定义列 + zoom: true, // 最大化最小化 + slots: { + buttons: 'toolbar-buttons', + }, + custom: { + // 自定义列-图标 + icon: 'vxe-icon-menu', + }, + }, + showOverflow: false, +}; diff --git a/apps/web-antd/src/views/system/notice/index.vue b/apps/web-antd/src/views/system/notice/index.vue new file mode 100644 index 00000000..58a66ece --- /dev/null +++ b/apps/web-antd/src/views/system/notice/index.vue @@ -0,0 +1,135 @@ + + + diff --git a/apps/web-antd/src/views/system/role-quick-nav/api/index.ts b/apps/web-antd/src/views/system/role-quick-nav/api/index.ts new file mode 100644 index 00000000..bcaf4de6 --- /dev/null +++ b/apps/web-antd/src/views/system/role-quick-nav/api/index.ts @@ -0,0 +1,58 @@ +import { requestClient } from '#/api/request'; + +const prefix = 'role-quick-nav/'; +/** + * 分页查询快捷导航列表 + * @param data + */ +export async function getRoleQuickNavList(data: any) { + return requestClient.get(`${prefix}list`, { params: data }); +} + +/** + * 获取详情 + * @param id + */ +export async function getRoleQuickNavInfo(id: number) { + return requestClient.get(`${prefix}detail`, { params: { id } }); +} + +/** + * 新增快捷导航 + * @param data + */ +export async function createRoleQuickNav(data: Record) { + return requestClient.post(`${prefix}create`, data); +} + +/** + * 编辑快捷导航 + * @param data + */ +export async function updateRoleQuickNav(data: Record) { + return requestClient.post(`${prefix}update`, data); +} + +/** + * 删除快捷导航 + * @param data + */ +export async function deleteRoleQuickNav(data: Record) { + return requestClient.post(`${prefix}delete`, data); +} + +/** + * 根据角色ID获取快捷导航 + * @param roleId + */ +export async function getRoleQuickNavByRoleId(roleId: number) { + return requestClient.get(`${prefix}get-by-role-id`, { params: { role_id: roleId } }); +} + +/** + * 批量保存快捷导航 + * @param data + */ +export async function batchSaveRoleQuickNav(data: Record) { + return requestClient.post(`${prefix}batch-save`, data); +} diff --git a/apps/web-antd/src/views/system/role-quick-nav/components/modal.vue b/apps/web-antd/src/views/system/role-quick-nav/components/modal.vue new file mode 100644 index 00000000..80546531 --- /dev/null +++ b/apps/web-antd/src/views/system/role-quick-nav/components/modal.vue @@ -0,0 +1,61 @@ + + diff --git a/apps/web-antd/src/views/system/role-quick-nav/config/form.ts b/apps/web-antd/src/views/system/role-quick-nav/config/form.ts new file mode 100644 index 00000000..1a024620 --- /dev/null +++ b/apps/web-antd/src/views/system/role-quick-nav/config/form.ts @@ -0,0 +1,107 @@ +import type { VbenFormProps } from '#/adapter/form'; + +import { getMenuTreeOptionSelect } from '#/views/system/menu/api'; +import { getRoleOption } from '#/views/system/role/api'; + +export const modalFormProps: VbenFormProps = { + wrapperClass: 'grid-cols-12', // 24栅格, + commonConfig: { + formItemClass: 'col-span-6', + // 所有表单项 + componentProps: { + class: 'w-full', + }, + }, + layout: 'horizontal', + schema: [ + { + component: 'VbenInput', + fieldName: 'id', + label: 'ID', + dependencies: { + show: false, + triggerFields: ['id'], + }, + }, + { + component: 'ApiSelect', + componentProps: { + api: getRoleOption, + labelField: 'name', + valueField: 'id', + placeholder: '请选择角色', + }, + fieldName: 'role_id', + label: '角色', + rules: 'required', + }, + { + component: 'ApiTreeSelect', + componentProps: { + childrenField: 'children', + labelField: 'title', + valueField: 'id', + api: getMenuTreeOptionSelect, + placeholder: '请选择菜单(选择后自动填充标题、图标、地址)', + onChange: (value: any, formApi: any) => { + // 选择菜单后,自动填充title、icon、url + if (value) { + // 这里需要通过API获取菜单详情来填充 + // 暂时由后端处理 + } + }, + }, + fieldName: 'menu_id', + label: '菜单', + defaultValue: 0, + }, + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入标题', + }, + fieldName: 'title', + label: '标题', + rules: 'required', + }, + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入图标', + }, + fieldName: 'icon', + label: '图标', + rules: 'required', + }, + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入跳转地址', + }, + fieldName: 'url', + label: '跳转地址', + rules: 'required', + }, + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入颜色(如:#00d8ff)', + }, + fieldName: 'color', + label: '颜色', + defaultValue: '#00d8ff', + rules: 'required', + }, + { + component: 'InputNumber', + componentProps: { + placeholder: '请输入排序', + }, + defaultValue: 0, + fieldName: 'sort', + label: '排序', + rules: 'required', + }, + ], + showDefaultActions: false, +}; diff --git a/apps/web-antd/src/views/system/role-quick-nav/config/search.ts b/apps/web-antd/src/views/system/role-quick-nav/config/search.ts new file mode 100644 index 00000000..35d8fade --- /dev/null +++ b/apps/web-antd/src/views/system/role-quick-nav/config/search.ts @@ -0,0 +1,37 @@ +import type { VbenFormProps } from '#/adapter/form'; + +import { getRoleOption } from '#/views/system/role/api'; + +export const formOptions: VbenFormProps = { + wrapperClass: 'grid-cols-12', + commonConfig: { + formItemClass: 'col-span-6', + componentProps: { + class: 'w-full', + }, + }, + layout: 'horizontal', + schema: [ + { + component: 'ApiSelect', + componentProps: { + api: getRoleOption, + labelField: 'name', + valueField: 'id', + placeholder: '请选择角色', + allowClear: true, + }, + fieldName: 'role_id', + label: '角色', + }, + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入标题', + }, + fieldName: 'title', + label: '标题', + }, + ], + showDefaultActions: false, +}; diff --git a/apps/web-antd/src/views/system/role-quick-nav/config/table.ts b/apps/web-antd/src/views/system/role-quick-nav/config/table.ts new file mode 100644 index 00000000..b69e8f2b --- /dev/null +++ b/apps/web-antd/src/views/system/role-quick-nav/config/table.ts @@ -0,0 +1,80 @@ +import type { VxeGridProps } from '#/adapter/vxe-table'; + +import { getRoleQuickNavList } from '../api'; + +interface RowType { + id: string; + role_id: number; + menu_id: number; + title: string; + icon: string; + url: string; + color: string; + sort: number; + created_at: string; +} + +export const gridOptions: VxeGridProps = { + checkboxConfig: { + highlight: true, + labelField: '', + }, + columnConfig: { + useKey: true, + }, + rowConfig: { + useKey: true, + }, + columns: [ + { type: 'checkbox', width: 60 }, + { field: 'id', align: 'left', title: 'ID', width: 100 }, + { field: 'role_id', align: 'left', title: '角色ID', width: 100 }, + { field: 'title', align: 'left', title: '标题' }, + { field: 'icon', title: '图标', slots: { default: 'icon' } }, + { field: 'url', title: '跳转地址' }, + { field: 'color', title: '颜色', width: 120 }, + { field: 'sort', title: '排序', width: 100 }, + { field: 'created_at', title: '创建时间' }, + { + type: 'html', + align: 'right', + title: '操作', + slots: { default: 'action' }, + width: 200, + }, + ], + keepSource: true, + pagerConfig: {}, + proxyConfig: { + ajax: { + // 请求后端接口方法 + query: async ({ page }, formValues) => { + return await getRoleQuickNavList({ + page: page.currentPage, + pageSize: page.pageSize, + ...formValues, + }); + }, + }, + }, + height: 'auto', + border: false, + toolbarConfig: { + // 是否显示搜索表单控制按钮 + // @ts-ignore 正式环境时有完整的类型声明 + search: true, + refresh: true, // 刷新 + print: false, // 打印 + export: false, // 导出 + // custom: true, // 自定义列 + zoom: true, // 最大化最小化 + slots: { + buttons: 'toolbar-buttons', + }, + custom: { + // 自定义列-图标 + icon: 'vxe-icon-menu', + }, + }, + showOverflow: false, +}; diff --git a/apps/web-antd/src/views/system/role-quick-nav/index.vue b/apps/web-antd/src/views/system/role-quick-nav/index.vue new file mode 100644 index 00000000..18a7383c --- /dev/null +++ b/apps/web-antd/src/views/system/role-quick-nav/index.vue @@ -0,0 +1,133 @@ + + + diff --git a/apps/web-antd/src/views/system/role/components/auth-menu.vue b/apps/web-antd/src/views/system/role/components/auth-menu.vue index 0c716050..dd7d9642 100644 --- a/apps/web-antd/src/views/system/role/components/auth-menu.vue +++ b/apps/web-antd/src/views/system/role/components/auth-menu.vue @@ -1,5 +1,5 @@