diff --git a/.env.development b/.env.development index f740e56..412c509 100644 --- a/.env.development +++ b/.env.development @@ -12,7 +12,8 @@ VITE_APP_BASE_API='/dev-api' VITE_LOCALHOUSET=true # 本地开发环境 VITE_BASE_API='http://192.168.0.212:8082' - +# 默认头像 +VITE_DEFAULT_USER_IMAGE='https://ts3.tc.mm.bing.net/th?id=ORMS.48a27ca8353fa47bf0dc8df38854d0e1&pid=Wdp&w=312&h=172&qlt=90&c=1&rs=1&dpr=1&p=0' # 应用访问路径 例如使用前缀 /admin/ VITE_APP_CONTEXT_PATH='/' diff --git a/src/api/system/conversation/index.ts b/src/api/system/conversation/index.ts index 8088ea6..0f94170 100644 --- a/src/api/system/conversation/index.ts +++ b/src/api/system/conversation/index.ts @@ -10,7 +10,7 @@ import request from '@/utils/request'; */ export const listCsSession = (query?: CsSessionQuery): AxiosPromise> => { return request({ - url: '/conversation/csSession/list', + url: '/cs/session/list', method: 'get', params: query }); @@ -22,7 +22,7 @@ export const listCsSession = (query?: CsSessionQuery): AxiosPromise => { return request({ - url: '/conversation/csSession/' + sessionId, + url: '/cs/session/' + sessionId, method: 'get' }); }; @@ -33,7 +33,7 @@ export const getCsSession = (sessionId: string | number): AxiosPromise { return request({ - url: '/conversation/csSession', + url: '/cs/session', method: 'post', data: data }); @@ -45,7 +45,7 @@ export const addCsSession = (data: CsSessionForm) => { */ export const updateCsSession = (data: CsSessionForm) => { return request({ - url: '/conversation/csSession', + url: '/cs/session', method: 'put', data: data }); @@ -58,7 +58,17 @@ export const updateCsSession = (data: CsSessionForm) => { */ export const delCsSession = (sessionId: string | number | Array) => { return request({ - url: '/conversation/csSession/' + sessionId, + url: '/cs/session/' + sessionId, + method: 'delete' + }); +}; +/** + * 关闭客服会话 + * @param sessionId + */ +export const closeCsSession = (sessionId: string) => { + return request({ + url: '/cs/session/' + sessionId + '/end', method: 'delete' }); }; diff --git a/src/api/system/conversation/types.ts b/src/api/system/conversation/types.ts index 6971f25..8342c56 100644 --- a/src/api/system/conversation/types.ts +++ b/src/api/system/conversation/types.ts @@ -2,7 +2,7 @@ export interface CsSessionVO { /** * 主键(雪花) */ - sessionId: string | number; + sessionId: string; /** * 会话编号 */ @@ -10,11 +10,11 @@ export interface CsSessionVO { /** * APP 用户 */ - userId: string | number; + userId: string; /** * 关联设备,可空 */ - deviceId: string | number; + deviceId: string; /** * 问题类型 */ @@ -30,7 +30,7 @@ export interface CsSessionVO { /** * 当前接待客服 */ - agentId: string | number; + agentId: string; /** * 接入方式(bot/bot_to_human/human) */ @@ -61,7 +61,7 @@ export interface CsSessionForm extends BaseEntity { /** * 主键(雪花) */ - sessionId?: string | number; + sessionId?: string; /** * 会话编号 */ @@ -69,11 +69,11 @@ export interface CsSessionForm extends BaseEntity { /** * APP 用户 */ - userId?: string | number; + userId?: string; /** * 关联设备,可空 */ - deviceId?: string | number; + deviceId?: string; /** * 问题类型 */ @@ -89,7 +89,7 @@ export interface CsSessionForm extends BaseEntity { /** * 当前接待客服 */ - agentId?: string | number; + agentId?: string; /** * 接入方式(bot/bot_to_human/human) */ @@ -124,11 +124,11 @@ export interface CsSessionQuery extends PageQuery { /** * APP 用户 */ - userId?: string | number; + userId?: string; /** * 关联设备,可空 */ - deviceId?: string | number; + deviceId?: string; /** * 问题类型 */ @@ -144,7 +144,7 @@ export interface CsSessionQuery extends PageQuery { /** * 当前接待客服 */ - agentId?: string | number; + agentId?: string; /** * 接入方式(bot/bot_to_human/human) */ @@ -169,8 +169,4 @@ export interface CsSessionQuery extends PageQuery { * 最后消息时间 */ lastMessageTime?: string; - /** - * 日期范围参数 - */ - params?: any; } diff --git a/src/api/system/cskb/index.ts b/src/api/system/cskb/index.ts index effb82c..d9f89c2 100644 --- a/src/api/system/cskb/index.ts +++ b/src/api/system/cskb/index.ts @@ -10,7 +10,7 @@ import request from '@/utils/request'; */ export const listCsKb = (query?: CsKbQuery): AxiosPromise> => { return request({ - url: '/system/csKb/list', + url: '/cs/kb/list', method: 'get', params: query }); @@ -22,7 +22,7 @@ export const listCsKb = (query?: CsKbQuery): AxiosPromise> => */ export const getCsKb = (kbId: string | number): AxiosPromise => { return request({ - url: '/system/csKb/' + kbId, + url: '/cs/kb/' + kbId, method: 'get' }); }; @@ -33,7 +33,7 @@ export const getCsKb = (kbId: string | number): AxiosPromise => { */ export const addCsKb = (data: CsKbForm) => { return request({ - url: '/system/csKb', + url: '/cs/kb', method: 'post', data: data }); @@ -45,7 +45,7 @@ export const addCsKb = (data: CsKbForm) => { */ export const updateCsKb = (data: CsKbForm) => { return request({ - url: '/system/csKb', + url: '/cs/kb', method: 'put', data: data }); @@ -58,7 +58,31 @@ export const updateCsKb = (data: CsKbForm) => { */ export const delCsKb = (kbId: string | number | Array) => { return request({ - url: '/system/csKb/' + kbId, + url: '/cs/kb/' + kbId, method: 'delete' }); }; +/** + * 移动客服知识库 + * @param kbId + * @param direction + */ +export const moveSkb = (kbId: string, direction: string = 'up'): AxiosPromise<{ moved: boolean }> => { + return request({ + url: '/cs/kb/move', + method: 'put', + data: { + kbId, + direction, + } + }); +}; +/** + * 知识库总览 + */ +export const overViewCsKb = (): AxiosPromise<{ "enabledCount": number, "stoppedCount": number }> => { + return request({ + url: '/cs/kb/overview', + method: 'get', + }); +}; diff --git a/src/api/system/cskb/types.ts b/src/api/system/cskb/types.ts index 1766c3e..bee4fe4 100644 --- a/src/api/system/cskb/types.ts +++ b/src/api/system/cskb/types.ts @@ -1,32 +1,72 @@ export interface CsKbVO { /** - * 主键(雪花) + * 快捷按钮 */ - kbId: string | number; + buttons: CzCsKbButton[]; /** - * 知识编号,如 KB-001 - */ - kbNo: string; - /** - * 问题标题 - */ - title: string; - /** - * 分类(设备配网/耗材滤芯/固件升级/设备报修;固件升级仅文案分类) + * 分类 */ category: string; /** - * 机器人回复内容 + * 内容 */ content: string; /** - * 排序权重,数字越小越靠前 + * 创建时间 + */ + createTime: string; + /** + * 知识库主键 + */ + kbId: string; + /** + * 知识编号 + */ + kbNo: string; + /** + * 排序 */ sort: number; /** * 状态(0停用 1启用) */ - status: boolean; + status: string; + /** + * 状态名称 + */ + statusName: string; + /** + * 标题 + */ + title: string; +} + +/** + * 知识库快捷按钮 cz_cs_kb_button。 + * + * CzCsKbButton + */ +export type CzCsKbButton = { + /** + * 按钮编码 + */ + buttonCode: string; + /** + * 主键 + */ + buttonId: string; + /** + * 知识库ID + */ + kbId: string; + /** + * 按钮文案 + */ + label: string; + /** + * 排序 + */ + sort: number; } export interface CsKbForm extends BaseEntity { @@ -57,7 +97,7 @@ export interface CsKbForm extends BaseEntity { /** * 状态(0停用 1启用) */ - status?: boolean; + status?: string; /** * 创建部门 */ @@ -104,7 +144,7 @@ export interface CsKbQuery extends PageQuery { /** * 状态(0停用 1启用) */ - status?: boolean; + status?: string; /** * 创建部门 */ diff --git a/src/api/system/cssAgent/index.ts b/src/api/system/cssAgent/index.ts index f625985..0c57d1b 100644 --- a/src/api/system/cssAgent/index.ts +++ b/src/api/system/cssAgent/index.ts @@ -10,7 +10,7 @@ import request from '@/utils/request'; */ export const listCsAgent = (query?: CsAgentQuery): AxiosPromise> => { return request({ - url: '/system/csAgent/list', + url: '/cs/agent/list', method: 'get', params: query }); @@ -22,7 +22,7 @@ export const listCsAgent = (query?: CsAgentQuery): AxiosPromise => { return request({ - url: '/system/csAgent/' + agentId, + url: '/cs/agent/' + agentId, method: 'get' }); }; @@ -33,7 +33,7 @@ export const getCsAgent = (agentId: string | number): AxiosPromise => */ export const addCsAgent = (data: CsAgentForm) => { return request({ - url: '/system/csAgent', + url: '/cs/agent', method: 'post', data: data }); @@ -45,7 +45,7 @@ export const addCsAgent = (data: CsAgentForm) => { */ export const updateCsAgent = (data: CsAgentForm) => { return request({ - url: '/system/csAgent', + url: '/cs/agent', method: 'put', data: data }); @@ -58,7 +58,51 @@ export const updateCsAgent = (data: CsAgentForm) => { */ export const delCsAgent = (agentId: string | number | Array) => { return request({ - url: '/system/csAgent/' + agentId, + url: '/cs/agent/' + agentId, method: 'delete' }); }; + +/** + * 客服人员 在线状态汇总 + */ +export const statCsAgent = (): AxiosPromise<{ + total: number, + onlineCount: number, + busyCount: number, + offlineCount: number +}> => { + return request({ + url: '/cs/agent/stat', + method: 'get', + }); +}; + + +/** + * 客服人员 重置密码 + */ +export const ResetPwdCsAgent = (agentId: string, password: string = '12345678') => { + return request({ + url: '/cs/agent/resetPwd', + method: 'put', + data: { + agentId: agentId, + password: password + } + }); +}; + + +/** + * 客服人员 批量禁用账号 + */ +export const BatchDisableCsAgent = (agentIds: string[]) => { + return request({ + url: '/cs/agent/batchDisable', + method: 'put', + data: { + agentIds: agentIds, + } + }); +}; diff --git a/src/api/system/cssAgent/types.ts b/src/api/system/cssAgent/types.ts index 8121990..8555920 100644 --- a/src/api/system/cssAgent/types.ts +++ b/src/api/system/cssAgent/types.ts @@ -2,7 +2,7 @@ export interface CsAgentVO { /** * 主键(雪花) */ - agentId: string | number; + agentId: string; /** * 客服编号,如 KF-001 */ @@ -34,18 +34,18 @@ export interface CsAgentVO { /** * 在线状态(0离线 1在线 2忙碌) */ - onlineStatus: boolean; + onlineStatus: string; /** * 账号状态(0启用 1禁用) */ - status: boolean; + status: string; } export interface CsAgentForm extends BaseEntity { /** * 主键(雪花) */ - agentId?: string | number; + agentId?: string; /** * 客服编号,如 KF-001 */ @@ -77,11 +77,11 @@ export interface CsAgentForm extends BaseEntity { /** * 在线状态(0离线 1在线 2忙碌) */ - onlineStatus?: boolean; + onlineStatus?: string; /** * 账号状态(0启用 1禁用) */ - status?: boolean; + status?: string; /** * 创建部门 */ @@ -136,11 +136,11 @@ export interface CsAgentQuery extends PageQuery { /** * 在线状态(0离线 1在线 2忙碌) */ - onlineStatus?: boolean; + onlineStatus?: string; /** * 账号状态(0启用 1禁用) */ - status?: boolean; + status?: string; /** * 创建部门 */ diff --git a/src/api/system/customer/index.ts b/src/api/system/customer/index.ts index 2b3ee6c..8f830f6 100644 --- a/src/api/system/customer/index.ts +++ b/src/api/system/customer/index.ts @@ -2,6 +2,9 @@ import type {UserForm, UserQuery, UserVO} from '@/api/system/customer/types'; import type {PageResult} from '@/api/types'; import type {AxiosPromise} from '@/utils/api-types'; import request from '@/utils/request'; +import {blobValidate} from "@/utils/ruoyi"; +import {saveBlob} from "@/utils/save"; +import type {LoadingInstance} from "element-plus"; /** * 查询App用户列表 @@ -10,7 +13,7 @@ import request from '@/utils/request'; */ export const listUser = (query?: UserQuery): AxiosPromise> => { return request({ - url: '/system/user/list', + url: '/customer/user/list', method: 'get', params: query }); @@ -20,9 +23,9 @@ export const listUser = (query?: UserQuery): AxiosPromise> => * 查询App用户详细 * @param userId */ -export const getUser = (userId: string | number): AxiosPromise => { +export const getUser = (userId: string): AxiosPromise => { return request({ - url: '/system/user/' + userId, + url: '/customer/user/' + userId, method: 'get' }); }; @@ -33,7 +36,7 @@ export const getUser = (userId: string | number): AxiosPromise => { */ export const addUser = (data: UserForm) => { return request({ - url: '/system/user', + url: '/customer/user', method: 'post', data: data }); @@ -45,12 +48,62 @@ export const addUser = (data: UserForm) => { */ export const updateUser = (data: UserForm) => { return request({ - url: '/system/user', + url: '/customer/user', method: 'put', data: data }); }; +/** + * 批量禁用App用户 + * @param userIds + */ +export const batchDisableUsers = (userIds: string[]) => { + return request({ + url: '/customer/user/batchDisable', + method: 'put', + data: { + userIds: userIds + } + }); +}; +/** + * 批量导出选择的App用户 + * @param userIds + * @param fileName + */ +export const exportSelectedUsers = (userIds: string[], fileName: string) => { + request({ + url: '/customer/user/exportSelected', + method: 'post', + data: { + userIds: userIds + } + }).then(async (resp: any) => { + + }) + .catch((r: any) => { + + }); +}; + + +/** + * 修改App用户 状态 启用/停用 + * @param userId + * @param status + */ +export const updateUserStatus = (userId: string, status: '0' | '1') => { + return request({ + url: '/customer/user/changeStatus', + method: 'put', + data: { + status: status, + userId: userId + } + }); +}; + /** * 删除App用户 @@ -58,7 +111,25 @@ export const updateUser = (data: UserForm) => { */ export const delUser = (userId: string | number | Array) => { return request({ - url: '/system/user/' + userId, + url: '/customer/user/' + userId, method: 'delete' }); }; +/** + * 重置App用户密码 + * @param userId + * @param password + */ +export const resetPwdUser = (userId: string, password: string) => { + return request({ + url: '/customer/user/resetPwd', + method: 'put', + data: { + userId: userId, + password: password + }, + headers: { + 'Content-Type': 'application/json' + } + }); +}; diff --git a/src/api/system/customer/types.ts b/src/api/system/customer/types.ts index 154efbe..d9ba817 100644 --- a/src/api/system/customer/types.ts +++ b/src/api/system/customer/types.ts @@ -2,7 +2,7 @@ export interface UserVO { /** * 用户ID(雪花ID) */ - userId: string | number; + userId: string; /** * 用户账号(默认使用手机号) */ @@ -22,7 +22,7 @@ export interface UserVO { /** * 头像 oss_id */ - avatarOssId: string | number; + avatarOssId: string; /** * 头像 URL */ @@ -58,7 +58,7 @@ export interface UserVO { /** * 账号状态(0正常 1停用) */ - status: boolean; + status: string; /** * 注销时间;停用仍用 status */ @@ -85,7 +85,7 @@ export interface UserForm extends BaseEntity { /** * 用户ID(雪花ID) */ - userId?: string | number; + userId?: string; /** * 用户账号(默认使用手机号) */ @@ -105,7 +105,7 @@ export interface UserForm extends BaseEntity { /** * 头像 oss_id */ - avatarOssId?: string | number; + avatarOssId?: string; /** * 头像 URL */ @@ -141,7 +141,7 @@ export interface UserForm extends BaseEntity { /** * 账号状态(0正常 1停用) */ - status?: boolean; + status?: string; /** * 注销时间;停用仍用 status */ @@ -158,22 +158,11 @@ export interface UserForm extends BaseEntity { * 登录次数 */ loginCount?: number; - /** - * 创建者 - */ - createBy?: number; + /** * 创建时间 */ createTime?: string; - /** - * 更新者 - */ - updateBy?: number; - /** - * 更新时间 - */ - updateTime?: string; /** * 备注 */ @@ -181,6 +170,8 @@ export interface UserForm extends BaseEntity { } export interface UserQuery extends PageQuery { + endTime?: string; + beginTime?: string; /** * 用户账号(默认使用手机号) */ @@ -236,7 +227,7 @@ export interface UserQuery extends PageQuery { /** * 账号状态(0正常 1停用) */ - status?: boolean; + status?: string; /** * 注销时间;停用仍用 status */ @@ -253,24 +244,8 @@ export interface UserQuery extends PageQuery { * 登录次数 */ loginCount?: number; - /** - * 创建者 - */ - createBy?: number; /** * 创建时间 */ createTime?: string; - /** - * 更新者 - */ - updateBy?: number; - /** - * 更新时间 - */ - updateTime?: string; - /** - * 日期范围参数 - */ - params?: any; } diff --git a/src/api/system/dashboard/index.ts b/src/api/system/dashboard/index.ts new file mode 100644 index 0000000..ee80148 --- /dev/null +++ b/src/api/system/dashboard/index.ts @@ -0,0 +1,16 @@ +// 查询部门列表 +import request from "@/utils/request"; + +export const dashboardHome = () => { + return request({ + url: '/dashboard/home', + method: 'get', + }); +}; + +export const dashboardTvoc = (range: string = '24h') => { + return request({ + url: '/dashboard/tvoc?range=' + range, + method: 'get', + }); +}; diff --git a/src/assets/styles/components/_search-panel.scss b/src/assets/styles/components/_search-panel.scss index 2a17b1a..de3f5e6 100644 --- a/src/assets/styles/components/_search-panel.scss +++ b/src/assets/styles/components/_search-panel.scss @@ -2,16 +2,20 @@ // --- 表格工具列链接按钮(全局) --- .data-table .el-button.is-link { - width: 28px !important; - height: 28px !important; + width: unset !important; + height: unset !important; min-width: 28px !important; padding: 0 !important; border-radius: var(--app-radius-md) !important; - background: var(--app-accent-soft) !important; + //background: var(--app-accent-soft) !important; +} + +.data-table .el-button--primary.is-link { + color: #4a7cff; } .data-table .el-button.is-link + .el-button.is-link { - margin-left: 4px !important; + margin-left: 12px !important; } .search-wrap { @@ -37,11 +41,10 @@ overflow: hidden; max-height: 560px; opacity: 1; - transition: - max-height 0.32s cubic-bezier(0.22, 1, 0.36, 1), - opacity 0.24s ease, - padding-top 0.24s ease, - padding-bottom 0.24s ease; + transition: max-height 0.32s cubic-bezier(0.22, 1, 0.36, 1), + opacity 0.24s ease, + padding-top 0.24s ease, + padding-bottom 0.24s ease; } } @@ -73,9 +76,8 @@ border-bottom: 2px solid currentColor; color: var(--app-text-muted); transform: rotate(-135deg); - transition: - transform 0.24s ease, - color 0.24s ease; + transition: transform 0.24s ease, + color 0.24s ease; } } diff --git a/src/components/Echarts/index.vue b/src/components/Echarts/index.vue index b58d6a6..9429668 100644 --- a/src/components/Echarts/index.vue +++ b/src/components/Echarts/index.vue @@ -31,13 +31,13 @@ const emit = defineEmits<{ }>(); const chartRef = ref(); -// 3. 使用 shallowRef 优化性能,因为 ECharts 实例不需要深层响应式 const chartInstance = shallowRef(null); let resizeObserver: ResizeObserver | null = null; const initChart = () => { if (!chartRef.value) return; + // 如果实例已存在,先销毁重建 if (chartInstance.value) { chartInstance.value.dispose(); chartInstance.value = null; @@ -50,22 +50,22 @@ const initChart = () => { emit('ready', instance); emit('rendered'); - instance.on('click', (params: any) => { + instance.on('click', (params: echarts.ECElementEvent) => { emit('click', params); }); }; const updateChart = () => { - if (chartInstance.value) { - chartInstance.value.setOption(props.options, { - notMerge: false, - lazyUpdate: false - }); - emit('rendered'); - } + if (!chartInstance.value) return; + // clear: true 清除不存在的系列/坐标轴,切换不同数量数据时防止残留旧图形 + chartInstance.value.setOption(props.options, { + notMerge: false, + lazyUpdate: false, + clear: true + }); + emit('rendered'); }; -// ✅ 核心修复:支持 scale 缩放后重绘 const resizeChart = () => { if (!chartInstance.value) return; chartInstance.value.resize(); @@ -74,19 +74,24 @@ const resizeChart = () => { const getInstance = () => chartInstance.value; const exportAsImage = (type: 'png' | 'svg' | 'jpeg' = 'png') => { - return ( - chartInstance.value?.getDataURL({ - type, - pixelRatio: 2, - backgroundColor: '#fff' - }) || '' - ); + return chartInstance.value?.getDataURL({ + type, + pixelRatio: 2, + backgroundColor: '#fff' + }) || ''; }; +// 深度监听 options 变化自动更新图表 watch(() => props.options, updateChart, {deep: true}); onMounted(() => { - nextTick(() => initChart()); + nextTick(() => { + if (props.immediate) { + initChart(); + } + }); + + // 开启自适应时监听容器尺寸 if (props.autoResize && chartRef.value) { resizeObserver = new ResizeObserver(resizeChart); resizeObserver.observe(chartRef.value); @@ -95,8 +100,11 @@ onMounted(() => { }); onBeforeUnmount(() => { + // 【重点修复】顺序:先销毁监听,再销毁图表实例,防止内存泄漏和控制台警告 window.removeEventListener('resize', resizeChart); resizeObserver?.disconnect(); + resizeObserver = null; + chartInstance.value?.dispose(); chartInstance.value = null; }); diff --git a/src/components/Editor/cskb.vue b/src/components/Editor/cskb.vue index 14a13ec..9d0fba8 100644 --- a/src/components/Editor/cskb.vue +++ b/src/components/Editor/cskb.vue @@ -28,7 +28,6 @@

以上为示例格式

支持插入图片与链接;回复中可引用设备型号,帮助用户快速对号入座

-
{{ wordCount }}/2000字
diff --git a/src/components/FormItemTip/index.vue b/src/components/FormItemTip/index.vue index d4eabac..d9124de 100644 --- a/src/components/FormItemTip/index.vue +++ b/src/components/FormItemTip/index.vue @@ -1,10 +1,10 @@