初始化询价智能体新仓:同步本地工程并排除密钥与 .env。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 账号管理:列表、新建/编辑、重置密码(Mock)。
|
||||
* 弹窗对齐原型 admin-accounts.html(原生 overlay,紧凑表单)。
|
||||
*/
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { mockAccounts, type AccountRow } from "/@/views/inquiry/mock/accounts";
|
||||
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
||||
import AdminModal from "/@/views/inquiry/components/AdminModal.vue";
|
||||
|
||||
const rows = ref<AccountRow[]>([...mockAccounts]);
|
||||
const filters = reactive({ keyword: "", status: "" as "" | "active" | "disabled" });
|
||||
const modalOpen = ref(false);
|
||||
const editing = ref<AccountRow | null>(null);
|
||||
const form = reactive({
|
||||
username: "",
|
||||
name: "",
|
||||
wecomUserId: "",
|
||||
roleId: "ops",
|
||||
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();
|
||||
if (kw && !`${r.username}${r.name}${r.wecomUserId}${r.phoneMasked}`.toLowerCase().includes(kw)) return false;
|
||||
if (filters.status && r.status !== filters.status) return false;
|
||||
return true;
|
||||
}),
|
||||
);
|
||||
|
||||
const modalTitle = computed(() => (editing.value ? "编辑账号" : "新建账号"));
|
||||
const modalSub = computed(() =>
|
||||
editing.value ? `正在编辑:${editing.value.username}` : "创建账号并绑定角色",
|
||||
);
|
||||
const modalOkText = computed(() => (editing.value ? "保存修改" : "创建账号"));
|
||||
|
||||
function openCreate() {
|
||||
editing.value = null;
|
||||
Object.assign(form, {
|
||||
username: "",
|
||||
name: "",
|
||||
wecomUserId: "",
|
||||
roleId: "ops",
|
||||
phone: "",
|
||||
status: "active",
|
||||
password: "",
|
||||
});
|
||||
modalOpen.value = true;
|
||||
}
|
||||
|
||||
function openEdit(row: AccountRow) {
|
||||
editing.value = row;
|
||||
Object.assign(form, {
|
||||
username: row.username,
|
||||
name: row.name,
|
||||
wecomUserId: row.wecomUserId || "",
|
||||
roleId: row.roleId,
|
||||
phone: row.phoneMasked,
|
||||
status: row.status,
|
||||
password: "",
|
||||
});
|
||||
modalOpen.value = true;
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (!form.username.trim() || !form.name.trim()) {
|
||||
message.warning("请填写账号与姓名");
|
||||
return;
|
||||
}
|
||||
if (!form.wecomUserId.trim()) {
|
||||
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("账号已创建");
|
||||
}
|
||||
modalOpen.value = false;
|
||||
}
|
||||
|
||||
const resetOpen = ref(false);
|
||||
const resetTarget = ref<AccountRow | null>(null);
|
||||
const resetForm = reactive({
|
||||
password: "",
|
||||
confirm: "",
|
||||
});
|
||||
|
||||
function resetPassword(row: AccountRow) {
|
||||
resetTarget.value = row;
|
||||
resetForm.password = "";
|
||||
resetForm.confirm = "";
|
||||
resetOpen.value = true;
|
||||
}
|
||||
|
||||
function closeReset() {
|
||||
resetOpen.value = false;
|
||||
resetTarget.value = null;
|
||||
}
|
||||
|
||||
function confirmResetPassword() {
|
||||
if (!resetForm.password.trim()) {
|
||||
message.warning("请输入新密码");
|
||||
return;
|
||||
}
|
||||
if (resetForm.password.length < 8) {
|
||||
message.warning("密码至少 8 位");
|
||||
return;
|
||||
}
|
||||
if (resetForm.password !== resetForm.confirm) {
|
||||
message.warning("两次输入的密码不一致");
|
||||
return;
|
||||
}
|
||||
message.success(`已重置 ${resetTarget.value?.username || ""} 的密码`);
|
||||
closeReset();
|
||||
}
|
||||
|
||||
function removeAccount(row: AccountRow) {
|
||||
if (row.username === "admin") {
|
||||
message.warning("系统管理员不可删除");
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: `删除账号 ${row.username}?`,
|
||||
okType: "danger",
|
||||
onOk() {
|
||||
rows.value = rows.value.filter((r) => r.id !== row.id);
|
||||
message.success("已删除");
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport defer to="#page-topbar-actions">
|
||||
<div class="account-topbar-actions">
|
||||
<button type="button" class="btn btn-secondary">
|
||||
<ProtoIcon name="download" :size="14" />导出
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @click="openCreate">
|
||||
<ProtoIcon name="plus" :size="14" />新建账号
|
||||
</button>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="toolbar">
|
||||
<div class="search">
|
||||
<ProtoIcon class="search-icon" name="search" :size="16" />
|
||||
<input v-model="filters.keyword" placeholder="搜索账号、姓名、企微ID、手机号..." />
|
||||
</div>
|
||||
<select v-model="filters.status">
|
||||
<option value="">状态</option>
|
||||
<option value="active">启用</option>
|
||||
<option value="disabled">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>账号</th>
|
||||
<th>姓名</th>
|
||||
<th>绑定角色</th>
|
||||
<th>手机号</th>
|
||||
<th>最后登录</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in filtered" :key="row.id">
|
||||
<td class="account-username">{{ row.username }}</td>
|
||||
<td>{{ row.name }}</td>
|
||||
<td>
|
||||
<span class="tag" :class="row.roleTag">{{ row.roleLabel }}</span>
|
||||
</td>
|
||||
<td>{{ row.phoneMasked }}</td>
|
||||
<td>{{ row.lastLogin }}</td>
|
||||
<td>
|
||||
<span class="tag" :class="row.status === 'active' ? 'tag-green' : 'tag-rose'">
|
||||
{{ row.status === "active" ? "启用" : "禁用" }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="account-row-actions">
|
||||
<button class="link-action" type="button" @click="openEdit(row)">编辑</button>
|
||||
<button class="link-action link-muted" type="button" @click="resetPassword(row)">
|
||||
重置密码
|
||||
</button>
|
||||
<button
|
||||
v-if="row.username !== 'admin'"
|
||||
class="link-action link-danger"
|
||||
type="button"
|
||||
@click="removeAccount(row)"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span>
|
||||
共 <b style="color: #0f172a">{{ filtered.length }}</b> 个账号
|
||||
</span>
|
||||
<div class="pages">
|
||||
<button class="is-active" type="button">1</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AdminModal
|
||||
v-model:open="modalOpen"
|
||||
:title="modalTitle"
|
||||
:subtitle="modalSub"
|
||||
size="md"
|
||||
:ok-text="modalOkText"
|
||||
@ok="save"
|
||||
>
|
||||
<div class="form-row">
|
||||
<label class="lbl">账号 <span class="req">*</span></label>
|
||||
<input
|
||||
v-model="form.username"
|
||||
class="field field-mono"
|
||||
type="text"
|
||||
placeholder="英文字母、数字、下划线"
|
||||
:readonly="Boolean(editing)"
|
||||
:class="{ 'is-readonly': Boolean(editing) }"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!editing" class="form-row">
|
||||
<label class="lbl">密码 <span class="req">*</span></label>
|
||||
<input
|
||||
v-model="form.password"
|
||||
class="field"
|
||||
type="password"
|
||||
placeholder="至少 8 位,含字母与数字"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="lbl">姓名 <span class="req">*</span></label>
|
||||
<input v-model="form.name" class="field" type="text" placeholder="账号持有人姓名" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="lbl">企业微信 ID <span class="req">*</span></label>
|
||||
<input
|
||||
v-model="form.wecomUserId"
|
||||
class="field field-mono"
|
||||
type="text"
|
||||
placeholder="请输入企微通讯录账号"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="lbl">手机号</label>
|
||||
<input v-model="form.phone" class="field" type="tel" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="lbl">绑定角色 <span class="req">*</span></label>
|
||||
<select v-model="form.roleId" class="field">
|
||||
<option v-for="r in roleOptions" :key="r.value" :value="r.value">{{ r.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ 'is-selected': form.status === 'disabled' }"
|
||||
@click="form.status = 'disabled'"
|
||||
>
|
||||
禁用
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</AdminModal>
|
||||
|
||||
<AdminModal
|
||||
v-model:open="resetOpen"
|
||||
title="重置密码"
|
||||
:subtitle="resetTarget ? `账号:${resetTarget.username}(${resetTarget.name})` : ''"
|
||||
size="sm"
|
||||
ok-text="确认重置"
|
||||
@ok="confirmResetPassword"
|
||||
>
|
||||
<div class="form-row">
|
||||
<label class="lbl">新密码 <span class="req">*</span></label>
|
||||
<input
|
||||
v-model="resetForm.password"
|
||||
class="field"
|
||||
type="password"
|
||||
placeholder="至少 8 位,含字母与数字"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="lbl">确认密码 <span class="req">*</span></label>
|
||||
<input
|
||||
v-model="resetForm.confirm"
|
||||
class="field"
|
||||
type="password"
|
||||
placeholder="再次输入新密码"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</AdminModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.account-topbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.account-username {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-weight: 800;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.account-row-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user