落地报价模板主账与后台:合同映射、灌种 SQL/脚本、智能体填表,并补测服运维探针与合同文档对齐。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,14 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 机器人回复语:九分类卡片 + 开关编辑,对齐原型。
|
||||
* 机器人回复语:对接主账 /inquiry/botReply。
|
||||
* 分类壳用本地元数据;条目内容全部来自库。
|
||||
*/
|
||||
import { reactive, ref } from "vue";
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
import { mockReplyCategories, type ReplyItem } from "/@/views/inquiry/mock/botReplies";
|
||||
import {
|
||||
disableReply,
|
||||
enableReply,
|
||||
getReplyListAll,
|
||||
saveReply,
|
||||
} from "/@/views/inquiry/botReply/botReply.api";
|
||||
import {
|
||||
emptyCategories,
|
||||
type ReplyCategory,
|
||||
type ReplyItem,
|
||||
} from "/@/views/inquiry/botReply/replyCategories";
|
||||
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
||||
import AdminModal from "/@/views/inquiry/components/AdminModal.vue";
|
||||
|
||||
const categories = ref(structuredClone(mockReplyCategories));
|
||||
const categories = ref<ReplyCategory[]>(emptyCategories());
|
||||
const loading = ref(false);
|
||||
const modalOpen = ref(false);
|
||||
const editing = ref<{ categoryId: string; item: ReplyItem | null }>({
|
||||
categoryId: "",
|
||||
@@ -20,9 +32,63 @@ const form = reactive({
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
function toggle(item: ReplyItem) {
|
||||
item.enabled = !item.enabled;
|
||||
message.success(item.enabled ? "已启用" : "已停用");
|
||||
function groupByCategory(list: any[]) {
|
||||
const cats = emptyCategories();
|
||||
const byId = Object.fromEntries(cats.map((c) => [c.id, c]));
|
||||
for (const row of list) {
|
||||
const code = row.categoryCode || "inquiring";
|
||||
let cat = byId[code];
|
||||
if (!cat) {
|
||||
cat = {
|
||||
id: code,
|
||||
title: row.categoryTitle || code,
|
||||
desc: "",
|
||||
tagClass: "tag-blue",
|
||||
items: [],
|
||||
};
|
||||
cats.push(cat);
|
||||
byId[code] = cat;
|
||||
}
|
||||
if (row.categoryTitle) cat.title = row.categoryTitle;
|
||||
cat.items.push({
|
||||
id: String(row.id),
|
||||
trigger: row.triggerScene || "",
|
||||
text: row.replyText || "",
|
||||
enabled: row.enabled === 1 || row.enabled === true,
|
||||
});
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res: any = await getReplyListAll();
|
||||
const list = Array.isArray(res) ? res : res?.result || res?.records || [];
|
||||
categories.value = groupByCategory(list as any[]);
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "加载回复语失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadList());
|
||||
|
||||
async function toggle(item: ReplyItem) {
|
||||
try {
|
||||
if (item.enabled) {
|
||||
await disableReply(item.id);
|
||||
item.enabled = false;
|
||||
message.success("已停用");
|
||||
} else {
|
||||
await enableReply(item.id);
|
||||
item.enabled = true;
|
||||
message.success("已启用");
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "切换失败");
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate(categoryId?: string) {
|
||||
@@ -41,28 +107,29 @@ function openEdit(categoryId: string, item: ReplyItem) {
|
||||
modalOpen.value = true;
|
||||
}
|
||||
|
||||
function save() {
|
||||
async function save() {
|
||||
if (!form.trigger.trim() || !form.text.trim()) {
|
||||
message.warning("请填写触发条件与话术");
|
||||
return;
|
||||
}
|
||||
const cat = categories.value.find((c) => c.id === editing.value.categoryId);
|
||||
if (!cat) return;
|
||||
if (editing.value.item) {
|
||||
editing.value.item.trigger = form.trigger;
|
||||
editing.value.item.text = form.text;
|
||||
editing.value.item.enabled = form.enabled;
|
||||
message.success("话术已更新");
|
||||
} else {
|
||||
cat.items.push({
|
||||
id: `r${Date.now()}`,
|
||||
trigger: form.trigger,
|
||||
text: form.text,
|
||||
enabled: form.enabled,
|
||||
});
|
||||
message.success("话术已添加");
|
||||
const body = {
|
||||
id: editing.value.item?.id,
|
||||
categoryCode: cat.id,
|
||||
categoryTitle: cat.title,
|
||||
triggerScene: form.trigger.trim(),
|
||||
replyText: form.text.trim(),
|
||||
enabled: form.enabled ? 1 : 0,
|
||||
};
|
||||
try {
|
||||
await saveReply(body, Boolean(editing.value.item));
|
||||
message.success(editing.value.item ? "话术已更新" : "话术已添加");
|
||||
modalOpen.value = false;
|
||||
await loadList();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || "保存失败");
|
||||
}
|
||||
modalOpen.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -73,6 +140,8 @@ function save() {
|
||||
</button>
|
||||
</Teleport>
|
||||
|
||||
<div v-if="loading" style="padding: 24px; color: #94a3b8">加载中…</div>
|
||||
|
||||
<article v-for="cat in categories" :key="cat.id" class="reply-category">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<h2>{{ cat.title }}</h2>
|
||||
@@ -80,6 +149,8 @@ function save() {
|
||||
</div>
|
||||
<p class="reply-category-desc">{{ cat.desc }}</p>
|
||||
|
||||
<div v-if="!cat.items.length" style="color: #94a3b8; font-size: 12px; padding: 8px 0">暂无话术</div>
|
||||
|
||||
<div v-for="item in cat.items" :key="item.id" class="reply-item">
|
||||
<div style="min-width: 0">
|
||||
<div class="reply-meta">触发:{{ item.trigger }}</div>
|
||||
@@ -93,13 +164,7 @@ function save() {
|
||||
:aria-label="item.enabled ? '停用' : '启用'"
|
||||
@click="toggle(item)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-icon-sm"
|
||||
@click="openEdit(cat.id, item)"
|
||||
>
|
||||
<ProtoIcon name="pencil" :size="16" />
|
||||
</button>
|
||||
<button type="button" class="link-action" @click="openEdit(cat.id, item)">编辑</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -111,37 +176,39 @@ function save() {
|
||||
ok-text="保存"
|
||||
@ok="save"
|
||||
>
|
||||
<div class="admin-modal-field">
|
||||
<label class="admin-modal-label">触发场景 / 条件 <span class="req">*</span></label>
|
||||
<div class="form-row">
|
||||
<label class="lbl">分类</label>
|
||||
<select v-model="editing.categoryId" class="field" :disabled="Boolean(editing.item)">
|
||||
<option v-for="c in categories" :key="c.id" :value="c.id">{{ c.title }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="lbl">触发条件 <span class="req">*</span></label>
|
||||
<input v-model="form.trigger" class="field" type="text" />
|
||||
</div>
|
||||
<div class="admin-modal-field">
|
||||
<label class="admin-modal-label">回复内容 <span class="req">*</span></label>
|
||||
<textarea
|
||||
v-model="form.text"
|
||||
class="field"
|
||||
rows="5"
|
||||
placeholder="支持 {{变量}} 占位符"
|
||||
/>
|
||||
<div class="form-row">
|
||||
<label class="lbl">回复正文 <span class="req">*</span></label>
|
||||
<textarea v-model="form.text" class="field" rows="5" placeholder="支持 {{ticket_no}} 等变量" />
|
||||
</div>
|
||||
<div class="admin-modal-field">
|
||||
<label class="admin-modal-label">启用状态</label>
|
||||
<div class="status-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ 'is-selected': form.enabled }"
|
||||
@click="form.enabled = true"
|
||||
>
|
||||
启用
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ 'is-selected': !form.enabled }"
|
||||
@click="form.enabled = false"
|
||||
>
|
||||
停用
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="lbl">状态</label>
|
||||
<label class="check-line">
|
||||
<input v-model="form.enabled" type="checkbox" />
|
||||
<span>启用</span>
|
||||
</label>
|
||||
</div>
|
||||
</AdminModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.check-line {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 34px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #344054;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user