初始化询价智能体新仓:同步本地工程并排除密钥与 .env。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-09-13 14:30:44 +08:00
co-authored by Cursor
commit 2a14def882
2621 changed files with 393165 additions and 0 deletions
@@ -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>