落地报价模板主账与后台:合同映射、灌种 SQL/脚本、智能体填表,并补测服运维探针与合同文档对齐。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 账号管理 API:走 Jeecg 系统用户,不另起 inquiry 账号表。
|
||||
* 企微 ID 暂写入 workNo(工号),与现网 Jeecg SysUser 字段对齐;无独立 wecom 列时避免改系统实体。
|
||||
*/
|
||||
import {
|
||||
changePassword,
|
||||
deleteUser,
|
||||
getAllRolesListNoByTenant,
|
||||
getUserRoles,
|
||||
list as listUsers,
|
||||
saveOrUpdateUser,
|
||||
} from '/@/views/system/user/user.api';
|
||||
|
||||
export {
|
||||
changePassword,
|
||||
deleteUser,
|
||||
getAllRolesListNoByTenant,
|
||||
getUserRoles,
|
||||
listUsers,
|
||||
saveOrUpdateUser,
|
||||
};
|
||||
|
||||
export type AccountRow = {
|
||||
id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
wecomUserId: string;
|
||||
roleId: string;
|
||||
roleLabel: string;
|
||||
roleTag: string;
|
||||
phoneMasked: string;
|
||||
lastLogin: string;
|
||||
status: 'active' | 'disabled';
|
||||
};
|
||||
|
||||
const ROLE_TAG_CYCLE = ['tag-violet', 'tag-blue', 'tag-teal', 'tag-amber'];
|
||||
|
||||
export function roleTagFor(index: number) {
|
||||
return ROLE_TAG_CYCLE[index % ROLE_TAG_CYCLE.length];
|
||||
}
|
||||
|
||||
/** Jeecg status:1 正常 → active;其余视为禁用。 */
|
||||
export function mapUserToAccount(
|
||||
u: any,
|
||||
roleId = '',
|
||||
roleLabel = '未绑定角色',
|
||||
roleTag = 'tag-blue',
|
||||
): AccountRow {
|
||||
return {
|
||||
id: String(u.id || ''),
|
||||
username: u.username || '',
|
||||
name: u.realname || u.username || '',
|
||||
wecomUserId: u.workNo || u.wecomId || u.thirdId || '',
|
||||
roleId,
|
||||
roleLabel,
|
||||
roleTag,
|
||||
phoneMasked: u.phone || '未填写',
|
||||
lastLogin: u.lastLoginTime || u.createTime || '—',
|
||||
status: Number(u.status) === 1 ? 'active' : 'disabled',
|
||||
};
|
||||
}
|
||||
@@ -1,15 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 账号管理:列表、新建/编辑、重置密码(Mock)。
|
||||
* 弹窗对齐原型 admin-accounts.html(原生 overlay,紧凑表单)。
|
||||
* 账号管理:对接 Jeecg /sys/user(主账登录账号)。
|
||||
* 与员工岗位页分离——本页管后台登录,员工页管企微业务角色。
|
||||
*/
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { mockAccounts, type AccountRow } from "/@/views/inquiry/mock/accounts";
|
||||
import {
|
||||
changePassword,
|
||||
deleteUser,
|
||||
getAllRolesListNoByTenant,
|
||||
getUserRoles,
|
||||
listUsers,
|
||||
mapUserToAccount,
|
||||
roleTagFor,
|
||||
saveOrUpdateUser,
|
||||
type AccountRow,
|
||||
} from "/@/views/inquiry/account/account.api";
|
||||
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
||||
import AdminModal from "/@/views/inquiry/components/AdminModal.vue";
|
||||
|
||||
const rows = ref<AccountRow[]>([...mockAccounts]);
|
||||
const rows = ref<AccountRow[]>([]);
|
||||
const loading = ref(false);
|
||||
const roleOptions = ref<{ value: string; label: string; tag: string }[]>([]);
|
||||
const filters = reactive({ keyword: "", status: "" as "" | "active" | "disabled" });
|
||||
const modalOpen = ref(false);
|
||||
const editing = ref<AccountRow | null>(null);
|
||||
@@ -17,19 +29,12 @@ const form = reactive({
|
||||
username: "",
|
||||
name: "",
|
||||
wecomUserId: "",
|
||||
roleId: "ops",
|
||||
roleId: "",
|
||||
phone: "",
|
||||
status: "active" as AccountRow["status"],
|
||||
password: "",
|
||||
});
|
||||
|
||||
const roleOptions = [
|
||||
{ value: "super", label: "超级管理员", tag: "tag-violet" },
|
||||
{ value: "ops", label: "运营管理员", tag: "tag-blue" },
|
||||
{ value: "config", label: "业务配置员", tag: "tag-teal" },
|
||||
{ value: "audit", label: "只读审计", tag: "tag-amber" },
|
||||
];
|
||||
|
||||
const filtered = computed(() =>
|
||||
rows.value.filter((r) => {
|
||||
const kw = filters.keyword.trim().toLowerCase();
|
||||
@@ -45,13 +50,61 @@ const modalSub = computed(() =>
|
||||
);
|
||||
const modalOkText = computed(() => (editing.value ? "保存修改" : "创建账号"));
|
||||
|
||||
async function loadRoles() {
|
||||
const res: any = await getAllRolesListNoByTenant({});
|
||||
const list = Array.isArray(res) ? res : res?.records || res?.result || [];
|
||||
roleOptions.value = (list as any[]).map((r, i) => ({
|
||||
value: String(r.id),
|
||||
label: r.roleName || r.roleCode || r.id,
|
||||
tag: roleTagFor(i),
|
||||
}));
|
||||
if (!form.roleId && roleOptions.value.length) {
|
||||
form.roleId = roleOptions.value.find((r) => r.label.includes("运营"))?.value || roleOptions.value[0].value;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
if (!roleOptions.value.length) await loadRoles();
|
||||
const res: any = await listUsers({ pageNo: 1, pageSize: 200 });
|
||||
const records = res?.records || res?.result?.records || [];
|
||||
const mapped: AccountRow[] = [];
|
||||
for (const u of records as any[]) {
|
||||
let roleId = "";
|
||||
let roleLabel = "未绑定角色";
|
||||
let roleTag = "tag-blue";
|
||||
try {
|
||||
const roles: any = await getUserRoles({ userid: u.id });
|
||||
const ids = Array.isArray(roles) ? roles : roles?.result || [];
|
||||
if (ids.length) {
|
||||
roleId = String(ids[0]);
|
||||
const hit = roleOptions.value.find((r) => r.value === roleId);
|
||||
roleLabel = hit?.label || roleLabel;
|
||||
roleTag = hit?.tag || roleTag;
|
||||
}
|
||||
} catch {
|
||||
/* 无角色仍展示账号 */
|
||||
}
|
||||
mapped.push(mapUserToAccount(u, roleId, roleLabel, roleTag));
|
||||
}
|
||||
rows.value = mapped;
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "加载账号失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadList());
|
||||
|
||||
function openCreate() {
|
||||
editing.value = null;
|
||||
Object.assign(form, {
|
||||
username: "",
|
||||
name: "",
|
||||
wecomUserId: "",
|
||||
roleId: "ops",
|
||||
roleId: roleOptions.value[0]?.value || "",
|
||||
phone: "",
|
||||
status: "active",
|
||||
password: "",
|
||||
@@ -65,15 +118,15 @@ function openEdit(row: AccountRow) {
|
||||
username: row.username,
|
||||
name: row.name,
|
||||
wecomUserId: row.wecomUserId || "",
|
||||
roleId: row.roleId,
|
||||
phone: row.phoneMasked,
|
||||
roleId: row.roleId || roleOptions.value[0]?.value || "",
|
||||
phone: row.phoneMasked === "未填写" ? "" : row.phoneMasked,
|
||||
status: row.status,
|
||||
password: "",
|
||||
});
|
||||
modalOpen.value = true;
|
||||
}
|
||||
|
||||
function save() {
|
||||
async function save() {
|
||||
if (!form.username.trim() || !form.name.trim()) {
|
||||
message.warning("请填写账号与姓名");
|
||||
return;
|
||||
@@ -82,51 +135,37 @@ function save() {
|
||||
message.warning("请填写企业微信 ID");
|
||||
return;
|
||||
}
|
||||
if (!/^[A-Za-z0-9._@-]+$/.test(form.wecomUserId.trim())) {
|
||||
message.warning("企业微信 ID 格式不正确");
|
||||
return;
|
||||
}
|
||||
if (!editing.value && !form.password.trim()) {
|
||||
message.warning("请填写初始密码");
|
||||
return;
|
||||
}
|
||||
const role = roleOptions.find((r) => r.value === form.roleId) || roleOptions[1];
|
||||
if (editing.value) {
|
||||
Object.assign(editing.value, {
|
||||
username: form.username,
|
||||
name: form.name,
|
||||
wecomUserId: form.wecomUserId.trim(),
|
||||
roleId: form.roleId,
|
||||
roleLabel: role.label,
|
||||
roleTag: role.tag,
|
||||
phoneMasked: form.phone,
|
||||
status: form.status,
|
||||
});
|
||||
message.success("账号已更新");
|
||||
} else {
|
||||
rows.value.push({
|
||||
id: `a${Date.now()}`,
|
||||
username: form.username,
|
||||
name: form.name,
|
||||
wecomUserId: form.wecomUserId.trim(),
|
||||
roleId: form.roleId,
|
||||
roleLabel: role.label,
|
||||
roleTag: role.tag,
|
||||
phoneMasked: form.phone || "未填写",
|
||||
lastLogin: "—",
|
||||
status: form.status,
|
||||
});
|
||||
message.success("账号已创建");
|
||||
if (!form.roleId) {
|
||||
message.warning("请选择角色");
|
||||
return;
|
||||
}
|
||||
const payload: Record<string, any> = {
|
||||
id: editing.value?.id,
|
||||
username: form.username.trim(),
|
||||
realname: form.name.trim(),
|
||||
workNo: form.wecomUserId.trim(),
|
||||
phone: form.phone.trim() || undefined,
|
||||
status: form.status === "active" ? 1 : 2,
|
||||
selectedroles: form.roleId,
|
||||
};
|
||||
if (!editing.value) payload.password = form.password;
|
||||
try {
|
||||
await saveOrUpdateUser(payload, Boolean(editing.value));
|
||||
message.success(editing.value ? "账号已更新" : "账号已创建");
|
||||
modalOpen.value = false;
|
||||
await loadList();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "保存失败");
|
||||
}
|
||||
modalOpen.value = false;
|
||||
}
|
||||
|
||||
const resetOpen = ref(false);
|
||||
const resetTarget = ref<AccountRow | null>(null);
|
||||
const resetForm = reactive({
|
||||
password: "",
|
||||
confirm: "",
|
||||
});
|
||||
const resetForm = reactive({ password: "", confirm: "" });
|
||||
|
||||
function resetPassword(row: AccountRow) {
|
||||
resetTarget.value = row;
|
||||
@@ -140,7 +179,7 @@ function closeReset() {
|
||||
resetTarget.value = null;
|
||||
}
|
||||
|
||||
function confirmResetPassword() {
|
||||
async function confirmResetPassword() {
|
||||
if (!resetForm.password.trim()) {
|
||||
message.warning("请输入新密码");
|
||||
return;
|
||||
@@ -153,8 +192,18 @@ function confirmResetPassword() {
|
||||
message.warning("两次输入的密码不一致");
|
||||
return;
|
||||
}
|
||||
message.success(`已重置 ${resetTarget.value?.username || ""} 的密码`);
|
||||
closeReset();
|
||||
if (!resetTarget.value) return;
|
||||
try {
|
||||
await changePassword({
|
||||
username: resetTarget.value.username,
|
||||
password: resetForm.password,
|
||||
confirmpassword: resetForm.confirm,
|
||||
});
|
||||
message.success(`已重置 ${resetTarget.value.username} 的密码`);
|
||||
closeReset();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "重置密码失败");
|
||||
}
|
||||
}
|
||||
|
||||
function removeAccount(row: AccountRow) {
|
||||
@@ -165,18 +214,30 @@ function removeAccount(row: AccountRow) {
|
||||
Modal.confirm({
|
||||
title: `删除账号 ${row.username}?`,
|
||||
okType: "danger",
|
||||
onOk() {
|
||||
rows.value = rows.value.filter((r) => r.id !== row.id);
|
||||
message.success("已删除");
|
||||
async onOk() {
|
||||
try {
|
||||
await deleteUser({ id: row.id }, () => undefined);
|
||||
message.success("已删除");
|
||||
await loadList();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "删除失败");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => filters.keyword,
|
||||
() => {
|
||||
/* 本地过滤即可,账号量通常很小 */
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport defer to="#page-topbar-actions">
|
||||
<div class="account-topbar-actions">
|
||||
<button type="button" class="btn btn-secondary">
|
||||
<button type="button" class="btn btn-secondary" disabled title="导出待后续开放">
|
||||
<ProtoIcon name="download" :size="14" />导出
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @click="openCreate">
|
||||
@@ -212,6 +273,12 @@ function removeAccount(row: AccountRow) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="loading">
|
||||
<td colspan="7" style="text-align: center; color: #94a3b8">加载中…</td>
|
||||
</tr>
|
||||
<tr v-else-if="!filtered.length">
|
||||
<td colspan="7" style="text-align: center; color: #94a3b8">暂无账号</td>
|
||||
</tr>
|
||||
<tr v-for="row in filtered" :key="row.id">
|
||||
<td class="account-username">{{ row.username }}</td>
|
||||
<td>{{ row.name }}</td>
|
||||
@@ -293,7 +360,7 @@ function removeAccount(row: AccountRow) {
|
||||
v-model="form.wecomUserId"
|
||||
class="field field-mono"
|
||||
type="text"
|
||||
placeholder="请输入企微通讯录账号"
|
||||
placeholder="写入工号字段,用于推送关联"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -309,11 +376,7 @@ function removeAccount(row: AccountRow) {
|
||||
<div class="form-row">
|
||||
<label class="lbl">状态</label>
|
||||
<div class="status-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ 'is-selected': form.status === 'active' }"
|
||||
@click="form.status = 'active'"
|
||||
>
|
||||
<button type="button" :class="{ 'is-selected': form.status === 'active' }" @click="form.status = 'active'">
|
||||
启用
|
||||
</button>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user