feat: 初始化 Vue 3 + TypeScript 项目并添加销售控制台页面
- 使用 Vue 3、TypeScript、Vite、Pinia 和 Vue Router 搭建项目基础结构 - 集成 Tailwind CSS 和 DaisyUI 作为 UI 框架 - 创建销售控制台主页面,包含客户列表、对话监控和客户画像面板 - 添加模拟数据以展示客户在不同销售阶段的流转状态 - 实现 AI/人工切换、消息发送、资料推送等核心交互功能
This commit is contained in:
36
247_Contry/.gitignore
vendored
Normal file
36
247_Contry/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Cypress
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Vitest
|
||||
__screenshots__/
|
||||
6
247_Contry/.prettierrc.json
Normal file
6
247_Contry/.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
||||
6
247_Contry/.vscode/extensions.json
vendored
Normal file
6
247_Contry/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"prettier.prettier-vscode"
|
||||
]
|
||||
}
|
||||
42
247_Contry/README.md
Normal file
42
247_Contry/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 247_Contry
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Recommended Browser Setup
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
```
|
||||
1
247_Contry/env.d.ts
vendored
Normal file
1
247_Contry/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
13
247_Contry/index.html
Normal file
13
247_Contry/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
37
247_Contry/package.json
Normal file
37
247_Contry/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "247-contry",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"format": "prettier --write --experimental-cli src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"pinia": "^3.0.4",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"vue": "^3.5.26",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node24": "^24.0.3",
|
||||
"@types/node": "^24.10.4",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"daisyui": "^5.5.14",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"prettier": "3.7.4",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.3.0",
|
||||
"vite-plugin-vue-devtools": "^8.0.5",
|
||||
"vue-tsc": "^3.2.1"
|
||||
}
|
||||
}
|
||||
2272
247_Contry/pnpm-lock.yaml
generated
Normal file
2272
247_Contry/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
247_Contry/public/favicon.ico
Normal file
BIN
247_Contry/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
11
247_Contry/src/App.vue
Normal file
11
247_Contry/src/App.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
14
247_Contry/src/main.ts
Normal file
14
247_Contry/src/main.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import './style.css'
|
||||
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
14
247_Contry/src/router/index.ts
Normal file
14
247_Contry/src/router/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'index',
|
||||
component: () => import('@/views/index/index.vue'),
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
||||
15
247_Contry/src/stores/mockData.ts
Normal file
15
247_Contry/src/stores/mockData.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// mockData.ts
|
||||
export const salesStages = [
|
||||
{ id: 'stage_1', name: '加微/浅建联', color: 'border-t-blue-500' },
|
||||
{ id: 'stage_2', name: '填表单/出报告', color: 'border-t-purple-500' },
|
||||
{ id: 'stage_3', name: '课前促到课', color: 'border-t-indigo-500' },
|
||||
{ id: 'stage_4', name: '课中/直播监控', color: 'border-t-orange-500' }, // 重点监控区
|
||||
{ id: 'stage_5', name: '待人工介入', color: 'border-t-red-500' }, // 异议/退费
|
||||
{ id: 'stage_6', name: '成交/合同', color: 'border-t-green-500' }
|
||||
];
|
||||
|
||||
export const customers = [
|
||||
{ id: 1, name: '李妈妈', stage: 'stage_4', intent: '高', aiStatus: 'active', lastMsg: '孩子正在看直播,挺喜欢的', tags: ['Day1', '厌学'] },
|
||||
{ id: 2, name: '王爸爸', stage: 'stage_5', intent: '中', aiStatus: 'frozen', lastMsg: '你们这个退费怎么退?', tags: ['价格敏感', '需人工'] },
|
||||
{ id: 3, name: '张家长', stage: 'stage_1', intent: '低', aiStatus: 'active', lastMsg: '通过了好友请求', tags: ['新客'] },
|
||||
];
|
||||
2
247_Contry/src/style.css
Normal file
2
247_Contry/src/style.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui";
|
||||
346
247_Contry/src/views/index/index.vue
Normal file
346
247_Contry/src/views/index/index.vue
Normal file
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<!-- 全局容器:无滚动条 -->
|
||||
<div class="flex h-screen w-screen bg-base-200 overflow-hidden font-sans text-base-content">
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- 1. 左侧:客户导航列表 (保持不变) -->
|
||||
<!-- ========================================== -->
|
||||
<aside class="w-80 flex flex-col bg-base-100 border-r border-base-200 z-20 shrink-0">
|
||||
|
||||
<!-- 顶部 Header -->
|
||||
<div class="h-16 flex items-center justify-between px-4 border-b border-base-200 shrink-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-lg bg-primary flex items-center justify-center text-white font-bold">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="font-bold text-lg tracking-tight">AI 销售控制台</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 垂直列表 -->
|
||||
<div class="flex-1 overflow-y-auto p-2 space-y-2 custom-scrollbar">
|
||||
<div v-for="stage in stages" :key="stage.id"
|
||||
class="collapse collapse-arrow border border-base-200 bg-base-100 rounded-box"
|
||||
:class="{ 'collapse-open': stage.id === activeStageId }">
|
||||
<input type="radio" name="stage-accordion" :checked="stage.id === activeStageId"
|
||||
@click="activeStageId = stage.id" />
|
||||
<div
|
||||
class="collapse-title min-h-[2.5rem] py-2 px-3 text-sm font-medium flex items-center justify-between hover:bg-base-200">
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full" :class="getStageColorDot(stage.color)"></span>
|
||||
{{ stage.name }}
|
||||
</span>
|
||||
<span class="opacity-50 text-xs">{{ getCustomersByStage(stage.id).length }}</span>
|
||||
</div>
|
||||
<div class="collapse-content px-0 pb-0">
|
||||
<div class="flex flex-col gap-1 p-1 bg-base-200/50">
|
||||
<div v-for="user in getCustomersByStage(stage.id)" :key="user.id"
|
||||
@click="selectCustomer(user)"
|
||||
class="p-3 rounded cursor-pointer border-l-4 transition-all hover:bg-base-100"
|
||||
:class="selectedCustomer?.id === user.id ? 'bg-white border-primary shadow-sm' : 'border-transparent opacity-80'">
|
||||
<div class="flex justify-between items-start mb-1">
|
||||
<span class="font-bold text-sm">{{ user.name }}</span>
|
||||
<span class="text-[10px] opacity-50">{{ user.time }}</span>
|
||||
</div>
|
||||
<div class="text-xs truncate opacity-70">{{ user.lastMsg }}</div>
|
||||
<div class="mt-2 flex gap-1">
|
||||
<div class="badge badge-xs border-0"
|
||||
:class="user.aiStatus === 'active' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'">
|
||||
{{ user.aiStatus === 'active' ? 'AI' : '人工' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- 2. 中间:核心对话监控 (放大版) -->
|
||||
<!-- ========================================== -->
|
||||
<main class="flex-1 flex flex-col bg-white border-r border-base-200 relative min-w-0">
|
||||
|
||||
<!-- 聊天头部 -->
|
||||
<div class="h-16 flex items-center justify-between px-6 border-b border-base-200 bg-white shrink-0">
|
||||
<div v-if="selectedCustomer">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="avatar placeholder">
|
||||
<div class="bg-neutral text-neutral-content rounded-full w-10">
|
||||
<span class="text-lg">{{ selectedCustomer.name[0] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-base">{{ selectedCustomer.name }}</div>
|
||||
<div class="text-xs text-base-content/50 flex items-center gap-2">
|
||||
<span>ID: {{ selectedCustomer.id }}</span>
|
||||
<span class="badge badge-xs badge-ghost">{{ selectedCustomer.tags.join(', ') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 核心开关 -->
|
||||
<div v-if="selectedCustomer"
|
||||
class="flex items-center gap-3 bg-base-100 px-4 py-2 rounded-full border border-base-200">
|
||||
<div class="flex flex-col items-end">
|
||||
<span class="text-xs font-bold" :class="isAiActive ? 'text-success' : 'text-warning'">
|
||||
{{ isAiActive ? '● AI 自动托管中' : '● 人工强制接管' }}
|
||||
</span>
|
||||
</div>
|
||||
<input type="checkbox" class="toggle toggle-success" v-model="isAiActive" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 聊天记录区域 -->
|
||||
<div class="flex-1 overflow-y-auto p-6 bg-slate-50 space-y-6 custom-scrollbar relative">
|
||||
<div v-if="!selectedCustomer"
|
||||
class="absolute inset-0 flex items-center justify-center text-base-content/30">
|
||||
<div class="text-center">
|
||||
<p>请选择左侧客户</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- 历史分割线 -->
|
||||
<div class="divider text-xs text-base-content/30">历史记录</div>
|
||||
|
||||
<div class="chat chat-start">
|
||||
<div class="chat-image avatar">
|
||||
<div class="w-10 rounded-full bg-base-300 text-center leading-10 text-xs">{{
|
||||
selectedCustomer.name[0] }}</div>
|
||||
</div>
|
||||
<div class="chat-header text-xs opacity-50 mb-1">家长</div>
|
||||
<div class="chat-bubble bg-white text-base-content shadow-sm border border-base-200">
|
||||
老师,上次说的那个什么家庭教育直播,几点开始啊?我怕赶不上。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat chat-end">
|
||||
<div class="chat-header text-xs opacity-50 mb-1">AI 助手</div>
|
||||
<div class="chat-bubble chat-bubble-primary text-white shadow-md">
|
||||
不用担心,直播是晚上8点开始哦。如果有事赶不上的话,明天也会生成回放链接发给您的。
|
||||
</div>
|
||||
<div class="chat-footer opacity-50 text-[10px] mt-1">AI 意图识别: 询问时间</div>
|
||||
</div>
|
||||
|
||||
<div class="chat chat-start">
|
||||
<div class="chat-image avatar">
|
||||
<div class="w-10 rounded-full bg-base-300 text-center leading-10 text-xs">{{
|
||||
selectedCustomer.name[0] }}</div>
|
||||
</div>
|
||||
<div class="chat-bubble bg-white text-base-content shadow-sm border border-base-200">
|
||||
{{ selectedCustomer.lastMsg }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统日志插入到聊天流中 (替代流程图) -->
|
||||
<div class="w-full flex justify-center my-4">
|
||||
<div
|
||||
class="bg-base-200 text-base-content/60 text-xs px-3 py-1 rounded-full flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
系统: 检测到意向升级 (中 -> 高),已推送到“活跃区”
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 底部输入区域 -->
|
||||
<div v-if="selectedCustomer" class="h-40 bg-white border-t border-base-200 p-4 shrink-0">
|
||||
<div class="relative h-full">
|
||||
<textarea
|
||||
class="textarea textarea-bordered w-full h-full pr-24 resize-none text-base focus:outline-none focus:border-primary transition-colors"
|
||||
:class="{ 'textarea-disabled bg-base-100': isAiActive }"
|
||||
:placeholder="isAiActive ? '🚫 AI 托管中,输入框已锁定。请先切换到人工接管模式...' : '请输入回复内容,按 Enter 发送...'"
|
||||
:disabled="isAiActive"></textarea>
|
||||
|
||||
<div class="absolute bottom-3 right-3 flex gap-2">
|
||||
<button class="btn btn-sm btn-ghost" :disabled="isAiActive">😊</button>
|
||||
<button class="btn btn-sm btn-primary px-6" :disabled="isAiActive">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- 3. 右侧:控制面板与信息 (固定宽度) -->
|
||||
<!-- ========================================== -->
|
||||
<aside v-if="selectedCustomer" class="w-96 bg-base-100 border-l border-base-200 flex flex-col shrink-0">
|
||||
|
||||
<!-- 客户画像卡片 -->
|
||||
<div class="p-5 border-b border-base-200 bg-base-50">
|
||||
<h3 class="text-xs font-bold text-base-content/40 uppercase mb-4 tracking-wider">客户画像</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3 mb-4">
|
||||
<div class="bg-white border border-base-200 p-3 rounded-lg text-center shadow-sm">
|
||||
<div class="text-xs text-base-content/60 mb-1">成交意向</div>
|
||||
<div class="text-2xl font-black text-primary">{{ selectedCustomer.intent === '高' ? 92 : 65 }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white border border-base-200 p-3 rounded-lg text-center shadow-sm">
|
||||
<div class="text-xs text-base-content/60 mb-1">当前阶段</div>
|
||||
<div class="text-sm font-bold mt-1">{{stages.find(s => s.id ===
|
||||
selectedCustomer.stage)?.name.split('/')[0]}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="opacity-60">微信来源</span>
|
||||
<span>直播间转入</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="opacity-60">孩子年级</span>
|
||||
<span>初二 (14岁)</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 mt-3">
|
||||
<span v-for="tag in selectedCustomer.tags" :key="tag"
|
||||
class="badge badge-sm badge-outline bg-white">{{
|
||||
tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工具箱 (垂直排列) -->
|
||||
<div class="flex-1 overflow-y-auto p-5 custom-scrollbar">
|
||||
<h3 class="text-xs font-bold text-base-content/40 uppercase mb-4 tracking-wider">快捷操作</h3>
|
||||
|
||||
<!-- 警告提示 -->
|
||||
<div v-if="isAiActive" class="alert alert-warning shadow-sm text-xs py-2 mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-4 w-4" fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<span>操作前请先关闭 AI</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 资料推送组 -->
|
||||
<div class="form-control">
|
||||
<label class="label pt-0"><span class="label-text font-bold text-xs">推送资料</span></label>
|
||||
<div class="join w-full">
|
||||
<select class="select select-bordered select-sm join-item w-full text-xs"
|
||||
:disabled="isAiActive">
|
||||
<option selected>Day1 直播回放链接</option>
|
||||
<option>《厌学心理》电子书</option>
|
||||
<option>价格表单 (6800元)</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-primary join-item" :disabled="isAiActive">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 常用话术组 -->
|
||||
<div class="form-control">
|
||||
<label class="label pt-0"><span class="label-text font-bold text-xs">常用话术</span></label>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button
|
||||
class="btn btn-sm btn-outline btn-block justify-start font-normal text-xs h-auto py-2"
|
||||
:disabled="isAiActive">
|
||||
"这确实让家长很头疼..." (共情)
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-outline btn-block justify-start font-normal text-xs h-auto py-2"
|
||||
:disabled="isAiActive">
|
||||
"咱们课程主要解决..." (产品)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider my-2"></div>
|
||||
|
||||
<!-- 强制干预 -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<button class="btn btn-sm btn-warning btn-outline" :disabled="isAiActive">催付定金</button>
|
||||
<button class="btn btn-sm btn-error btn-outline" :disabled="isAiActive">拉黑/终止</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 简易日志区 (替代流程图) -->
|
||||
<div class="h-48 bg-base-900 border-t border-base-300 p-4 shrink-0 bg-base-200">
|
||||
<h3 class="text-xs font-bold text-base-content/40 uppercase mb-2 tracking-wider flex justify-between">
|
||||
近期动态
|
||||
<span class="text-[10px] font-normal cursor-pointer hover:text-primary">查看全部 ></span>
|
||||
</h3>
|
||||
<div class="space-y-2 overflow-y-auto h-32 custom-scrollbar pr-1">
|
||||
<div class="text-[11px] flex gap-2">
|
||||
<span class="opacity-50 font-mono">10:42</span>
|
||||
<span class="text-success">工具调用成功: 发送资料</span>
|
||||
</div>
|
||||
<div class="text-[11px] flex gap-2">
|
||||
<span class="opacity-50 font-mono">10:40</span>
|
||||
<span>AI 识别意图: 索要资料</span>
|
||||
</div>
|
||||
<div class="text-[11px] flex gap-2">
|
||||
<span class="opacity-50 font-mono">09:15</span>
|
||||
<span class="text-blue-500">人工介入: 冻结 AI</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
// --- 数据部分 (保持不变) ---
|
||||
const stages = [
|
||||
{ id: 'stage_1', name: '加微/浅建联', color: 'blue' },
|
||||
{ id: 'stage_2', name: '填表单/报告', color: 'purple' },
|
||||
{ id: 'stage_3', name: '课前促到课', color: 'indigo' },
|
||||
{ id: 'stage_4', name: '课中/活跃', color: 'orange' },
|
||||
{ id: 'stage_5', name: '待人工介入', color: 'red' },
|
||||
{ id: 'stage_6', name: '成交/合同', color: 'green' },
|
||||
];
|
||||
|
||||
const customers = [
|
||||
{ id: 101, name: '李妈妈', stage: 'stage_4', intent: '高', aiStatus: 'active', lastMsg: '孩子正在看直播,挺喜欢的', tags: ['Day1', '厌学'], time: '1m' },
|
||||
{ id: 102, name: '王爸爸', stage: 'stage_5', intent: '中', aiStatus: 'frozen', lastMsg: '你们这个退费怎么退?', tags: ['价格敏感'], time: '2h' },
|
||||
{ id: 103, name: '张家长', stage: 'stage_1', intent: '低', aiStatus: 'active', lastMsg: '通过了好友请求', tags: ['新客'], time: '5h' },
|
||||
{ id: 105, name: '刘女士', stage: 'stage_4', intent: '中', aiStatus: 'active', lastMsg: '还没收到报告', tags: ['急切'], time: '1d' },
|
||||
];
|
||||
|
||||
const activeStageId = ref('stage_4');
|
||||
const selectedCustomer = ref<any>(customers[0]);
|
||||
const isAiActive = ref(true);
|
||||
|
||||
const getCustomersByStage = (stageId: string) => customers.filter(c => c.stage === stageId);
|
||||
const getStageColorDot = (color: string) => {
|
||||
const map: any = { blue: 'bg-blue-500', purple: 'bg-purple-500', indigo: 'bg-indigo-500', orange: 'bg-orange-500', red: 'bg-red-500', green: 'bg-green-500' };
|
||||
return map[color] || 'bg-gray-500';
|
||||
};
|
||||
const selectCustomer = (user: any) => {
|
||||
selectedCustomer.value = user;
|
||||
isAiActive.value = user.aiStatus === 'active';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 滚动条美化 */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
</style>
|
||||
12
247_Contry/tsconfig.app.json
Normal file
12
247_Contry/tsconfig.app.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
11
247_Contry/tsconfig.json
Normal file
11
247_Contry/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
247_Contry/tsconfig.node.json
Normal file
19
247_Contry/tsconfig.node.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node24/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
19
247_Contry/vite.config.ts
Normal file
19
247_Contry/vite.config.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
tailwindcss()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user