From 688685c59fcc4e32feb56585ccab2ab5b1d5cd11 Mon Sep 17 00:00:00 2001 From: Code Migration Date: Sun, 18 Oct 2026 13:32:57 +0800 Subject: [PATCH] chore: seed migration objects batch 23 --- web/src/api/im/registrationInvite.ts | 77 +++ web/src/api/imAuditLog/index.ts | 19 + web/src/api/imGroup/index.ts | 82 ++++ web/src/api/imRealname/index.ts | 25 + web/src/api/imTenant/index.ts | 37 ++ web/src/api/imUser/index.ts | 19 + web/src/api/imWorkOrder/index.ts | 28 ++ .../im/registrationInvite/BatchViewModal.vue | 50 ++ .../im/registrationInvite/CreateModal.vue | 89 ++++ web/src/views/im/registrationInvite/index.vue | 201 ++++++++ web/src/views/im/registrationInvite/model.ts | 144 ++++++ web/src/views/imAuditLog/index.vue | 92 ++++ web/src/views/imAuditLog/model.ts | 119 +++++ web/src/views/imAuditLog/view.vue | 82 ++++ web/src/views/imGroup/governance.vue | 216 ++++++++ web/src/views/imGroup/index.vue | 129 +++++ web/src/views/imGroup/members.vue | 73 +++ web/src/views/imGroup/model.ts | 460 ++++++++++++++++++ web/src/views/imGroup/view.vue | 106 ++++ web/src/views/imRealname/index.vue | 107 ++++ web/src/views/imRealname/model.ts | 118 +++++ web/src/views/imRealname/view.vue | 53 ++ web/src/views/imTenant/edit.vue | 182 +++++++ web/src/views/imTenant/index.vue | 138 ++++++ web/src/views/imTenant/model.ts | 218 +++++++++ web/src/views/imUser/index.vue | 105 ++++ web/src/views/imUser/model.ts | 202 ++++++++ web/src/views/imUser/view.vue | 110 +++++ web/src/views/imWorkOrder/index.vue | 135 +++++ web/src/views/imWorkOrder/model.ts | 149 ++++++ web/src/views/imWorkOrder/view.vue | 89 ++++ web/src/views/org/user/list.vue | 26 +- web/src/views/org/user/model.ts | 2 + .../system/config/ImRegistrationSetting.vue | 85 ++++ web/src/views/system/config/system.vue | 10 + 35 files changed, 3776 insertions(+), 1 deletion(-) create mode 100644 web/src/api/im/registrationInvite.ts create mode 100644 web/src/api/imAuditLog/index.ts create mode 100644 web/src/api/imGroup/index.ts create mode 100644 web/src/api/imRealname/index.ts create mode 100644 web/src/api/imTenant/index.ts create mode 100644 web/src/api/imUser/index.ts create mode 100644 web/src/api/imWorkOrder/index.ts create mode 100644 web/src/views/im/registrationInvite/BatchViewModal.vue create mode 100644 web/src/views/im/registrationInvite/CreateModal.vue create mode 100644 web/src/views/im/registrationInvite/index.vue create mode 100644 web/src/views/im/registrationInvite/model.ts create mode 100644 web/src/views/imAuditLog/index.vue create mode 100644 web/src/views/imAuditLog/model.ts create mode 100644 web/src/views/imAuditLog/view.vue create mode 100644 web/src/views/imGroup/governance.vue create mode 100644 web/src/views/imGroup/index.vue create mode 100644 web/src/views/imGroup/members.vue create mode 100644 web/src/views/imGroup/model.ts create mode 100644 web/src/views/imGroup/view.vue create mode 100644 web/src/views/imRealname/index.vue create mode 100644 web/src/views/imRealname/model.ts create mode 100644 web/src/views/imRealname/view.vue create mode 100644 web/src/views/imTenant/edit.vue create mode 100644 web/src/views/imTenant/index.vue create mode 100644 web/src/views/imTenant/model.ts create mode 100644 web/src/views/imUser/index.vue create mode 100644 web/src/views/imUser/model.ts create mode 100644 web/src/views/imUser/view.vue create mode 100644 web/src/views/imWorkOrder/index.vue create mode 100644 web/src/views/imWorkOrder/model.ts create mode 100644 web/src/views/imWorkOrder/view.vue create mode 100644 web/src/views/system/config/ImRegistrationSetting.vue diff --git a/web/src/api/im/registrationInvite.ts b/web/src/api/im/registrationInvite.ts new file mode 100644 index 0000000..3bc8c48 --- /dev/null +++ b/web/src/api/im/registrationInvite.ts @@ -0,0 +1,77 @@ +import { http } from '@/utils/http/axios'; + +export function List(params) { + return http.request({ + url: '/imInvite/list', + method: 'POST', + params, + }); +} + +export function Create(params) { + return http.request({ + url: '/imInvite/create', + method: 'POST', + params, + }); +} + +export function BatchCreate(params) { + return http.request({ + url: '/imInvite/batchCreate', + method: 'POST', + params, + }); +} + +export function BatchView(params) { + return http.request({ + url: '/imInvite/batchView', + method: 'GET', + params, + }); +} + +export function Revoke(params) { + return http.request({ + url: '/imInvite/revoke', + method: 'POST', + params, + }); +} + +export function BatchRevoke(params) { + return http.request({ + url: '/imInvite/batchRevoke', + method: 'POST', + params, + }); +} + +export function Export(params) { + return http.request({ + url: '/imInvite/export', + method: 'POST', + params, + responseType: 'blob', + requestOptions: { + isTransformResponse: false, + isReturnNativeResponse: true, + }, + }); +} + +export function GetRegistrationConfig() { + return http.request({ + url: '/imRegistration/config/get', + method: 'GET', + }); +} + +export function UpdateRegistrationConfig(params) { + return http.request({ + url: '/imRegistration/config/update', + method: 'POST', + params, + }); +} diff --git a/web/src/api/imAuditLog/index.ts b/web/src/api/imAuditLog/index.ts new file mode 100644 index 0000000..52bdfc8 --- /dev/null +++ b/web/src/api/imAuditLog/index.ts @@ -0,0 +1,19 @@ +import { http } from '@/utils/http/axios'; + +// 获取IM审计日志列表 +export function List(params) { + return http.request({ + url: '/imAuditLog/list', + method: 'get', + params, + }); +} + +// 获取IM审计日志详情 +export function View(params) { + return http.request({ + url: '/imAuditLog/view', + method: 'GET', + params, + }); +} diff --git a/web/src/api/imGroup/index.ts b/web/src/api/imGroup/index.ts new file mode 100644 index 0000000..8f5cac2 --- /dev/null +++ b/web/src/api/imGroup/index.ts @@ -0,0 +1,82 @@ +import { http } from '@/utils/http/axios'; + +// 获取IM群组列表 +export function List(params) { + return http.request({ + url: '/imGroup/list', + method: 'get', + params, + }); +} + +// 获取IM群组详情 +export function View(params) { + return http.request({ + url: '/imGroup/view', + method: 'GET', + params, + }); +} + +// 获取IM群成员列表 +export function MemberList(params) { + return http.request({ + url: '/imGroup/memberList', + method: 'GET', + params, + }); +} + +// 获取IM群治理配置 +export function Governance(params) { + return http.request({ + url: '/imGroup/governance', + method: 'GET', + params, + }); +} + +// 获取IM入群申请列表 +export function JoinRequestList(params) { + return http.request({ + url: '/imGroup/joinRequestList', + method: 'GET', + params, + }); +} + +// 获取IM群邀请列表 +export function InvitationList(params) { + return http.request({ + url: '/imGroup/invitationList', + method: 'GET', + params, + }); +} + +// 获取IM群公告列表 +export function AnnouncementList(params) { + return http.request({ + url: '/imGroup/announcementList', + method: 'GET', + params, + }); +} + +// 获取IM群禁言列表 +export function MuteList(params) { + return http.request({ + url: '/imGroup/muteList', + method: 'GET', + params, + }); +} + +// 获取IM群管理日志列表 +export function AdminLogList(params) { + return http.request({ + url: '/imGroup/adminLogList', + method: 'GET', + params, + }); +} diff --git a/web/src/api/imRealname/index.ts b/web/src/api/imRealname/index.ts new file mode 100644 index 0000000..b62b897 --- /dev/null +++ b/web/src/api/imRealname/index.ts @@ -0,0 +1,25 @@ +import { http } from '@/utils/http/axios'; + +export function List(params) { + return http.request({ + url: '/imRealname/list', + method: 'get', + params, + }); +} + +export function View(params) { + return http.request({ + url: '/imRealname/view', + method: 'GET', + params, + }); +} + +export function Review(params) { + return http.request({ + url: '/imRealname/review', + method: 'POST', + params, + }); +} diff --git a/web/src/api/imTenant/index.ts b/web/src/api/imTenant/index.ts new file mode 100644 index 0000000..bab11ec --- /dev/null +++ b/web/src/api/imTenant/index.ts @@ -0,0 +1,37 @@ +import { http } from '@/utils/http/axios'; + +// 获取IM租户列表 +export function List(params) { + return http.request({ + url: '/imTenant/list', + method: 'get', + params, + }); +} + +// 添加/编辑IM租户 +export function Edit(params) { + return http.request({ + url: '/imTenant/edit', + method: 'POST', + params, + }); +} + +// 修改IM租户状态 +export function Status(params) { + return http.request({ + url: '/imTenant/status', + method: 'POST', + params, + }); +} + +// 获取IM租户指定详情 +export function View(params) { + return http.request({ + url: '/imTenant/view', + method: 'GET', + params, + }); +} diff --git a/web/src/api/imUser/index.ts b/web/src/api/imUser/index.ts new file mode 100644 index 0000000..e5a058d --- /dev/null +++ b/web/src/api/imUser/index.ts @@ -0,0 +1,19 @@ +import { http } from '@/utils/http/axios'; + +// 获取IM用户列表 +export function List(params) { + return http.request({ + url: '/imUser/list', + method: 'get', + params, + }); +} + +// 获取IM用户指定详情 +export function View(params) { + return http.request({ + url: '/imUser/view', + method: 'GET', + params, + }); +} diff --git a/web/src/api/imWorkOrder/index.ts b/web/src/api/imWorkOrder/index.ts new file mode 100644 index 0000000..fa6b4e2 --- /dev/null +++ b/web/src/api/imWorkOrder/index.ts @@ -0,0 +1,28 @@ +import { http } from '@/utils/http/axios'; + +// 获取IM举报反馈工单列表 +export function List(params) { + return http.request({ + url: '/imWorkOrder/list', + method: 'get', + params, + }); +} + +// 获取IM举报反馈工单详情 +export function View(params) { + return http.request({ + url: '/imWorkOrder/view', + method: 'GET', + params, + }); +} + +// 更新IM举报反馈工单状态 +export function Status(params) { + return http.request({ + url: '/imWorkOrder/status', + method: 'POST', + params, + }); +} diff --git a/web/src/views/im/registrationInvite/BatchViewModal.vue b/web/src/views/im/registrationInvite/BatchViewModal.vue new file mode 100644 index 0000000..c81cdb8 --- /dev/null +++ b/web/src/views/im/registrationInvite/BatchViewModal.vue @@ -0,0 +1,50 @@ + + + + + + diff --git a/web/src/views/im/registrationInvite/CreateModal.vue b/web/src/views/im/registrationInvite/CreateModal.vue new file mode 100644 index 0000000..45708ec --- /dev/null +++ b/web/src/views/im/registrationInvite/CreateModal.vue @@ -0,0 +1,89 @@ + + + + diff --git a/web/src/views/im/registrationInvite/index.vue b/web/src/views/im/registrationInvite/index.vue new file mode 100644 index 0000000..4ee676c --- /dev/null +++ b/web/src/views/im/registrationInvite/index.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/web/src/views/im/registrationInvite/model.ts b/web/src/views/im/registrationInvite/model.ts new file mode 100644 index 0000000..dc1701b --- /dev/null +++ b/web/src/views/im/registrationInvite/model.ts @@ -0,0 +1,144 @@ +import { computed, ref } from 'vue'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { useDictStore } from '@/store/modules/dict'; +import { List as TenantList } from '@/api/imTenant'; + +const dict = useDictStore(); + +export const tenantOptions = ref([]); + +export class State { + public id = ''; + public batchId = ''; + public code = ''; + public status = ''; + public expiresAt = ''; + public usedBy = ''; + public usedAt = ''; + public requestId = ''; + public createdBy = ''; + public remark = ''; + public createdAt = ''; +} + +export const statusOptions = [ + { label: '有效', value: 'active', type: 'success' }, + { label: '已使用', value: 'used', type: 'info' }, + { label: '已过期', value: 'expired', type: 'warning' }, + { label: '已撤销', value: 'revoked', type: 'error' }, +]; + +export const schemas = ref([ + { + field: 'tenantId', + component: 'NSelect', + label: '租户', + componentProps: { + clearable: false, + options: computed(() => tenantOptions.value), + placeholder: '请选择租户', + }, + }, + { + field: 'inviteCode', + component: 'NInput', + label: '邀请码', + componentProps: { + placeholder: '完整5位,区分大小写', + maxlength: 5, + }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + componentProps: { + clearable: true, + options: statusOptions, + placeholder: '请选择状态', + }, + }, + { + field: 'batchId', + component: 'NInput', + label: '批次ID', + componentProps: { + placeholder: '请输入批次ID', + }, + }, + { + field: 'createdBy', + component: 'NInput', + label: '创建人', + componentProps: { + placeholder: '请输入创建人ID', + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, + { + field: 'expiresAt', + component: 'NDatePicker', + label: '到期时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, +]); + +export const columns = [ + { title: 'ID', key: 'id', align: 'left', width: 150 }, + { title: '邀请码', key: 'code', align: 'left', width: 120 }, + { title: '状态', key: 'status', align: 'left', width: 110 }, + { title: '批次ID', key: 'batchId', align: 'left', width: 150 }, + { title: '到期时间', key: 'expiresAt', align: 'left', width: 190 }, + { title: '使用用户', key: 'usedBy', align: 'left', width: 120 }, + { title: '使用时间', key: 'usedAt', align: 'left', width: 190 }, + { title: '创建人', key: 'createdBy', align: 'left', width: 110 }, + { title: '创建时间', key: 'createdAt', align: 'left', width: 190 }, + { title: '备注', key: 'remark', align: 'left', width: 220 }, + { title: '请求ID', key: 'requestId', align: 'left', width: 220 }, +]; + +export function loadOptions() { + dict.loadOptions(['sys_switch']); +} + +export async function loadTenantOptions() { + const res = await TenantList({ page: 1, perPage: 20, state: 1 }); + tenantOptions.value = (res?.list || []).map((item) => ({ + label: `${item.name}(${item.code})`, + value: item.id, + })); + return tenantOptions.value; +} + +export function statusLabel(status: string) { + return statusOptions.find((item) => item.value === status)?.label || status; +} + +export function localRfc3339(value: number | null) { + if (!value) { + return ''; + } + const date = new Date(value); + const pad = (n: number) => `${n}`.padStart(2, '0'); + const offset = -date.getTimezoneOffset(); + const sign = offset >= 0 ? '+' : '-'; + const hours = pad(Math.floor(Math.abs(offset) / 60)); + const minutes = pad(Math.abs(offset) % 60); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad( + date.getHours() + )}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${sign}${hours}:${minutes}`; +} diff --git a/web/src/views/imAuditLog/index.vue b/web/src/views/imAuditLog/index.vue new file mode 100644 index 0000000..b2fe921 --- /dev/null +++ b/web/src/views/imAuditLog/index.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/web/src/views/imAuditLog/model.ts b/web/src/views/imAuditLog/model.ts new file mode 100644 index 0000000..5593494 --- /dev/null +++ b/web/src/views/imAuditLog/model.ts @@ -0,0 +1,119 @@ +import { computed, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { List as TenantList } from '@/api/imTenant'; + +export const tenantOptions = ref([]); + +export class State { + public tenantId = 0; + public tenantName = ''; + public id = 0; + public actorUid = 0; + public actorRole = ''; + public action = ''; + public targetType = ''; + public targetId = ''; + public reason = ''; + public requestId = ''; + public ip = ''; + public userAgent = ''; + public beforeSnapshot = ''; + public afterSnapshot = ''; + public metadata = ''; + public createdAt = ''; + + constructor(state?: Partial) { + if (state) Object.assign(this, state); + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) return cloneDeep(state); + return new State(state); + } + return new State(); +} + +export const schemas = ref([ + { + field: 'tenantId', + component: 'NSelect', + label: '租户', + componentProps: { + clearable: false, + options: computed(() => tenantOptions.value), + placeholder: '请选择租户', + }, + }, + { + field: 'actorUid', + component: 'NInput', + label: '操作人UID', + componentProps: { placeholder: '请输入操作人UID' }, + }, + { + field: 'action', + component: 'NInput', + label: '动作', + componentProps: { placeholder: '请输入操作类型' }, + }, + { + field: 'targetType', + component: 'NInput', + label: '目标类型', + componentProps: { placeholder: '请输入目标类型' }, + }, + { + field: 'targetId', + component: 'NInput', + label: '目标ID', + componentProps: { placeholder: '请输入目标ID' }, + }, + { + field: 'requestId', + component: 'NInput', + label: '请求ID', + componentProps: { placeholder: '请输入请求ID' }, + }, + { + field: 'keyword', + component: 'NInput', + label: '关键词', + componentProps: { placeholder: '请输入动作/目标/原因关键词' }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '操作时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, +]); + +export const columns = [ + { title: 'ID', key: 'id', align: 'left', width: 90 }, + { title: '操作人UID', key: 'actorUid', align: 'left', width: 120 }, + { title: '角色', key: 'actorRole', align: 'left', width: 120 }, + { title: '动作', key: 'action', align: 'left', width: 190 }, + { title: '目标类型', key: 'targetType', align: 'left', width: 120 }, + { title: '目标ID', key: 'targetId', align: 'left', width: 170 }, + { title: '原因', key: 'reason', align: 'left', width: 220 }, + { title: '请求ID', key: 'requestId', align: 'left', width: 190 }, + { title: 'IP', key: 'ip', align: 'left', width: 130 }, + { title: '操作时间', key: 'createdAt', align: 'left', width: 180 }, +]; + +export async function loadTenantOptions() { + const res = await TenantList({ page: 1, perPage: 20 }); + tenantOptions.value = (res?.list || []).map((item) => ({ + label: `${item.name}(${item.code})`, + value: item.id, + })); + return tenantOptions.value; +} diff --git a/web/src/views/imAuditLog/view.vue b/web/src/views/imAuditLog/view.vue new file mode 100644 index 0000000..0b45c3b --- /dev/null +++ b/web/src/views/imAuditLog/view.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/web/src/views/imGroup/governance.vue b/web/src/views/imGroup/governance.vue new file mode 100644 index 0000000..04c52d2 --- /dev/null +++ b/web/src/views/imGroup/governance.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/web/src/views/imGroup/index.vue b/web/src/views/imGroup/index.vue new file mode 100644 index 0000000..0895caf --- /dev/null +++ b/web/src/views/imGroup/index.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/web/src/views/imGroup/members.vue b/web/src/views/imGroup/members.vue new file mode 100644 index 0000000..5706d9d --- /dev/null +++ b/web/src/views/imGroup/members.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/web/src/views/imGroup/model.ts b/web/src/views/imGroup/model.ts new file mode 100644 index 0000000..0c12098 --- /dev/null +++ b/web/src/views/imGroup/model.ts @@ -0,0 +1,460 @@ +import { computed, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { useDictStore } from '@/store/modules/dict'; +import { List as TenantList } from '@/api/imTenant'; + +const dict = useDictStore(); + +export const tenantOptions = ref([]); + +export class State { + public tenantId = 0; // 租户ID + public tenantName = ''; // 租户名称 + public topic = ''; // Topic + public state = 0; // 状态 + public ownerId = 0; // 群主ID + public seqId = 0; // 最新消息序号 + public delId = 0; // 删除序号 + public subCnt = 0; // 成员数 + public businessMemberCount = 0; // 业务成员数 + public touchedAt = ''; // 活跃时间 + public createdAt = ''; // 创建时间 + public updatedAt = ''; // 修改时间 + public publicJson = ''; // 公开资料 + public trustedJson = ''; // 可信资料 + public tagsJson = ''; // 标签 + public auxJson = ''; // 辅助资料 + public accessJson = ''; // 访问权限 + public useBt = 0; // 是否开启BT + public groupName = ''; // 群名称 + public avatarFileId = ''; // 头像文件ID + public description = ''; // 简介 + public groupStatus = ''; // 业务状态 + public joinApproval = 0; // 入群审批 + public muteAll = 0; // 全员禁言 + public memberUploadFiles = 0; // 成员上传文件 + public memberPrivateChat = 0; // 成员私聊 + public memberInvite = 0; // 成员邀请 + public maxMemberCount = 0; // 最大成员数 + public maxAdminCount = 0; // 最大管理员数 + public settingsVersion = 0; // 治理版本 + public settingsUpdatedAt = ''; // 治理更新时间 + + constructor(state?: Partial) { + if (state) { + Object.assign(this, state); + } + } +} + +export class MemberState { + public userId = 0; // 用户ID + public nickname = ''; // 昵称 + public state = 0; // 用户状态 + public modeWant = ''; // 期望权限 + public modeGiven = ''; // 授予权限 + public recvSeqId = 0; // 接收序号 + public readSeqId = 0; // 已读序号 + public delId = 0; // 删除序号 + public deletedAt = ''; // 删除时间 + public createdAt = ''; // 加入时间 + public updatedAt = ''; // 修改时间 + public privateJson = ''; // 私有资料 + public role = ''; // 业务角色 + public memberStatus = ''; // 业务状态 + public groupNickname = ''; // 群昵称 + public pinned = 0; // 置顶 + public muteNotifications = 0; // 免打扰 + public joinSource = ''; // 加入来源 + public joinedBy = 0; // 邀请/处理人 + public joinedAt = ''; // 业务加入时间 + public leftAt = ''; // 离开时间 + public removedBy = 0; // 移除人 + public version = 0; // 版本 +} + +export class GovernanceState { + public tenantId = 0; + public tenantName = ''; + public topic = ''; + public ownerUserId = 0; + public name = ''; + public avatarFileId = ''; + public description = ''; + public groupStatus = ''; + public joinApproval = 0; + public muteAll = 0; + public memberUploadFiles = 0; + public memberPrivateChat = 0; + public memberInvite = 0; + public maxMemberCount = 0; + public maxAdminCount = 0; + public memberCount = 0; + public version = 0; + public createdBy = 0; + public dissolvedBy = 0; + public dissolvedAt = ''; + public createdAt = ''; + public updatedAt = ''; +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) { + return cloneDeep(state); + } + return new State(state); + } + return new State(); +} + +// 表格搜索表单 +export const schemas = ref([ + { + field: 'tenantId', + component: 'NSelect', + label: '租户', + componentProps: { + clearable: false, + options: computed(() => tenantOptions.value), + placeholder: '请选择租户', + }, + }, + { + field: 'topic', + component: 'NInput', + label: 'Topic', + componentProps: { + placeholder: '请输入Topic', + }, + }, + { + field: 'ownerId', + component: 'NInput', + label: '群主ID', + componentProps: { + placeholder: '请输入群主ID', + }, + }, + { + field: 'state', + component: 'NSelect', + label: '状态', + componentProps: { + clearable: true, + options: computed(() => dict.getOptions('ImGroupStateOptions')), + placeholder: '请选择状态', + }, + }, + { + field: 'groupStatus', + component: 'NSelect', + label: '业务状态', + componentProps: { + clearable: true, + options: computed(() => dict.getOptions('ImGroupStatusOptions')), + placeholder: '请选择业务状态', + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, + { + field: 'touchedAt', + component: 'NDatePicker', + label: '活跃时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, +]); + +// 表格列 +export const columns = [ + { + title: 'Topic', + key: 'topic', + align: 'left', + width: 220, + }, + { + title: '状态', + key: 'state', + align: 'left', + width: 110, + render(row: State) { + return dict.getLabel('ImGroupStateOptions', row.state); + }, + }, + { + title: '业务状态', + key: 'groupStatus', + align: 'left', + width: 110, + render(row: State) { + return dict.getLabel('ImGroupStatusOptions', row.groupStatus) || '-'; + }, + }, + { + title: '群名称', + key: 'groupName', + align: 'left', + width: 160, + }, + { + title: '群主ID', + key: 'ownerId', + align: 'left', + width: 120, + }, + { + title: '成员数', + key: 'businessMemberCount', + align: 'left', + width: 100, + }, + { + title: '入群审批', + key: 'joinApproval', + align: 'left', + width: 100, + render(row: State) { + return row.joinApproval ? '开启' : '关闭'; + }, + }, + { + title: '全员禁言', + key: 'muteAll', + align: 'left', + width: 100, + render(row: State) { + return row.muteAll ? '开启' : '关闭'; + }, + }, + { + title: '最新序号', + key: 'seqId', + align: 'left', + width: 110, + }, + { + title: '活跃时间', + key: 'touchedAt', + align: 'left', + width: 180, + }, + { + title: '创建时间', + key: 'createdAt', + align: 'left', + width: 180, + }, + { + title: '修改时间', + key: 'updatedAt', + align: 'left', + width: 180, + }, +]; + +export const memberColumns = [ + { + title: '用户ID', + key: 'userId', + align: 'left', + width: 120, + }, + { + title: '昵称', + key: 'nickname', + align: 'left', + width: 160, + }, + { + title: '状态', + key: 'memberStatus', + align: 'left', + width: 110, + render(row: MemberState) { + return dict.getLabel('ImGroupMemberStatusOptions', row.memberStatus) || dict.getLabel('ImUserStateOptions', row.state); + }, + }, + { + title: '角色', + key: 'role', + align: 'left', + width: 100, + render(row: MemberState) { + return dict.getLabel('ImGroupRoleOptions', row.role); + }, + }, + { + title: '群昵称', + key: 'groupNickname', + align: 'left', + width: 140, + }, + { + title: '授予权限', + key: 'modeGiven', + align: 'left', + width: 110, + }, + { + title: '期望权限', + key: 'modeWant', + align: 'left', + width: 110, + }, + { + title: '已读序号', + key: 'readSeqId', + align: 'left', + width: 110, + }, + { + title: '接收序号', + key: 'recvSeqId', + align: 'left', + width: 110, + }, + { + title: '删除时间', + key: 'deletedAt', + align: 'left', + width: 180, + }, + { + title: '业务加入时间', + key: 'joinedAt', + align: 'left', + width: 180, + }, +]; + +export const joinRequestColumns = [ + { title: '申请ID', key: 'requestId', align: 'left', width: 180 }, + { title: '申请人ID', key: 'applicantUserId', align: 'left', width: 120 }, + { title: '邀请人ID', key: 'inviterUserId', align: 'left', width: 120 }, + { title: '来源', key: 'source', align: 'left', width: 100 }, + { title: '申请消息', key: 'message', align: 'left', width: 220 }, + { + title: '状态', + key: 'status', + align: 'left', + width: 110, + render(row) { + return dict.getLabel('ImGroupRequestStatusOptions', row.status); + }, + }, + { title: '拒绝原因', key: 'rejectReason', align: 'left', width: 180 }, + { title: '处理人', key: 'handledBy', align: 'left', width: 110 }, + { title: '处理时间', key: 'handledAt', align: 'left', width: 180 }, + { title: '过期时间', key: 'expiresAt', align: 'left', width: 180 }, + { title: '创建时间', key: 'createdAt', align: 'left', width: 180 }, +]; + +export const invitationColumns = [ + { title: '邀请ID', key: 'invitationId', align: 'left', width: 180 }, + { title: '邀请人ID', key: 'inviterUserId', align: 'left', width: 120 }, + { title: '目标用户ID', key: 'targetUserId', align: 'left', width: 120 }, + { title: '邀请消息', key: 'message', align: 'left', width: 220 }, + { + title: '状态', + key: 'status', + align: 'left', + width: 110, + render(row) { + return dict.getLabel('ImGroupRequestStatusOptions', row.status); + }, + }, + { title: '结果', key: 'result', align: 'left', width: 110 }, + { title: '入群申请ID', key: 'joinRequestId', align: 'left', width: 180 }, + { title: '过期时间', key: 'expiresAt', align: 'left', width: 180 }, + { title: '处理时间', key: 'handledAt', align: 'left', width: 180 }, + { title: '创建时间', key: 'createdAt', align: 'left', width: 180 }, +]; + +export const announcementColumns = [ + { title: '公告ID', key: 'announcementId', align: 'left', width: 180 }, + { title: '发布人ID', key: 'authorUserId', align: 'left', width: 120 }, + { title: '内容', key: 'content', align: 'left', width: 320 }, + { + title: '状态', + key: 'status', + align: 'left', + width: 110, + render(row) { + return dict.getLabel('ImGroupAnnouncementStatusOptions', row.status); + }, + }, + { title: '版本', key: 'version', align: 'left', width: 90 }, + { title: '发布时间', key: 'publishedAt', align: 'left', width: 180 }, + { title: '删除人', key: 'deletedBy', align: 'left', width: 110 }, + { title: '删除时间', key: 'deletedAt', align: 'left', width: 180 }, +]; + +export const muteColumns = [ + { title: '用户ID', key: 'userId', align: 'left', width: 120 }, + { title: '禁言人', key: 'mutedBy', align: 'left', width: 120 }, + { title: '禁言时间', key: 'mutedAt', align: 'left', width: 180 }, + { title: '过期时间', key: 'expiresAt', align: 'left', width: 180 }, + { title: '原因', key: 'reason', align: 'left', width: 220 }, + { + title: '状态', + key: 'status', + align: 'left', + width: 110, + render(row) { + return dict.getLabel('ImGroupMuteStatusOptions', row.status); + }, + }, + { title: '解除人', key: 'revokedBy', align: 'left', width: 120 }, + { title: '解除时间', key: 'revokedAt', align: 'left', width: 180 }, +]; + +export const adminLogColumns = [ + { title: '日志ID', key: 'logId', align: 'left', width: 180 }, + { title: '操作人', key: 'operatorId', align: 'left', width: 120 }, + { title: '目标用户', key: 'targetUserId', align: 'left', width: 120 }, + { title: '动作', key: 'action', align: 'left', width: 160 }, + { title: '对象类型', key: 'objectType', align: 'left', width: 110 }, + { title: '对象ID', key: 'objectId', align: 'left', width: 160 }, + { title: '请求ID', key: 'requestId', align: 'left', width: 180 }, + { title: '创建时间', key: 'createdAt', align: 'left', width: 180 }, +]; + +// 加载字典数据选项 +export function loadOptions() { + dict.loadOptions([ + 'ImGroupStateOptions', + 'ImUserStateOptions', + 'ImGroupStatusOptions', + 'ImGroupRoleOptions', + 'ImGroupMemberStatusOptions', + 'ImGroupRequestStatusOptions', + 'ImGroupMuteStatusOptions', + 'ImGroupAnnouncementStatusOptions', + 'ImGroupOutboxStatusOptions', + ]); +} + +export async function loadTenantOptions() { + const res = await TenantList({ page: 1, perPage: 20 }); + tenantOptions.value = (res?.list || []).map((item) => ({ + label: `${item.name}(${item.code})`, + value: item.id, + })); + return tenantOptions.value; +} diff --git a/web/src/views/imGroup/view.vue b/web/src/views/imGroup/view.vue new file mode 100644 index 0000000..b70c143 --- /dev/null +++ b/web/src/views/imGroup/view.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/web/src/views/imRealname/index.vue b/web/src/views/imRealname/index.vue new file mode 100644 index 0000000..187d249 --- /dev/null +++ b/web/src/views/imRealname/index.vue @@ -0,0 +1,107 @@ + + + diff --git a/web/src/views/imRealname/model.ts b/web/src/views/imRealname/model.ts new file mode 100644 index 0000000..b2ab6f9 --- /dev/null +++ b/web/src/views/imRealname/model.ts @@ -0,0 +1,118 @@ +import { computed, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { useDictStore } from '@/store/modules/dict'; +import { List as TenantList } from '@/api/imTenant'; + +const dict = useDictStore(); + +export const tenantOptions = ref([]); + +export class State { + public tenantId = 0; + public tenantName = ''; + public id = 0; + public userId = 0; + public submissionId = ''; + public status = ''; + public maskedName = ''; + public maskedIdNumber = ''; + public idType = ''; + public idNumberHash = ''; + public reviewReason = ''; + public verifiedAt = ''; + public createdAt = ''; + public updatedAt = ''; + + constructor(state?: Partial) { + if (state) Object.assign(this, state); + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) return cloneDeep(state); + return new State(state); + } + return new State(); +} + +export const schemas = ref([ + { + field: 'tenantId', + component: 'NSelect', + label: '租户', + componentProps: { + clearable: false, + options: computed(() => tenantOptions.value), + placeholder: '请选择租户', + }, + }, + { + field: 'userId', + component: 'NInput', + label: '用户ID', + componentProps: { placeholder: '请输入用户ID' }, + }, + { + field: 'submissionId', + component: 'NInput', + label: '提交ID', + componentProps: { placeholder: '请输入提交ID' }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + componentProps: { + clearable: true, + options: computed(() => dict.getOptions('ImRealnameStatusOptions')), + placeholder: '请选择状态', + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, +]); + +export const columns = [ + { title: 'ID', key: 'id', align: 'left', width: 80 }, + { title: '用户ID', key: 'userId', align: 'left', width: 120 }, + { title: '提交ID', key: 'submissionId', align: 'left', width: 180 }, + { + title: '状态', + key: 'status', + align: 'left', + width: 110, + render(row: State) { + return dict.getLabel('ImRealnameStatusOptions', row.status); + }, + }, + { title: '脱敏姓名', key: 'maskedName', align: 'left', width: 120 }, + { title: '脱敏证件号', key: 'maskedIdNumber', align: 'left', width: 180 }, + { title: '证件类型', key: 'idType', align: 'left', width: 120 }, + { title: '审核原因', key: 'reviewReason', align: 'left', width: 220 }, + { title: '通过时间', key: 'verifiedAt', align: 'left', width: 180 }, + { title: '创建时间', key: 'createdAt', align: 'left', width: 180 }, +]; + +export function loadOptions() { + dict.loadOptions(['ImRealnameStatusOptions']); +} + +export async function loadTenantOptions() { + const res = await TenantList({ page: 1, perPage: 20 }); + tenantOptions.value = (res?.list || []).map((item) => ({ + label: `${item.name}(${item.code})`, + value: item.id, + })); + return tenantOptions.value; +} diff --git a/web/src/views/imRealname/view.vue b/web/src/views/imRealname/view.vue new file mode 100644 index 0000000..a78cbe3 --- /dev/null +++ b/web/src/views/imRealname/view.vue @@ -0,0 +1,53 @@ + + + diff --git a/web/src/views/imTenant/edit.vue b/web/src/views/imTenant/edit.vue new file mode 100644 index 0000000..9382033 --- /dev/null +++ b/web/src/views/imTenant/edit.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/web/src/views/imTenant/index.vue b/web/src/views/imTenant/index.vue new file mode 100644 index 0000000..8a433db --- /dev/null +++ b/web/src/views/imTenant/index.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/web/src/views/imTenant/model.ts b/web/src/views/imTenant/model.ts new file mode 100644 index 0000000..e70ace1 --- /dev/null +++ b/web/src/views/imTenant/model.ts @@ -0,0 +1,218 @@ +import { computed, h, ref } from 'vue'; +import { NTag } from 'naive-ui'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { renderPopoverMemberSumma, MemberSumma } from '@/utils'; +import { useDictStore } from '@/store/modules/dict'; + +const dict = useDictStore(); + +export class State { + public id = 0; // ID + public code = ''; // 租户编码 + public name = ''; // 租户名称 + public tenantDesc = ''; // 租户描述 + public dbName = ''; // 业务库名 + public dbDsn = ''; // 独立连接串 + public state = 1; // 状态 + public maxUsers = 0; // 最大用户数 + public maxGroups = 0; // 最大群组数 + public maxGroupMembers = 0; // 最大群成员数 + public dbDsnConfigured = false; // 是否配置独立连接串 + public createdBy = 0; // 创建者 + public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息 + public updatedBy = 0; // 更新者 + public updatedBySumma?: null | MemberSumma = null; // 更新者摘要信息 + public createdAt = ''; // 创建时间 + public updatedAt = ''; // 修改时间 + + constructor(state?: Partial) { + if (state) { + Object.assign(this, state); + } + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) { + return cloneDeep(state); + } + return new State(state); + } + return new State(); +} + +// 表单验证规则 +export const rules = { + code: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入租户编码', + }, + name: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入租户名称', + }, + state: { + required: true, + trigger: ['blur', 'change'], + type: 'number', + message: '请选择租户状态', + }, +}; + +// 表格搜索表单 +export const schemas = ref([ + { + field: 'code', + component: 'NInput', + label: '租户编码', + componentProps: { + placeholder: '请输入租户编码', + }, + }, + { + field: 'name', + component: 'NInput', + label: '租户名称', + componentProps: { + placeholder: '请输入租户名称', + }, + }, + { + field: 'dbName', + component: 'NInput', + label: '业务库名', + componentProps: { + placeholder: '请输入业务库名', + }, + }, + { + field: 'state', + component: 'NSelect', + label: '状态', + componentProps: { + clearable: true, + options: computed(() => dict.getOptions('ImTenantStateOptions')), + placeholder: '请选择状态', + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, +]); + +// 表格列 +export const columns = [ + { + title: 'ID', + key: 'id', + align: 'left', + width: 70, + }, + { + title: '租户编码', + key: 'code', + align: 'left', + width: 150, + }, + { + title: '租户名称', + key: 'name', + align: 'left', + width: 180, + }, + { + title: '状态', + key: 'state', + align: 'left', + width: 120, + render(row: State) { + return dict.getLabel('ImTenantStateOptions', row.state); + }, + }, + { + title: '业务库名', + key: 'dbName', + align: 'left', + width: 160, + }, + { + title: '独立连接串', + key: 'dbDsnConfigured', + align: 'left', + width: 120, + render(row: State) { + return h( + NTag, + { type: row.dbDsnConfigured ? 'success' : 'default', bordered: false }, + { default: () => (row.dbDsnConfigured ? '已配置' : '未配置') } + ); + }, + }, + { + title: '最大用户', + key: 'maxUsers', + align: 'left', + width: 110, + render(row: State) { + return row.maxUsers > 0 ? row.maxUsers : '不限'; + }, + }, + { + title: '最大群组', + key: 'maxGroups', + align: 'left', + width: 110, + render(row: State) { + return row.maxGroups > 0 ? row.maxGroups : '不限'; + }, + }, + { + title: '群成员上限', + key: 'maxGroupMembers', + align: 'left', + width: 120, + render(row: State) { + return row.maxGroupMembers > 0 ? row.maxGroupMembers : '不限'; + }, + }, + { + title: '创建者', + key: 'createdBy', + align: 'left', + width: 150, + render(row: State) { + return renderPopoverMemberSumma(row.createdBySumma); + }, + }, + { + title: '创建时间', + key: 'createdAt', + align: 'left', + width: 180, + }, + { + title: '修改时间', + key: 'updatedAt', + align: 'left', + width: 180, + }, +]; + +// 加载字典数据选项 +export function loadOptions() { + dict.loadOptions(['ImTenantStateOptions']); +} diff --git a/web/src/views/imUser/index.vue b/web/src/views/imUser/index.vue new file mode 100644 index 0000000..e9636ba --- /dev/null +++ b/web/src/views/imUser/index.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/web/src/views/imUser/model.ts b/web/src/views/imUser/model.ts new file mode 100644 index 0000000..d40a0a0 --- /dev/null +++ b/web/src/views/imUser/model.ts @@ -0,0 +1,202 @@ +import { computed, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { useDictStore } from '@/store/modules/dict'; +import { List as TenantList } from '@/api/imTenant'; + +const dict = useDictStore(); + +export const tenantOptions = ref([]); + +export class State { + public tenantId = 0; // 租户ID + public tenantName = ''; // 租户名称 + public userId = 0; // 用户ID + public nickname = ''; // 昵称 + public publicAccount = ''; // 公开账号 + public gender = ''; // 性别 + public region = ''; // 地区 + public state = 0; // 账号状态 + public authVersion = 0; // 认证版本 + public passwordUpdatedAt = ''; // 密码更新时间 + public authNames = ''; // 登录标识 + public credentials = ''; // 凭证 + public lastSeen = ''; // 最后在线时间 + public userAgent = ''; // 客户端 + public createdAt = ''; // 创建时间 + public updatedAt = ''; // 修改时间 + public signature = ''; // 个性签名 + public avatarFileId = ''; // 头像文件ID + public birthDate = ''; // 出生日期 + public profileJson = ''; // Tinode Public资料 + public trustedJson = ''; // Tinode Trusted资料 + public tagsJson = ''; // Tinode标签 + public accessJson = ''; // 访问权限 + public profileVersion = 0; // 资料版本 + + constructor(state?: Partial) { + if (state) { + Object.assign(this, state); + } + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) { + return cloneDeep(state); + } + return new State(state); + } + return new State(); +} + +// 表格搜索表单 +export const schemas = ref([ + { + field: 'tenantId', + component: 'NSelect', + label: '租户', + componentProps: { + clearable: false, + options: computed(() => tenantOptions.value), + placeholder: '请选择租户', + }, + }, + { + field: 'userId', + component: 'NInput', + label: '用户ID', + componentProps: { + placeholder: '请输入用户ID', + }, + }, + { + field: 'nickname', + component: 'NInput', + label: '昵称', + componentProps: { + placeholder: '请输入昵称', + }, + }, + { + field: 'authName', + component: 'NInput', + label: '登录标识', + componentProps: { + placeholder: '请输入登录名/手机号/邮箱', + }, + }, + { + field: 'state', + component: 'NSelect', + label: '状态', + componentProps: { + clearable: true, + options: computed(() => dict.getOptions('ImUserStateOptions')), + placeholder: '请选择状态', + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, + { + field: 'lastSeen', + component: 'NDatePicker', + label: '最后在线', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, +]); + +// 表格列 +export const columns = [ + { + title: '用户ID', + key: 'userId', + align: 'left', + width: 120, + }, + { + title: '昵称', + key: 'nickname', + align: 'left', + width: 160, + }, + { + title: '公开账号', + key: 'publicAccount', + align: 'left', + width: 150, + }, + { + title: '状态', + key: 'state', + align: 'left', + width: 110, + render(row: State) { + return dict.getLabel('ImUserStateOptions', row.state); + }, + }, + { + title: '登录标识', + key: 'authNames', + align: 'left', + width: 240, + }, + { + title: '凭证', + key: 'credentials', + align: 'left', + width: 220, + }, + { + title: '最后在线', + key: 'lastSeen', + align: 'left', + width: 180, + }, + { + title: '客户端', + key: 'userAgent', + align: 'left', + width: 240, + }, + { + title: '创建时间', + key: 'createdAt', + align: 'left', + width: 180, + }, + { + title: '修改时间', + key: 'updatedAt', + align: 'left', + width: 180, + }, +]; + +// 加载字典数据选项 +export function loadOptions() { + dict.loadOptions(['ImUserStateOptions']); +} + +export async function loadTenantOptions() { + const res = await TenantList({ page: 1, perPage: 20 }); + tenantOptions.value = (res?.list || []).map((item) => ({ + label: `${item.name}(${item.code})`, + value: item.id, + })); + return tenantOptions.value; +} diff --git a/web/src/views/imUser/view.vue b/web/src/views/imUser/view.vue new file mode 100644 index 0000000..be64064 --- /dev/null +++ b/web/src/views/imUser/view.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/web/src/views/imWorkOrder/index.vue b/web/src/views/imWorkOrder/index.vue new file mode 100644 index 0000000..5d53875 --- /dev/null +++ b/web/src/views/imWorkOrder/index.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/web/src/views/imWorkOrder/model.ts b/web/src/views/imWorkOrder/model.ts new file mode 100644 index 0000000..72b89c7 --- /dev/null +++ b/web/src/views/imWorkOrder/model.ts @@ -0,0 +1,149 @@ +import { computed, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { useDictStore } from '@/store/modules/dict'; +import { List as TenantList } from '@/api/imTenant'; + +const dict = useDictStore(); + +export const tenantOptions = ref([]); + +export const workOrderTypeOptions = [ + { label: '举报', value: 'abuse' }, + { label: '反馈', value: 'feedback' }, +]; + +export class State { + public tenantId = 0; + public tenantName = ''; + public type = 'abuse'; + public id = 0; + public orderId = ''; + public userId = 0; + public targetType = ''; + public targetSummary = ''; + public category = ''; + public content = ''; + public status = ''; + public description = ''; + public contact = ''; + public evidenceJson = ''; + public snapshotJson = ''; + public clientContextJson = ''; + public includeDiagnostics = 0; + public idempotencyKey = ''; + public createdAt = ''; + public updatedAt = ''; + + constructor(state?: Partial) { + if (state) Object.assign(this, state); + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) return cloneDeep(state); + return new State(state); + } + return new State(); +} + +export const schemas = ref([ + { + field: 'tenantId', + component: 'NSelect', + label: '租户', + componentProps: { + clearable: false, + options: computed(() => tenantOptions.value), + placeholder: '请选择租户', + }, + }, + { + field: 'type', + component: 'NSelect', + label: '类型', + defaultValue: 'abuse', + componentProps: { + clearable: false, + options: workOrderTypeOptions, + placeholder: '请选择类型', + }, + }, + { + field: 'userId', + component: 'NInput', + label: '提交人ID', + componentProps: { placeholder: '请输入提交人ID' }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + componentProps: { + clearable: true, + options: computed(() => dict.getOptions('ImWorkOrderStatusOptions')), + placeholder: '请选择状态', + }, + }, + { + field: 'keyword', + component: 'NInput', + label: '关键词', + componentProps: { placeholder: '请输入内容关键词' }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + }, + }, +]); + +export const columns = [ + { + title: '类型', + key: 'type', + align: 'left', + width: 90, + render(row: State) { + return row.type === 'abuse' ? '举报' : '反馈'; + }, + }, + { title: 'ID', key: 'id', align: 'left', width: 80 }, + { title: '工单ID', key: 'orderId', align: 'left', width: 180 }, + { title: '提交人ID', key: 'userId', align: 'left', width: 120 }, + { title: '目标类型', key: 'targetType', align: 'left', width: 110 }, + { title: '目标摘要', key: 'targetSummary', align: 'left', width: 220 }, + { title: '分类', key: 'category', align: 'left', width: 130 }, + { title: '内容', key: 'content', align: 'left', width: 260 }, + { + title: '状态', + key: 'status', + align: 'left', + width: 110, + render(row: State) { + return dict.getLabel('ImWorkOrderStatusOptions', row.status); + }, + }, + { title: '创建时间', key: 'createdAt', align: 'left', width: 180 }, + { title: '修改时间', key: 'updatedAt', align: 'left', width: 180 }, +]; + +export function loadOptions() { + dict.loadOptions(['ImWorkOrderStatusOptions']); +} + +export async function loadTenantOptions() { + const res = await TenantList({ page: 1, perPage: 20 }); + tenantOptions.value = (res?.list || []).map((item) => ({ + label: `${item.name}(${item.code})`, + value: item.id, + })); + return tenantOptions.value; +} diff --git a/web/src/views/imWorkOrder/view.vue b/web/src/views/imWorkOrder/view.vue new file mode 100644 index 0000000..d9b29a5 --- /dev/null +++ b/web/src/views/imWorkOrder/view.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/web/src/views/org/user/list.vue b/web/src/views/org/user/list.vue index 22336e5..fc4bf12 100644 --- a/web/src/views/org/user/list.vue +++ b/web/src/views/org/user/list.vue @@ -104,6 +104,17 @@ + + + + + + diff --git a/web/src/views/system/config/system.vue b/web/src/views/system/config/system.vue index 81b8451..78f0da5 100644 --- a/web/src/views/system/config/system.vue +++ b/web/src/views/system/config/system.vue @@ -28,6 +28,7 @@ + @@ -70,6 +71,9 @@ }), LoginSetting = defineAsyncComponent(() => { return import('./LoginSetting.vue'); + }), + ImRegistrationSetting = defineAsyncComponent(() => { + return import('./ImRegistrationSetting.vue'); }); const typeTabList = [ { @@ -127,6 +131,11 @@ desc: '公众号/开放平台/小程序配置等', key: 11, }, + { + name: 'IM注册', + desc: 'IM注册短信发送控制', + key: 12, + }, ]; export default defineComponent({ components: { @@ -141,6 +150,7 @@ PaySetting, WechatSetting, LoginSetting, + ImRegistrationSetting, }, setup() { const router = useRouter();