9/30
1. 设备管理界面完成 2. 用户管理界面完成 3. 会话列表完成
This commit is contained in:
@@ -8,6 +8,12 @@ VITE_APP_ENV='development'
|
||||
# 开发环境
|
||||
VITE_APP_BASE_API='/dev-api'
|
||||
|
||||
# 本机环境
|
||||
VITE_LOCALHOUSET=true
|
||||
# 本地开发环境
|
||||
VITE_BASE_API='http://192.168.0.212:8082'
|
||||
|
||||
|
||||
# 应用访问路径 例如使用前缀 /admin/
|
||||
VITE_APP_CONTEXT_PATH='/'
|
||||
|
||||
|
||||
64
src/api/system/agreement/index.ts
Normal file
64
src/api/system/agreement/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type {AgreementForm, AgreementQuery, AgreementVO} from '@/api/system/agreement/types';
|
||||
import type {PageResult} from '@/api/types';
|
||||
import type {AxiosPromise} from '@/utils/api-types';
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 查询协议配置列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const listAgreement = (query?: AgreementQuery): AxiosPromise<PageResult<AgreementVO>> => {
|
||||
return request({
|
||||
url: '/system/agreement/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询协议配置详细
|
||||
* @param agreementId
|
||||
*/
|
||||
export const getAgreement = (agreementId: string | number): AxiosPromise<AgreementVO> => {
|
||||
return request({
|
||||
url: '/system/agreement/' + agreementId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增协议配置
|
||||
* @param data
|
||||
*/
|
||||
export const addAgreement = (data: AgreementForm) => {
|
||||
return request({
|
||||
url: '/system/agreement',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改协议配置
|
||||
* @param data
|
||||
*/
|
||||
export const updateAgreement = (data: AgreementForm) => {
|
||||
return request({
|
||||
url: '/system/agreement',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 删除协议配置
|
||||
* @param agreementId
|
||||
*/
|
||||
export const delAgreement = (agreementId: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/system/agreement/' + agreementId,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
156
src/api/system/agreement/types.ts
Normal file
156
src/api/system/agreement/types.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
export interface AgreementVO {
|
||||
/**
|
||||
* 主键(雪花)
|
||||
*/
|
||||
agreementId: string | number;
|
||||
/**
|
||||
* 协议编号
|
||||
*/
|
||||
agreementNo: string;
|
||||
/**
|
||||
* 类型(user/privacy/service)
|
||||
*/
|
||||
agreementType: string;
|
||||
/**
|
||||
* 协议名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
versionName: string;
|
||||
/**
|
||||
* 协议正文
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* 状态(0草稿 1已生效 2停用)
|
||||
*/
|
||||
status: boolean;
|
||||
/**
|
||||
* 生效时间
|
||||
*/
|
||||
publishTime: string;
|
||||
/**
|
||||
* 已生效类型;同类型仅一条,非生效或已删除为 NULL
|
||||
*/
|
||||
activeType: string;
|
||||
}
|
||||
|
||||
export interface AgreementForm extends BaseEntity {
|
||||
/**
|
||||
* 主键(雪花)
|
||||
*/
|
||||
agreementId?: string | number;
|
||||
/**
|
||||
* 协议编号
|
||||
*/
|
||||
agreementNo?: string;
|
||||
/**
|
||||
* 类型(user/privacy/service)
|
||||
*/
|
||||
agreementType?: string;
|
||||
/**
|
||||
* 协议名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
versionName?: string;
|
||||
/**
|
||||
* 协议正文
|
||||
*/
|
||||
content?: string;
|
||||
/**
|
||||
* 状态(0草稿 1已生效 2停用)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 生效时间
|
||||
*/
|
||||
publishTime?: string;
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
/**
|
||||
* 已生效类型;同类型仅一条,非生效或已删除为 NULL
|
||||
*/
|
||||
activeType?: string;
|
||||
}
|
||||
|
||||
export interface AgreementQuery extends PageQuery {
|
||||
/**
|
||||
* 协议编号
|
||||
*/
|
||||
agreementNo?: string;
|
||||
/**
|
||||
* 类型(user/privacy/service)
|
||||
*/
|
||||
agreementType?: string;
|
||||
/**
|
||||
* 协议名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
versionName?: string;
|
||||
/**
|
||||
* 协议正文
|
||||
*/
|
||||
content?: string;
|
||||
/**
|
||||
* 状态(0草稿 1已生效 2停用)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 生效时间
|
||||
*/
|
||||
publishTime?: string;
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
/**
|
||||
* 已生效类型;同类型仅一条,非生效或已删除为 NULL
|
||||
*/
|
||||
activeType?: string;
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
64
src/api/system/conversation/index.ts
Normal file
64
src/api/system/conversation/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type {CsSessionForm, CsSessionQuery, CsSessionVO} from '@/api/system/conversation/types';
|
||||
import type {PageResult} from '@/api/types';
|
||||
import type {AxiosPromise} from '@/utils/api-types';
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 查询客服会话列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const listCsSession = (query?: CsSessionQuery): AxiosPromise<PageResult<CsSessionVO>> => {
|
||||
return request({
|
||||
url: '/conversation/csSession/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询客服会话详细
|
||||
* @param sessionId
|
||||
*/
|
||||
export const getCsSession = (sessionId: string | number): AxiosPromise<CsSessionVO> => {
|
||||
return request({
|
||||
url: '/conversation/csSession/' + sessionId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增客服会话
|
||||
* @param data
|
||||
*/
|
||||
export const addCsSession = (data: CsSessionForm) => {
|
||||
return request({
|
||||
url: '/conversation/csSession',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改客服会话
|
||||
* @param data
|
||||
*/
|
||||
export const updateCsSession = (data: CsSessionForm) => {
|
||||
return request({
|
||||
url: '/conversation/csSession',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 删除客服会话
|
||||
* @param sessionId
|
||||
*/
|
||||
export const delCsSession = (sessionId: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/conversation/csSession/' + sessionId,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
176
src/api/system/conversation/types.ts
Normal file
176
src/api/system/conversation/types.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
export interface CsSessionVO {
|
||||
/**
|
||||
* 主键(雪花)
|
||||
*/
|
||||
sessionId: string | number;
|
||||
/**
|
||||
* 会话编号
|
||||
*/
|
||||
sessionNo: string;
|
||||
/**
|
||||
* APP 用户
|
||||
*/
|
||||
userId: string | number;
|
||||
/**
|
||||
* 关联设备,可空
|
||||
*/
|
||||
deviceId: string | number;
|
||||
/**
|
||||
* 问题类型
|
||||
*/
|
||||
problemType: string;
|
||||
/**
|
||||
* 用户问题摘要
|
||||
*/
|
||||
summary: string;
|
||||
/**
|
||||
* 状态(0机器人 1排队 2人工 3已结束)
|
||||
*/
|
||||
status: boolean;
|
||||
/**
|
||||
* 当前接待客服
|
||||
*/
|
||||
agentId: string | number;
|
||||
/**
|
||||
* 接入方式(bot/bot_to_human/human)
|
||||
*/
|
||||
accessType: string;
|
||||
/**
|
||||
* 排队序号
|
||||
*/
|
||||
queueNo: number;
|
||||
/**
|
||||
* 会话开始时间
|
||||
*/
|
||||
startTime: string;
|
||||
/**
|
||||
* 人工接入时间
|
||||
*/
|
||||
acceptTime: string;
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime: string;
|
||||
/**
|
||||
* 最后消息时间
|
||||
*/
|
||||
lastMessageTime: string;
|
||||
}
|
||||
|
||||
export interface CsSessionForm extends BaseEntity {
|
||||
/**
|
||||
* 主键(雪花)
|
||||
*/
|
||||
sessionId?: string | number;
|
||||
/**
|
||||
* 会话编号
|
||||
*/
|
||||
sessionNo?: string;
|
||||
/**
|
||||
* APP 用户
|
||||
*/
|
||||
userId?: string | number;
|
||||
/**
|
||||
* 关联设备,可空
|
||||
*/
|
||||
deviceId?: string | number;
|
||||
/**
|
||||
* 问题类型
|
||||
*/
|
||||
problemType?: string;
|
||||
/**
|
||||
* 用户问题摘要
|
||||
*/
|
||||
summary?: string;
|
||||
/**
|
||||
* 状态(0机器人 1排队 2人工 3已结束)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 当前接待客服
|
||||
*/
|
||||
agentId?: string | number;
|
||||
/**
|
||||
* 接入方式(bot/bot_to_human/human)
|
||||
*/
|
||||
accessType?: string;
|
||||
/**
|
||||
* 排队序号
|
||||
*/
|
||||
queueNo?: number;
|
||||
/**
|
||||
* 会话开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
/**
|
||||
* 人工接入时间
|
||||
*/
|
||||
acceptTime?: string;
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime?: string;
|
||||
/**
|
||||
* 最后消息时间
|
||||
*/
|
||||
lastMessageTime?: string;
|
||||
}
|
||||
|
||||
export interface CsSessionQuery extends PageQuery {
|
||||
/**
|
||||
* 会话编号
|
||||
*/
|
||||
sessionNo?: string;
|
||||
/**
|
||||
* APP 用户
|
||||
*/
|
||||
userId?: string | number;
|
||||
/**
|
||||
* 关联设备,可空
|
||||
*/
|
||||
deviceId?: string | number;
|
||||
/**
|
||||
* 问题类型
|
||||
*/
|
||||
problemType?: string;
|
||||
/**
|
||||
* 用户问题摘要
|
||||
*/
|
||||
summary?: string;
|
||||
/**
|
||||
* 状态(0机器人 1排队 2人工 3已结束)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 当前接待客服
|
||||
*/
|
||||
agentId?: string | number;
|
||||
/**
|
||||
* 接入方式(bot/bot_to_human/human)
|
||||
*/
|
||||
accessType?: string;
|
||||
/**
|
||||
* 排队序号
|
||||
*/
|
||||
queueNo?: number;
|
||||
/**
|
||||
* 会话开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
/**
|
||||
* 人工接入时间
|
||||
*/
|
||||
acceptTime?: string;
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime?: string;
|
||||
/**
|
||||
* 最后消息时间
|
||||
*/
|
||||
lastMessageTime?: string;
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
64
src/api/system/cskb/index.ts
Normal file
64
src/api/system/cskb/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type {CsKbForm, CsKbQuery, CsKbVO} from '@/api/system/cskb/types';
|
||||
import type {PageResult} from '@/api/types';
|
||||
import type {AxiosPromise} from '@/utils/api-types';
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 查询客服知识库列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const listCsKb = (query?: CsKbQuery): AxiosPromise<PageResult<CsKbVO>> => {
|
||||
return request({
|
||||
url: '/system/csKb/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询客服知识库详细
|
||||
* @param kbId
|
||||
*/
|
||||
export const getCsKb = (kbId: string | number): AxiosPromise<CsKbVO> => {
|
||||
return request({
|
||||
url: '/system/csKb/' + kbId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增客服知识库
|
||||
* @param data
|
||||
*/
|
||||
export const addCsKb = (data: CsKbForm) => {
|
||||
return request({
|
||||
url: '/system/csKb',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改客服知识库
|
||||
* @param data
|
||||
*/
|
||||
export const updateCsKb = (data: CsKbForm) => {
|
||||
return request({
|
||||
url: '/system/csKb',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 删除客服知识库
|
||||
* @param kbId
|
||||
*/
|
||||
export const delCsKb = (kbId: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/system/csKb/' + kbId,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
132
src/api/system/cskb/types.ts
Normal file
132
src/api/system/cskb/types.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
export interface CsKbVO {
|
||||
/**
|
||||
* 主键(雪花)
|
||||
*/
|
||||
kbId: string | number;
|
||||
/**
|
||||
* 知识编号,如 KB-001
|
||||
*/
|
||||
kbNo: string;
|
||||
/**
|
||||
* 问题标题
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* 分类(设备配网/耗材滤芯/固件升级/设备报修;固件升级仅文案分类)
|
||||
*/
|
||||
category: string;
|
||||
/**
|
||||
* 机器人回复内容
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* 排序权重,数字越小越靠前
|
||||
*/
|
||||
sort: number;
|
||||
/**
|
||||
* 状态(0停用 1启用)
|
||||
*/
|
||||
status: boolean;
|
||||
}
|
||||
|
||||
export interface CsKbForm extends BaseEntity {
|
||||
/**
|
||||
* 主键(雪花)
|
||||
*/
|
||||
kbId?: string | number;
|
||||
/**
|
||||
* 知识编号,如 KB-001
|
||||
*/
|
||||
kbNo?: string;
|
||||
/**
|
||||
* 问题标题
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* 分类(设备配网/耗材滤芯/固件升级/设备报修;固件升级仅文案分类)
|
||||
*/
|
||||
category?: string;
|
||||
/**
|
||||
* 机器人回复内容
|
||||
*/
|
||||
content?: string;
|
||||
/**
|
||||
* 排序权重,数字越小越靠前
|
||||
*/
|
||||
sort?: number;
|
||||
/**
|
||||
* 状态(0停用 1启用)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
export interface CsKbQuery extends PageQuery {
|
||||
/**
|
||||
* 知识编号,如 KB-001
|
||||
*/
|
||||
kbNo?: string;
|
||||
/**
|
||||
* 问题标题
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* 分类(设备配网/耗材滤芯/固件升级/设备报修;固件升级仅文案分类)
|
||||
*/
|
||||
category?: string;
|
||||
/**
|
||||
* 机器人回复内容
|
||||
*/
|
||||
content?: string;
|
||||
/**
|
||||
* 排序权重,数字越小越靠前
|
||||
*/
|
||||
sort?: number;
|
||||
/**
|
||||
* 状态(0停用 1启用)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
64
src/api/system/cssAgent/index.ts
Normal file
64
src/api/system/cssAgent/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type {CsAgentForm, CsAgentQuery, CsAgentVO} from '@/api/system/cssAgent/types';
|
||||
import type {PageResult} from '@/api/types';
|
||||
import type {AxiosPromise} from '@/utils/api-types';
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 查询客服人员列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const listCsAgent = (query?: CsAgentQuery): AxiosPromise<PageResult<CsAgentVO>> => {
|
||||
return request({
|
||||
url: '/system/csAgent/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询客服人员详细
|
||||
* @param agentId
|
||||
*/
|
||||
export const getCsAgent = (agentId: string | number): AxiosPromise<CsAgentVO> => {
|
||||
return request({
|
||||
url: '/system/csAgent/' + agentId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增客服人员
|
||||
* @param data
|
||||
*/
|
||||
export const addCsAgent = (data: CsAgentForm) => {
|
||||
return request({
|
||||
url: '/system/csAgent',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改客服人员
|
||||
* @param data
|
||||
*/
|
||||
export const updateCsAgent = (data: CsAgentForm) => {
|
||||
return request({
|
||||
url: '/system/csAgent',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 删除客服人员
|
||||
* @param agentId
|
||||
*/
|
||||
export const delCsAgent = (agentId: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/system/csAgent/' + agentId,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
168
src/api/system/cssAgent/types.ts
Normal file
168
src/api/system/cssAgent/types.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
export interface CsAgentVO {
|
||||
/**
|
||||
* 主键(雪花)
|
||||
*/
|
||||
agentId: string | number;
|
||||
/**
|
||||
* 客服编号,如 KF-001
|
||||
*/
|
||||
agentNo: string;
|
||||
/**
|
||||
* 工号
|
||||
*/
|
||||
jobNo: string;
|
||||
/**
|
||||
* 客服姓名
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 登录账号
|
||||
*/
|
||||
account: string;
|
||||
/**
|
||||
* 密码(BCrypt)
|
||||
*/
|
||||
password: string;
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
phone: string;
|
||||
/**
|
||||
* 头像 oss_id
|
||||
*/
|
||||
avatarOssId: string | number;
|
||||
/**
|
||||
* 在线状态(0离线 1在线 2忙碌)
|
||||
*/
|
||||
onlineStatus: boolean;
|
||||
/**
|
||||
* 账号状态(0启用 1禁用)
|
||||
*/
|
||||
status: boolean;
|
||||
}
|
||||
|
||||
export interface CsAgentForm extends BaseEntity {
|
||||
/**
|
||||
* 主键(雪花)
|
||||
*/
|
||||
agentId?: string | number;
|
||||
/**
|
||||
* 客服编号,如 KF-001
|
||||
*/
|
||||
agentNo?: string;
|
||||
/**
|
||||
* 工号
|
||||
*/
|
||||
jobNo?: string;
|
||||
/**
|
||||
* 客服姓名
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 登录账号
|
||||
*/
|
||||
account?: string;
|
||||
/**
|
||||
* 密码(BCrypt)
|
||||
*/
|
||||
password?: string;
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
phone?: string;
|
||||
/**
|
||||
* 头像 oss_id
|
||||
*/
|
||||
avatarOssId?: string | number;
|
||||
/**
|
||||
* 在线状态(0离线 1在线 2忙碌)
|
||||
*/
|
||||
onlineStatus?: boolean;
|
||||
/**
|
||||
* 账号状态(0启用 1禁用)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
export interface CsAgentQuery extends PageQuery {
|
||||
/**
|
||||
* 客服编号,如 KF-001
|
||||
*/
|
||||
agentNo?: string;
|
||||
/**
|
||||
* 工号
|
||||
*/
|
||||
jobNo?: string;
|
||||
/**
|
||||
* 客服姓名
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 登录账号
|
||||
*/
|
||||
account?: string;
|
||||
/**
|
||||
* 密码(BCrypt)
|
||||
*/
|
||||
password?: string;
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
phone?: string;
|
||||
/**
|
||||
* 头像 oss_id
|
||||
*/
|
||||
avatarOssId?: string | number;
|
||||
/**
|
||||
* 在线状态(0离线 1在线 2忙碌)
|
||||
*/
|
||||
onlineStatus?: boolean;
|
||||
/**
|
||||
* 账号状态(0启用 1禁用)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
64
src/api/system/customer/index.ts
Normal file
64
src/api/system/customer/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* 查询App用户列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const listUser = (query?: UserQuery): AxiosPromise<PageResult<UserVO>> => {
|
||||
return request({
|
||||
url: '/system/user/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询App用户详细
|
||||
* @param userId
|
||||
*/
|
||||
export const getUser = (userId: string | number): AxiosPromise<UserVO> => {
|
||||
return request({
|
||||
url: '/system/user/' + userId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增App用户
|
||||
* @param data
|
||||
*/
|
||||
export const addUser = (data: UserForm) => {
|
||||
return request({
|
||||
url: '/system/user',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改App用户
|
||||
* @param data
|
||||
*/
|
||||
export const updateUser = (data: UserForm) => {
|
||||
return request({
|
||||
url: '/system/user',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 删除App用户
|
||||
* @param userId
|
||||
*/
|
||||
export const delUser = (userId: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/system/user/' + userId,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
276
src/api/system/customer/types.ts
Normal file
276
src/api/system/customer/types.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
export interface UserVO {
|
||||
/**
|
||||
* 用户ID(雪花ID)
|
||||
*/
|
||||
userId: string | number;
|
||||
/**
|
||||
* 用户账号(默认使用手机号)
|
||||
*/
|
||||
userName: string;
|
||||
/**
|
||||
* 用户展示编号,如 U20260918001
|
||||
*/
|
||||
userNo: string;
|
||||
/**
|
||||
* 手机号码
|
||||
*/
|
||||
phonenumber: string;
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName: string;
|
||||
/**
|
||||
* 头像 oss_id
|
||||
*/
|
||||
avatarOssId: string | number;
|
||||
/**
|
||||
* 头像 URL
|
||||
*/
|
||||
avatarUrl: string;
|
||||
/**
|
||||
* 密码(BCrypt 加密)
|
||||
*/
|
||||
password: string;
|
||||
/**
|
||||
* 最近一次修改密码时间
|
||||
*/
|
||||
passwordUpdateTime: string;
|
||||
/**
|
||||
* 性别(0未知 1男 2女)
|
||||
*/
|
||||
gender: string;
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
birthday: string;
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
province: string;
|
||||
/**
|
||||
* 城市
|
||||
*/
|
||||
city: string;
|
||||
/**
|
||||
* 区县
|
||||
*/
|
||||
district: string;
|
||||
/**
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
status: boolean;
|
||||
/**
|
||||
* 注销时间;停用仍用 status
|
||||
*/
|
||||
cancelTime: string;
|
||||
/**
|
||||
* 最后登录IP
|
||||
*/
|
||||
loginIp: string;
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
loginDate: string;
|
||||
/**
|
||||
* 登录次数
|
||||
*/
|
||||
loginCount: number;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark: string;
|
||||
}
|
||||
|
||||
export interface UserForm extends BaseEntity {
|
||||
/**
|
||||
* 用户ID(雪花ID)
|
||||
*/
|
||||
userId?: string | number;
|
||||
/**
|
||||
* 用户账号(默认使用手机号)
|
||||
*/
|
||||
userName?: string;
|
||||
/**
|
||||
* 用户展示编号,如 U20260918001
|
||||
*/
|
||||
userNo?: string;
|
||||
/**
|
||||
* 手机号码
|
||||
*/
|
||||
phonenumber?: string;
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
/**
|
||||
* 头像 oss_id
|
||||
*/
|
||||
avatarOssId?: string | number;
|
||||
/**
|
||||
* 头像 URL
|
||||
*/
|
||||
avatarUrl?: string;
|
||||
/**
|
||||
* 密码(BCrypt 加密)
|
||||
*/
|
||||
password?: string;
|
||||
/**
|
||||
* 最近一次修改密码时间
|
||||
*/
|
||||
passwordUpdateTime?: string;
|
||||
/**
|
||||
* 性别(0未知 1男 2女)
|
||||
*/
|
||||
gender?: string;
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
birthday?: string;
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
province?: string;
|
||||
/**
|
||||
* 城市
|
||||
*/
|
||||
city?: string;
|
||||
/**
|
||||
* 区县
|
||||
*/
|
||||
district?: string;
|
||||
/**
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 注销时间;停用仍用 status
|
||||
*/
|
||||
cancelTime?: string;
|
||||
/**
|
||||
* 最后登录IP
|
||||
*/
|
||||
loginIp?: string;
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
loginDate?: string;
|
||||
/**
|
||||
* 登录次数
|
||||
*/
|
||||
loginCount?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface UserQuery extends PageQuery {
|
||||
/**
|
||||
* 用户账号(默认使用手机号)
|
||||
*/
|
||||
userName?: string;
|
||||
/**
|
||||
* 用户展示编号,如 U20260918001
|
||||
*/
|
||||
userNo?: string;
|
||||
/**
|
||||
* 手机号码
|
||||
*/
|
||||
phonenumber?: string;
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
/**
|
||||
* 头像 oss_id
|
||||
*/
|
||||
avatarOssId?: string | number;
|
||||
/**
|
||||
* 头像 URL
|
||||
*/
|
||||
avatarUrl?: string;
|
||||
/**
|
||||
* 密码(BCrypt 加密)
|
||||
*/
|
||||
password?: string;
|
||||
/**
|
||||
* 最近一次修改密码时间
|
||||
*/
|
||||
passwordUpdateTime?: string;
|
||||
/**
|
||||
* 性别(0未知 1男 2女)
|
||||
*/
|
||||
gender?: string;
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
birthday?: string;
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
province?: string;
|
||||
/**
|
||||
* 城市
|
||||
*/
|
||||
city?: string;
|
||||
/**
|
||||
* 区县
|
||||
*/
|
||||
district?: string;
|
||||
/**
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 注销时间;停用仍用 status
|
||||
*/
|
||||
cancelTime?: string;
|
||||
/**
|
||||
* 最后登录IP
|
||||
*/
|
||||
loginIp?: string;
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
loginDate?: string;
|
||||
/**
|
||||
* 登录次数
|
||||
*/
|
||||
loginCount?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
64
src/api/system/device/index.ts
Normal file
64
src/api/system/device/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type {DeviceForm, DeviceQuery, DeviceVO} from '@/api/system/device/types';
|
||||
import type {PageResult} from '@/api/types';
|
||||
import type {AxiosPromise} from '@/utils/api-types';
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 查询设备主列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const listDevice = (query?: DeviceQuery): AxiosPromise<PageResult<DeviceVO>> => {
|
||||
return request({
|
||||
url: '/device/device/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询设备主详细
|
||||
* @param deviceId
|
||||
*/
|
||||
export const getDevice = (deviceId: string | number): AxiosPromise<DeviceVO> => {
|
||||
return request({
|
||||
url: '/device/device/' + deviceId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增设备主
|
||||
* @param data
|
||||
*/
|
||||
export const addDevice = (data: DeviceForm) => {
|
||||
return request({
|
||||
url: '/device/device',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改设备主
|
||||
* @param data
|
||||
*/
|
||||
export const updateDevice = (data: DeviceForm) => {
|
||||
return request({
|
||||
url: '/device/device',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 删除设备主
|
||||
* @param deviceId
|
||||
*/
|
||||
export const delDevice = (deviceId: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/device/device/' + deviceId,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
428
src/api/system/device/types.ts
Normal file
428
src/api/system/device/types.ts
Normal file
@@ -0,0 +1,428 @@
|
||||
export interface DeviceVO {
|
||||
/**
|
||||
* 主键(雪花,仅内部关联)
|
||||
*/
|
||||
deviceId: string | number;
|
||||
/**
|
||||
* 业务编号,未删除记录内唯一;逻辑删除后可再次使用
|
||||
*/
|
||||
deviceNo: string;
|
||||
/**
|
||||
* 绑定码IMEI,可空;未删除记录内唯一,逻辑删除后可再次使用
|
||||
*/
|
||||
imei: string;
|
||||
/**
|
||||
* 用户侧名称≤20字符
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 产品模板
|
||||
*/
|
||||
productId: string | number;
|
||||
/**
|
||||
* 型号快照,新增时取自产品模板
|
||||
*/
|
||||
model: string;
|
||||
/**
|
||||
* 出厂序列号
|
||||
*/
|
||||
sn: string;
|
||||
/**
|
||||
* 硬件版本
|
||||
*/
|
||||
hwVersion: string;
|
||||
/**
|
||||
* 软件版本
|
||||
*/
|
||||
swVersion: string;
|
||||
/**
|
||||
* 固件版本
|
||||
*/
|
||||
fwVersion: string;
|
||||
/**
|
||||
* 数据包版本
|
||||
*/
|
||||
pkgVersion: string;
|
||||
/**
|
||||
* 设备IP
|
||||
*/
|
||||
ip: string;
|
||||
/**
|
||||
* MAC,未删除记录内唯一;逻辑删除后可再次添加
|
||||
*/
|
||||
mac: string;
|
||||
/**
|
||||
* WiFi SSID
|
||||
*/
|
||||
wifiSsid: string | number;
|
||||
/**
|
||||
* 蓝牙(0未连接 1已连接)
|
||||
*/
|
||||
bluetoothConnected: string;
|
||||
/**
|
||||
* 状态(0未激活 1在线 2离线)
|
||||
*/
|
||||
status: boolean;
|
||||
/**
|
||||
* 当前绑定APP用户
|
||||
*/
|
||||
bindUserId: string | number;
|
||||
/**
|
||||
* 最近一次绑定时间
|
||||
*/
|
||||
bindTime: string;
|
||||
/**
|
||||
* 当前家庭
|
||||
*/
|
||||
homeId: string | number;
|
||||
/**
|
||||
* 当前房间
|
||||
*/
|
||||
locationId: string | number;
|
||||
/**
|
||||
* 信号等级(0离线 1弱 2一般 3强)
|
||||
*/
|
||||
signalLevel: number;
|
||||
/**
|
||||
* 最新经度
|
||||
*/
|
||||
lng: number;
|
||||
/**
|
||||
* 最新纬度
|
||||
*/
|
||||
lat: number;
|
||||
/**
|
||||
* 地址(设备上报)
|
||||
*/
|
||||
address: string;
|
||||
/**
|
||||
* 充电(0否 1充电中;无电池设备为空)
|
||||
*/
|
||||
charging: string;
|
||||
/**
|
||||
* 设备电量0-100
|
||||
*/
|
||||
deviceBattery: number;
|
||||
/**
|
||||
* 最近一次运行数据上报时间
|
||||
*/
|
||||
lastReportTime: string;
|
||||
/**
|
||||
* 最后在线时间
|
||||
*/
|
||||
lastOnlineTime: string;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark: string;
|
||||
/**
|
||||
* 未删除时的业务编号
|
||||
*/
|
||||
activeDeviceNo: string;
|
||||
/**
|
||||
* 未删除时的IMEI
|
||||
*/
|
||||
activeImei: string;
|
||||
/**
|
||||
* 未删除时的MAC
|
||||
*/
|
||||
activeMac: string;
|
||||
}
|
||||
|
||||
export interface DeviceForm extends BaseEntity {
|
||||
/**
|
||||
* 主键(雪花,仅内部关联)
|
||||
*/
|
||||
deviceId?: string | number;
|
||||
/**
|
||||
* 业务编号,未删除记录内唯一;逻辑删除后可再次使用
|
||||
*/
|
||||
deviceNo?: string;
|
||||
/**
|
||||
* 绑定码IMEI,可空;未删除记录内唯一,逻辑删除后可再次使用
|
||||
*/
|
||||
imei?: string;
|
||||
/**
|
||||
* 用户侧名称≤20字符
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 产品模板
|
||||
*/
|
||||
productId?: string | number;
|
||||
/**
|
||||
* 型号快照,新增时取自产品模板
|
||||
*/
|
||||
model?: string;
|
||||
/**
|
||||
* 出厂序列号
|
||||
*/
|
||||
sn?: string;
|
||||
/**
|
||||
* 硬件版本
|
||||
*/
|
||||
hwVersion?: string;
|
||||
/**
|
||||
* 软件版本
|
||||
*/
|
||||
swVersion?: string;
|
||||
/**
|
||||
* 固件版本
|
||||
*/
|
||||
fwVersion?: string;
|
||||
/**
|
||||
* 数据包版本
|
||||
*/
|
||||
pkgVersion?: string;
|
||||
/**
|
||||
* 设备IP
|
||||
*/
|
||||
ip?: string;
|
||||
/**
|
||||
* MAC,未删除记录内唯一;逻辑删除后可再次添加
|
||||
*/
|
||||
mac?: string;
|
||||
/**
|
||||
* WiFi SSID
|
||||
*/
|
||||
wifiSsid?: string | number;
|
||||
/**
|
||||
* 蓝牙(0未连接 1已连接)
|
||||
*/
|
||||
bluetoothConnected?: string;
|
||||
/**
|
||||
* 状态(0未激活 1在线 2离线)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 当前绑定APP用户
|
||||
*/
|
||||
bindUserId?: string | number;
|
||||
/**
|
||||
* 最近一次绑定时间
|
||||
*/
|
||||
bindTime?: string;
|
||||
/**
|
||||
* 当前家庭
|
||||
*/
|
||||
homeId?: string | number;
|
||||
/**
|
||||
* 当前房间
|
||||
*/
|
||||
locationId?: string | number;
|
||||
/**
|
||||
* 信号等级(0离线 1弱 2一般 3强)
|
||||
*/
|
||||
signalLevel?: number;
|
||||
/**
|
||||
* 最新经度
|
||||
*/
|
||||
lng?: number;
|
||||
/**
|
||||
* 最新纬度
|
||||
*/
|
||||
lat?: number;
|
||||
/**
|
||||
* 地址(设备上报)
|
||||
*/
|
||||
address?: string;
|
||||
/**
|
||||
* 充电(0否 1充电中;无电池设备为空)
|
||||
*/
|
||||
charging?: string;
|
||||
/**
|
||||
* 设备电量0-100
|
||||
*/
|
||||
deviceBattery?: number;
|
||||
/**
|
||||
* 最近一次运行数据上报时间
|
||||
*/
|
||||
lastReportTime?: string;
|
||||
/**
|
||||
* 最后在线时间
|
||||
*/
|
||||
lastOnlineTime?: string;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
/**
|
||||
* 未删除时的业务编号
|
||||
*/
|
||||
activeDeviceNo?: string;
|
||||
/**
|
||||
* 未删除时的IMEI
|
||||
*/
|
||||
activeImei?: string;
|
||||
/**
|
||||
* 未删除时的MAC
|
||||
*/
|
||||
activeMac?: string;
|
||||
}
|
||||
|
||||
export interface DeviceQuery extends PageQuery {
|
||||
/**
|
||||
* 业务编号,未删除记录内唯一;逻辑删除后可再次使用
|
||||
*/
|
||||
deviceNo?: string;
|
||||
/**
|
||||
* 绑定码IMEI,可空;未删除记录内唯一,逻辑删除后可再次使用
|
||||
*/
|
||||
imei?: string;
|
||||
/**
|
||||
* 用户侧名称≤20字符
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 产品模板
|
||||
*/
|
||||
productId?: string | number;
|
||||
/**
|
||||
* 型号快照,新增时取自产品模板
|
||||
*/
|
||||
model?: string;
|
||||
/**
|
||||
* 出厂序列号
|
||||
*/
|
||||
sn?: string;
|
||||
/**
|
||||
* 硬件版本
|
||||
*/
|
||||
hwVersion?: string;
|
||||
/**
|
||||
* 软件版本
|
||||
*/
|
||||
swVersion?: string;
|
||||
/**
|
||||
* 固件版本
|
||||
*/
|
||||
fwVersion?: string;
|
||||
/**
|
||||
* 数据包版本
|
||||
*/
|
||||
pkgVersion?: string;
|
||||
/**
|
||||
* 设备IP
|
||||
*/
|
||||
ip?: string;
|
||||
/**
|
||||
* MAC,未删除记录内唯一;逻辑删除后可再次添加
|
||||
*/
|
||||
mac?: string;
|
||||
/**
|
||||
* WiFi SSID
|
||||
*/
|
||||
wifiSsid?: string | number;
|
||||
/**
|
||||
* 蓝牙(0未连接 1已连接)
|
||||
*/
|
||||
bluetoothConnected?: string;
|
||||
/**
|
||||
* 状态(0未激活 1在线 2离线)
|
||||
*/
|
||||
status?: boolean;
|
||||
/**
|
||||
* 当前绑定APP用户
|
||||
*/
|
||||
bindUserId?: string | number;
|
||||
/**
|
||||
* 最近一次绑定时间
|
||||
*/
|
||||
bindTime?: string;
|
||||
/**
|
||||
* 当前家庭
|
||||
*/
|
||||
homeId?: string | number;
|
||||
/**
|
||||
* 当前房间
|
||||
*/
|
||||
locationId?: string | number;
|
||||
/**
|
||||
* 信号等级(0离线 1弱 2一般 3强)
|
||||
*/
|
||||
signalLevel?: number;
|
||||
/**
|
||||
* 最新经度
|
||||
*/
|
||||
lng?: number;
|
||||
/**
|
||||
* 最新纬度
|
||||
*/
|
||||
lat?: number;
|
||||
/**
|
||||
* 地址(设备上报)
|
||||
*/
|
||||
address?: string;
|
||||
/**
|
||||
* 充电(0否 1充电中;无电池设备为空)
|
||||
*/
|
||||
charging?: string;
|
||||
/**
|
||||
* 设备电量0-100
|
||||
*/
|
||||
deviceBattery?: number;
|
||||
/**
|
||||
* 最近一次运行数据上报时间
|
||||
*/
|
||||
lastReportTime?: string;
|
||||
/**
|
||||
* 最后在线时间
|
||||
*/
|
||||
lastOnlineTime?: string;
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
createDept?: number;
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
/**
|
||||
* 未删除时的业务编号
|
||||
*/
|
||||
activeDeviceNo?: string;
|
||||
/**
|
||||
* 未删除时的IMEI
|
||||
*/
|
||||
activeImei?: string;
|
||||
/**
|
||||
* 未删除时的MAC
|
||||
*/
|
||||
activeMac?: string;
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
@@ -8,8 +8,7 @@ body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
background: var(--app-shell-bg);
|
||||
font-family:
|
||||
'MiSans', 'HarmonyOS Sans SC', 'PingFang SC', 'Source Han Sans SC', 'Noto Sans SC', 'Hiragino Sans GB',
|
||||
font-family: 'MiSans', 'HarmonyOS Sans SC', 'PingFang SC', 'Source Han Sans SC', 'Noto Sans SC', 'Hiragino Sans GB',
|
||||
'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
@@ -88,3 +87,18 @@ h6 {
|
||||
line-height: 1.1;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
ul, li {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
// 通用卡片
|
||||
.commonCard {
|
||||
padding: 24px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* 业务列表/卡片页:分页、树、列表、el-card 等(与 vendors/element-plus 通用覆盖叠加,入口顺序见 index.scss)。
|
||||
/* 业务列表/卡片页:分页、树、列表、el-card 等(与 vendors/element-plus 通用覆盖叠加,入口顺序见 index.vue.scss)。
|
||||
* 属「页面模式」而非 EP 公共主题,故放在 components。 */
|
||||
|
||||
// --- 历史「表单分区标题」样式 ---
|
||||
@@ -51,8 +51,7 @@
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
border-color: var(--app-surface-border);
|
||||
overflow: hidden;
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
transition: box-shadow 0.2s ease,
|
||||
border-color 0.2s ease;
|
||||
background: var(--app-surface-bg);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
margin: 0;
|
||||
height: 40px;
|
||||
color: var(--side-menu-text) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--side-menu-hover-bg) !important;
|
||||
color: var(--side-menu-hover-text) !important;
|
||||
@@ -54,11 +55,11 @@
|
||||
//margin: 3px 8px;
|
||||
//background: transparent !important;
|
||||
background: #f7f7f7 !important;
|
||||
|
||||
&.is-active {
|
||||
background: var(--side-menu-active-bg) !important;
|
||||
color: var(--side-menu-active-text) !important;
|
||||
box-shadow:
|
||||
inset 3px 0 0 var(--side-menu-active-line),
|
||||
box-shadow: inset 3px 0 0 var(--side-menu-active-line),
|
||||
inset 0 0 0 1px var(--side-menu-active-border);
|
||||
}
|
||||
|
||||
@@ -75,11 +76,11 @@
|
||||
//margin: 3px 8px;
|
||||
//background: transparent !important;
|
||||
background: #f7f7f7 !important;
|
||||
|
||||
&.is-active {
|
||||
background: var(--side-menu-active-bg) !important;
|
||||
color: var(--side-menu-active-text) !important;
|
||||
box-shadow:
|
||||
inset 3px 0 0 var(--side-menu-active-line),
|
||||
box-shadow: inset 3px 0 0 var(--side-menu-active-line),
|
||||
inset 0 0 0 1px var(--side-menu-active-border);
|
||||
}
|
||||
|
||||
@@ -111,3 +112,48 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#app {
|
||||
// 侧边栏滚动容器,假设侧边栏滚动区域 class 为 sidebar-scroll
|
||||
.beautify_scroll {
|
||||
// Firefox 滚动条
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--side-scrollbar-thumb) var(--side-scrollbar-bg);
|
||||
|
||||
// webkit 滚动条
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px; // 滚动条宽度
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: var(--side-scrollbar-bg);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--side-scrollbar-thumb);
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: var(--side-scrollbar-thumb-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// 浅色主题变量
|
||||
--side-scrollbar-bg: transparent;
|
||||
--side-scrollbar-thumb: #c0c4cc;
|
||||
--side-scrollbar-thumb-hover: #909399;
|
||||
|
||||
// 暗黑主题覆盖变量
|
||||
&.theme-dark {
|
||||
--side-scrollbar-bg: transparent;
|
||||
--side-scrollbar-thumb: #414243;
|
||||
--side-scrollbar-thumb-hover: #58595b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,4 +64,6 @@
|
||||
--el-box-shadow-light: var(--app-shadow-sm);
|
||||
--el-box-shadow: var(--app-shadow-md);
|
||||
--el-bg-color-page: var(--app-shell-bg);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -28,12 +28,21 @@
|
||||
|
||||
// --- primary 品牌色(亮色;暗色见 tokens/_theme-dark) ---
|
||||
.el-button--primary {
|
||||
--el-button-bg-color: var(--app-accent-strong);
|
||||
border: none;
|
||||
--el-button-bg-color: #2563eb;
|
||||
--el-button-border-color: var(--app-accent-strong);
|
||||
--el-button-hover-bg-color: #4a7cff;
|
||||
--el-button-hover-border-color: #4a7cff;
|
||||
--el-button-active-bg-color: #2e5ce0;
|
||||
--el-button-active-border-color: #2e5ce0;
|
||||
|
||||
&.is-plain {
|
||||
//color: rgba(37, 99, 235, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--info {
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
// --- 表格 CSS 变量接业务 token ---
|
||||
@@ -82,3 +91,4 @@ html.dark .el-table {
|
||||
.el-select__wrapper {
|
||||
box-shadow: 0 0 0 1px var(--el-border-color) inset;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ const getBreadcrumb = () => {
|
||||
}
|
||||
// 判断是否为首页
|
||||
if (!isDashboard(matched[0])) {
|
||||
// matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched);
|
||||
// matched = [{ path: '/index.vue', meta: { title: '首页' } }].concat(matched);
|
||||
}
|
||||
levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false);
|
||||
};
|
||||
|
||||
346
src/components/Editor/cskb.vue
Normal file
346
src/components/Editor/cskb.vue
Normal file
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<div class="editor-shell">
|
||||
<EditorToolbar
|
||||
:default-config="toolbarConfig"
|
||||
:editor="editorRef"
|
||||
class="editor-toolbar"
|
||||
mode="default"
|
||||
/>
|
||||
<div :style="styles" class="editor-body">
|
||||
<WangEditor
|
||||
v-model="content"
|
||||
:default-config="editorConfig"
|
||||
class="editor-content"
|
||||
mode="default"
|
||||
@onChange="handleChange"
|
||||
@onCreated="handleCreated"
|
||||
/>
|
||||
</div>
|
||||
<!-- 底部提示、示例、字数统计 -->
|
||||
<div v-if="simpleToolbar" class="editor-tip">
|
||||
<p>请输入标准回复话术,可使用「有序步骤」插入分步指引,例如:</p>
|
||||
<div class="demo-box">
|
||||
<ol>
|
||||
<li>确认设备已通电并处于配网模式(指示灯橙色快闪)</li>
|
||||
<li>打开APP「我的设备」→ 添加设备,选择对应型号</li>
|
||||
<li>输入2.4G Wi-Fi密码并等待约30秒完成连接</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p>以上为示例格式</p>
|
||||
<p>支持插入图片与链接;回复中可引用设备型号,帮助用户快速对号入座</p>
|
||||
<div class="word-count">{{ wordCount }}/2000字</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import '@wangeditor-next/editor/dist/css/style.css';
|
||||
import type {IDomEditor, IEditorConfig, IToolbarConfig} from '@wangeditor-next/editor';
|
||||
import {Editor as WangEditor, Toolbar as EditorToolbar} from '@wangeditor-next/editor-for-vue';
|
||||
import modal from '@/plugins/modal';
|
||||
import {propTypes} from '@/utils/propTypes';
|
||||
import {globalHeaders} from '@/utils/request';
|
||||
import {shallowRef, ref, computed, watch, onBeforeUnmount} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
/* 编辑器的内容 */
|
||||
modelValue: propTypes.string,
|
||||
/* 高度 */
|
||||
height: propTypes.number.def(400),
|
||||
/* 最小高度 */
|
||||
minHeight: propTypes.number.def(400),
|
||||
/* 只读 */
|
||||
readOnly: propTypes.bool.def(false),
|
||||
/* 图片上传文件大小限制(MB) */
|
||||
fileSize: propTypes.number.def(5),
|
||||
/* 视频上传文件大小限制(MB) */
|
||||
videoSize: propTypes.number.def(100),
|
||||
/* 类型(base64格式、url格式) */
|
||||
type: propTypes.string.def('url'),
|
||||
/* 精简工具栏(仅加粗/有序步骤/图片/链接/清除格式) */
|
||||
simpleToolbar: propTypes.bool.def(false)
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const editorRef = shallowRef<IDomEditor>();
|
||||
const content = ref('');
|
||||
const wordCount = ref(0);
|
||||
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
||||
const uploadOssUrl = baseUrl + '/resource/oss/upload';
|
||||
|
||||
const styles = computed(() => {
|
||||
const style: Record<string, string> = {};
|
||||
if (props.minHeight) {
|
||||
style.minHeight = `${props.minHeight}px`;
|
||||
}
|
||||
if (props.height) {
|
||||
style.height = `${props.height}px`;
|
||||
}
|
||||
return style;
|
||||
});
|
||||
|
||||
const toolbarConfig = computed<Partial<IToolbarConfig>>(() => {
|
||||
if (props.simpleToolbar) {
|
||||
return {
|
||||
toolbarKeys: [
|
||||
'bold',
|
||||
'numberedList',
|
||||
'uploadImage',
|
||||
'insertLink',
|
||||
'clearStyle',
|
||||
],
|
||||
excludeKeys: []
|
||||
};
|
||||
} else {
|
||||
const excludeKeys: string[] = ['fullScreen'];
|
||||
if (!props.type) {
|
||||
excludeKeys.push('uploadImage');
|
||||
excludeKeys.push('uploadVideo');
|
||||
}
|
||||
return {excludeKeys};
|
||||
}
|
||||
});
|
||||
|
||||
/* ==================== OSS 上传 ==================== */
|
||||
|
||||
const uploadToOss = async (file: File): Promise<{ url: string; fileName: string }> => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const res = await fetch(uploadOssUrl, {
|
||||
method: 'POST',
|
||||
headers: globalHeaders(),
|
||||
body: formData
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.code === 200) {
|
||||
return data.data;
|
||||
}
|
||||
throw new Error(data.msg || '上传失败');
|
||||
};
|
||||
|
||||
/* ==================== 文件校验 ==================== */
|
||||
|
||||
const validateImageFile = (file: File) => {
|
||||
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
modal.msgError('图片格式错误!');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (props.fileSize) {
|
||||
const isLt = file.size / 1024 / 1024 < props.fileSize;
|
||||
if (!isLt) {
|
||||
modal.msgError(`上传图片大小不能超过 ${props.fileSize} MB!`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const validateVideoFile = (file: File) => {
|
||||
const allowedTypes = ['video/mp4', 'video/webm', 'video/ogg'];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
modal.msgError('视频格式错误, 请上传 mp4/webm/ogg 格式!');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (props.videoSize) {
|
||||
const isLt = file.size / 1024 / 1024 < props.videoSize;
|
||||
if (!isLt) {
|
||||
modal.msgError(`上传视频大小不能超过 ${props.videoSize} MB!`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/* ==================== wangeditor 菜单配置 ==================== */
|
||||
|
||||
const getUploadImageMenuConfig = () => {
|
||||
if (props.type === 'base64') {
|
||||
return {
|
||||
allowedFileTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/svg+xml'],
|
||||
customUpload(file: File, insertFn: (url: string, alt?: string, href?: string) => void) {
|
||||
if (!validateImageFile(file)) return;
|
||||
|
||||
modal.loading('正在上传图片,请稍候...');
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
insertFn(reader.result as string, file.name);
|
||||
modal.closeLoading();
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
modal.msgError('图片插入失败');
|
||||
modal.closeLoading();
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
allowedFileTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/svg+xml'],
|
||||
async customUpload(file: File, insertFn: (url: string, alt?: string, href?: string) => void) {
|
||||
if (!validateImageFile(file)) return;
|
||||
|
||||
modal.loading('正在上传图片,请稍候...');
|
||||
try {
|
||||
const result = await uploadToOss(file);
|
||||
insertFn(result.url, file.name, result.url);
|
||||
} catch {
|
||||
modal.msgError('图片上传失败');
|
||||
} finally {
|
||||
modal.closeLoading();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const getUploadVideoMenuConfig = () => ({
|
||||
allowedFileTypes: ['video/mp4', 'video/webm', 'video/ogg'],
|
||||
async customUpload(file: File, insertFn: (url: string, poster?: string) => void) {
|
||||
if (!validateVideoFile(file)) return;
|
||||
|
||||
modal.loading('正在上传视频,请稍候...');
|
||||
try {
|
||||
const result = await uploadToOss(file);
|
||||
insertFn(result.url);
|
||||
} catch {
|
||||
modal.msgError('视频上传失败');
|
||||
} finally {
|
||||
modal.closeLoading();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const editorConfig = computed<Partial<IEditorConfig>>(() => ({
|
||||
placeholder: props.simpleToolbar ? '请输入标准回复话术...' : '请输入内容',
|
||||
autoFocus: false,
|
||||
maxLength: 2000,
|
||||
MENU_CONF: {
|
||||
uploadImage: getUploadImageMenuConfig() as any,
|
||||
uploadVideo: getUploadVideoMenuConfig() as any
|
||||
},
|
||||
customMenu: [
|
||||
{
|
||||
key: 'numberedList',
|
||||
title: '有序步骤'
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
/* ==================== 事件处理 ==================== */
|
||||
|
||||
const handleChange = () => {
|
||||
if (!editorRef.value) return;
|
||||
wordCount.value = editorRef.value.getText().length;
|
||||
};
|
||||
|
||||
const syncReadOnly = () => {
|
||||
const editor = editorRef.value;
|
||||
if (!editor) return;
|
||||
|
||||
if (props.readOnly) {
|
||||
editor.disable();
|
||||
} else {
|
||||
editor.enable();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreated = (editor: IDomEditor) => {
|
||||
editorRef.value = editor;
|
||||
syncReadOnly();
|
||||
wordCount.value = editor.getText().length;
|
||||
};
|
||||
|
||||
/* ==================== 双向数据绑定(直接透传HTML) ==================== */
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
value => {
|
||||
if (value !== content.value) {
|
||||
content.value = value || '';
|
||||
}
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
|
||||
watch(content, value => {
|
||||
emit('update:modelValue', value);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.readOnly,
|
||||
() => {
|
||||
syncReadOnly();
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
editorRef.value?.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editor-shell {
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: var(--el-border-radius-base);
|
||||
overflow: hidden;
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.editor-toolbar {
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.editor-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.editor-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.editor-shell :deep(.w-e-toolbar) {
|
||||
border: 0 !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.editor-shell :deep(.w-e-text-container) {
|
||||
border: 0 !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.editor-shell :deep(.w-e-text-container [data-slate-editor]) {
|
||||
min-height: inherit;
|
||||
}
|
||||
|
||||
.editor-tip {
|
||||
padding: 12px 16px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.demo-box {
|
||||
background: #f5f7fa;
|
||||
padding: 8px 12px;
|
||||
margin: 6px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.word-count {
|
||||
text-align: right;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
371
src/components/FormItemTip/index.vue
Normal file
371
src/components/FormItemTip/index.vue
Normal file
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<el-form-item
|
||||
:label="label"
|
||||
:required="required"
|
||||
:show-message="false"
|
||||
class="form-item-tip-wrapper"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<!-- label插槽,支持父组件自定义label -->
|
||||
<template #label>
|
||||
<slot name="label">{{ label }}</slot>
|
||||
</template>
|
||||
|
||||
<!-- 输入框模式 -->
|
||||
<el-input
|
||||
v-if="type === 'input'"
|
||||
:model-value="modelValue"
|
||||
class="inputItem"
|
||||
v-bind="inputAttrs"
|
||||
@input="$emit('update:modelValue', $event)"
|
||||
>
|
||||
<template #suffix>
|
||||
<slot name="suffix"></slot>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<!-- 输入框模式 -->
|
||||
<el-input
|
||||
v-if="type === 'inputNumber'"
|
||||
:model-value="modelValue"
|
||||
class="inputItem"
|
||||
type="number"
|
||||
v-bind="inputAttrs"
|
||||
@input="$emit('update:modelValue', $event)"
|
||||
>
|
||||
<template #suffix>
|
||||
<slot name="suffix"></slot>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<!-- 文本域模式 -->
|
||||
<el-input
|
||||
v-else-if="type === 'textarea'"
|
||||
:model-value="modelValue"
|
||||
:rows="5"
|
||||
class="inputItem"
|
||||
type="textarea"
|
||||
v-bind="inputAttrs"
|
||||
@input="$emit('update:modelValue', $event)"
|
||||
/>
|
||||
<!-- 单选框模式(账号状态) -->
|
||||
<el-radio-group
|
||||
v-else-if="type === 'radio'"
|
||||
:model-value="modelValue"
|
||||
class="radioGroup"
|
||||
@change="$emit('update:modelValue', $event)"
|
||||
>
|
||||
<el-radio
|
||||
v-for="item in radioOptions"
|
||||
:key="item.value"
|
||||
:class="{
|
||||
active: modelValue === item.value
|
||||
}"
|
||||
:label="item.value"
|
||||
class="radio"
|
||||
>{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
|
||||
<!-- 单个日期-->
|
||||
<el-date-picker
|
||||
v-else-if="type === 'date'"
|
||||
:model-value="modelValue"
|
||||
format="YYYY-MM-DD"
|
||||
placeholder="选择时间"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
/>
|
||||
<!-- 头像-->
|
||||
<div v-else-if="type === 'uploadUserAvatar'" class="uploadUserAvatar">
|
||||
<div class="img">
|
||||
<el-image :src="imageUrl" style="width: 100%;height: 100%; object-fit: cover"></el-image>
|
||||
</div>
|
||||
<div>
|
||||
<el-upload
|
||||
ref="upload"
|
||||
:auto-upload="false"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:limit="1"
|
||||
:on-change="handleChange"
|
||||
:on-exceed="handleExceed"
|
||||
:show-file-list="false"
|
||||
accept=".jpg,.png"
|
||||
>
|
||||
<template #trigger>
|
||||
<div class="actionUpload">点击替换头像</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<div class="tip">支持 jpg / png,建议 200×200,不超过 2MB</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 多选-->
|
||||
<el-checkbox-group
|
||||
v-else-if="type === 'checkBox'"
|
||||
:model-value="modelValue"
|
||||
style="width: 100%"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="item in radioOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
class="checkBox"
|
||||
>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
|
||||
<div v-else-if="type === 'switch'" style="width: 100%">
|
||||
<el-switch
|
||||
:model-value="Boolean(modelValue)"
|
||||
class="custom-switch"
|
||||
style="margin-right: 12px;"
|
||||
@change="$emit('update:modelValue', $event)"
|
||||
/>
|
||||
<slot name="switchRight"></slot>
|
||||
</div>
|
||||
<!-- 底部提示文字 -->
|
||||
<div v-if="tip" class="form-item-tip-text">
|
||||
{{ tip }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue'
|
||||
import {genFileId} from 'element-plus'
|
||||
import type {UploadInstance, UploadProps, UploadRawFile} from 'element-plus'
|
||||
|
||||
defineOptions({inheritAttrs: false})
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number, Array] as PropType<string | number | Array<string | number>>,
|
||||
default: ''
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tip: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'input'
|
||||
},
|
||||
|
||||
inputAttrs: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
radioOptions: {
|
||||
type: Array as PropType<Array<{ label: string, value: string | number }>>,
|
||||
default: () => []
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'file-change'])
|
||||
|
||||
|
||||
const upload = ref<UploadInstance>()
|
||||
const defaultImag = "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"
|
||||
const imageUrl = ref('')
|
||||
if (props.type === 'uploadUserAvatar') {
|
||||
const str = props.modelValue as string;
|
||||
imageUrl.value = (str.trim() === '' ? defaultImag : props.modelValue as string)
|
||||
console.log(imageUrl.value)
|
||||
}
|
||||
// 保存选中的文件,等表单提交时再传给后端
|
||||
const avatarFile = ref<File | null>(null)
|
||||
|
||||
// 成功获取 临时地址
|
||||
// ✅ on-change:选中文件立刻预览(重点!)
|
||||
const handleChange: UploadProps['onChange'] = (uploadFile) => {
|
||||
avatarFile.value = uploadFile.raw!
|
||||
imageUrl.value = URL.createObjectURL(uploadFile.raw!)
|
||||
emit('file-change', uploadFile.raw)
|
||||
|
||||
}
|
||||
// 超出 覆盖
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
upload.value!.clearFiles()
|
||||
const file = files[0] as UploadRawFile
|
||||
file.uid = genFileId()
|
||||
upload.value!.handleStart(file)
|
||||
}
|
||||
// 拦截格式
|
||||
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||||
console.log(rawFile)
|
||||
// 修复:jpg mime是 image/jpeg,不是 image/jpg
|
||||
if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
|
||||
ElMessage.error('只支持jpg,png格式图片')
|
||||
return false
|
||||
} else if (rawFile.size / 1024 / 1024 > 2) {
|
||||
ElMessage.error('图片大小不能大于2MB')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
.el-checkbox__input {
|
||||
.el-checkbox__inner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
|
||||
&::after {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-checkbox__label {
|
||||
color: rgba(107, 114, 128, 1) !important;
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
.el-checkbox__label {
|
||||
color: #2563EB !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-checkbox__input.is-checked {
|
||||
.el-checkbox__inner {
|
||||
background-color: #2563EB !important
|
||||
}
|
||||
|
||||
.el-checkbox__inner:after {
|
||||
border-color: #2563EB !important
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-radio) {
|
||||
.el-radio__input {
|
||||
.el-radio__inner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid rgba(209, 213, 219, 1);
|
||||
|
||||
&::after {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
.el-radio__inner {
|
||||
border: 1px solid #2563eb;
|
||||
background-color: #2563eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
background-color: rgba(239, 246, 255, 1) !important;
|
||||
|
||||
.el-radio__label {
|
||||
color: rgba(37, 99, 235, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkBox {
|
||||
padding: 14px 17px;
|
||||
min-height: 44px;
|
||||
|
||||
height: 44px;
|
||||
background-color: rgba(248, 250, 252, 1);
|
||||
border: 1px solid rgba(226, 232, 240, 1);
|
||||
|
||||
&.is-checked {
|
||||
background-color: rgba(239, 246, 255, 1);
|
||||
border: 1px solid rgba(191, 219, 254, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.form-item-tip-text {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.uploadUserAvatar {
|
||||
width: 100%;
|
||||
min-height: 90px;
|
||||
border-radius: 12px 12px 12px 12px;
|
||||
background-color: rgba(247, 249, 252, 1);
|
||||
border: 1px solid rgba(196, 201, 209, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 17px;
|
||||
|
||||
.img {
|
||||
width: 57px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(241, 245, 249, 1);
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.actionUpload {
|
||||
line-height: 18px;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
color: rgba(37, 99, 235, 1);
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-top: 4px;
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.inputItem {
|
||||
min-height: 44px;
|
||||
border-radius: 12px 12px 12px 12px;
|
||||
background-color: rgba(247, 249, 252, 1);
|
||||
border: 1px solid rgba(229, 231, 235, 1);
|
||||
}
|
||||
|
||||
.radioGroup {
|
||||
.radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 80px;
|
||||
height: 44px;
|
||||
padding: 14px 21px;
|
||||
border-radius: 12px 12px 12px 12px;
|
||||
border: 1px solid rgba(229, 231, 235, 1);
|
||||
background-color: rgba(247, 249, 252, 1);
|
||||
|
||||
&.active {
|
||||
border-color: rgba(37, 99, 235, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -45,7 +45,7 @@ import { isHttp } from '@/utils/validate';
|
||||
// 顶部栏初始数
|
||||
const visibleNumber = ref<number>(-1);
|
||||
// 隐藏侧边栏路由
|
||||
const hideList = ['/index', '/user/profile'];
|
||||
const hideList = ['/index.vue', '/user/profile'];
|
||||
|
||||
const appStore = useAppStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
@@ -99,7 +99,7 @@ const childrenMenus = computed(() => {
|
||||
const activeMenu = computed(() => {
|
||||
const {meta} = route;
|
||||
let path = meta.activeMenu || route.path;
|
||||
if (path === '/index' || route.path === '/index') {
|
||||
if (path === '/index.vue' || route.path === '/index.vue') {
|
||||
path = '/system/user';
|
||||
}
|
||||
let activePath = path;
|
||||
@@ -214,8 +214,7 @@ onMounted(() => {
|
||||
border-radius: 13px;
|
||||
color: var(--topbar-pill-text) !important;
|
||||
background: transparent !important;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
transition: background-color 0.2s ease,
|
||||
color 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
@@ -305,8 +304,7 @@ onMounted(() => {
|
||||
border-radius: 12px;
|
||||
color: var(--app-text-title) !important;
|
||||
background: transparent !important;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
transition: background-color 0.2s ease,
|
||||
color 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
@@ -365,8 +363,7 @@ html.dark .mix-topnav-menu.el-menu--horizontal {
|
||||
html.dark .mix-topnav-popper .el-menu--popup {
|
||||
border-color: rgba(71, 85, 105, 0.34);
|
||||
background: linear-gradient(180deg, rgba(15, 23, 42, 0.98), rgba(30, 41, 59, 0.94)) !important;
|
||||
box-shadow:
|
||||
0 20px 42px rgba(0, 0, 0, 0.34),
|
||||
box-shadow: 0 20px 42px rgba(0, 0, 0, 0.34),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
|
||||
65
src/components/cardNav/index.vue
Normal file
65
src/components/cardNav/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts" name="cardNav" setup>
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
lineColor: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '#2563eb'
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="cardNav">
|
||||
<div :style="{
|
||||
backgroundColor: props.lineColor
|
||||
}" class="line"></div>
|
||||
<div class="content">
|
||||
<div class="title">{{ props.title }}</div>
|
||||
<div v-if="props.subTitle" class="subTitle">{{ props.subTitle }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cardNav {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.line {
|
||||
width: 7px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(37, 99, 235, 1);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
.title {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
43
src/components/tableTag/index.vue
Normal file
43
src/components/tableTag/index.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script lang="ts" name="tableTag" setup>
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String as PropType<"" | "default" | "text" | "primary" | "success" | "warning" | "info" | "danger">,
|
||||
default: 'info'
|
||||
},
|
||||
circleColor: {
|
||||
type: String,
|
||||
default: '#2563eb',
|
||||
},
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
num: String,
|
||||
numColor: {
|
||||
type: String,
|
||||
default: '#111827',
|
||||
},
|
||||
title: String,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-button :plain="props.plain" :type="props.type">
|
||||
<i :style="{
|
||||
backgroundColor: props.circleColor,
|
||||
}" class="circle"></i>
|
||||
<span style="margin:0 8px;color: rgba(75, 85, 99, 1);">{{ props.title }}</span>
|
||||
<span :style="{
|
||||
color: props.numColor,
|
||||
}" style="font-weight: bold">{{ props.num }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.circle {
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(124, 58, 237, 1)
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<section class="app-main">
|
||||
<section class="app-main beautify_scroll">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition :enter-active-class="animate" mode="out-in">
|
||||
<keep-alive :include="tagsViewStore.cachedViews">
|
||||
@@ -50,10 +50,11 @@ function addIframe() {
|
||||
<style lang="scss" scoped>
|
||||
.app-main {
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
//padding: 12px;
|
||||
overflow: auto;
|
||||
padding: 0 12px;
|
||||
|
||||
&:fullscreen,
|
||||
&:-webkit-full-screen,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
>
|
||||
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
|
||||
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
|
||||
<!-- <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />-->
|
||||
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon) || '' "/>
|
||||
<template #title>
|
||||
<span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
|
||||
</template>
|
||||
|
||||
@@ -181,7 +181,7 @@ const isFirstView = () => {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return tag.fullPath === '/index' || tag.fullPath === visitedViews.value[1]?.fullPath;
|
||||
return tag.fullPath === '/index.vue' || tag.fullPath === visitedViews.value[1]?.fullPath;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -313,7 +313,8 @@ const closeOthersTags = () => {
|
||||
if (!tag) {
|
||||
return;
|
||||
}
|
||||
router.push(tag.fullPath || tag.path || '/').catch(() => {});
|
||||
router.push(tag.fullPath || tag.path || '/').catch(() => {
|
||||
});
|
||||
tab.closeOtherPage(tag).then(() => {
|
||||
moveToCurrentTag();
|
||||
});
|
||||
@@ -505,8 +506,7 @@ onBeforeUnmount(() => {
|
||||
background-color: var(--app-surface-bg);
|
||||
border: 1px solid var(--app-surface-border);
|
||||
border-radius: var(--app-radius-md);
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
transition: box-shadow 0.2s ease,
|
||||
transform 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
@@ -549,8 +549,7 @@ onBeforeUnmount(() => {
|
||||
margin-left: 5px;
|
||||
margin-top: 4px;
|
||||
border-radius: var(--app-radius-md);
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
transition: box-shadow 0.2s ease,
|
||||
transform 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
@@ -620,8 +619,7 @@ onBeforeUnmount(() => {
|
||||
background-color: var(--app-surface-bg);
|
||||
border: 1px solid var(--app-surface-border);
|
||||
border-radius: var(--app-radius-md);
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
transition: box-shadow 0.2s ease,
|
||||
transform 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
|
||||
@@ -111,7 +111,7 @@ const setLayout = () => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
//padding: 12px 12px 0;
|
||||
padding: 0 12px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
redirect: '/index',
|
||||
redirect: '/index.vue',
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
path: '/index.vue',
|
||||
component: () => import('@/views/index/index.vue'),
|
||||
name: 'Index',
|
||||
meta: {title: '首页', icon: 'dashboard', affix: true}
|
||||
|
||||
438
src/views/system/agreement/index.vue
Normal file
438
src/views/system/agreement/index.vue
Normal file
@@ -0,0 +1,438 @@
|
||||
<template>
|
||||
<div class="p-2 page-shell system-agreement-page">
|
||||
<div class="search-wrap">
|
||||
<el-card :class="{ 'is-collapsed': !showSearch }" class="search-panel" shadow="hover">
|
||||
<template #header>
|
||||
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
|
||||
<div><h3>筛选条件</h3></div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="query-form">
|
||||
<el-form-item label="协议编号" prop="agreementNo">
|
||||
<el-input v-model="queryParams.agreementNo" clearable placeholder="请输入协议编号"
|
||||
@keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="agreementType">
|
||||
<el-select v-model="queryParams.agreementType" clearable placeholder="请选择类型">
|
||||
<el-option label="请选择字典生成" value=""/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="协议名称" prop="name">
|
||||
<el-input v-model="queryParams.name" clearable placeholder="请输入协议名称" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="版本号" prop="versionName">
|
||||
<el-input v-model="queryParams.versionName" clearable placeholder="请输入版本号"
|
||||
@keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" clearable placeholder="请选择状态">
|
||||
<el-option :value="true" label="是"/>
|
||||
<el-option :value="false" label="否"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="生效时间" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRangePublishTime"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
end-placeholder="结束日期"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建部门" prop="createDept">
|
||||
<el-input-number v-model="queryParams.createDept" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建者" prop="createBy">
|
||||
<el-input-number v-model="queryParams.createBy" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRangeCreateTime"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
end-placeholder="结束日期"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新者" prop="updateBy">
|
||||
<el-input-number v-model="queryParams.updateBy" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新时间" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRangeUpdateTime"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
end-placeholder="结束日期"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="已生效类型;同类型仅一条,非生效或已删除为 NULL" prop="activeType">
|
||||
<el-select v-model="queryParams.activeType"
|
||||
clearable placeholder="请选择已生效类型;同类型仅一条,非生效或已删除为 NULL">
|
||||
<el-option label="请选择字典生成" value=""/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="Search" type="primary" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card class="table-panel" shadow="hover">
|
||||
<template #header>
|
||||
<div class="toolbar-shell">
|
||||
<div class="table-heading">
|
||||
<h3>协议配置列表</h3>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button v-hasPermi="['system:agreement:add']" icon="Plus" plain type="primary" @click="handleAdd">新增
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['system:agreement:edit']" :disabled="single" icon="Edit" plain type="success"
|
||||
@click="handleUpdate()">修改
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['system:agreement:remove']" :disabled="multiple" icon="Delete" plain type="danger"
|
||||
@click="handleDelete()">删除
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['system:agreement:export']" icon="Download" plain type="warning"
|
||||
@click="handleExport">导出
|
||||
</el-button>
|
||||
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="agreementList" border class="data-table"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column align="center" type="selection" width="55"/>
|
||||
<el-table-column align="center" label="主键" prop="agreementId"/>
|
||||
<el-table-column align="center" label="协议编号" prop="agreementNo"/>
|
||||
<el-table-column align="center" label="类型" prop="agreementType"/>
|
||||
<el-table-column align="center" label="协议名称" prop="name"/>
|
||||
<el-table-column align="center" label="版本号" prop="versionName"/>
|
||||
<el-table-column align="center" label="协议正文" prop="content"/>
|
||||
<el-table-column align="center" label="状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
disabled
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="生效时间" prop="publishTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.publishTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="已生效类型;同类型仅一条,非生效或已删除为 NULL" prop="activeType"/>
|
||||
<el-table-column align="center" class-name="small-padding fixed-width" label="操作">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button v-hasPermi="['system:agreement:edit']" icon="Edit" link type="primary"
|
||||
@click="handleUpdate(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button v-hasPermi="['system:agreement:remove']" icon="Delete" link type="primary"
|
||||
@click="handleDelete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNum"
|
||||
:total="total" @pagination="getList"/>
|
||||
</el-card>
|
||||
<!-- 添加或修改协议配置对话框 -->
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" append-to-body width="500px">
|
||||
<el-form ref="agreementFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="协议编号" prop="agreementNo">
|
||||
<el-input v-model="form.agreementNo" placeholder="请输入协议编号"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="agreementType">
|
||||
<el-select v-model="form.agreementType" placeholder="请选择类型">
|
||||
<el-option label="请选择字典生成" value=""/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="协议名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入协议名称"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="版本号" prop="versionName">
|
||||
<el-input v-model="form.versionName" placeholder="请输入版本号"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="协议正文">
|
||||
<editor v-model="form.content" :min-height="192"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-switch
|
||||
v-model="form.status"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="生效时间" prop="publishTime">
|
||||
<el-date-picker v-model="form.publishTime"
|
||||
clearable
|
||||
placeholder="请选择生效时间"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建部门" prop="createDept">
|
||||
<el-input-number v-model="form.createDept" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建者" prop="createBy">
|
||||
<el-input-number v-model="form.createBy" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker v-model="form.createTime"
|
||||
clearable
|
||||
placeholder="请选择创建时间"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新者" prop="updateBy">
|
||||
<el-input-number v-model="form.updateBy" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新时间" prop="updateTime">
|
||||
<el-date-picker v-model="form.updateTime"
|
||||
clearable
|
||||
placeholder="请选择更新时间"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="已生效类型;同类型仅一条,非生效或已删除为 NULL" prop="activeType">
|
||||
<el-select v-model="form.activeType" placeholder="请选择已生效类型;同类型仅一条,非生效或已删除为 NULL">
|
||||
<el-option label="请选择字典生成" value=""/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="Agreement" setup>
|
||||
import {
|
||||
addAgreement,
|
||||
delAgreement,
|
||||
getAgreement,
|
||||
listAgreement,
|
||||
updateAgreement
|
||||
} from '@/api/system/agreement';
|
||||
import {AgreementForm, AgreementQuery, AgreementVO} from '@/api/system/agreement/types';
|
||||
import {useLoading} from '@/hooks/async/useLoading';
|
||||
import {useFormDialog} from '@/hooks/dialog/useFormDialog';
|
||||
import {useDateRangeQuery} from '@/hooks/form/useDateRangeQuery';
|
||||
import {useSearchReset} from '@/hooks/form/useSearchReset';
|
||||
import {useSearchToggle} from '@/hooks/form/useSearchToggle';
|
||||
import {useTableSelection} from '@/hooks/table/useTableSelection';
|
||||
import {parseTime} from '@/utils/ruoyi';
|
||||
import modal from '@/plugins/modal';
|
||||
import {download as requestDownload} from '@/utils/request';
|
||||
|
||||
|
||||
const agreementList = ref<AgreementVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const {loading, withLoading} = useLoading(true);
|
||||
const {showSearch} = useSearchToggle();
|
||||
const total = ref(0);
|
||||
const {
|
||||
dateRange: dateRangePublishTime,
|
||||
applyDateRange: applyPublishTimeDateRange,
|
||||
resetDateRange: resetPublishTimeDateRange
|
||||
} = useDateRangeQuery('PublishTime');
|
||||
const {
|
||||
dateRange: dateRangeCreateTime,
|
||||
applyDateRange: applyCreateTimeDateRange,
|
||||
resetDateRange: resetCreateTimeDateRange
|
||||
} = useDateRangeQuery('CreateTime');
|
||||
const {
|
||||
dateRange: dateRangeUpdateTime,
|
||||
applyDateRange: applyUpdateTimeDateRange,
|
||||
resetDateRange: resetUpdateTimeDateRange
|
||||
} = useDateRangeQuery('UpdateTime');
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const agreementFormRef = ref<ElFormInstance>();
|
||||
|
||||
const initFormData: AgreementForm = {
|
||||
agreementId: undefined,
|
||||
agreementNo: undefined,
|
||||
agreementType: undefined,
|
||||
name: undefined,
|
||||
versionName: undefined,
|
||||
content: undefined,
|
||||
status: undefined,
|
||||
publishTime: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
createTime: undefined,
|
||||
updateBy: undefined,
|
||||
updateTime: undefined,
|
||||
activeType: undefined,
|
||||
}
|
||||
const data = reactive<PageData<AgreementForm, AgreementQuery>>({
|
||||
form: {...initFormData},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
agreementNo: undefined,
|
||||
agreementType: undefined,
|
||||
name: undefined,
|
||||
versionName: undefined,
|
||||
content: undefined,
|
||||
status: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
updateBy: undefined,
|
||||
activeType: undefined,
|
||||
params: {
|
||||
publishTime: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
agreementId: [
|
||||
{required: true, message: "主键不能为空", trigger: "change"}
|
||||
],
|
||||
agreementNo: [
|
||||
{required: true, message: "协议编号不能为空", trigger: "blur"}
|
||||
],
|
||||
agreementType: [
|
||||
{required: true, message: "类型不能为空", trigger: "change"}
|
||||
],
|
||||
name: [
|
||||
{required: true, message: "协议名称不能为空", trigger: "blur"}
|
||||
],
|
||||
versionName: [
|
||||
{required: true, message: "版本号不能为空", trigger: "blur"}
|
||||
],
|
||||
status: [
|
||||
{required: true, message: "状态不能为空", trigger: "change"}
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
const {queryParams, form, rules} = toRefs(data);
|
||||
const {ids, single, multiple, handleSelectionChange} = useTableSelection<AgreementVO>(item => item.agreementId);
|
||||
const {dialog, resetForm: reset, openDialog, showDialog, closeDialog} = useFormDialog({
|
||||
form,
|
||||
formRef: agreementFormRef,
|
||||
initialFormData: initFormData
|
||||
});
|
||||
|
||||
/** 查询协议配置列表 */
|
||||
const getList = async () => {
|
||||
await withLoading(async () => {
|
||||
let params = queryParams.value;
|
||||
params = applyPublishTimeDateRange(params);
|
||||
params = applyCreateTimeDateRange(params);
|
||||
params = applyUpdateTimeDateRange(params);
|
||||
const res = await listAgreement(params);
|
||||
agreementList.value = res.data?.rows;
|
||||
total.value = res.data?.total;
|
||||
});
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
const {resetQuery} = useSearchReset({
|
||||
queryFormRef,
|
||||
queryParams,
|
||||
pageNumKey: 'pageNum',
|
||||
pageSizeKey: 'pageSize',
|
||||
initialPageSize: 10,
|
||||
resetExtras: () => {
|
||||
resetPublishTimeDateRange();
|
||||
resetCreateTimeDateRange();
|
||||
resetUpdateTimeDateRange();
|
||||
},
|
||||
afterReset: () => {
|
||||
handleQuery();
|
||||
}
|
||||
});
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
openDialog('添加协议配置');
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: Partial<AgreementVO>) => {
|
||||
reset();
|
||||
const _agreementId = row?.agreementId || ids.value[0];
|
||||
const res = await getAgreement(_agreementId);
|
||||
Object.assign(form.value, res.data);
|
||||
showDialog('修改协议配置');
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
agreementFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.agreementId) {
|
||||
await updateAgreement(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addAgreement(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
modal.msgSuccess('操作成功');
|
||||
closeDialog();
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: Partial<AgreementVO>) => {
|
||||
const _agreementIds = row?.agreementId || ids.value;
|
||||
await modal.confirm('是否确认删除协议配置编号为"' + _agreementIds + '"的数据项?');
|
||||
await delAgreement(_agreementIds);
|
||||
modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
requestDownload(
|
||||
'system/agreement/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`agreement_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
229
src/views/system/conversation/index.vue
Normal file
229
src/views/system/conversation/index.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<div class="p-2 app-container monitor-online-page">
|
||||
<Pagecrumb></Pagecrumb>
|
||||
<div class="search-wrap">
|
||||
<el-card class="search-panel" shadow="hover">
|
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="query-form" label-position="top">
|
||||
<el-form-item label="会话ID" prop="sessionNo">
|
||||
<el-input v-model="queryParams.sessionNo" clearable placeholder="请输入会话编号" size="large"
|
||||
style="width: 150px" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户手机号" prop="ipaddr">
|
||||
<el-input v-model="queryParams.ipaddr" clearable placeholder="请输入用户手机号" size="large"
|
||||
style="width: 150px" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="会话状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
:empty-values="[null, undefined]"
|
||||
:value-on-clear="null"
|
||||
clearable
|
||||
placeholder="请选择会话状态"
|
||||
size="large"
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option label="机器人" value="0"></el-option>
|
||||
<el-option label="排队" value="1"></el-option>
|
||||
<el-option label="人工" value="2"></el-option>
|
||||
<el-option label="已结束" value="3"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="问题类型" prop="problemType">
|
||||
<el-select
|
||||
v-model="queryParams.problemType"
|
||||
:empty-values="[null, undefined]"
|
||||
:value-on-clear="null"
|
||||
clearable
|
||||
placeholder="请选择问题类型"
|
||||
size="large"
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option label="在线" value="0"></el-option>
|
||||
<el-option label="离线" value="1"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关联设备" prop="deviceId">
|
||||
<el-input v-model="queryParams.deviceId" clearable placeholder="请输入MAC/设备名称" size="large"
|
||||
style="width: 150px" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="会话创建时间" prop="startTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.startTime"
|
||||
end-placeholder="结束日期"
|
||||
range-separator="-"
|
||||
single-panel
|
||||
size="large"
|
||||
start-placeholder="开始日期"
|
||||
style="width: 220px"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="接待客服" prop="agentId">
|
||||
<el-select
|
||||
v-model="queryParams.agentId"
|
||||
:empty-values="[null, undefined]"
|
||||
:value-on-clear="null"
|
||||
clearable
|
||||
size="large"
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option label="在线" value="0"></el-option>
|
||||
<el-option label="离线" value="1"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="large" type="primary" @click="handleQuery">搜索</el-button>
|
||||
<el-button size="large" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-card class="table-panel" shadow="hover">
|
||||
<template #header>
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<el-button type="primary">
|
||||
<span style="margin:0 8px;color: #fff;">全部</span>
|
||||
<span style="color: #fff;font-weight: bold">1286</span>
|
||||
</el-button>
|
||||
<tableTag circle-color="#7c3aed" num="862" title="机器人接待"></tableTag>
|
||||
<tableTag circle-color="#ea580c" num="7" title="排队等待"></tableTag>
|
||||
<tableTag num="12" title="人工接待"></tableTag>
|
||||
<tableTag circle-color="#94a3b8" num="405" title="已结束"></tableTag>
|
||||
</div>
|
||||
<div>
|
||||
<el-button plain type="info">批量导出</el-button>
|
||||
<el-button plain type="info">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="onlineList"
|
||||
border
|
||||
class="data-table"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column align="center" type="selection"></el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="会话ID" prop="tokenId"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="用户信息" prop="userName"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="关联设备" prop="clientKey"/>
|
||||
<el-table-column align="center" label="会话状态">
|
||||
<template #default="scope">
|
||||
<el-tag type="primary">在线</el-tag>
|
||||
<el-tag type="danger">离线</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="当前接待人" prop="deptName"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="用户问题摘要" prop="ipaddr"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="排队序号" prop="loginLocation"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="等待时长" prop="os"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="最后消息时间" prop="browser"/>
|
||||
<el-table-column align="center" class-name="small-padding fixed-width" label="操作" min-width="200">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-button
|
||||
v-hasPermi="['monitor:online:forceLogout']"
|
||||
type="primary"
|
||||
@click="handleMain(scope.row)"
|
||||
>进入会话
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['monitor:online:forceLogout']"
|
||||
plain
|
||||
type="info"
|
||||
@click="handleMain(scope.row)"
|
||||
>用户档案
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['monitor:online:forceLogout']"
|
||||
plain
|
||||
type="danger"
|
||||
@click="handleMain(scope.row)"
|
||||
>关闭会话
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNum"
|
||||
:total="total"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="Online" setup>
|
||||
import {to} from 'await-to-js';
|
||||
import {listCsSession as initData} from '@/api/system/conversation/index';
|
||||
import {CsSessionQuery, CsSessionVO} from '@/api/system/conversation/types';
|
||||
import modal from '@/plugins/modal';
|
||||
import {useDict} from '@/utils/dict';
|
||||
import {parseTime} from '@/utils/ruoyi';
|
||||
|
||||
const {sys_device_type} = toRefs<any>(useDict('sys_device_type'));
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const onlineList = ref<CsSessionVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
|
||||
const queryParams = ref<CsSessionQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
ipaddr: '',
|
||||
userName: ''
|
||||
});
|
||||
|
||||
/** 查询登录日志列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await initData(queryParams.value);
|
||||
onlineList.value = res.data?.rows;
|
||||
total.value = res.data?.total;
|
||||
loading.value = false;
|
||||
};
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
function handleMain(row) {
|
||||
console.log(row);
|
||||
router.push({
|
||||
path: '/conversation/conversationMain'
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/assets/styles/components/page-shell' as pageShell;
|
||||
|
||||
@include pageShell.table-crud-page;
|
||||
|
||||
|
||||
.circle {
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(124, 58, 237, 1)
|
||||
}
|
||||
</style>
|
||||
0
src/views/system/conversation/main.vue
Normal file
0
src/views/system/conversation/main.vue
Normal file
306
src/views/system/cskb/add.vue
Normal file
306
src/views/system/cskb/add.vue
Normal file
@@ -0,0 +1,306 @@
|
||||
<script lang="ts" setup>
|
||||
import FormItemTip from "@/components/FormItemTip/index.vue";
|
||||
import cskbEditor from '@/components/Editor/cskb.vue'
|
||||
|
||||
const router = useRouter();
|
||||
const form = ref({
|
||||
phone: "",
|
||||
username: "",
|
||||
sex: 1,
|
||||
creatDay: '',
|
||||
status: 1,
|
||||
quickAction: [],
|
||||
img: ''
|
||||
})
|
||||
|
||||
|
||||
function handleBack() {
|
||||
router.replace({
|
||||
path: "/conversation/cskbList"
|
||||
})
|
||||
}
|
||||
|
||||
function submit() {
|
||||
console.log(form.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="edit">
|
||||
<div class="flex justify-between">
|
||||
<pagecrumb></pagecrumb>
|
||||
<el-button plain type="info" @click="handleBack"> 返回客服列表</el-button>
|
||||
</div>
|
||||
<div class="tipBox">
|
||||
<div class="tip1">新增的知识条目保存后需要「启用」才会在 APP 端展示
|
||||
</div>
|
||||
<div class="tip2">回复内容支持有序步骤与图片,建议控制在 200 字以内,便于用户快速阅读。
|
||||
</div>
|
||||
</div>
|
||||
<!-- form -->
|
||||
<section class="commonCard">
|
||||
<div class="flex justify-between flex-items-center">
|
||||
<card-nav subTitle="配置问题标题、分类与标准回复话术" title="新增知识条目"></card-nav>
|
||||
<div class="unSave">
|
||||
<i class="square"></i>
|
||||
<span>未保存</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formbox">
|
||||
<el-form label-position="top">
|
||||
<div class="title">
|
||||
基础信息
|
||||
</div>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.phone"
|
||||
:inputAttrs="{ placeholder: '请输入问题标题,如「连不上 Wi-Fi」', showWordLimit:true ,maxlength:30}"
|
||||
:required="true"
|
||||
label="问题标题"
|
||||
prop="phone"
|
||||
tip="建议使用用户口语化问法,便于模糊匹配"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请选择问题分类'}"
|
||||
:required="true"
|
||||
label="问题分类"
|
||||
prop="username"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<div>回复内容 <span>*</span></div>
|
||||
<div>
|
||||
<cskbEditor v-model="form.creatDay" :simple-toolbar="true"></cskbEditor>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.status"
|
||||
:inputAttrs="{ placeholder: '', disabled: false }"
|
||||
:radioOptions="[
|
||||
{label:'启用',value:1},
|
||||
{label:'禁用',value:0},
|
||||
]"
|
||||
label="账号状态"
|
||||
prop="status"
|
||||
tip="禁用后该客服无法登录工作台,其接待中会话将转回排队"
|
||||
type="radio"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="title">
|
||||
关联与排序
|
||||
</div>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.quickAction"
|
||||
:radio-options="[
|
||||
{label: '已解决',value:1},
|
||||
{label: '转人工',value:0}
|
||||
]"
|
||||
label="关联快捷按钮"
|
||||
prop="phone"
|
||||
tip="回复气泡下方展示的引导按钮,可多选"
|
||||
type="checkBox"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请输入排序'}"
|
||||
:required="true"
|
||||
label="排序号"
|
||||
prop="username"
|
||||
tip="决定该条目在 APP 常见问题列表中的展示顺序"
|
||||
type="inputNumber"
|
||||
>
|
||||
<template #suffix>越小越靠前</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
label="启用状态"
|
||||
tip="关闭则保存为草稿,仅后台可见"
|
||||
type="switch"
|
||||
>
|
||||
<template #switchRight>
|
||||
<span
|
||||
style="color: rgba(17, 24, 39, 1);font-size: 13px;font-weight: bolder">启用后立即在APP端展示</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</section>
|
||||
<div class="footer">
|
||||
<div class="footer-tip">标题、分类与回复内容为必填项;排序号越小展示越靠前</div>
|
||||
<div>
|
||||
<el-button>取消</el-button>
|
||||
<el-button plain type="primary" @click="submit">保存并继续新增</el-button>
|
||||
<el-button type="primary" @click="submit">保存并启用</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit {
|
||||
overflow: auto;
|
||||
padding-bottom: 50px;
|
||||
|
||||
.tipBox {
|
||||
margin-top: 16px;
|
||||
background-color: rgba(239, 246, 255, 1);
|
||||
border: 1px solid rgba(219, 234, 254, 1);
|
||||
padding: 13px 53px;
|
||||
|
||||
.tip1 {
|
||||
line-height: 18px;
|
||||
color: rgba(30, 64, 175, 1);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tip2 {
|
||||
line-height: 16px;
|
||||
color: rgba(59, 130, 246, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.customerInfoBox {
|
||||
margin-top: 16px;
|
||||
|
||||
.infoBox {
|
||||
.customer-Img {
|
||||
width: 73px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
font-weight: bold;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.circle {
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
background-color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.subInfoBox {
|
||||
line-height: 20px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
||||
.dat {
|
||||
margin: 0 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.subTitle {
|
||||
line-height: 14px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.unSave {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 79px;
|
||||
height: 28px;
|
||||
background-color: rgba(248, 250, 252, 1);
|
||||
border: 1px solid rgba(241, 245, 249, 1);
|
||||
|
||||
.square {
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 6px;
|
||||
margin-right: 8px;
|
||||
background-color: rgba(148, 163, 184, 1);
|
||||
}
|
||||
|
||||
span {
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.formbox {
|
||||
margin-top: 46px;
|
||||
|
||||
.title {
|
||||
line-height: 16px;
|
||||
color: rgba(55, 65, 81, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
margin: 24px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 27px;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.footer-tip {
|
||||
line-height: 14px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:deep(.el-button) {
|
||||
padding: 14px 25px;
|
||||
min-height: 44px;
|
||||
border-radius: 0;
|
||||
border: 1px solid rgba(226, 232, 240, 1);
|
||||
|
||||
|
||||
&.el-button--primary.is-plain {
|
||||
color: #2563EB;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button + .el-button {
|
||||
margin-left: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
306
src/views/system/cskb/edit.vue
Normal file
306
src/views/system/cskb/edit.vue
Normal file
@@ -0,0 +1,306 @@
|
||||
<script lang="ts" setup>
|
||||
import FormItemTip from "@/components/FormItemTip/index.vue";
|
||||
import cskbEditor from '@/components/Editor/cskb.vue'
|
||||
|
||||
const router = useRouter();
|
||||
const form = ref({
|
||||
phone: "",
|
||||
username: "",
|
||||
sex: 1,
|
||||
creatDay: '',
|
||||
status: 1,
|
||||
quickAction: [],
|
||||
img: ''
|
||||
})
|
||||
|
||||
|
||||
function handleBack() {
|
||||
router.replace({
|
||||
path: "/conversation/cskbList"
|
||||
})
|
||||
}
|
||||
|
||||
function submit() {
|
||||
console.log(form.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="edit">
|
||||
<div class="flex justify-between">
|
||||
<pagecrumb></pagecrumb>
|
||||
<el-button plain type="info" @click="handleBack"> 返回客服列表</el-button>
|
||||
</div>
|
||||
<div class="tipBox">
|
||||
<div class="tip1">新增的知识条目保存后需要「启用」才会在 APP 端展示
|
||||
</div>
|
||||
<div class="tip2">回复内容支持有序步骤与图片,建议控制在 200 字以内,便于用户快速阅读。
|
||||
</div>
|
||||
</div>
|
||||
<!-- form -->
|
||||
<section class="commonCard">
|
||||
<div class="flex justify-between flex-items-center">
|
||||
<card-nav subTitle="配置问题标题、分类与标准回复话术" title="新增知识条目"></card-nav>
|
||||
<div class="unSave">
|
||||
<i class="square"></i>
|
||||
<span>未保存</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formbox">
|
||||
<el-form label-position="top">
|
||||
<div class="title">
|
||||
基础信息
|
||||
</div>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.phone"
|
||||
:inputAttrs="{ placeholder: '请输入问题标题,如「连不上 Wi-Fi」', showWordLimit:true ,maxlength:30}"
|
||||
:required="true"
|
||||
label="问题标题"
|
||||
prop="phone"
|
||||
tip="建议使用用户口语化问法,便于模糊匹配"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请选择问题分类'}"
|
||||
:required="true"
|
||||
label="问题分类"
|
||||
prop="username"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<div>回复内容 <span>*</span></div>
|
||||
<div>
|
||||
<cskbEditor v-model="form.creatDay" :simple-toolbar="true"></cskbEditor>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.status"
|
||||
:inputAttrs="{ placeholder: '', disabled: false }"
|
||||
:radioOptions="[
|
||||
{label:'启用',value:1},
|
||||
{label:'禁用',value:0},
|
||||
]"
|
||||
label="账号状态"
|
||||
prop="status"
|
||||
tip="禁用后该客服无法登录工作台,其接待中会话将转回排队"
|
||||
type="radio"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="title">
|
||||
关联与排序
|
||||
</div>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.quickAction"
|
||||
:radio-options="[
|
||||
{label: '已解决',value:1},
|
||||
{label: '转人工',value:0}
|
||||
]"
|
||||
label="关联快捷按钮"
|
||||
prop="phone"
|
||||
tip="回复气泡下方展示的引导按钮,可多选"
|
||||
type="checkBox"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请输入排序'}"
|
||||
:required="true"
|
||||
label="排序号"
|
||||
prop="username"
|
||||
tip="决定该条目在 APP 常见问题列表中的展示顺序"
|
||||
type="inputNumber"
|
||||
>
|
||||
<template #suffix>越小越靠前</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
label="启用状态"
|
||||
tip="关闭则保存为草稿,仅后台可见"
|
||||
type="switch"
|
||||
>
|
||||
<template #switchRight>
|
||||
<span
|
||||
style="color: rgba(17, 24, 39, 1);font-size: 13px;font-weight: bolder">启用后立即在APP端展示</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</section>
|
||||
<div class="footer">
|
||||
<div class="footer-tip">标题、分类与回复内容为必填项;排序号越小展示越靠前</div>
|
||||
<div>
|
||||
<el-button>取消</el-button>
|
||||
<el-button plain type="primary" @click="submit">保存并继续新增</el-button>
|
||||
<el-button type="primary" @click="submit">保存并启用</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit {
|
||||
overflow: auto;
|
||||
padding-bottom: 50px;
|
||||
|
||||
.tipBox {
|
||||
margin-top: 16px;
|
||||
background-color: rgba(239, 246, 255, 1);
|
||||
border: 1px solid rgba(219, 234, 254, 1);
|
||||
padding: 13px 53px;
|
||||
|
||||
.tip1 {
|
||||
line-height: 18px;
|
||||
color: rgba(30, 64, 175, 1);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tip2 {
|
||||
line-height: 16px;
|
||||
color: rgba(59, 130, 246, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.customerInfoBox {
|
||||
margin-top: 16px;
|
||||
|
||||
.infoBox {
|
||||
.customer-Img {
|
||||
width: 73px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
font-weight: bold;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.circle {
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
background-color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.subInfoBox {
|
||||
line-height: 20px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
||||
.dat {
|
||||
margin: 0 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.subTitle {
|
||||
line-height: 14px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.unSave {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 79px;
|
||||
height: 28px;
|
||||
background-color: rgba(248, 250, 252, 1);
|
||||
border: 1px solid rgba(241, 245, 249, 1);
|
||||
|
||||
.square {
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 6px;
|
||||
margin-right: 8px;
|
||||
background-color: rgba(148, 163, 184, 1);
|
||||
}
|
||||
|
||||
span {
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.formbox {
|
||||
margin-top: 46px;
|
||||
|
||||
.title {
|
||||
line-height: 16px;
|
||||
color: rgba(55, 65, 81, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
margin: 24px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 27px;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.footer-tip {
|
||||
line-height: 14px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:deep(.el-button) {
|
||||
padding: 14px 25px;
|
||||
min-height: 44px;
|
||||
border-radius: 0;
|
||||
border: 1px solid rgba(226, 232, 240, 1);
|
||||
|
||||
|
||||
&.el-button--primary.is-plain {
|
||||
color: #2563EB;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button + .el-button {
|
||||
margin-left: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
359
src/views/system/cskb/index.vue
Normal file
359
src/views/system/cskb/index.vue
Normal file
@@ -0,0 +1,359 @@
|
||||
<template>
|
||||
<div class="p-2 page-shell system-csKb-page">
|
||||
<div class="search-wrap">
|
||||
<pagecrumb></pagecrumb>
|
||||
<el-card :class="{ 'is-collapsed': !showSearch }" class="search-panel" shadow="hover" style="margin-top:12px;">
|
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="query-form" label-position="top">
|
||||
<el-form-item label="问题标题" prop="title">
|
||||
<el-input v-model="queryParams.title" clearable placeholder="请输入问题标题" size="large"
|
||||
@keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-input v-model="queryParams.category" clearable placeholder="请输入分类" size="large"
|
||||
@keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" clearable placeholder="请选择状态" size="large">
|
||||
<el-option :value="true" label="是"/>
|
||||
<el-option :value="false" label="否"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="large" type="primary" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" size="large" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card class="table-panel" shadow="hover">
|
||||
<template #header>
|
||||
<div class="toolbar-shell">
|
||||
<div>
|
||||
<el-button type="primary">
|
||||
<span style="margin:0 8px;color: #fff;">全部</span>
|
||||
<span style="color: #fff;font-weight: bold">86</span>
|
||||
</el-button>
|
||||
<tableTag circle-color="#16A34A" num="78" title="启用"></tableTag>
|
||||
<tableTag circle-color="#ef4444" num="8" num-color="#DC2626" title="禁用"></tableTag>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button :disabled="multiple" plain size="large" type="info" @click="handleDelete()">批量删除
|
||||
</el-button>
|
||||
<el-button size="large" type="primary" @click="handleAdd">新增问题
|
||||
</el-button>
|
||||
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="csKbList" border class="data-table"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column align="center" type="selection" width="55"/>
|
||||
<el-table-column align="center" label="ID" prop="kbId"/>
|
||||
<el-table-column align="center" label="问题标题" prop="title"/>
|
||||
<el-table-column align="center" label="分类" prop="category"/>
|
||||
<el-table-column align="center" label="回复内容" prop="content"/>
|
||||
<el-table-column align="center" label="关联快捷按钮" prop="sort"/>
|
||||
<el-table-column align="center" label="排序号" prop="sort"/>
|
||||
<el-table-column align="center" label="状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
disabled
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" class-name="small-padding fixed-width" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button v-hasPermi="['system:cskb:edit']" link type="primary"
|
||||
@click="handleUpdate(scope.row)">上移
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['system:cskb:edit']" link type="primary"
|
||||
@click="handleUpdate(scope.row)">下移
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['system:cskb:edit']" link type="primary"
|
||||
@click="handleUpdate(scope.row)">编辑
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['system:cskb:edit']" link type="primary"
|
||||
@click="handleUpdate(scope.row)">禁用
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['system:cskb:remove']" link type="primary"
|
||||
@click="handleDelete(scope.row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNum"
|
||||
:total="total" @pagination="getList"/>
|
||||
</el-card>
|
||||
<!-- 添加或修改客服知识库对话框 -->
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" append-to-body width="500px">
|
||||
<el-form ref="csKbFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="知识编号,如 KB-001" prop="kbNo">
|
||||
<el-input v-model="form.kbNo" placeholder="请输入知识编号,如 KB-001"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="问题标题" prop="title">
|
||||
<el-input v-model="form.title" placeholder="请输入问题标题"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-input v-model="form.category" placeholder="请输入分类"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="机器人回复内容">
|
||||
<editor v-model="form.content" :min-height="192"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序权重,数字越小越靠前" prop="sort">
|
||||
<el-input-number v-model="form.sort" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-switch
|
||||
v-model="form.status"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建部门" prop="createDept">
|
||||
<el-input-number v-model="form.createDept" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建者" prop="createBy">
|
||||
<el-input-number v-model="form.createBy" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker v-model="form.createTime"
|
||||
clearable
|
||||
placeholder="请选择创建时间"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新者" prop="updateBy">
|
||||
<el-input-number v-model="form.updateBy" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新时间" prop="updateTime">
|
||||
<el-date-picker v-model="form.updateTime"
|
||||
clearable
|
||||
placeholder="请选择更新时间"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="CsKb" setup>
|
||||
import {
|
||||
addCsKb,
|
||||
delCsKb,
|
||||
getCsKb,
|
||||
listCsKb,
|
||||
updateCsKb
|
||||
} from '@/api/system/csKb';
|
||||
import {CsKbForm, CsKbQuery, CsKbVO} from '@/api/system/csKb/types';
|
||||
import {useLoading} from '@/hooks/async/useLoading';
|
||||
import {useFormDialog} from '@/hooks/dialog/useFormDialog';
|
||||
import {useDateRangeQuery} from '@/hooks/form/useDateRangeQuery';
|
||||
import {useSearchReset} from '@/hooks/form/useSearchReset';
|
||||
import {useSearchToggle} from '@/hooks/form/useSearchToggle';
|
||||
import {useTableSelection} from '@/hooks/table/useTableSelection';
|
||||
import modal from '@/plugins/modal';
|
||||
import {download as requestDownload} from '@/utils/request';
|
||||
|
||||
|
||||
const csKbList = ref<CsKbVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const {loading, withLoading} = useLoading(true);
|
||||
const {showSearch} = useSearchToggle();
|
||||
const total = ref(0);
|
||||
const {
|
||||
dateRange: dateRangeCreateTime,
|
||||
applyDateRange: applyCreateTimeDateRange,
|
||||
resetDateRange: resetCreateTimeDateRange
|
||||
} = useDateRangeQuery('CreateTime');
|
||||
const {
|
||||
dateRange: dateRangeUpdateTime,
|
||||
applyDateRange: applyUpdateTimeDateRange,
|
||||
resetDateRange: resetUpdateTimeDateRange
|
||||
} = useDateRangeQuery('UpdateTime');
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const csKbFormRef = ref<ElFormInstance>();
|
||||
|
||||
const initFormData: CsKbForm = {
|
||||
kbId: undefined,
|
||||
kbNo: undefined,
|
||||
title: undefined,
|
||||
category: undefined,
|
||||
content: undefined,
|
||||
sort: undefined,
|
||||
status: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
createTime: undefined,
|
||||
updateBy: undefined,
|
||||
updateTime: undefined,
|
||||
}
|
||||
const data = reactive<PageData<CsKbForm, CsKbQuery>>({
|
||||
form: {...initFormData},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
kbNo: undefined,
|
||||
title: undefined,
|
||||
category: undefined,
|
||||
content: undefined,
|
||||
sort: undefined,
|
||||
status: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
updateBy: undefined,
|
||||
params: {
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
kbId: [
|
||||
{required: true, message: "主键不能为空", trigger: "change"}
|
||||
],
|
||||
kbNo: [
|
||||
{required: true, message: "知识编号,如 KB-001不能为空", trigger: "blur"}
|
||||
],
|
||||
title: [
|
||||
{required: true, message: "问题标题不能为空", trigger: "blur"}
|
||||
],
|
||||
category: [
|
||||
{required: true, message: "分类不能为空", trigger: "blur"}
|
||||
],
|
||||
sort: [
|
||||
{required: true, message: "排序权重,数字越小越靠前不能为空", trigger: "change"}
|
||||
],
|
||||
status: [
|
||||
{required: true, message: "状态不能为空", trigger: "change"}
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
const {queryParams, form, rules} = toRefs(data);
|
||||
const {ids, single, multiple, handleSelectionChange} = useTableSelection<CsKbVO>(item => item.kbId);
|
||||
const {dialog, resetForm: reset, openDialog, showDialog, closeDialog} = useFormDialog({
|
||||
form,
|
||||
formRef: csKbFormRef,
|
||||
initialFormData: initFormData
|
||||
});
|
||||
|
||||
/** 查询客服知识库列表 */
|
||||
const getList = async () => {
|
||||
await withLoading(async () => {
|
||||
let params = queryParams.value;
|
||||
params = applyCreateTimeDateRange(params);
|
||||
params = applyUpdateTimeDateRange(params);
|
||||
const res = await listCsKb(params);
|
||||
csKbList.value = res.data?.rows;
|
||||
total.value = res.data?.total;
|
||||
});
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
const {resetQuery} = useSearchReset({
|
||||
queryFormRef,
|
||||
queryParams,
|
||||
pageNumKey: 'pageNum',
|
||||
pageSizeKey: 'pageSize',
|
||||
initialPageSize: 10,
|
||||
resetExtras: () => {
|
||||
resetCreateTimeDateRange();
|
||||
resetUpdateTimeDateRange();
|
||||
},
|
||||
afterReset: () => {
|
||||
handleQuery();
|
||||
}
|
||||
});
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
openDialog('添加客服知识库');
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: Partial<CsKbVO>) => {
|
||||
reset();
|
||||
const _kbId = row?.kbId || ids.value[0];
|
||||
const res = await getCsKb(_kbId);
|
||||
Object.assign(form.value, res.data);
|
||||
showDialog('修改客服知识库');
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
csKbFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.kbId) {
|
||||
await updateCsKb(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addCsKb(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
modal.msgSuccess('操作成功');
|
||||
closeDialog();
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: Partial<CsKbVO>) => {
|
||||
const _kbIds = row?.kbId || ids.value;
|
||||
await modal.confirm('是否确认删除客服知识库编号为"' + _kbIds + '"的数据项?');
|
||||
await delCsKb(_kbIds);
|
||||
modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
requestDownload(
|
||||
'system/cskb/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`csKb_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.circle {
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(124, 58, 237, 1)
|
||||
}
|
||||
</style>
|
||||
182
src/views/system/cssAgent/Add.vue
Normal file
182
src/views/system/cssAgent/Add.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<script lang="ts" setup>
|
||||
import FormItemTip from "@/components/FormItemTip/index.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const form = ref({
|
||||
phone: "",
|
||||
username: "",
|
||||
sex: 1,
|
||||
creatDay: '',
|
||||
status: 1,
|
||||
img: ''
|
||||
})
|
||||
|
||||
|
||||
function handleBack() {
|
||||
router.replace({
|
||||
path: "/customer/customerList"
|
||||
})
|
||||
}
|
||||
|
||||
function handleChangeFile(file: File) {
|
||||
console.log(file)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="edit">
|
||||
<div class="flex justify-between">
|
||||
<pagecrumb></pagecrumb>
|
||||
<el-button plain type="info" @click="handleBack"> 返回客服列表</el-button>
|
||||
</div>
|
||||
<!-- form -->
|
||||
<section class="commonCard">
|
||||
<div class="flex justify-between flex-items-center">
|
||||
<card-nav subTitle="填写客服基础信息、登录凭证与接待权限" title="新增客服账号"></card-nav>
|
||||
<el-tag style="color:rgba(107, 114, 128, 1)" type="info">
|
||||
<span> 带<span style="color: red;font-weight: bold;">* </span>为必填项</span>
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="formbox">
|
||||
<el-form label-position="top">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.phone"
|
||||
:inputAttrs="{ placeholder: '请输入工号,如 8823', disabled: true , showWordLimit:false ,maxlength:11}"
|
||||
:required="true"
|
||||
label="客服工号"
|
||||
prop="phone"
|
||||
tip="建议使用 4 位数字,用于内部标识与排班识别"
|
||||
>
|
||||
<template #suffix>不可修改</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请输入客服姓名', disabled: false , showWordLimit:true ,maxlength:20}"
|
||||
:required="true"
|
||||
prop="username"
|
||||
tip="必填,将在会话列表与用户端展示"
|
||||
>
|
||||
<template #label>
|
||||
<span>客服姓名</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请输入登录账号', disabled: false, showWordLimit:false ,maxlength:11}"
|
||||
:required="true"
|
||||
prop="username"
|
||||
tip="该手机号同时作为账号找回与首次登录验证凭证"
|
||||
>
|
||||
<template #label>
|
||||
<span>登录账号(手机号)</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.img"
|
||||
label="账号状态"
|
||||
prop="img"
|
||||
type="uploadUserAvatar"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.status"
|
||||
:inputAttrs="{ placeholder: '', disabled: false }"
|
||||
:radioOptions="[
|
||||
{label:'启用',value:1},
|
||||
{label:'禁用',value:0},
|
||||
]"
|
||||
label="账号状态"
|
||||
prop="status"
|
||||
tip="禁用后该客服无法登录工作台,其接待中会话将转回排队"
|
||||
type="radio"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</section>
|
||||
<div class="footer">
|
||||
<el-button>取消</el-button>
|
||||
<el-button type="primary">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit {
|
||||
.customerInfoBox {
|
||||
.infoBox {
|
||||
|
||||
.customer-Img {
|
||||
width: 73px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
font-weight: bold;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.circle {
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
background-color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.subInfoBox {
|
||||
line-height: 20px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
||||
.dat {
|
||||
margin: 0 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.subTitle {
|
||||
line-height: 14px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.formbox {
|
||||
margin-top: 46px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 27px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
210
src/views/system/cssAgent/edit.vue
Normal file
210
src/views/system/cssAgent/edit.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<script lang="ts" setup>
|
||||
import FormItemTip from "@/components/FormItemTip/index.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const form = ref({
|
||||
phone: "",
|
||||
username: "",
|
||||
sex: 1,
|
||||
creatDay: '',
|
||||
status: 1,
|
||||
img: ''
|
||||
})
|
||||
|
||||
|
||||
function handleBack() {
|
||||
router.replace({
|
||||
path: "/customer/customerList"
|
||||
})
|
||||
}
|
||||
|
||||
function handleChangeFile(file: File) {
|
||||
console.log(file)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="edit">
|
||||
<div class="flex justify-between">
|
||||
<pagecrumb></pagecrumb>
|
||||
<el-button plain type="info" @click="handleBack"> 返回客服列表</el-button>
|
||||
</div>
|
||||
<!-- 用户信息-->
|
||||
<section class="customerInfoBox commonCard flex justify-between flex-items-center">
|
||||
<div class="flex infoBox">
|
||||
<el-image
|
||||
class="customer-Img"
|
||||
src="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"></el-image>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div>
|
||||
<span class="userName">林小满</span>
|
||||
<el-tag effect="light" round style="margin: 0 12px " type="success">
|
||||
<div class="flex flex-items-center">
|
||||
<span>启用</span></div>
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="subInfoBox">
|
||||
<span> KF-001 </span>
|
||||
<span class="dat">·</span>
|
||||
<span>工号 8823 </span>
|
||||
<span class="dat">·</span>
|
||||
<span>138****6688</span>
|
||||
<span class="dat">·</span>
|
||||
<span>创建于 2025-11-08</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
当前在线·接待中3/5
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- form -->
|
||||
<section class="commonCard">
|
||||
<card-nav subTitle="工号唯一且不可重复;账号状态切换后该客服将无法登录工作台" title="客服账号信息"></card-nav>
|
||||
<div class="formbox">
|
||||
<el-form label-position="top">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.phone"
|
||||
:inputAttrs="{ placeholder: '请输入工号', disabled: true , showWordLimit:false ,maxlength:11}"
|
||||
:required="true"
|
||||
label="工号"
|
||||
prop="phone"
|
||||
tip="必填且全平台唯一,示例:8823"
|
||||
>
|
||||
<template #suffix>不可修改</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请输入客服姓名', disabled: false , showWordLimit:true ,maxlength:20}"
|
||||
:required="true"
|
||||
prop="username"
|
||||
tip="必填,将在会话列表与用户端展示"
|
||||
>
|
||||
<template #label>
|
||||
<span>客服姓名</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请输入登录账号', disabled: false , showWordLimit:true ,maxlength:20}"
|
||||
:required="true"
|
||||
prop="username"
|
||||
tip="必填,手机号格式,全平台唯一,同时作为登录账号"
|
||||
>
|
||||
<template #label>
|
||||
<span>登录账号</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '不修改请留空', disabled: false , showWordLimit:true ,maxlength:20, minlength:8}"
|
||||
:required="true"
|
||||
prop="username"
|
||||
tip="如需修改请重新输入,长度 8–20 位,含字母与数字"
|
||||
>
|
||||
<template #label>
|
||||
<span>登录密码</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.status"
|
||||
:inputAttrs="{ placeholder: '', disabled: false }"
|
||||
:radioOptions="[
|
||||
{label:'启用',value:1},
|
||||
{label:'禁用',value:0},
|
||||
]"
|
||||
label="账号状态"
|
||||
prop="status"
|
||||
tip="禁用后该客服无法登录工作台,其接待中会话将转回排队"
|
||||
type="radio"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</section>
|
||||
<div class="footer">
|
||||
<el-button>取消</el-button>
|
||||
<el-button type="primary">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit {
|
||||
.customerInfoBox {
|
||||
.infoBox {
|
||||
|
||||
.customer-Img {
|
||||
width: 73px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
font-weight: bold;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.circle {
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
background-color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.subInfoBox {
|
||||
line-height: 20px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
||||
.dat {
|
||||
margin: 0 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.subTitle {
|
||||
line-height: 14px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.formbox {
|
||||
margin-top: 46px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 27px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
394
src/views/system/cssAgent/index.vue
Normal file
394
src/views/system/cssAgent/index.vue
Normal file
@@ -0,0 +1,394 @@
|
||||
<template>
|
||||
<div class="p-2 page-shell system-csAgent-page">
|
||||
<div class="search-wrap">
|
||||
<pagecrumb></pagecrumb>
|
||||
<el-card :class="{ 'is-collapsed': !showSearch }" class="search-panel" shadow="hover">
|
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="query-form" label-position="top">
|
||||
|
||||
<el-form-item label="客服姓名" prop="name">
|
||||
<el-input v-model="queryParams.name" clearable placeholder="请输入客服姓名" size="large"
|
||||
style="width:240px;"
|
||||
@keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="客服工号" prop="jobNo">
|
||||
<el-input v-model="queryParams.jobNo" clearable placeholder="请输入客服工号" size="large"
|
||||
style="width:240px;"
|
||||
@keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="账号状态" prop="status">
|
||||
<el-select v-model="queryParams.status" clearable placeholder="请选择账号状态" size="large"
|
||||
style="width:180px;">
|
||||
<el-option :value="true" label="是"/>
|
||||
<el-option :value="false" label="否"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="在线状态" prop="onlineStatus">
|
||||
<el-select v-model="queryParams.onlineStatus" clearable placeholder="请选择在线状态" size="large"
|
||||
style="width:180px;">
|
||||
<el-option :value="true" label="是"/>
|
||||
<el-option :value="false" label="否"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="large" type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button size="large" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card class="table-panel" shadow="hover">
|
||||
<template #header>
|
||||
<div class="toolbar-shell">
|
||||
<div class="table-heading">
|
||||
<el-button type="primary">
|
||||
<span style="margin:0 8px;color: #fff;">全部</span>
|
||||
<span style="color: #fff;font-weight: bold">1286</span>
|
||||
</el-button>
|
||||
<tableTag circle-color="#16a34a" num="6" title="在线"></tableTag>
|
||||
<tableTag circle-color="#ea580c" num="2" title="忙碌"></tableTag>
|
||||
<tableTag circle-color="#94a3b8" num="4" title="离线"></tableTag>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button v-hasPermi="['system:csAgent:edit']" :disabled="single" plain type="info"
|
||||
@click="handleUpdate()">批量禁用账号
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['system:csAgent:add']" type="primary" @click="handleAdd">新增客服
|
||||
</el-button>
|
||||
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="csAgentList" border class="data-table"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column align="center" type="selection" width="55"/>
|
||||
<el-table-column align="center" label="主键" prop="agentId"/>
|
||||
<el-table-column align="center" label="客服编号,如 KF-001" prop="agentNo"/>
|
||||
<el-table-column align="center" label="工号" prop="jobNo"/>
|
||||
<el-table-column align="center" label="客服姓名" prop="name"/>
|
||||
<el-table-column align="center" label="登录账号" prop="account"/>
|
||||
<el-table-column align="center" label="密码" prop="password"/>
|
||||
<el-table-column align="center" label="手机号" prop="phone"/>
|
||||
<el-table-column align="center" label="头像 oss_id" prop="avatarOssId"/>
|
||||
<el-table-column align="center" label="在线状态" prop="onlineStatus" width="120">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.onlineStatus"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
disabled
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="账号状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
disabled
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" class-name="small-padding fixed-width" label="操作">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button v-hasPermi="['system:csAgent:edit']" icon="Edit" link type="primary"
|
||||
@click="handleUpdate(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button v-hasPermi="['system:csAgent:remove']" icon="Delete" link type="primary"
|
||||
@click="handleDelete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNum"
|
||||
:total="total" @pagination="getList"/>
|
||||
</el-card>
|
||||
<!-- 添加或修改客服人员对话框 -->
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" append-to-body width="500px">
|
||||
<el-form ref="csAgentFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="客服编号,如 KF-001" prop="agentNo">
|
||||
<el-input v-model="form.agentNo" placeholder="请输入客服编号,如 KF-001"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="工号" prop="jobNo">
|
||||
<el-input v-model="form.jobNo" placeholder="请输入工号"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="客服姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入客服姓名"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录账号" prop="account">
|
||||
<el-input v-model="form.account" placeholder="请输入登录账号"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input v-model="form.password" placeholder="请输入密码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="头像 oss_id" prop="avatarOssId">
|
||||
<el-input-number v-model="form.avatarOssId" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="在线状态" prop="onlineStatus">
|
||||
<el-switch
|
||||
v-model="form.onlineStatus"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="账号状态" prop="status">
|
||||
<el-switch
|
||||
v-model="form.status"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建部门" prop="createDept">
|
||||
<el-input-number v-model="form.createDept" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建者" prop="createBy">
|
||||
<el-input-number v-model="form.createBy" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker v-model="form.createTime"
|
||||
clearable
|
||||
placeholder="请选择创建时间"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新者" prop="updateBy">
|
||||
<el-input-number v-model="form.updateBy" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新时间" prop="updateTime">
|
||||
<el-date-picker v-model="form.updateTime"
|
||||
clearable
|
||||
placeholder="请选择更新时间"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="CsAgent" setup>
|
||||
import tableTag from '@/components/tableTag/index.vue'
|
||||
import {
|
||||
addCsAgent,
|
||||
delCsAgent,
|
||||
getCsAgent,
|
||||
listCsAgent,
|
||||
updateCsAgent
|
||||
} from '@/api/system/cssAgent';
|
||||
import {CsAgentForm, CsAgentQuery, CsAgentVO} from '@/api/system/cssAgent/types';
|
||||
import {useLoading} from '@/hooks/async/useLoading';
|
||||
import {useFormDialog} from '@/hooks/dialog/useFormDialog';
|
||||
import {useDateRangeQuery} from '@/hooks/form/useDateRangeQuery';
|
||||
import {useSearchReset} from '@/hooks/form/useSearchReset';
|
||||
import {useSearchToggle} from '@/hooks/form/useSearchToggle';
|
||||
import {useTableSelection} from '@/hooks/table/useTableSelection';
|
||||
import modal from '@/plugins/modal';
|
||||
import {download as requestDownload} from '@/utils/request';
|
||||
|
||||
|
||||
const csAgentList = ref<CsAgentVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const {loading, withLoading} = useLoading(true);
|
||||
const {showSearch} = useSearchToggle();
|
||||
const total = ref(0);
|
||||
const {
|
||||
dateRange: dateRangeCreateTime,
|
||||
applyDateRange: applyCreateTimeDateRange,
|
||||
resetDateRange: resetCreateTimeDateRange
|
||||
} = useDateRangeQuery('CreateTime');
|
||||
const {
|
||||
dateRange: dateRangeUpdateTime,
|
||||
applyDateRange: applyUpdateTimeDateRange,
|
||||
resetDateRange: resetUpdateTimeDateRange
|
||||
} = useDateRangeQuery('UpdateTime');
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const csAgentFormRef = ref<ElFormInstance>();
|
||||
|
||||
const initFormData: CsAgentForm = {
|
||||
agentId: undefined,
|
||||
agentNo: undefined,
|
||||
jobNo: undefined,
|
||||
name: undefined,
|
||||
account: undefined,
|
||||
password: undefined,
|
||||
phone: undefined,
|
||||
avatarOssId: undefined,
|
||||
onlineStatus: undefined,
|
||||
status: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
createTime: undefined,
|
||||
updateBy: undefined,
|
||||
updateTime: undefined,
|
||||
}
|
||||
const data = reactive<PageData<CsAgentForm, CsAgentQuery>>({
|
||||
form: {...initFormData},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
agentNo: undefined,
|
||||
jobNo: undefined,
|
||||
name: undefined,
|
||||
account: undefined,
|
||||
password: undefined,
|
||||
phone: undefined,
|
||||
avatarOssId: undefined,
|
||||
onlineStatus: undefined,
|
||||
status: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
updateBy: undefined,
|
||||
params: {
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
agentId: [
|
||||
{required: true, message: "主键不能为空", trigger: "change"}
|
||||
],
|
||||
agentNo: [
|
||||
{required: true, message: "客服编号,如 KF-001不能为空", trigger: "blur"}
|
||||
],
|
||||
jobNo: [
|
||||
{required: true, message: "工号不能为空", trigger: "blur"}
|
||||
],
|
||||
name: [
|
||||
{required: true, message: "客服姓名不能为空", trigger: "blur"}
|
||||
],
|
||||
account: [
|
||||
{required: true, message: "登录账号不能为空", trigger: "blur"}
|
||||
],
|
||||
password: [
|
||||
{required: true, message: "密码不能为空", trigger: "blur"}
|
||||
],
|
||||
onlineStatus: [
|
||||
{required: true, message: "在线状态不能为空", trigger: "change"}
|
||||
],
|
||||
status: [
|
||||
{required: true, message: "账号状态不能为空", trigger: "change"}
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
const {queryParams, form, rules} = toRefs(data);
|
||||
const {ids, single, multiple, handleSelectionChange} = useTableSelection<CsAgentVO>(item => item.agentId);
|
||||
const {dialog, resetForm: reset, openDialog, showDialog, closeDialog} = useFormDialog({
|
||||
form,
|
||||
formRef: csAgentFormRef,
|
||||
initialFormData: initFormData
|
||||
});
|
||||
|
||||
/** 查询客服人员列表 */
|
||||
const getList = async () => {
|
||||
await withLoading(async () => {
|
||||
let params = queryParams.value;
|
||||
params = applyCreateTimeDateRange(params);
|
||||
params = applyUpdateTimeDateRange(params);
|
||||
const res = await listCsAgent(params);
|
||||
csAgentList.value = res.data?.rows;
|
||||
total.value = res.data?.total;
|
||||
});
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
const {resetQuery} = useSearchReset({
|
||||
queryFormRef,
|
||||
queryParams,
|
||||
pageNumKey: 'pageNum',
|
||||
pageSizeKey: 'pageSize',
|
||||
initialPageSize: 10,
|
||||
resetExtras: () => {
|
||||
resetCreateTimeDateRange();
|
||||
resetUpdateTimeDateRange();
|
||||
},
|
||||
afterReset: () => {
|
||||
handleQuery();
|
||||
}
|
||||
});
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
openDialog('添加客服人员');
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: Partial<CsAgentVO>) => {
|
||||
reset();
|
||||
const _agentId = row?.agentId || ids.value[0];
|
||||
const res = await getCsAgent(_agentId);
|
||||
Object.assign(form.value, res.data);
|
||||
showDialog('修改客服人员');
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
csAgentFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.agentId) {
|
||||
await updateCsAgent(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addCsAgent(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
modal.msgSuccess('操作成功');
|
||||
closeDialog();
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: Partial<CsAgentVO>) => {
|
||||
const _agentIds = row?.agentId || ids.value;
|
||||
await modal.confirm('是否确认删除客服人员编号为"' + _agentIds + '"的数据项?');
|
||||
await delCsAgent(_agentIds);
|
||||
modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
requestDownload(
|
||||
'system/csAgent/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`csAgent_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
242
src/views/system/customer/edit.vue
Normal file
242
src/views/system/customer/edit.vue
Normal file
@@ -0,0 +1,242 @@
|
||||
<script lang="ts" setup>
|
||||
import FormItemTip from "@/components/FormItemTip/index.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const form = ref({
|
||||
phone: "",
|
||||
username: "",
|
||||
sex: 1,
|
||||
creatDay: '',
|
||||
status: 1,
|
||||
img: ''
|
||||
})
|
||||
|
||||
|
||||
function handleBack() {
|
||||
router.replace({
|
||||
path: "/customer/customerList"
|
||||
})
|
||||
}
|
||||
|
||||
function handleChangeFile(file: File) {
|
||||
console.log(file)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="edit">
|
||||
<div class="flex justify-between">
|
||||
<pagecrumb></pagecrumb>
|
||||
<el-button plain type="info" @click="handleBack"> 返回用户列表</el-button>
|
||||
</div>
|
||||
<!-- 用户信息-->
|
||||
<section class="customerInfoBox commonCard flex justify-between flex-items-center">
|
||||
<div class="flex infoBox">
|
||||
<el-image
|
||||
class="customer-Img"
|
||||
src="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"></el-image>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div>
|
||||
<span class="userName">林小满</span>
|
||||
<el-tag effect="light" round style="margin: 0 12px " type="success">
|
||||
<div class="flex flex-items-center"><i class="circle"></i>
|
||||
<span>正常</span></div>
|
||||
</el-tag>
|
||||
<el-tag effect="light" round type="info">150****0831</el-tag>
|
||||
</div>
|
||||
<div class="subInfoBox">
|
||||
<span>用户ID: U20260918001 </span>
|
||||
<span class="dat">·</span>
|
||||
<span>注册时间: 2026-01-10 14:20</span>
|
||||
<span class="dat">·</span>
|
||||
<span>最后登录: 2026-09-17 09:35</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- form -->
|
||||
<section class="commonCard">
|
||||
<card-nav subTitle="手机号为账号唯一标识不可修改;个人资料与账号状态可维护" title="用户资料"></card-nav>
|
||||
<div class="formbox">
|
||||
<el-form label-position="top">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.phone"
|
||||
:inputAttrs="{ placeholder: '请输入手机号', disabled: true , showWordLimit:false ,maxlength:11}"
|
||||
label="手机号"
|
||||
prop="phone"
|
||||
tip="账号唯一标识,如需更换手机号请联系用户重新注册"
|
||||
>
|
||||
<template #suffix>不可修改</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请输入用户昵称', disabled: false , showWordLimit:true ,maxlength:20}"
|
||||
:required="true"
|
||||
prop="username"
|
||||
tip="必填,最多 20 个字符,将同步展示在 APP 端"
|
||||
>
|
||||
<template #label>
|
||||
<span>用户昵称</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.sex"
|
||||
:radioOptions="[
|
||||
{label:'男',value:1},
|
||||
{label:'女',value:0},
|
||||
{label:'未填写',value:2},
|
||||
]"
|
||||
prop="sex"
|
||||
tip="选填,选择「未填写」时 APP 端不展示性别"
|
||||
type="radio"
|
||||
>
|
||||
<template #label>
|
||||
<span>性别</span>
|
||||
<span class="subTitle">(选填)</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.creatDay"
|
||||
:inputAttrs="{ placeholder: '请选择生日'}"
|
||||
label="生日"
|
||||
prop="creatDay"
|
||||
tip="选填,修改后将同步至用户个人资料"
|
||||
type="date"
|
||||
>
|
||||
<template #label>
|
||||
<span>生日</span>
|
||||
<span class="subTitle">(选填)</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.status"
|
||||
:inputAttrs="{ placeholder: '', disabled: false }"
|
||||
:radioOptions="[
|
||||
{label:'正常',value:1},
|
||||
{label:'禁用',value:0},
|
||||
]"
|
||||
label="账号状态"
|
||||
prop="status"
|
||||
tip="切换为「禁用」后该账号将无法登录 APP,且已登录设备将下线"
|
||||
type="radio"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.username"
|
||||
:inputAttrs="{ placeholder: '请输入用户昵称', disabled: true }"
|
||||
:required="true"
|
||||
label="头像"
|
||||
prop="username"
|
||||
tip="必填,最多 20 个字符,将同步展示在 APP 端"
|
||||
type="uploadUserAvatar"
|
||||
@file-change="handleChangeFile"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<FormItemTip
|
||||
v-model="form.phone"
|
||||
:inputAttrs="{ placeholder: '请输入后台内部备注,如工单号、对接人、异常说明等信息', disabled: false , showWordLimit:true ,maxlength:20}"
|
||||
:required="false"
|
||||
label="备注"
|
||||
prop="phone"
|
||||
type="textarea"
|
||||
>
|
||||
<template #label>
|
||||
<span>备注 </span>
|
||||
<span>(选填 · 仅后台管理员可见)</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</section>
|
||||
<div class="footer">
|
||||
<el-button>取消</el-button>
|
||||
<el-button type="primary">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit {
|
||||
.customerInfoBox {
|
||||
.infoBox {
|
||||
|
||||
.customer-Img {
|
||||
width: 73px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
font-weight: bold;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.circle {
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
background-color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.subInfoBox {
|
||||
line-height: 20px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
||||
.dat {
|
||||
margin: 0 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.subTitle {
|
||||
line-height: 14px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.formbox {
|
||||
margin-top: 46px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 27px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
209
src/views/system/customer/index.vue
Normal file
209
src/views/system/customer/index.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<div class="p-2 app-container monitor-online-page">
|
||||
<Pagecrumb></Pagecrumb>
|
||||
<div class="search-wrap">
|
||||
<el-card class="search-panel" shadow="hover">
|
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="query-form" label-position="top">
|
||||
<el-form-item label="手机号" prop="phoneNumber">
|
||||
<el-input v-model="queryParams.phoneNumber" clearable placeholder="请输入手机号" size="large"
|
||||
style="width: 190px;" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户昵称" prop="nickName">
|
||||
<el-input v-model="queryParams.nickName" clearable placeholder="请输入用户昵称" size="large"
|
||||
style="width: 190px;" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-select
|
||||
v-model="queryParams.gender"
|
||||
:value-on-clear="null"
|
||||
clearable
|
||||
size="large"
|
||||
style="width:100px;"
|
||||
>
|
||||
<el-option label="女" value="2"></el-option>
|
||||
<el-option label="男" value="1"></el-option>
|
||||
<el-option label="未知" value="0"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="所在地区" prop="userName">
|
||||
<el-select
|
||||
v-model="queryParams.ipaddr"
|
||||
:empty-values="[null, undefined]"
|
||||
:value-on-clear="null"
|
||||
clearable
|
||||
size="large"
|
||||
style="width: 190px;"
|
||||
>
|
||||
<el-option label="在线" value="0"></el-option>
|
||||
<el-option label="离线" value="1"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="注册时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
size="large"
|
||||
style="width:150px;"
|
||||
type="date"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="账号状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
:value-on-clear="null"
|
||||
clearable
|
||||
size="large"
|
||||
style="width:100px;"
|
||||
>
|
||||
<el-option label="正常" value="1"></el-option>
|
||||
<el-option label="停用" value="0"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="large" type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button size="large" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-card class="table-panel" shadow="hover">
|
||||
<template #header>
|
||||
<div class="flex justify-end">
|
||||
<el-button plain type="info">批量导出用户信息</el-button>
|
||||
<el-button plain type="danger">批量禁用账号</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="onlineList"
|
||||
class="data-table"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column align="center" type="selection"></el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="用户昵称" prop="nickName"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="手机号" prop="phoneNumber"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="性别" prop="gender">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.gender === '1'">男</span>
|
||||
<span v-else-if="scope.row.gender==='2'">女</span>
|
||||
<span v-else>未填写</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="生日" prop="birthday"></el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="所在地区" prop="deptName"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="绑定设备量" prop="ipaddr"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="注册时间" prop="creatTime"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="最后登录时间" prop="loginDate"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="账号状态" prop="status">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === '0'" type="success">正常</el-tag>
|
||||
<el-tag v-else type="success">异常</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" class-name="small-padding fixed-width" label="操作" min-width="250">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['monitor:online:forceLogout']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleMain(scope.row)"
|
||||
>查看详情
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['monitor:online:forceLogout']"
|
||||
plain
|
||||
type="info"
|
||||
>编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['monitor:online:forceLogout']"
|
||||
plain
|
||||
type="info"
|
||||
>重置密码
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === '0'"
|
||||
v-hasPermi="['monitor:online:forceLogout']"
|
||||
plain
|
||||
type="warning"
|
||||
>禁用
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === '1'"
|
||||
v-hasPermi="['monitor:online:forceLogout']"
|
||||
plain
|
||||
type="success"
|
||||
>启用
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNum"
|
||||
:total="total"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {listUser as initData} from '@/api/system/customer/index';
|
||||
import {useDict} from '@/utils/dict';
|
||||
import {UserQuery, UserVO} from "@/api/system/customer/types";
|
||||
|
||||
const {sys_device_type} = toRefs<any>(useDict('sys_device_type'));
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const onlineList = ref<UserVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
|
||||
const queryParams = ref<UserQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
ipaddr: '',
|
||||
userName: ''
|
||||
});
|
||||
|
||||
/** 查询登录日志列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await initData(queryParams.value);
|
||||
onlineList.value = res.data?.rows;
|
||||
total.value = res.data?.total;
|
||||
loading.value = false;
|
||||
};
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
function handleMain(row) {
|
||||
console.log(row);
|
||||
router.push({
|
||||
path: '/customer/customerMain'
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
//@use '@/assets/styles/components/page-shell' as pageShell;
|
||||
//
|
||||
//@include pageShell.table-crud-page;
|
||||
</style>
|
||||
311
src/views/system/customer/main.vue
Normal file
311
src/views/system/customer/main.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<script lang="ts" setup>
|
||||
import Pagecrumb from '@/components/Pagecrumb/index.vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function handleEdit() {
|
||||
router.push({
|
||||
path: '/customer/customerEdit'
|
||||
})
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
router.push({
|
||||
path: '/customer/customerList'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="Main">
|
||||
<div class="flex justify-between">
|
||||
<Pagecrumb></Pagecrumb>
|
||||
<el-button plain type="info" @click="handleBack">返回用户列表</el-button>
|
||||
</div>
|
||||
<!-- 用户信息-->
|
||||
<section class="customerInfoBox commonCard flex justify-between flex-items-center">
|
||||
<div class="flex infoBox">
|
||||
<el-image
|
||||
class="customer-Img"
|
||||
src="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"></el-image>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div>
|
||||
<span class="userName">林小满</span>
|
||||
<el-tag effect="light" round style="margin: 0 12px " type="success">
|
||||
<div class="flex flex-items-center"><i class="circle"></i>
|
||||
<span>正常</span></div>
|
||||
</el-tag>
|
||||
<el-tag effect="light" round type="info">150****0831</el-tag>
|
||||
</div>
|
||||
<div class="subInfoBox">
|
||||
<span>用户ID: U20260918001 </span>
|
||||
<span class="dat">·</span>
|
||||
<span>注册时间: 2026-01-10 14:20</span>
|
||||
<span class="dat">·</span>
|
||||
<span>最后登录: 2026-09-17 09:35</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-button size="large" type="primary" @click="handleEdit">编辑用户</el-button>
|
||||
<el-button plain size="large" type="primary">重置密码</el-button>
|
||||
<el-button plain size="large" type="danger">禁用账号</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<!-- 账号信息-->
|
||||
<div class="userNameBox commonCard">
|
||||
<cardNav line-color="#2563eb" title="账号基础信息"></cardNav>
|
||||
<ul class=" userNameInfoBox">
|
||||
<li>
|
||||
<span class="label">用户ID</span>
|
||||
<span class="value">U20260918001</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">手机号</span>
|
||||
<span class="value">138****6688</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">账号状态</span>
|
||||
<el-tag type="success">正常</el-tag>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">注册时间</span>
|
||||
<span class="value">2026-01-10 14:20</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">最后登录时间</span>
|
||||
<span class="value">2026-09-17 09:35</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">登录次数</span>
|
||||
<span class="value">126次</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- 个人信息-->
|
||||
<div class="userInfoBox commonCard">
|
||||
<cardNav line-color="#14b8a6" title="个人资料"></cardNav>
|
||||
<div class="userInfoInfoBox">
|
||||
<div class="userInfoImageBox">
|
||||
<el-image
|
||||
class="userImage"
|
||||
src="https://ts1.tc.mm.bing.net/th?id=OADD2.1174278572182418_11B688OHMGU9KL6&pid=21.2&c=3&roil=0.0242&roit=0.0014&roir=0.9742&roib=1&w=334&h=184&dynsize=1&qlt=90&dpr=1.50"></el-image>
|
||||
<div class="description">APP端头像</div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="label">昵称</span>
|
||||
<span class="value">黎晓曼</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">性别</span>
|
||||
<span class="value">男</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">生日</span>
|
||||
<span class="value">1926-09-18</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label"> 所在地区</span>
|
||||
<span class="value">上海市・浦东新区</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
个人资料由用户在 APP 端自行维护,后台仅可查看;如需调整请在「编辑用户」中操作。
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<!-- 绑定设备清单-->
|
||||
<div class="bindDevicesListBox commonCard">
|
||||
<div class="flex justify-between">
|
||||
<card-nav line-color="#f59e0b" subTitle="该用户名下全部绑定设备,点击操作可跳转设备详情"
|
||||
title="绑定设备清单"></card-nav>
|
||||
<el-tag type="primary">共3台</el-tag>
|
||||
</div>
|
||||
<el-table
|
||||
class="data-table"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="设备名称" prop="tokenId"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="MAC地址" prop="userName"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="型号" prop="clientKey"/>
|
||||
<el-table-column align="center" label="在线状态">
|
||||
<template #default="scope">
|
||||
<el-tag type="primary">在线</el-tag>
|
||||
<el-tag type="danger">离线</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="绑定时间" prop="userName"/>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button plain type="primary">查看设备详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.Main {
|
||||
.customerInfoBox {
|
||||
.infoBox {
|
||||
|
||||
.customer-Img {
|
||||
width: 73px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
font-weight: bold;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.circle {
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
background-color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.subInfoBox {
|
||||
line-height: 20px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
||||
.dat {
|
||||
margin: 0 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.userNameBox {
|
||||
min-height: 389px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.userNameInfoBox {
|
||||
flex: 1;
|
||||
margin-top: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
li {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.label {
|
||||
line-height: 16px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.value {
|
||||
line-height: 17px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.userInfoBox {
|
||||
.userInfoInfoBox {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 32px;
|
||||
display: flex;
|
||||
|
||||
.userInfoImageBox {
|
||||
width: 100px;
|
||||
margin-right: 24px;
|
||||
|
||||
.userImage {
|
||||
width: 97px;
|
||||
height: 96px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.description {
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.label {
|
||||
line-height: 16px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.value {
|
||||
height: 17px;
|
||||
line-height: 17px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 44px;
|
||||
border-radius: 12px 12px 12px 12px;
|
||||
background-color: rgba(247, 249, 252, 1);
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
line-height: 18px;
|
||||
|
||||
padding: 13px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.bindDevicesListBox {
|
||||
margin-top: 16px;
|
||||
|
||||
.data-table {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
474
src/views/system/device/devicelist/edit.vue
Normal file
474
src/views/system/device/devicelist/edit.vue
Normal file
@@ -0,0 +1,474 @@
|
||||
<script lang="ts" setup>
|
||||
import cardNav from '@/components/cardNav/index.vue';
|
||||
import FormItemTip from '@/components/FormItemTip/index.vue';
|
||||
import {DeviceForm} from "@/api/system/device/types";
|
||||
import {getDevice} from "@/api/system/device";
|
||||
|
||||
function submitForm() {
|
||||
if (!form.value.name || form.value.name.trim() === "") {
|
||||
return ElMessage.warning('设备名称不能为空')
|
||||
}
|
||||
if (!form.value.houseName || form.value.houseName.trim() === "") {
|
||||
return ElMessage.warning('所在房间不能为空')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
function backMain() {
|
||||
router.replace(
|
||||
{
|
||||
path: '/device/deviceMain'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
const initFormData: DeviceForm = {
|
||||
deviceId: undefined,
|
||||
deviceNo: undefined,
|
||||
imei: undefined,
|
||||
name: undefined,
|
||||
productId: undefined,
|
||||
model: undefined,
|
||||
sn: undefined,
|
||||
hwVersion: undefined,
|
||||
swVersion: undefined,
|
||||
fwVersion: undefined,
|
||||
pkgVersion: undefined,
|
||||
ip: undefined,
|
||||
mac: undefined,
|
||||
wifiSsid: undefined,
|
||||
bluetoothConnected: undefined,
|
||||
status: undefined,
|
||||
bindUserId: undefined,
|
||||
bindTime: undefined,
|
||||
homeId: undefined,
|
||||
locationId: undefined,
|
||||
signalLevel: undefined,
|
||||
lng: undefined,
|
||||
lat: undefined,
|
||||
address: undefined,
|
||||
charging: undefined,
|
||||
deviceBattery: undefined,
|
||||
lastReportTime: undefined,
|
||||
lastOnlineTime: undefined,
|
||||
remark: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
createTime: undefined,
|
||||
updateBy: undefined,
|
||||
updateTime: undefined,
|
||||
activeDeviceNo: undefined,
|
||||
activeImei: undefined,
|
||||
activeMac: undefined,
|
||||
}
|
||||
|
||||
const form = ref();
|
||||
|
||||
function init() {
|
||||
form.value = {...initFormData};
|
||||
console.log(form.value);
|
||||
if (route.query.id) {
|
||||
getDevice(route.query.id as string).then(res => {
|
||||
Object.assign(form.value, res.data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="edit">
|
||||
<nav class="flex justify-between">
|
||||
<Pagecrumb></Pagecrumb>
|
||||
<div>
|
||||
<el-button plain type="info" @click="backMain">返回设备详情</el-button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="infoBox">
|
||||
<div class="info-item">
|
||||
<el-image class="info_img"
|
||||
src="https://ts2.tc.mm.bing.net/th/id/OIP-C.2KXRoOZ1EPBDMf6jrp6xeAHaE7?r=0&rs=1&pid=ImgDetedit&o=7&rm=3"></el-image>
|
||||
<div class="info">
|
||||
<div class="nav">
|
||||
<div class="title">Z2空气净化器</div>
|
||||
<div class="tag online">
|
||||
<i class="circle"></i>
|
||||
<span>在线</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="describe">DEV-2026-000138·AA:BB:CC:DD:EE:B·客厅·CZ-Z2023</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editCard commonCard">
|
||||
<card-nav line-color="#2563eb" subTitle="仅可修改设备名称、所在房间与备注,实时运行数据不可编辑"
|
||||
title="静态档案信息"></card-nav>
|
||||
|
||||
<el-form :model="form" label-position="top" style="margin-top:44px;">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.name"
|
||||
:inputAttrs="{ placeholder: '请输入设备名称', disabled: false , showWordLimit:true ,maxlength:20}"
|
||||
:required="true"
|
||||
label="设备名称"
|
||||
prop="name"
|
||||
tip="必填,最多20个字符"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.model"
|
||||
:inputAttrs="{ placeholder: '请输入产品型号', disabled: true }"
|
||||
:required="false"
|
||||
prop="model"
|
||||
tip="设备型号出厂固定,不支持修改"
|
||||
>
|
||||
<template #label>
|
||||
<span>产品型号</span>
|
||||
<el-tag style="margin-left: 4px" type="info">锁定</el-tag>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.mac"
|
||||
:inputAttrs="{ placeholder: '请输入MAC地址', disabled: true }"
|
||||
:required="false"
|
||||
prop="mac"
|
||||
tip="MAC 地址为设备唯一标识,不支持修改"
|
||||
>
|
||||
<template #label>
|
||||
<span>MAC地址</span>
|
||||
<el-tag style="margin-left: 4px" type="info">锁定</el-tag>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<FormItemTip
|
||||
v-model="form.houseName"
|
||||
:inputAttrs="{ placeholder: '请输入所在房间', }"
|
||||
:required="true"
|
||||
label="所在房间"
|
||||
prop="houseName"
|
||||
tip="必填,可选:客厅 / 洗衣间 / 阳台 / 厨房"
|
||||
>
|
||||
</FormItemTip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div>
|
||||
<FormItemTip
|
||||
v-model="form.remark"
|
||||
:inputAttrs="{ placeholder: '请输入备注信息,如采购批次、安装位置说明等' }"
|
||||
:required="false"
|
||||
prop="remark"
|
||||
type="textarea"
|
||||
>
|
||||
<template #label>
|
||||
<span>备注</span>
|
||||
<span style="color:rgba(156, 163, 175, 1);font-size: 12px">(选填)</span>
|
||||
</template>
|
||||
</FormItemTip>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<div class="readTimeInfo">
|
||||
<div class="nav flex justify-between">
|
||||
<div>
|
||||
<span class="title">实时运行数据</span>
|
||||
<el-tag style="margin-left:4px" type="info">只读</el-tag>
|
||||
</div>
|
||||
<span class="tip">由设备蓝牙自动上报,后台不可编辑 · 更新于 2026-09-10 12:50:30</span>
|
||||
</div>
|
||||
|
||||
|
||||
<ul>
|
||||
<li class="li-item">
|
||||
<div class="title">TVOC数值</div>
|
||||
<div class="value">
|
||||
<span class="Num">286</span>
|
||||
<el-tag type="success">优</el-tag>
|
||||
</div>
|
||||
</li>
|
||||
<li class="li-item">
|
||||
<div class="title">运行模式</div>
|
||||
<div class="value">
|
||||
<span class="Num">桌面模式</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="li-item">
|
||||
<div class="title">风速挡位</div>
|
||||
<div class="value">
|
||||
<span class="Num">标准</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="li-item">
|
||||
<div class="title">供电模式</div>
|
||||
<div class="value">
|
||||
<span class="Num">USB供电</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="li-item">
|
||||
<div class="title">滤芯寿命</div>
|
||||
<div class="value">
|
||||
<span class="Num">76%</span>
|
||||
</div>
|
||||
<div class="tip">剩余128天</div>
|
||||
</li>
|
||||
<li class="li-item">
|
||||
<div class="title">电池电量</div>
|
||||
<div class="value">
|
||||
<span class="Num">85%</span>
|
||||
</div>
|
||||
<div class="tip">剩余128天</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tip">
|
||||
运行数据来源于设备上报,无法在后台修改;如需调整运行模式与风速,请在 APP 设备详情页操作。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<el-button size="large" type="default">取消</el-button>
|
||||
<el-button size="large" type="primary" @click="submitForm">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.infoBox {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-height: 177px;
|
||||
//min-width: 750px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
margin-top: 8px;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.info_img {
|
||||
width: 57px;
|
||||
height: 56px;
|
||||
border-radius: 12px 12px 12px 12px;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin-left: 24px;
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.title {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 22px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-left: 5px;
|
||||
width: 62px;
|
||||
height: 28px;
|
||||
border-radius: 14px;
|
||||
|
||||
&.online {
|
||||
background-color: rgba(232, 247, 238, 1);
|
||||
color: rgba(22, 163, 74, 1);
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.circle {
|
||||
margin-right: 6px;
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.describe {
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-top: 28px;
|
||||
|
||||
.info-list-item {
|
||||
.title {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.value {
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeBox {
|
||||
.title {
|
||||
text-align: right;
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.time {
|
||||
line-height: 22px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 16px;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
|
||||
span {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(37, 99, 235, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.circle {
|
||||
margin-right: 6px;
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: #2563eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editCard {
|
||||
min-height: 763px;
|
||||
|
||||
.readTimeInfo {
|
||||
.nav {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.title {
|
||||
line-height: 17px;
|
||||
color: rgba(31, 41, 55, 1);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tip {
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ul {
|
||||
gap: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
|
||||
.li-item {
|
||||
height: 102px;
|
||||
border-radius: 12px 12px 12px 12px;
|
||||
background-color: rgba(247, 249, 252, 1);
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
padding: 17px;
|
||||
|
||||
.title {
|
||||
margin-bottom: 10px;
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.value {
|
||||
margin: 4px 0 8px 0;
|
||||
|
||||
.Num {
|
||||
line-height: 24px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
line-height: 14px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 11px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > .tip {
|
||||
margin-top: 16px;
|
||||
height: 42px;
|
||||
border-radius: 12px 12px 12px 12px;
|
||||
background-color: rgba(241, 245, 249, 1);
|
||||
padding: 12px;
|
||||
line-height: 18px;
|
||||
color: rgba(100, 116, 139, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
366
src/views/system/device/devicelist/index.vue
Normal file
366
src/views/system/device/devicelist/index.vue
Normal file
@@ -0,0 +1,366 @@
|
||||
<template>
|
||||
<div class="p-2 page-shell system-device-page">
|
||||
<div class="search-wrap">
|
||||
<el-card :class="{ 'is-collapsed': !showSearch }" class="search-panel" shadow="hover">
|
||||
<template #header>
|
||||
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
|
||||
<div><h3>筛选条件</h3></div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="query-form" label-position="top">
|
||||
<el-form-item label="设备名称" prop="name">
|
||||
<el-input v-model="queryParams.name" clearable placeholder="请输入设备名称" size="large"
|
||||
@keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="产品型号" prop="model">
|
||||
<el-input v-model="queryParams.model" clearable placeholder="请输入产品型号" size="large"
|
||||
@keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="在线状态" prop="status">
|
||||
<el-select v-model="queryParams.status" clearable placeholder="请选择状态" size="large">
|
||||
<el-option :value="true" label="是"/>
|
||||
<el-option :value="false" label="否"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="large" type="primary" @click="handleQuery">搜索</el-button>
|
||||
<el-button size="large" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card class="table-panel" shadow="hover">
|
||||
<template #header>
|
||||
<div class="toolbar-shell">
|
||||
<div class="table-heading">
|
||||
<el-button type="primary">
|
||||
<span style="margin:0 8px;color: #fff;">全部</span>
|
||||
<span style="color: #fff;font-weight: bold">1286</span>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button v-hasPermi="['system:device:export']" plain size="large" type="info" @click="handleExport">
|
||||
批量导出
|
||||
</el-button>
|
||||
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="deviceList" border class="data-table"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column align="center" type="selection" width="55"/>
|
||||
<el-table-column align="center" label="设备名称" prop="name"/>
|
||||
<el-table-column align="center" label="产品型号" prop="model"/>
|
||||
<el-table-column align="center" label="所在房间" prop="homeId"/>
|
||||
<el-table-column align="center" label="在线状态" prop="status">
|
||||
<template #default>
|
||||
<el-tag type="success">在线</el-tag>
|
||||
<el-tag type="danger">离线</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="当前TVOC" prop="tvoc">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<div>{{ scope.row.tvoc ?? '--' }}</div>
|
||||
<div>
|
||||
<el-tag v-if="scope.row.airLevel == '0'" type="primary">优</el-tag>
|
||||
<el-tag v-else-if="scope.row.airLevel == '1'" type="success">良</el-tag>
|
||||
<el-tag v-else-if="scope.row.airLevel == '2'" type="warning">差</el-tag>
|
||||
<el-tag v-else-if="scope.row.airLevel == '3'" type="danger">污染</el-tag>
|
||||
<el-tag v-else type="info">--</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="滤芯寿命" prop="fwVersion">
|
||||
<template #default="scope">
|
||||
<span :class="{
|
||||
lowLife: scope.row.filterLife < 50
|
||||
}" class="defaultLife">{{ scope.row.filterLife }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="电池电量" prop="deviceBattery">
|
||||
<template #default="scope">
|
||||
<span :class="{
|
||||
lowBattery: scope.row.filterLife < 50
|
||||
}" class="defaultLife">{{ scope.row.filterLife }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="运行模式" prop="runMode">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.runMode == '2'" style="color: rgba(17, 24, 39, 1);">桌面模式</span>
|
||||
<span v-else-if="scope.row.runMode == '1'" style="color: rgba(17, 24, 39, 1);">车载模式</span>
|
||||
<span v-else style="color: rgba(17, 24, 39, 1);">自动</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="风速挡位" prop="mac">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.fanLevel == '1'" style="color: rgba(17, 24, 39, 1);">强力</span>
|
||||
<span v-else-if="scope.row.fanLevel == '2'" style="color: rgba(17, 24, 39, 1);">低档</span>
|
||||
<span v-else style="color: rgba(17, 24, 39, 1);">标准</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="创建时间" prop="wifiSsid"/>
|
||||
<el-table-column align="center" class-name="small-padding fixed-width" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button v-hasPermi="['system:device:edit']" plain type="primary"
|
||||
@click="handleUpdate(scope.row)">查看详情
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['system:device:remove']" plain type="info"
|
||||
@click="handleDelete(scope.row)">编辑
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNum"
|
||||
:total="total" @pagination="getList"/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="Device" setup>
|
||||
import {
|
||||
delDevice,
|
||||
listDevice,
|
||||
} from '@/api/system/device';
|
||||
import {DeviceForm, DeviceQuery, DeviceVO} from '@/api/system/device/types';
|
||||
import {useLoading} from '@/hooks/async/useLoading';
|
||||
import {useFormDialog} from '@/hooks/dialog/useFormDialog';
|
||||
import {useDateRangeQuery} from '@/hooks/form/useDateRangeQuery';
|
||||
import {useSearchReset} from '@/hooks/form/useSearchReset';
|
||||
import {useSearchToggle} from '@/hooks/form/useSearchToggle';
|
||||
import {useTableSelection} from '@/hooks/table/useTableSelection';
|
||||
import modal from '@/plugins/modal';
|
||||
import {download as requestDownload} from '@/utils/request';
|
||||
|
||||
|
||||
const router = useRouter();
|
||||
const deviceList = ref<DeviceVO[]>([]);
|
||||
const {loading, withLoading} = useLoading(true);
|
||||
const {showSearch} = useSearchToggle();
|
||||
const total = ref(0);
|
||||
const {
|
||||
applyDateRange: applyBindTimeDateRange,
|
||||
resetDateRange: resetBindTimeDateRange
|
||||
} = useDateRangeQuery('BindTime');
|
||||
const {
|
||||
applyDateRange: applyLastReportTimeDateRange,
|
||||
resetDateRange: resetLastReportTimeDateRange
|
||||
} = useDateRangeQuery('LastReportTime');
|
||||
const {
|
||||
applyDateRange: applyLastOnlineTimeDateRange,
|
||||
resetDateRange: resetLastOnlineTimeDateRange
|
||||
} = useDateRangeQuery('LastOnlineTime');
|
||||
const {
|
||||
applyDateRange: applyCreateTimeDateRange,
|
||||
resetDateRange: resetCreateTimeDateRange
|
||||
} = useDateRangeQuery('CreateTime');
|
||||
const {
|
||||
applyDateRange: applyUpdateTimeDateRange,
|
||||
resetDateRange: resetUpdateTimeDateRange
|
||||
} = useDateRangeQuery('UpdateTime');
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const deviceFormRef = ref<ElFormInstance>();
|
||||
|
||||
const initFormData: DeviceForm = {
|
||||
deviceId: undefined,
|
||||
deviceNo: undefined,
|
||||
imei: undefined,
|
||||
name: undefined,
|
||||
productId: undefined,
|
||||
model: undefined,
|
||||
sn: undefined,
|
||||
hwVersion: undefined,
|
||||
swVersion: undefined,
|
||||
fwVersion: undefined,
|
||||
pkgVersion: undefined,
|
||||
ip: undefined,
|
||||
mac: undefined,
|
||||
wifiSsid: undefined,
|
||||
bluetoothConnected: undefined,
|
||||
status: undefined,
|
||||
bindUserId: undefined,
|
||||
bindTime: undefined,
|
||||
homeId: undefined,
|
||||
locationId: undefined,
|
||||
signalLevel: undefined,
|
||||
lng: undefined,
|
||||
lat: undefined,
|
||||
address: undefined,
|
||||
charging: undefined,
|
||||
deviceBattery: undefined,
|
||||
lastReportTime: undefined,
|
||||
lastOnlineTime: undefined,
|
||||
remark: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
createTime: undefined,
|
||||
updateBy: undefined,
|
||||
updateTime: undefined,
|
||||
activeDeviceNo: undefined,
|
||||
activeImei: undefined,
|
||||
activeMac: undefined,
|
||||
}
|
||||
const data = reactive<PageData<DeviceForm, DeviceQuery>>({
|
||||
form: {...initFormData},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deviceNo: undefined,
|
||||
imei: undefined,
|
||||
name: undefined,
|
||||
productId: undefined,
|
||||
model: undefined,
|
||||
sn: undefined,
|
||||
hwVersion: undefined,
|
||||
swVersion: undefined,
|
||||
fwVersion: undefined,
|
||||
pkgVersion: undefined,
|
||||
ip: undefined,
|
||||
mac: undefined,
|
||||
wifiSsid: undefined,
|
||||
bluetoothConnected: undefined,
|
||||
status: undefined,
|
||||
bindUserId: undefined,
|
||||
homeId: undefined,
|
||||
locationId: undefined,
|
||||
signalLevel: undefined,
|
||||
lng: undefined,
|
||||
lat: undefined,
|
||||
address: undefined,
|
||||
charging: undefined,
|
||||
deviceBattery: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
updateBy: undefined,
|
||||
activeDeviceNo: undefined,
|
||||
activeImei: undefined,
|
||||
activeMac: undefined,
|
||||
params: {
|
||||
bindTime: undefined,
|
||||
lastReportTime: undefined,
|
||||
lastOnlineTime: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
deviceId: [
|
||||
{required: true, message: "主键不能为空", trigger: "change"}
|
||||
],
|
||||
deviceNo: [
|
||||
{required: true, message: "业务编号,未删除记录内唯一;逻辑删除后可再次使用不能为空", trigger: "blur"}
|
||||
],
|
||||
bluetoothConnected: [
|
||||
{required: true, message: "蓝牙不能为空", trigger: "blur"}
|
||||
],
|
||||
status: [
|
||||
{required: true, message: "状态不能为空", trigger: "change"}
|
||||
],
|
||||
signalLevel: [
|
||||
{required: true, message: "信号等级不能为空", trigger: "change"}
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
const {queryParams, form} = toRefs(data);
|
||||
const {ids, handleSelectionChange} = useTableSelection<DeviceVO>(item => item.deviceId);
|
||||
useFormDialog({
|
||||
form,
|
||||
formRef: deviceFormRef,
|
||||
initialFormData: initFormData
|
||||
});
|
||||
|
||||
/** 查询设备主列表 */
|
||||
const getList = async () => {
|
||||
await withLoading(async () => {
|
||||
let params = queryParams.value;
|
||||
params = applyBindTimeDateRange(params);
|
||||
params = applyLastReportTimeDateRange(params);
|
||||
params = applyLastOnlineTimeDateRange(params);
|
||||
params = applyCreateTimeDateRange(params);
|
||||
params = applyUpdateTimeDateRange(params);
|
||||
const res = await listDevice(params);
|
||||
deviceList.value = res.data?.rows;
|
||||
total.value = res.data?.total;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
const {resetQuery} = useSearchReset({
|
||||
queryFormRef,
|
||||
queryParams,
|
||||
pageNumKey: 'pageNum',
|
||||
pageSizeKey: 'pageSize',
|
||||
initialPageSize: 10,
|
||||
resetExtras: () => {
|
||||
resetBindTimeDateRange();
|
||||
resetLastReportTimeDateRange();
|
||||
resetLastOnlineTimeDateRange();
|
||||
resetCreateTimeDateRange();
|
||||
resetUpdateTimeDateRange();
|
||||
},
|
||||
afterReset: () => {
|
||||
handleQuery();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function handleUpdate(row: Partial<DeviceVO>) {
|
||||
router.push({
|
||||
path: '/device/deviceMain',
|
||||
query: {
|
||||
id: row.deviceId,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: Partial<DeviceVO>) => {
|
||||
const _deviceIds = row?.deviceId || ids.value;
|
||||
await modal.confirm('是否确认删除该设备?');
|
||||
await delDevice(_deviceIds);
|
||||
modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
requestDownload(
|
||||
'system/device/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`device_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.defaultLife {
|
||||
color: gray;
|
||||
|
||||
&.lowLife {
|
||||
color: red;
|
||||
}
|
||||
|
||||
&.lowBattery {
|
||||
color: #4B5563;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
611
src/views/system/device/devicelist/main.vue
Normal file
611
src/views/system/device/devicelist/main.vue
Normal file
@@ -0,0 +1,611 @@
|
||||
<script lang="ts" setup>
|
||||
import cardNav from '@/components/cardNav/index.vue';
|
||||
import {getDevice} from "@/api/system/device";
|
||||
import {DeviceForm} from "@/api/system/device/types";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function backList() {
|
||||
router.replace({
|
||||
path: '/device/deviceList'
|
||||
})
|
||||
}
|
||||
|
||||
function editDevice() {
|
||||
router.push({
|
||||
path: '/device/deviceEdit'
|
||||
})
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
const initFormData: DeviceForm = {
|
||||
deviceId: undefined,
|
||||
deviceNo: undefined,
|
||||
imei: undefined,
|
||||
name: undefined,
|
||||
productId: undefined,
|
||||
model: undefined,
|
||||
sn: undefined,
|
||||
hwVersion: undefined,
|
||||
swVersion: undefined,
|
||||
fwVersion: undefined,
|
||||
pkgVersion: undefined,
|
||||
ip: undefined,
|
||||
mac: undefined,
|
||||
wifiSsid: undefined,
|
||||
bluetoothConnected: undefined,
|
||||
status: undefined,
|
||||
bindUserId: undefined,
|
||||
bindTime: undefined,
|
||||
homeId: undefined,
|
||||
locationId: undefined,
|
||||
signalLevel: undefined,
|
||||
lng: undefined,
|
||||
lat: undefined,
|
||||
address: undefined,
|
||||
charging: undefined,
|
||||
deviceBattery: undefined,
|
||||
lastReportTime: undefined,
|
||||
lastOnlineTime: undefined,
|
||||
remark: undefined,
|
||||
createDept: undefined,
|
||||
createBy: undefined,
|
||||
createTime: undefined,
|
||||
updateBy: undefined,
|
||||
updateTime: undefined,
|
||||
activeDeviceNo: undefined,
|
||||
activeImei: undefined,
|
||||
activeMac: undefined,
|
||||
}
|
||||
|
||||
const form = ref();
|
||||
|
||||
function init() {
|
||||
form.value = {...initFormData};
|
||||
console.log(form.value);
|
||||
if (route.query.id) {
|
||||
getDevice(route.query.id as string).then(res => {
|
||||
Object.assign(form.value, res.data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="Main">
|
||||
<nav class="flex justify-between">
|
||||
<Pagecrumb></Pagecrumb>
|
||||
<div>
|
||||
<el-button plain type="info" @click="backList">返回设备列表</el-button>
|
||||
<el-button type="primary" @click="editDevice">编辑设备</el-button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="infoBox">
|
||||
<div class="info-item">
|
||||
<el-image class="info_img"
|
||||
src="https://ts2.tc.mm.bing.net/th/id/OIP-C.2KXRoOZ1EPBDMf6jrp6xeAHaE7?r=0&rs=1&pid=ImgDetMain&o=7&rm=3"></el-image>
|
||||
<div class="info">
|
||||
<div class="nav">
|
||||
<div class="title">{{ form.name }}</div>
|
||||
<div class="tag online">
|
||||
<i class="circle"></i>
|
||||
<span>在线</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<div class="info-list-item">
|
||||
<div class="title">设备ID</div>
|
||||
<div class="value">{{ form.deviceid }}</div>
|
||||
</div>
|
||||
<div class="info-list-item">
|
||||
<div class="title">MAC地址</div>
|
||||
<div class="value">{{ form.mac }}</div>
|
||||
</div>
|
||||
<div class="info-list-item">
|
||||
<div class="title">所在房间</div>
|
||||
<div class="value">{{ form.locationName }}</div>
|
||||
</div>
|
||||
<div class="info-list-item">
|
||||
<div class="title">产品型号</div>
|
||||
<div class="value">{{ form.model }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeBox">
|
||||
<div class="title">最后上报时间</div>
|
||||
<div class="time">{{ form.lastReportTime }}</div>
|
||||
<div class="tip">
|
||||
<i class="circle"></i>
|
||||
<span>每30s自动刷新</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="baseInfo">
|
||||
<cardNav subTitle="静态档案信息,如需修改请前往「编辑设备」页" title="设备基础档案"></cardNav>
|
||||
<div class=" gridBox">
|
||||
<div class="grid-item">
|
||||
<div class="title">设备ID</div>
|
||||
<div class="value">{{ form.deviceId }}</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="title">设备名称</div>
|
||||
<div class="value">{{ form.name }}</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="title">MAC地址</div>
|
||||
<div class="value">{{ form.mac }}</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="title">产品型号</div>
|
||||
<div class="value">{{ form.model }}</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="title">所在房间</div>
|
||||
<div class="value">{{ form.homeName }}</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="title">档案创建时间</div>
|
||||
<div class="value">{{ form.createTime }}</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="title">档案更新时间</div>
|
||||
<div class="value">{{ form.updateTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="readTimeBox">
|
||||
<cardNav line-color="#14b8a6" subTitle="设备蓝牙上报 · 只读 · 每 30 秒自动刷新"
|
||||
title="设备实时运行状态"></cardNav>
|
||||
<div class="flexBox">
|
||||
<div class="flex-item">
|
||||
<div class="flex-item-nav">
|
||||
<i class="circle"></i>
|
||||
<span>环境数据</span>
|
||||
</div>
|
||||
<div class="numBox">
|
||||
<div class="num">{{ form.tvoc }}</div>
|
||||
<div class="tag">
|
||||
<i class="circle"></i>
|
||||
<span>在线</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subTitle">TVOC 传感器原始读数(未换算为浓度值)</div>
|
||||
<div class="footer">
|
||||
<span>数据更新时间</span>
|
||||
<span style="color: rgba(17, 24, 39, 1);">2026-09-30 12:50:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-item flex flex-col">
|
||||
<div class="flex-item-nav">
|
||||
<i class="circle" style="background: #2563eb"></i>
|
||||
<span>运行数据</span>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col justify-evenly">
|
||||
<div class="footer" style="margin-top: 0">
|
||||
<span>运行模式</span>
|
||||
<div>
|
||||
<span v-if="form.runMode == '2'" style="color: rgba(17, 24, 39, 1);">桌面模式</span>
|
||||
<span v-else-if="form.runMode == '1'" style="color: rgba(17, 24, 39, 1);">车载模式</span>
|
||||
<span v-else style="color: rgba(17, 24, 39, 1);">自动</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer" style="margin-top: 0">
|
||||
<span>风速挡位</span>
|
||||
<div>
|
||||
<span v-if="form.fanLevel == '1'" style="color: rgba(17, 24, 39, 1);">强力</span>
|
||||
<span v-else-if="form.fanLevel == '2'" style="color: rgba(17, 24, 39, 1);">低档</span>
|
||||
<span v-else style="color: rgba(17, 24, 39, 1);">标准</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer" style="margin-top: 0">
|
||||
<span>供电模式</span>
|
||||
<div>
|
||||
<span v-if="form.powerMode == '0'" style="color: rgba(17, 24, 39, 1);">USB供电</span>
|
||||
<span v-else style="color: rgba(17, 24, 39, 1);">电池供电</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer" style="margin-top: 0">
|
||||
<span>停车关机模式</span>
|
||||
<el-tag v-if="form.parkingShutdown == '1'" type="success">开启</el-tag>
|
||||
<el-tag v-else type="danger">关闭</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-item flex flex-col ">
|
||||
<div class="flex-item-nav">
|
||||
<i class="circle" style="background: #f59e0b"></i>
|
||||
<span>耗材与电量</span>
|
||||
</div>
|
||||
<div class="flex-1 flex justify-evenly">
|
||||
<div class="ageBox">
|
||||
<div class="title">滤芯寿命</div>
|
||||
<div class="processInfo">
|
||||
<span class="num">76</span>
|
||||
<span class="unit">%</span>
|
||||
</div>
|
||||
<el-progress :percentage="30" :show-text="false"></el-progress>
|
||||
<div class="tip">
|
||||
<span>预计剩余可用128天</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="ageBox BatteryBox">
|
||||
<div class="title">滤芯寿命</div>
|
||||
<div class="processInfo">
|
||||
<span class="num">76</span>
|
||||
<span class="unit">%</span>
|
||||
</div>
|
||||
<el-progress :percentage="30" :show-text="false" color="#22C55E"></el-progress>
|
||||
<el-tag type="success">充电中</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-item flex flex-col ">
|
||||
<div class="flex-item-nav">
|
||||
<i class="circle" style="background: #6366f1"></i>
|
||||
<span>策略说明 · 停车关机模式</span>
|
||||
</div>
|
||||
<ul style="margin-top: 16px;">
|
||||
<li class="modelLi">
|
||||
<el-tag type="success">开启</el-tag>
|
||||
<span class="describe">USB 外部供电断开后,电池供电继续运行 5 分钟消杀再自动关机;低电量 SOC<10% 时关闭等离子消杀;SOC<5% 整机锁机(防止过放)。</span>
|
||||
</li>
|
||||
<li class="modelLi">
|
||||
<el-tag type="info">关闭</el-tag>
|
||||
<span class="describe">不执行该套低电量策略,仅保留基础净化逻辑。</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.Main {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.infoBox {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-height: 177px;
|
||||
min-width: 750px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
margin-top: 8px;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.info_img {
|
||||
width: 97px;
|
||||
height: 97px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin-left: 24px;
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
|
||||
.title {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 22px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-left: 5px;
|
||||
width: 62px;
|
||||
height: 28px;
|
||||
border-radius: 14px;
|
||||
|
||||
&.online {
|
||||
background-color: rgba(232, 247, 238, 1);
|
||||
color: rgba(22, 163, 74, 1);
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.circle {
|
||||
margin-right: 6px;
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.info-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-top: 28px;
|
||||
|
||||
.info-list-item {
|
||||
.title {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.value {
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeBox {
|
||||
.title {
|
||||
text-align: right;
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.time {
|
||||
line-height: 22px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 16px;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
|
||||
span {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgba(37, 99, 235, 1);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.circle {
|
||||
margin-right: 6px;
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: #2563eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.baseInfo {
|
||||
padding: 25px;
|
||||
min-height: 248px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
margin: 16px 0;
|
||||
|
||||
.gridBox {
|
||||
margin-top: 44px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(381px, 1fr));
|
||||
gap: 20px;
|
||||
|
||||
@media (max-width: 1659px) {
|
||||
grid-template-columns: repeat(3, minmax(381px, 1fr));
|
||||
}
|
||||
@media (max-width: 1250px) {
|
||||
grid-template-columns: repeat(2, minmax(381px, 1fr));
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
grid-template-columns: repeat(1, minmax(381px, 1fr));
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
max-width: 381px;
|
||||
|
||||
.title {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.value {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.readTimeBox {
|
||||
padding: 25px;
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
|
||||
.flexBox {
|
||||
margin-top: 44px;
|
||||
gap: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(381px, 1fr));
|
||||
@media (max-width: 900px) {
|
||||
grid-template-columns: repeat(1, minmax(381px, 1fr));
|
||||
}
|
||||
|
||||
.flex-item {
|
||||
min-height: 201px;
|
||||
border-radius: 12px 12px 12px 12px;
|
||||
background-color: rgba(247, 249, 252, 1);
|
||||
border: 1px solid rgba(238, 240, 243, 1);
|
||||
padding: 21px;
|
||||
|
||||
.flex-item-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.circle {
|
||||
margin-right: 6px;
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(22, 163, 74, 1);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.numBox {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
margin-top: 17px;
|
||||
|
||||
.num {
|
||||
line-height: 43px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 36px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-left: 5px;
|
||||
width: 62px;
|
||||
height: 28px;
|
||||
border-radius: 14px;
|
||||
|
||||
$backColor: rgba(232, 247, 238, 1);
|
||||
$textColor: rgba(22, 163, 74, 1);
|
||||
|
||||
background-color: $backColor;
|
||||
color: $textColor;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.circle {
|
||||
margin-right: 6px;
|
||||
display: block;
|
||||
width: 7px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: $textColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
margin-top: 12px;
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
line-height: 14px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
// 滤芯寿命
|
||||
.ageBox {
|
||||
flex: 1;
|
||||
margin-top: 16px;
|
||||
|
||||
.title {
|
||||
line-height: 18px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.processInfo {
|
||||
margin: 12px 0;
|
||||
|
||||
.num {
|
||||
line-height: 31px;
|
||||
color: rgba(17, 24, 39, 1);
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.unit {
|
||||
line-height: 17px;
|
||||
color: rgba(107, 114, 128, 1);
|
||||
font-size: 14px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
line-height: 16px;
|
||||
color: rgba(156, 163, 175, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 2px;
|
||||
height: 96px;
|
||||
background-color: rgba(238, 240, 243, 1);
|
||||
margin: 15px 24px;
|
||||
}
|
||||
|
||||
.modelLi {
|
||||
margin-bottom: 12px;
|
||||
|
||||
span.describe {
|
||||
margin-left: 12px;
|
||||
line-height: 16px;
|
||||
color: rgba(75, 85, 99, 1);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
|
||||
<el-form-item label="文件名" prop="fileName">
|
||||
<el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.fileName" clearable placeholder="请输入文件名" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="原名" prop="originalName">
|
||||
<el-input
|
||||
@@ -42,7 +42,7 @@
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务商" prop="service">
|
||||
<el-input v-model="queryParams.service" placeholder="请输入服务商" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.service" clearable placeholder="请输入服务商" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
|
||||
@@ -109,11 +109,11 @@
|
||||
@selection-change="handleSelectionChange"
|
||||
@header-click="handleHeaderCLick"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="对象存储主键" align="center" prop="ossId" />
|
||||
<el-table-column label="文件名" align="center" prop="fileName" />
|
||||
<el-table-column label="原名" align="center" prop="originalName" />
|
||||
<el-table-column label="文件后缀" align="center" prop="fileSuffix" />
|
||||
<el-table-column align="center" type="selection" width="55"/>
|
||||
<el-table-column v-if="false" align="center" label="对象存储主键" prop="ossId"/>
|
||||
<el-table-column align="center" label="文件名" prop="fileName"/>
|
||||
<el-table-column align="center" label="原名" prop="originalName"/>
|
||||
<el-table-column align="center" label="文件后缀" prop="fileSuffix"/>
|
||||
<el-table-column label="文件展示" align="center" prop="url">
|
||||
<template #default="scope">
|
||||
<ImagePreview
|
||||
@@ -131,8 +131,8 @@
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传人" align="center" prop="createByName" />
|
||||
<el-table-column label="服务商" align="center" prop="service" sortable="custom" />
|
||||
<el-table-column align="center" label="上传人" prop="createByName"/>
|
||||
<el-table-column align="center" label="服务商" prop="service" sortable="custom"/>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="下载" placement="top">
|
||||
@@ -267,21 +267,25 @@ const getList = async () => {
|
||||
showTable.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
function checkFileSuffix(fileSuffix: string | string[]) {
|
||||
const arr = ['.png', '.jpg', '.jpeg'];
|
||||
const suffixArray = Array.isArray(fileSuffix) ? fileSuffix : [fileSuffix];
|
||||
return suffixArray.some(suffix => arr.includes(suffix.toLowerCase()));
|
||||
}
|
||||
|
||||
/** 取消按钮 */
|
||||
function cancel() {
|
||||
reset();
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
const {resetQuery} = useSearchReset({
|
||||
queryFormRef,
|
||||
queryParams,
|
||||
@@ -343,7 +347,7 @@ const handleOrderChange = (prop: string, order: string) => {
|
||||
};
|
||||
/** 任务日志列表查询 */
|
||||
const handleOssConfig = () => {
|
||||
router.push('/system/oss-config/index');
|
||||
router.push('/system/oss-config/index.vue');
|
||||
};
|
||||
/** 文件按钮操作 */
|
||||
const handleFile = () => {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.tableName" clearable placeholder="请输入表名称" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input
|
||||
@@ -99,12 +99,12 @@
|
||||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数据源" align="center" prop="dataName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />
|
||||
<el-table-column label="更新时间" align="center" prop="updateTime" width="160" />
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="数据源" prop="dataName"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="表名称" prop="tableName"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="表描述" prop="tableComment"/>
|
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="实体" prop="className"/>
|
||||
<el-table-column align="center" label="创建时间" prop="createTime" width="160"/>
|
||||
<el-table-column align="center" label="更新时间" prop="updateTime" width="160"/>
|
||||
<el-table-column label="操作" align="center" width="330" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="预览" placement="top">
|
||||
@@ -314,7 +314,7 @@ const copyTextSuccess = () => {
|
||||
const handleEditTable = (row?: Partial<TableVO>) => {
|
||||
const tableId = row?.tableId || ids.value[0];
|
||||
router.push({
|
||||
path: '/tool/gen-edit/index/' + tableId,
|
||||
path: '/tool/gen-edit/index.vue/' + tableId,
|
||||
query: {pageNum: queryParams.value.pageNum}
|
||||
});
|
||||
};
|
||||
@@ -347,6 +347,7 @@ onMounted(() => {
|
||||
|
||||
.el-tab-pane {
|
||||
background-color: #282c34;
|
||||
|
||||
.el-link {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import autoprefixer from 'autoprefixer'; // css自动添加兼容性前缀
|
||||
|
||||
export default defineConfig(({mode, command}) => {
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
console.log(env);
|
||||
return {
|
||||
// 部署生产环境和开发环境下的URL。
|
||||
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
|
||||
@@ -30,7 +31,7 @@ export default defineConfig(({ mode, command }) => {
|
||||
open: true,
|
||||
proxy: {
|
||||
[env.VITE_APP_BASE_API]: {
|
||||
target: 'http://localhost:5660',
|
||||
target: env.VITE_LOCALHOUSET === 'false' ? 'http://localhost:5660' : env.VITE_BASE_API,
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: path => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
||||
|
||||
Reference in New Issue
Block a user