更新文件以保持同步

This commit is contained in:
乘风
2026-01-09 10:15:29 +08:00
parent 6835177537
commit efe0685189
34 changed files with 4731 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
# EditorConfig 帮助不同编辑器和 IDE 维护一致的编码风格
# https://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{json,yml,yaml}]
indent_size = 2
[*.vue]
indent_size = 2

View File

@@ -0,0 +1,19 @@
node_modules
dist
dist-ssr
*.local
# Build outputs
*.min.js
*.min.css
# Config files
*.config.js
*.config.cjs
*.config.mjs
# Static assets
public
# Logs
*.log

View File

@@ -0,0 +1,24 @@
/* eslint-env node */
require('eslint-plugin-vue')
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
env: {
browser: true,
es2021: true,
node: true,
},
rules: {
'vue/multi-word-component-names': 'off',
'vue/no-unused-vars': 'warn',
'no-unused-vars': 'warn',
},
}

33
TouchVueThree/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Environment variables
.env
.env.local
.env.production.local
.env.development.local
# Build outputs
*.tsbuildinfo

4
TouchVueThree/.npmrc Normal file
View File

@@ -0,0 +1,4 @@
# npm 配置
registry=https://registry.npmmirror.com
save-exact=false
engine-strict=false

View File

@@ -0,0 +1,30 @@
# Dependencies
node_modules
package-lock.json
pnpm-lock.yaml
yarn.lock
# Build outputs
dist
dist-ssr
*.local
# Logs
*.log
# Editor
.vscode
.idea
# Cache
.cache
.parcel-cache
# Environment
.env
.env.local
.env.production.local
# Other
*.min.js
*.min.css

10
TouchVueThree/.prettierrc Normal file
View File

@@ -0,0 +1,10 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5",
"arrowParens": "always",
"endOfLine": "lf"
}

8
TouchVueThree/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss"
]
}

View File

@@ -0,0 +1,27 @@
# 更新日志
所有重要的项目变更都会记录在此文件中。
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)
版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
## [1.0.0] - 2024-01-XX
### 新增
- 初始化项目
- 集成 Vite + Vue 3 + Tailwind CSS
- 创建基础项目结构
- 添加聊天模块ChatList, ChatWindow, ChatDetail
- 添加账号管理页面Accounts
- 添加数据看板页面Analytics
- 创建全局导航栏组件Sidebar
- 创建 AI 算力展示组件AICreditPill
- 配置 ESLint 和 Prettier
- 添加环境变量支持
### 配置
- Vite 构建配置
- Tailwind CSS 主题配置
- PostCSS 配置
- 路径别名配置(@ → src
- 编辑器配置文件

21
TouchVueThree/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 触客宝 Pro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

122
TouchVueThree/README.md Normal file
View File

@@ -0,0 +1,122 @@
# 触客宝 Pro - Vue 3 版本
基于 Vite + Vue 3 + Tailwind CSS 构建的现代化工作台应用。
## 技术栈
- **Vue 3** - 渐进式 JavaScript 框架
- **Vite** - 下一代前端构建工具
- **Tailwind CSS** - 实用优先的 CSS 框架
- **ESLint** - 代码质量检查
- **Prettier** - 代码格式化
## 项目结构
```
TouchVueThree/
├── src/
│ ├── assets/ # 静态资源
│ │ └── style.css # 全局样式Tailwind 指令)
│ ├── components/ # 公共组件
│ │ ├── Sidebar.vue # 全局左侧导航栏
│ │ └── AICreditPill.vue # AI算力展示胶囊
│ ├── views/ # 页面视图
│ │ ├── Chat/ # 聊天模块
│ │ ├── Accounts.vue # 账号管理
│ │ └── Analytics.vue # 数据看板
│ ├── App.vue # 根组件
│ └── main.js # 应用入口
├── public/ # 公共静态资源
├── index.html # HTML 入口
└── vite.config.js # Vite 配置
```
## 开发指南
### 安装依赖
```bash
npm install
# 或
pnpm install
# 或
yarn install
```
### 启动开发服务器
```bash
npm run dev
# 或
pnpm dev
# 或
yarn dev
```
开发服务器将在 `http://localhost:5173` 启动。
### 构建生产版本
```bash
npm run build
# 或
pnpm build
# 或
yarn build
```
构建产物将输出到 `dist/` 目录。
### 预览生产构建
```bash
npm run preview
# 或
pnpm preview
# 或
yarn preview
```
### 代码检查
```bash
# ESLint 检查并自动修复
npm run lint
# Prettier 格式化代码
npm run format
```
## 环境变量
复制 `.env.example``.env` 并根据需要配置环境变量。
```bash
cp .env.example .env
```
## 功能模块
### 1. 聊天模块 (Chat)
- **ChatList** - 消息列表
- **ChatWindow** - 聊天窗口
- **ChatDetail** - CRM 资料详情
### 2. 账号管理 (Accounts)
- 微信账号管理
- 账号状态监控
### 3. 数据看板 (Analytics)
- 业务数据统计
- 可视化图表展示
## 开发规范
- 使用 Vue 3 Composition API
- 组件采用 `<script setup>` 语法
- 样式使用 Tailwind CSS 工具类
- 代码提交前运行 lint 和 format
## 许可证
MIT

21
TouchVueThree/env.d.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
readonly VITE_APP_VERSION: string
readonly VITE_API_BASE_URL: string
readonly VITE_API_TIMEOUT: string
readonly VITE_ENABLE_DEBUG: string
readonly VITE_ENABLE_MOCK: string
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

11
TouchVueThree/env.example Normal file
View File

@@ -0,0 +1,11 @@
# 应用配置
VITE_APP_TITLE=触客宝 Pro
VITE_APP_VERSION=1.0.0
# API 配置
VITE_API_BASE_URL=http://localhost:3000/api
VITE_API_TIMEOUT=10000
# 功能开关
VITE_ENABLE_DEBUG=false
VITE_ENABLE_MOCK=false

25
TouchVueThree/index.html Normal file
View File

@@ -0,0 +1,25 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>触客宝 Pro - 全局工作台</title>
<!-- Font Awesome -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<!-- Google Fonts -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700&family=Inter:wght@400;500&display=swap"
/>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": false,
"strict": false,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": ["vite/client"],
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
},
"include": ["src/**/*.js", "src/**/*.vue", "env.d.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,27 @@
{
"name": "touchvue-three",
"version": "1.0.0",
"type": "module",
"private": true,
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"vue": "^3.4.21"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.18",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.20.1",
"postcss": "^8.4.35",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.1",
"vite": "^5.1.4"
}
}

2146
TouchVueThree/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -0,0 +1,3 @@
# Public 目录
# 此目录用于存放静态资源文件,如 favicon.ico、图片等
# 目录中的文件会直接被复制到构建输出的根目录

86
TouchVueThree/src/App.vue Normal file
View File

@@ -0,0 +1,86 @@
<template>
<div
class="flex h-screen w-screen bg-[#F8FAFC] text-slate-700 overflow-hidden font-sans"
>
<!-- 全局侧边栏 -->
<Sidebar
:current-tab="currentTab"
@change-tab="handleTabChange"
@logo-click="handleLogoClick"
@add-account="handleAddAccount"
@open-settings="handleOpenSettings"
@select-account="handleSelectAccount"
/>
<!-- 主内容区域 -->
<main class="flex-1 relative overflow-hidden">
<Transition name="fade" mode="out-in">
<component :is="activeComponent" />
</Transition>
</main>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import Sidebar from './components/Sidebar.vue'
import ChatIndex from './views/Chat/Index.vue'
import Contacts from './views/Contacts.vue'
import Analytics from './views/Analytics.vue'
const currentTab = ref('chat')
const activeComponent = computed(() => {
switch (currentTab.value) {
case 'chat':
return ChatIndex
case 'contacts':
return Contacts // 通讯录页面
case 'analytics':
return Analytics
default:
return ChatIndex
}
})
// 处理标签切换
const handleTabChange = (tab) => {
currentTab.value = tab
}
// 处理 Logo 点击
const handleLogoClick = () => {
// 可以跳转到首页或执行其他操作
currentTab.value = 'chat'
}
// 处理添加账号
const handleAddAccount = () => {
// TODO: 打开添加账号对话框或跳转到账号管理页面
console.log('添加账号')
currentTab.value = 'contacts'
}
// 处理打开设置
const handleOpenSettings = () => {
// TODO: 打开设置对话框或跳转到设置页面
console.log('打开设置')
}
// 处理选择账号
const handleSelectAccount = (account) => {
// TODO: 切换到选中的账号
console.log('选择账号:', account)
}
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -0,0 +1,28 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 自定义样式 */
body {
font-family: "Plus Jakarta Sans", "Inter", sans-serif;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* 页面切换动画 */
.fade-in {
animation: fadeIn 0.3s ease-out forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@@ -0,0 +1,65 @@
<template>
<div class="group relative">
<!-- 胶囊主体 -->
<div
class="flex items-center gap-3 bg-gradient-to-r from-indigo-500 to-purple-600 text-white pl-4 pr-1 py-1 rounded-full shadow-md cursor-pointer hover:shadow-lg hover:-translate-y-0.5 transition-all"
>
<div class="flex flex-col leading-none items-end">
<span class="text-[9px] font-bold opacity-80 uppercase tracking-wide"
>AI 算力</span
>
<span class="text-sm font-bold font-mono">{{ animatedBalance }}</span>
</div>
<div
class="w-8 h-8 bg-white text-indigo-600 rounded-full flex items-center justify-center shadow-inner"
>
<i
class="fa-solid fa-bolt text-sm"
:class="{ 'animate-pulse': isAnimating }"
></i>
</div>
</div>
<!-- 悬浮详情 (Dropdown) -->
<div
class="absolute top-full right-0 mt-2 w-64 bg-white rounded-xl shadow-xl border border-gray-100 p-4 hidden group-hover:block z-50"
>
<div class="flex justify-between items-center mb-3">
<span class="text-xs font-bold text-gray-700">算力钱包</span>
<span
class="text-[10px] bg-green-100 text-green-700 px-2 py-0.5 rounded-full"
>状态正常</span
>
</div>
<div class="h-2 bg-gray-100 rounded-full mb-2 overflow-hidden">
<div class="h-full bg-indigo-500 w-3/4 rounded-full"></div>
</div>
<div class="text-xs text-gray-400 mb-3">本月额度剩余 75%</div>
<button
class="w-full py-1.5 bg-slate-800 text-white text-xs rounded-lg hover:bg-slate-700 transition"
>
立即充值
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
balance: { type: Number, default: 0 },
})
const isAnimating = ref(false)
const animatedBalance = computed(() => props.balance.toLocaleString())
// 暴露给父组件调用的方法:播放扣费动画
const playDeductAnimation = () => {
isAnimating.value = true
setTimeout(() => (isAnimating.value = false), 500)
}
defineExpose({ playDeductAnimation })
</script>

View File

@@ -0,0 +1,183 @@
<template>
<div
class="w-16 bg-slate-900 flex flex-col items-center py-6 flex-shrink-0 z-20"
>
<!-- Logo -->
<div
class="w-10 h-10 bg-indigo-500 rounded-lg flex items-center justify-center text-white font-bold text-xl mb-8 shadow-lg cursor-pointer hover:scale-105 transition-transform"
@click="$emit('logo-click')"
>
<i class="fa-solid fa-robot"></i>
</div>
<!-- 功能模块 -->
<div class="space-y-6 flex flex-col items-center w-full">
<!-- 聚合聊天 -->
<div
@click="$emit('change-tab', 'chat')"
class="group relative cursor-pointer"
:class="
currentTab === 'chat'
? 'text-indigo-400'
: 'text-gray-500 hover:text-gray-300'
"
>
<i class="fa-solid fa-comments text-2xl transition-colors"></i>
<span
class="absolute left-14 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition whitespace-nowrap z-50 pointer-events-none"
>
聚合聊天
</span>
</div>
<!-- 通讯录 -->
<div
@click="$emit('change-tab', 'contacts')"
class="group relative cursor-pointer transition-colors"
:class="
currentTab === 'contacts'
? 'text-indigo-400'
: 'text-gray-500 hover:text-gray-300'
"
>
<i class="fa-solid fa-address-book text-xl"></i>
<span
class="absolute left-14 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition whitespace-nowrap z-50 pointer-events-none"
>
通讯录
</span>
</div>
<!-- 数据看板 -->
<div
@click="$emit('change-tab', 'analytics')"
class="group relative cursor-pointer transition-colors"
:class="
currentTab === 'analytics'
? 'text-indigo-400'
: 'text-gray-500 hover:text-gray-300'
"
>
<i class="fa-solid fa-chart-pie text-xl"></i>
<span
class="absolute left-14 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition whitespace-nowrap z-50 pointer-events-none"
>
数据看板
</span>
</div>
</div>
<!-- 分隔线 -->
<div class="mt-8 mb-4 w-8 border-b border-gray-700"></div>
<!-- 多账号头像 (模拟微信多开) -->
<div
class="space-y-4 flex flex-col items-center overflow-y-auto no-scrollbar flex-1 min-h-0"
>
<div
v-for="account in accounts"
:key="account.id"
@click="selectAccount(account.id)"
class="relative cursor-pointer group"
>
<img
:src="account.avatar"
:class="[
'w-10 h-10 rounded-full border-2 object-cover transition-all',
account.active
? 'border-green-500 opacity-100'
: 'border-transparent hover:border-gray-500 opacity-60 hover:opacity-100',
]"
:alt="account.name"
/>
<!-- 未读消息红点 -->
<div
v-if="account.hasNotification"
class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-slate-900"
></div>
</div>
<!-- 添加账号按钮 -->
<div
@click="$emit('add-account')"
class="w-10 h-10 rounded-full bg-gray-800 flex items-center justify-center text-gray-400 hover:text-white cursor-pointer hover:bg-gray-700 transition"
>
<i class="fa-solid fa-plus"></i>
</div>
</div>
<!-- 底部设置 -->
<div class="mt-auto pb-4">
<div
@click="$emit('open-settings')"
class="w-10 h-10 rounded-full bg-slate-800 flex items-center justify-center text-gray-400 hover:text-white cursor-pointer transition-colors"
>
<i class="fa-solid fa-gear"></i>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
defineProps({
currentTab: {
type: String,
default: 'chat',
},
})
const emit = defineEmits([
'change-tab',
'logo-click',
'add-account',
'open-settings',
'select-account',
])
// 账号列表数据
const accounts = ref([
{
id: 'wx1',
name: '账号1',
avatar: 'https://i.pravatar.cc/150?u=wx1',
active: true,
hasNotification: true,
},
{
id: 'wx2',
name: '账号2',
avatar: 'https://i.pravatar.cc/150?u=wx2',
active: false,
hasNotification: false,
},
])
// 选择账号
const selectAccount = (accountId) => {
// 更新激活状态
accounts.value.forEach((account) => {
account.active = account.id === accountId
})
// 触发事件
const account = accounts.value.find((acc) => acc.id === accountId)
if (account) {
// 可以在这里清除通知
account.hasNotification = false
// 触发选择账号事件
emit('select-account', account)
}
}
</script>
<style scoped>
/* 自定义滚动条隐藏 */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>

View File

@@ -0,0 +1,6 @@
import { createApp } from 'vue'
import App from './App.vue'
import './assets/style.css'
const app = createApp(App)
app.mount('#app')

View File

@@ -0,0 +1,35 @@
<template>
<div class="p-8 h-full overflow-y-auto custom-scroll">
<div class="max-w-6xl mx-auto">
<div class="flex justify-between items-center mb-8">
<h2 class="text-2xl font-bold text-slate-800">聚合账号管理</h2>
<button
class="bg-primary text-white px-6 py-2.5 rounded-xl font-bold shadow-lg shadow-indigo-200 flex items-center gap-2"
>
<i class="fa-solid fa-qrcode"></i> 扫码接入
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div
class="bg-white rounded-2xl p-6 border border-gray-100 shadow-sm hover:shadow-md transition-all relative overflow-hidden group"
>
<span
class="absolute top-3 right-3 bg-green-100 text-green-700 text-xs px-2 py-1 rounded-lg font-bold"
>在线</span
>
<div class="flex items-center gap-4 mb-6">
<img
src="https://i.pravatar.cc/150?u=wx1"
class="w-16 h-16 rounded-2xl border-2 border-gray-100"
/>
<div>
<h3 class="font-bold text-lg text-slate-800">商务-大刘</h3>
<p class="text-xs text-gray-400">ID: business_01</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,42 @@
<template>
<div class="p-8 h-full overflow-y-auto custom-scroll">
<div class="max-w-6xl mx-auto">
<h2 class="text-2xl font-bold text-slate-800 mb-6">AI 效能看板</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div
class="bg-gradient-to-br from-[#4F46E5] to-[#7C3AED] rounded-2xl p-6 text-white shadow-lg relative overflow-hidden"
>
<div class="relative z-10">
<div class="text-indigo-100 text-sm font-medium mb-1">
AI 算力余额
</div>
<div class="text-4xl font-bold font-mono tracking-tight mb-4">
12,450
</div>
</div>
</div>
</div>
<div class="bg-white rounded-2xl p-6 border border-gray-100 shadow-sm">
<h3 class="font-bold text-slate-800 mb-6">近7天接待趋势</h3>
<div class="h-64 flex items-end justify-between gap-2 px-2">
<div
v-for="h in [20, 30, 45, 25, 60, 40, 15]"
:key="h"
class="w-full flex flex-col gap-1 items-center group"
>
<div
:style="{ height: h + 'px' }"
class="w-4 bg-purple-400 rounded-t-sm opacity-80 group-hover:opacity-100 transition-all"
></div>
<div
:style="{ height: h / 2 + 'px' }"
class="w-4 bg-indigo-500 rounded-b-sm opacity-80 group-hover:opacity-100 transition-all"
></div>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,95 @@
<template>
<div
class="bg-white border-l border-gray-100 flex flex-col flex-shrink-0 transition-all duration-300 overflow-hidden"
:style="{ width: visible ? '300px' : '0', opacity: visible ? '1' : '0', pointerEvents: visible ? 'auto' : 'none' }"
>
<!-- Tab 切换 -->
<div class="flex p-1 bg-gray-50 m-4 rounded-lg border border-gray-100">
<button
@click="tab = 'profile'"
class="flex-1 py-1.5 text-xs font-bold rounded-md transition"
:class="
tab === 'profile'
? 'bg-white text-indigo-600 shadow-sm'
: 'text-gray-500'
"
>
资料
</button>
<button
@click="tab = 'scripts'"
class="flex-1 py-1.5 text-xs font-bold rounded-md transition"
:class="
tab === 'scripts'
? 'bg-white text-indigo-600 shadow-sm'
: 'text-gray-500'
"
>
话术
</button>
</div>
<div class="flex-1 overflow-y-auto px-5 pb-5 custom-scroll">
<!-- 资料内容 -->
<div v-if="tab === 'profile'" class="space-y-6">
<div class="text-center">
<img
src="https://i.pravatar.cc/150?u=8"
class="w-20 h-20 rounded-full border-4 border-white shadow-lg mx-auto"
/>
<h3 class="mt-2 font-bold text-slate-800">大野</h3>
<p class="text-xs text-gray-400">ID: ys666wow</p>
</div>
<div>
<h4 class="text-xs font-bold text-gray-400 uppercase mb-2">标签</h4>
<div class="flex flex-wrap gap-2">
<span
class="px-2 py-1 bg-red-50 text-red-600 text-xs rounded border border-red-100"
>高意向</span
>
<span
class="px-2 py-1 bg-blue-50 text-blue-600 text-xs rounded border border-blue-100"
>游戏玩家</span
>
</div>
</div>
<div
class="bg-yellow-50 p-3 rounded-lg border border-yellow-100 text-xs text-yellow-700"
>
<strong>跟进提醒</strong><br />明天上午10点确认订单情况
</div>
</div>
<!-- 话术内容 -->
<div v-else class="space-y-3">
<input
type="text"
placeholder="搜索话术..."
class="w-full bg-gray-50 text-xs py-2 px-3 rounded-lg border border-gray-200 mb-2"
/>
<div
v-for="i in 3"
:key="i"
class="p-3 bg-white border border-gray-100 rounded-xl hover:border-indigo-200 cursor-pointer group"
>
<h5
class="text-xs font-bold text-slate-700 mb-1 group-hover:text-indigo-600"
>
通用开场 {{ i }}
</h5>
<p class="text-xs text-gray-500 truncate">
您好我是您的专属顾问很高兴...
</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({ visible: Boolean, chat: Object })
const tab = ref('profile')
</script>

View File

@@ -0,0 +1,112 @@
<template>
<div class="w-80 border-r border-gray-100 flex flex-col bg-white">
<!-- 搜索栏 -->
<div class="p-4 border-b border-gray-50">
<h2 class="font-bold text-lg text-slate-800 mb-3">消息 (12)</h2>
<div class="relative">
<i
class="fa-solid fa-magnifying-glass absolute left-3 top-3 text-gray-400 text-xs"
></i>
<input
type="text"
placeholder="搜索联系人..."
class="w-full bg-gray-50 text-sm pl-9 pr-4 py-2 rounded-lg border-transparent focus:bg-white focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-all outline-none"
/>
</div>
<!-- 快捷标签 -->
<div class="flex gap-2 mt-3 overflow-x-auto no-scrollbar pb-1">
<span
class="px-3 py-1 bg-indigo-50 text-indigo-600 text-xs font-bold rounded-md cursor-pointer whitespace-nowrap"
>全部</span
>
<span
class="px-3 py-1 bg-white border border-gray-200 text-gray-500 hover:bg-gray-50 text-xs rounded-md cursor-pointer whitespace-nowrap"
>未读</span
>
<span
class="px-3 py-1 bg-purple-50 text-purple-600 border border-purple-100 text-xs rounded-md cursor-pointer whitespace-nowrap flex items-center gap-1"
>
<i class="fa-solid fa-robot"></i> AI托管
</span>
</div>
</div>
<!-- 列表 -->
<div class="flex-1 overflow-y-auto custom-scroll">
<div
v-for="chat in chats"
:key="chat.id"
@click="$emit('select', chat.id)"
class="p-3 mx-2 my-1 rounded-xl cursor-pointer transition-all border flex gap-3 items-center"
:class="
activeId === chat.id
? 'bg-indigo-50 border-indigo-100'
: 'hover:bg-gray-50 border-transparent'
"
>
<div class="relative">
<img :src="chat.avatar" class="w-11 h-11 rounded-lg object-cover" />
<div
v-if="chat.unread"
class="absolute -top-1.5 -right-1.5 bg-red-500 text-white text-[10px] h-4 min-w-[16px] px-1 flex items-center justify-center rounded-full border border-white"
>
{{ chat.unread }}
</div>
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline mb-1">
<h3 class="font-bold text-sm text-slate-700 truncate">
{{ chat.name }}
</h3>
<span class="text-[10px] text-gray-400">{{ chat.time }}</span>
</div>
<div class="flex items-center gap-1">
<span
v-if="chat.isAi"
class="bg-purple-100 text-purple-600 text-[9px] px-1 rounded font-bold"
>AI</span
>
<p class="text-xs text-gray-400 truncate">{{ chat.lastMsg }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
defineProps(['activeId'])
defineEmits(['select'])
const chats = ref([
{
id: 1,
name: '大野(朋友圈业务)',
avatar: 'https://i.pravatar.cc/150?u=8',
time: '11:45',
lastMsg: '已为您生成3条回复建议...',
unread: 0,
isAi: true,
},
{
id: 2,
name: '京东内购福利群',
avatar: 'https://i.pravatar.cc/150?u=group',
time: '10:20',
lastMsg: '张三: 谁有券?',
unread: 12,
isAi: false,
},
{
id: 3,
name: 'VIP客户-王总',
avatar: 'https://i.pravatar.cc/150?u=ceo',
time: '昨天',
lastMsg: '合同已确认',
unread: 0,
isAi: false,
},
])
</script>

View File

@@ -0,0 +1,275 @@
<template>
<div class="flex-1 flex flex-col min-w-0 bg-[#F8FAFC] relative">
<!-- Header (关键修改功能按钮 + 算力展示) -->
<header
class="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-6 shadow-sm z-10"
>
<!-- Left: Chat Info -->
<div class="flex items-center gap-4">
<h2 class="font-bold text-lg text-gray-800">大野(朋友圈业务)</h2>
<span
class="px-2 py-0.5 bg-green-50 text-green-600 text-xs rounded border border-green-200 flex items-center gap-1"
>
<span
class="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse"
></span>
微信在线
</span>
</div>
<!-- Right: Functions & AI Credits -->
<div class="flex items-center gap-6">
<!-- Functional Buttons (Clean Style) -->
<div class="flex items-center gap-2 border-r border-gray-200 pr-6">
<button
class="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:text-indigo-600 hover:bg-indigo-50 rounded transition"
title="发朋友圈"
@click="$emit('post-moments')"
>
<i class="fa-brands fa-weixin"></i>
<span class="hidden xl:inline">发朋友圈</span>
</button>
<button
class="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:text-indigo-600 hover:bg-indigo-50 rounded transition"
title="同步消息"
@click="$emit('sync-messages')"
>
<i class="fa-solid fa-arrows-rotate"></i>
<span class="hidden xl:inline">同步</span>
</button>
<button
class="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:text-indigo-600 hover:bg-indigo-50 rounded transition"
title="创建任务"
@click="$emit('create-task')"
>
<i class="fa-regular fa-calendar-check"></i>
</button>
</div>
<!-- 算力胶囊 -->
<AICreditPill ref="creditPill" :balance="balance" />
<!-- Toggle Sidebar Info -->
<button
@click="$emit('toggle-detail')"
class="text-gray-400 hover:text-indigo-600 transition ml-2"
>
<i class="fa-solid fa-circle-info text-xl"></i>
</button>
</div>
</header>
<!-- 消息流 -->
<div
class="flex-1 overflow-y-auto p-6 space-y-6 custom-scroll"
ref="scrollContainer"
>
<div class="text-center">
<span
class="text-[10px] text-gray-400 bg-gray-100 px-3 py-1 rounded-full"
>今天 10:23</span
>
</div>
<!-- 消息列表 -->
<div
v-for="(msg, i) in messages"
:key="i"
class="flex gap-3 items-end group"
:class="{ 'flex-row-reverse': msg.isMe }"
>
<img
:src="
msg.isMe
? 'https://i.pravatar.cc/150?u=wx1'
: 'https://i.pravatar.cc/150?u=8'
"
class="w-8 h-8 rounded-lg shadow-sm"
/>
<div
class="flex flex-col gap-1 max-w-[70%]"
:class="{ 'items-end': msg.isMe }"
>
<span class="text-[10px] text-gray-400 mx-1">{{
msg.isMe ? '我' : '大野'
}}</span>
<!-- AI 标识 -->
<div v-if="msg.isAi" class="flex items-center gap-1 mb-0.5">
<span
class="text-[9px] bg-purple-50 text-purple-600 border border-purple-100 px-1 rounded"
>AI 生成</span
>
</div>
<div
class="p-3 rounded-2xl text-sm leading-relaxed shadow-sm relative"
:class="
msg.isMe
? 'bg-indigo-600 text-white rounded-br-none'
: 'bg-white text-slate-700 border border-gray-100 rounded-bl-none'
"
>
{{ msg.text }}
</div>
</div>
</div>
<!-- AI 建议卡片 (Interactive) -->
<div v-if="showAiCard" class="flex justify-end pr-12 animate-fade-in-up">
<div
class="bg-gradient-to-br from-purple-50 to-white border border-purple-100 rounded-xl p-4 shadow-lg max-w-md relative overflow-hidden"
>
<div class="flex justify-between items-center mb-2">
<div
class="flex items-center gap-2 text-purple-600 font-bold text-xs"
>
<i class="fa-solid fa-sparkles"></i> AI 建议回复
</div>
<span
class="text-[10px] text-orange-500 bg-orange-50 px-2 py-0.5 rounded border border-orange-100"
>预估消耗 5 算力</span
>
</div>
<p
class="text-sm text-slate-600 mb-3 bg-white/50 p-2 rounded border border-purple-50 border-dashed"
>
没问题的既然是老客户这次直接帮您申请赠送幻化服务现在下单的话我这就去安排打手准备
</p>
<div class="flex justify-end gap-2">
<button
@click="showAiCard = false"
class="px-3 py-1.5 text-xs text-gray-400 hover:text-gray-600"
>
忽略
</button>
<button
@click="useAiSuggestion"
class="px-4 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white text-xs rounded-lg shadow-sm transition flex items-center gap-2"
>
<i class="fa-solid fa-paper-plane"></i> 发送
</button>
</div>
</div>
</div>
</div>
<!-- 输入框 -->
<div class="p-4 bg-white border-t border-gray-100 z-10">
<div class="flex justify-between items-center mb-2 px-1">
<div class="flex gap-4 text-gray-400">
<i
class="fa-regular fa-face-smile hover:text-indigo-600 cursor-pointer"
></i>
<i
class="fa-solid fa-folder-open hover:text-indigo-600 cursor-pointer"
></i>
</div>
<div
class="flex items-center gap-2 cursor-pointer"
@click="aiMode = !aiMode"
>
<span class="text-xs font-bold text-slate-500">AI 辅助</span>
<div
class="w-8 h-4 bg-gray-200 rounded-full relative transition-colors"
:class="{ 'bg-indigo-600': aiMode }"
>
<div
class="w-3 h-3 bg-white rounded-full absolute top-0.5 transition-all shadow-sm"
:class="aiMode ? 'right-0.5' : 'left-0.5'"
></div>
</div>
</div>
</div>
<div class="relative">
<textarea
v-model="inputMsg"
class="w-full h-20 bg-gray-50 border border-gray-200 rounded-xl p-3 text-sm focus:bg-white focus:border-indigo-500 focus:outline-none resize-none transition"
placeholder="输入消息..."
></textarea>
<button
@click="sendMsg"
class="absolute bottom-3 right-3 bg-slate-900 text-white px-4 py-1.5 rounded-lg text-xs font-bold hover:bg-slate-700 transition"
>
发送
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
import AICreditPill from '../../components/AICreditPill.vue'
defineEmits([
'toggle-detail',
'post-moments',
'sync-messages',
'create-task',
])
const creditPill = ref(null)
const scrollContainer = ref(null)
const balance = ref(12450)
const showAiCard = ref(true)
const aiMode = ref(true)
const inputMsg = ref('')
const messages = ref([
{
text: '老板,上次说的那个魔兽代练套餐,如果是老客户的话,还能再送一次幻化吗?',
isMe: false,
isAi: false,
},
])
const scrollToBottom = () => {
nextTick(() => {
if (scrollContainer.value) {
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
}
})
}
const useAiSuggestion = () => {
// 1. 扣费动画
creditPill.value?.playDeductAnimation()
balance.value -= 5
// 2. 发送消息
messages.value.push({
text: '亲,没问题的!既然是老客户,这次直接帮您申请赠送幻化服务。现在下单的话,我这就去安排打手准备。',
isMe: true,
isAi: true,
})
// 3. 隐藏卡片
showAiCard.value = false
scrollToBottom()
}
const sendMsg = () => {
if (!inputMsg.value.trim()) return
messages.value.push({ text: inputMsg.value, isMe: true, isAi: false })
inputMsg.value = ''
scrollToBottom()
}
</script>
<style>
.animate-fade-in-up {
animation: fadeInUp 0.4s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<div class="flex h-full w-full">
<ChatList @select="selectChat" :activeId="activeChatId" />
<ChatWindow
:chat="activeChat"
:credit-balance="creditBalance"
@deduct-credit="handleCreditDeduction"
@toggle-detail="showDetail = !showDetail"
/>
<ChatDetail :visible="showDetail" :chat="activeChat" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import ChatList from './ChatList.vue'
import ChatWindow from './ChatWindow.vue'
import ChatDetail from './ChatDetail.vue'
const showDetail = ref(false)
const activeChatId = ref(1)
const creditBalance = ref(12450)
// 模拟数据
const chats = [
{
id: 1,
name: '大野(朋友圈业务)',
avatar: 'https://i.pravatar.cc/150?u=8',
time: '11:45',
msg: '已为您生成 3 条回复建议...',
isAi: true,
unread: 0,
type: 'user',
},
{
id: 2,
name: '京东内购福利群',
avatar: 'https://i.pravatar.cc/150?u=group',
time: '10:20',
msg: '张三: [图片] 谁有这个券?',
isAi: false,
unread: 99,
type: 'group',
},
]
const activeChat = computed(() =>
chats.find((c) => c.id === activeChatId.value)
)
const selectChat = (id) => {
activeChatId.value = id
}
const handleCreditDeduction = (amount) => {
creditBalance.value -= amount
}
</script>

View File

@@ -0,0 +1,306 @@
<template>
<div class="h-full overflow-y-auto custom-scroll bg-[#F8FAFC]">
<div class="p-8 max-w-7xl mx-auto">
<!-- 头部 -->
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-slate-800 mb-1">通讯录管理</h1>
<p class="text-sm text-gray-500">管理您的所有联系人</p>
</div>
<div class="flex items-center gap-3">
<div class="relative">
<i
class="fa-solid fa-magnifying-glass absolute left-3 top-3 text-gray-400 text-xs"
></i>
<input
v-model="searchQuery"
type="text"
placeholder="搜索联系人..."
class="bg-white border border-gray-200 text-sm pl-9 pr-4 py-2 rounded-lg focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-all outline-none w-64"
/>
</div>
<button
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors flex items-center gap-2 text-sm font-medium"
>
<i class="fa-solid fa-plus"></i>
添加联系人
</button>
</div>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div
class="bg-white rounded-xl p-4 border border-gray-100 shadow-sm hover:shadow-md transition-shadow"
>
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">总联系人</p>
<p class="text-2xl font-bold text-slate-800">{{ contacts.length }}</p>
</div>
<div
class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center"
>
<i class="fa-solid fa-address-book text-indigo-600 text-xl"></i>
</div>
</div>
</div>
<div
class="bg-white rounded-xl p-4 border border-gray-100 shadow-sm hover:shadow-md transition-shadow"
>
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">最近联系</p>
<p class="text-2xl font-bold text-slate-800">
{{ recentContactsCount }}
</p>
</div>
<div
class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center"
>
<i class="fa-solid fa-clock text-green-600 text-xl"></i>
</div>
</div>
</div>
<div
class="bg-white rounded-xl p-4 border border-gray-100 shadow-sm hover:shadow-md transition-shadow"
>
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">未读消息</p>
<p class="text-2xl font-bold text-slate-800">{{ unreadCount }}</p>
</div>
<div
class="w-12 h-12 bg-red-100 rounded-lg flex items-center justify-center"
>
<i class="fa-solid fa-envelope text-red-600 text-xl"></i>
</div>
</div>
</div>
</div>
<!-- 联系人列表 -->
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
<div class="p-4 border-b border-gray-100 flex items-center gap-2">
<span
v-for="tag in tags"
:key="tag.id"
@click="selectedTag = tag.id"
:class="[
'px-3 py-1.5 text-xs font-medium rounded-lg cursor-pointer transition-colors',
selectedTag === tag.id
? 'bg-indigo-100 text-indigo-700'
: 'bg-gray-50 text-gray-600 hover:bg-gray-100',
]"
>
{{ tag.name }}
<span
v-if="tag.count"
class="ml-1 text-xs"
:class="selectedTag === tag.id ? 'text-indigo-500' : 'text-gray-400'"
>
({{ tag.count }})
</span>
</span>
</div>
<div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
v-for="contact in filteredContacts"
:key="contact.id"
class="p-4 border border-gray-100 rounded-xl flex items-center gap-4 hover:shadow-md hover:border-indigo-200 transition-all bg-white group cursor-pointer"
@click="openChat(contact)"
>
<div class="relative">
<img
:src="contact.avatar"
class="w-12 h-12 rounded-full object-cover ring-2 ring-white group-hover:ring-indigo-100 transition-all"
:alt="contact.name"
/>
<div
v-if="contact.isOnline"
class="absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 bg-green-500 rounded-full border-2 border-white"
></div>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<div class="font-semibold text-slate-800 truncate">
{{ contact.name }}
</div>
<span
v-if="contact.hasUnread"
class="w-2 h-2 bg-red-500 rounded-full flex-shrink-0"
></span>
</div>
<div class="text-xs text-gray-500 truncate">
{{ contact.lastContact || '暂无联系记录' }}
</div>
<div class="text-xs text-gray-400 mt-0.5">
{{ contact.phone || '未绑定手机号' }}
</div>
</div>
<button
@click.stop="startChat(contact)"
class="opacity-0 group-hover:opacity-100 text-indigo-600 hover:bg-indigo-50 p-2 rounded-lg transition-all"
title="发起聊天"
>
<i class="fa-regular fa-comment-dots"></i>
</button>
</div>
</div>
<!-- 空状态 -->
<div
v-if="filteredContacts.length === 0"
class="text-center py-12 text-gray-400"
>
<i class="fa-solid fa-address-book text-4xl mb-3 opacity-50"></i>
<p class="text-sm">暂无联系人</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const searchQuery = ref('')
const selectedTag = ref('all')
// 标签列表
const tags = ref([
{ id: 'all', name: '全部', count: 0 },
{ id: 'recent', name: '最近联系', count: 0 },
{ id: 'unread', name: '未读消息', count: 0 },
])
// 联系人数据
const contacts = ref([
{
id: 1,
name: '客户 1',
avatar: 'https://i.pravatar.cc/150?u=11',
phone: '138****8888',
lastContact: '最后联系3天前',
isOnline: true,
hasUnread: true,
},
{
id: 2,
name: '客户 2',
avatar: 'https://i.pravatar.cc/150?u=12',
phone: '139****9999',
lastContact: '最后联系5天前',
isOnline: false,
hasUnread: false,
},
{
id: 3,
name: '客户 3',
avatar: 'https://i.pravatar.cc/150?u=13',
phone: '137****7777',
lastContact: '最后联系1周前',
isOnline: true,
hasUnread: true,
},
{
id: 4,
name: '客户 4',
avatar: 'https://i.pravatar.cc/150?u=14',
phone: '136****6666',
lastContact: '最后联系2周前',
isOnline: false,
hasUnread: false,
},
{
id: 5,
name: '客户 5',
avatar: 'https://i.pravatar.cc/150?u=15',
phone: '135****5555',
lastContact: '最后联系1个月前',
isOnline: false,
hasUnread: false,
},
{
id: 6,
name: '客户 6',
avatar: 'https://i.pravatar.cc/150?u=16',
phone: '134****4444',
lastContact: '最后联系:今天',
isOnline: true,
hasUnread: false,
},
])
// 计算属性
const recentContactsCount = computed(() => {
return contacts.value.filter((c) => c.lastContact.includes('天')).length
})
const unreadCount = computed(() => {
return contacts.value.filter((c) => c.hasUnread).length
})
// 更新标签计数
tags.value[0].count = contacts.value.length
tags.value[1].count = recentContactsCount.value
tags.value[2].count = unreadCount.value
// 过滤联系人
const filteredContacts = computed(() => {
let result = contacts.value
// 按标签过滤
if (selectedTag.value === 'recent') {
result = result.filter((c) => c.lastContact.includes('天'))
} else if (selectedTag.value === 'unread') {
result = result.filter((c) => c.hasUnread)
}
// 按搜索关键词过滤
if (searchQuery.value.trim()) {
const query = searchQuery.value.toLowerCase()
result = result.filter(
(c) =>
c.name.toLowerCase().includes(query) ||
c.phone?.includes(query) ||
''
)
}
return result
})
// 打开聊天
const openChat = (contact) => {
// TODO: 跳转到聊天页面并打开该联系人
console.log('打开聊天:', contact)
}
// 发起聊天
const startChat = (contact) => {
// TODO: 发起与联系人的聊天
console.log('发起聊天:', contact)
}
</script>
<style scoped>
/* 自定义滚动条样式 */
.custom-scroll::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scroll::-webkit-scrollbar-track {
background: transparent;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #e2e8f0;
border-radius: 3px;
}
.custom-scroll::-webkit-scrollbar-thumb:hover {
background: #cbd5e1;
}
</style>

View File

@@ -0,0 +1,18 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
primary: '#4F46E5', // Indigo 600
primaryLight: '#EEF2FF',
accent: '#8B5CF6', // Violet 500
accentLight: '#F3E8FF',
},
fontFamily: {
sans: ['"Plus Jakarta Sans"', 'Inter', 'sans-serif'],
},
},
},
plugins: [],
}

View File

@@ -0,0 +1,38 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
open: true,
port: 5173,
host: '0.0.0.0',
},
build: {
outDir: 'dist',
assetsDir: 'assets',
chunkSizeWarningLimit: 2000,
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue'],
},
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
},
},
minify: 'esbuild',
sourcemap: false,
},
})

View File

@@ -0,0 +1,803 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>触客宝 Pro - AI聚合工作台</title>
<!-- Tailwind CSS (样式库) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- FontAwesome (图标库) -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<!-- Google Fonts -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
}
/* 隐藏滚动条但保留功能 */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* 自定义细滚动条 */
.custom-scroll::-webkit-scrollbar {
width: 4px;
}
.custom-scroll::-webkit-scrollbar-track {
background: transparent;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
.custom-scroll::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* 动画过渡类 */
.transition-width {
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 消息气泡样式 */
.msg-bubble {
max-width: 80%;
position: relative;
}
.msg-bubble::before {
content: '';
position: absolute;
width: 8px;
height: 8px;
}
</style>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: '#4F46E5', // 品牌色
brandHover: '#4338ca',
ai: '#8B5CF6', // AI功能色
},
},
},
}
</script>
</head>
<body
class="bg-gray-100 h-screen w-screen overflow-hidden flex text-slate-700"
>
<!-- 1. 左侧:账号与全局导航 (Account Sidebar) -->
<div
class="w-[70px] bg-slate-900 flex flex-col items-center py-6 flex-shrink-0 z-30 shadow-xl"
>
<!-- Brand Logo -->
<div
class="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center text-white text-lg shadow-lg mb-8 cursor-pointer hover:scale-105 transition"
>
<i class="fa-solid fa-bolt"></i>
</div>
<!-- Main Nav -->
<nav class="space-y-6 w-full flex flex-col items-center">
<div
class="nav-item active text-indigo-400 border-l-4 border-indigo-400 w-full flex justify-center cursor-pointer"
>
<i class="fa-solid fa-comments text-xl"></i>
</div>
<div
class="nav-item text-gray-500 hover:text-gray-300 w-full flex justify-center cursor-pointer transition"
>
<i class="fa-solid fa-address-book text-xl"></i>
</div>
<div
class="nav-item text-gray-500 hover:text-gray-300 w-full flex justify-center cursor-pointer transition"
>
<i class="fa-solid fa-chart-line text-xl"></i>
</div>
</nav>
<div class="w-8 border-b border-gray-700 my-6"></div>
<!-- WeChat Accounts -->
<div
class="flex-1 flex flex-col items-center gap-4 overflow-y-auto no-scrollbar w-full px-2"
>
<!-- Account 1 (Active) -->
<div
class="relative group cursor-pointer"
onclick="switchAccount(this)"
>
<img
src="https://i.pravatar.cc/150?u=wx1"
class="w-10 h-10 rounded-lg border-2 border-green-500 transition hover:opacity-90"
/>
<div
class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-slate-900"
></div>
<!-- Tooltip -->
<div
class="absolute left-14 top-1 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition whitespace-nowrap z-50 pointer-events-none"
>
商务号01 (在线)
</div>
</div>
<!-- Account 2 -->
<div
class="relative group cursor-pointer opacity-60 hover:opacity-100 transition"
onclick="switchAccount(this)"
>
<img
src="https://i.pravatar.cc/150?u=wx2"
class="w-10 h-10 rounded-lg border-2 border-transparent group-hover:border-gray-500"
/>
<div
class="absolute left-14 top-1 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition whitespace-nowrap z-50 pointer-events-none"
>
销售小王
</div>
</div>
<!-- Add New -->
<button
class="w-10 h-10 rounded-lg border border-dashed border-gray-600 text-gray-500 hover:text-white hover:border-gray-400 flex items-center justify-center transition"
>
<i class="fa-solid fa-plus"></i>
</button>
</div>
<div class="mb-4 text-gray-500 hover:text-white cursor-pointer">
<i class="fa-solid fa-gear text-xl"></i>
</div>
</div>
<!-- 2. 中间:聊天列表 (Chat List) -->
<div
class="w-80 bg-white border-r border-gray-200 flex flex-col flex-shrink-0 z-20"
>
<!-- Search & Filter -->
<div class="p-4 pb-2">
<h2 class="font-bold text-xl text-gray-800 mb-4 px-1">消息</h2>
<div class="relative group">
<i
class="fa-solid fa-magnifying-glass absolute left-3 top-3 text-gray-400 group-focus-within:text-brand transition"
></i>
<input
type="text"
placeholder="搜索联系人、群聊"
class="w-full bg-gray-100 border-none rounded-lg pl-10 pr-4 py-2.5 text-sm focus:ring-2 focus:ring-brand/50 transition"
/>
</div>
<!-- Quick Filters -->
<div
class="flex gap-2 mt-4 pb-2 border-b border-gray-100 overflow-x-auto no-scrollbar"
>
<button
class="px-3 py-1 bg-brand/10 text-brand text-xs font-semibold rounded-full whitespace-nowrap"
>
全部
</button>
<button
class="px-3 py-1 text-gray-500 hover:bg-gray-100 text-xs font-medium rounded-full whitespace-nowrap"
>
未读 (12)
</button>
<button
class="px-3 py-1 text-purple-600 bg-purple-50 hover:bg-purple-100 text-xs font-medium rounded-full whitespace-nowrap flex items-center gap-1"
>
<i class="fa-solid fa-robot"></i> AI托管
</button>
</div>
</div>
<!-- Chat Items List -->
<div class="flex-1 overflow-y-auto custom-scroll" id="chatListContainer">
<!-- JS will populate interactive items here, putting static ones for now -->
<!-- Item 1: AI Active -->
<div
class="chat-item p-3 mx-2 rounded-lg cursor-pointer bg-blue-50/50 border border-blue-100 transition relative"
onclick="selectChat(this, '大野(朋友圈业务)', true)"
>
<div class="flex gap-3">
<img
src="https://i.pravatar.cc/150?u=8"
class="w-11 h-11 rounded-lg object-cover"
/>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline mb-0.5">
<h3 class="font-semibold text-gray-900 truncate text-sm">
大野(朋友圈业务)
</h3>
<span class="text-[10px] text-gray-400">11:45</span>
</div>
<div class="flex items-center gap-1">
<span
class="text-[10px] px-1 rounded bg-purple-100 text-purple-600 font-bold border border-purple-200"
>AI</span
>
<p class="text-xs text-gray-500 truncate">
已帮您生成3条回复建议...
</p>
</div>
</div>
</div>
</div>
<!-- Item 2: Unread -->
<div
class="chat-item p-3 mx-2 rounded-lg cursor-pointer hover:bg-gray-50 transition border-b border-transparent hover:border-gray-100"
onclick="selectChat(this, '京东内购群', false)"
>
<div class="flex gap-3">
<div class="relative">
<img
src="https://i.pravatar.cc/150?u=group"
class="w-11 h-11 rounded-lg object-cover grayscale"
/>
<div
class="absolute -top-1 -right-1 bg-red-500 text-white text-[10px] w-4 h-4 flex items-center justify-center rounded-full border border-white"
>
5
</div>
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline mb-0.5">
<h3 class="font-medium text-gray-700 truncate text-sm">
京东内购群
</h3>
<span class="text-[10px] text-gray-400">10:20</span>
</div>
<p class="text-xs text-gray-400 truncate">
张三: 谁有新的优惠券链接?
</p>
</div>
</div>
</div>
<!-- Item 3 -->
<div
class="chat-item p-3 mx-2 rounded-lg cursor-pointer hover:bg-gray-50 transition"
onclick="selectChat(this, 'VIP客户-李总', false)"
>
<div class="flex gap-3">
<img
src="https://i.pravatar.cc/150?u=ceo"
class="w-11 h-11 rounded-lg object-cover"
/>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline mb-0.5">
<h3 class="font-medium text-gray-700 truncate text-sm">
VIP客户-李总
</h3>
<span class="text-[10px] text-gray-400">昨天</span>
</div>
<p class="text-xs text-gray-400 truncate">
好的,合同我让财务看一下。
</p>
</div>
</div>
</div>
</div>
</div>
<!-- 3. 右侧:主聊天区域 (Main Chat) -->
<div class="flex-1 flex flex-col bg-slate-50 min-w-0 relative">
<!-- Header (关键修改:功能按钮 + 算力展示) -->
<header
class="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-6 shadow-sm z-10"
>
<!-- Left: Chat Info -->
<div class="flex items-center gap-4">
<h2 id="chatTitle" class="font-bold text-lg text-gray-800">
大野(朋友圈业务)
</h2>
<span
class="px-2 py-0.5 bg-green-50 text-green-600 text-xs rounded border border-green-200 flex items-center gap-1"
>
<span
class="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse"
></span>
微信在线
</span>
</div>
<!-- Right: Functions & AI Credits -->
<div class="flex items-center gap-6">
<!-- Functional Buttons (Clean Style) -->
<div class="flex items-center gap-2 border-r border-gray-200 pr-6">
<button
class="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:text-brand hover:bg-indigo-50 rounded transition"
title="发朋友圈"
>
<i class="fa-brands fa-weixin"></i>
<span class="hidden xl:inline">发朋友圈</span>
</button>
<button
class="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:text-brand hover:bg-indigo-50 rounded transition"
title="同步消息"
>
<i class="fa-solid fa-arrows-rotate"></i>
<span class="hidden xl:inline">同步</span>
</button>
<button
class="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:text-brand hover:bg-indigo-50 rounded transition"
title="创建任务"
>
<i class="fa-regular fa-calendar-check"></i>
</button>
</div>
<!-- AI Compute Balance (算力值) - Highlighted -->
<div
class="group relative flex items-center gap-3 bg-gradient-to-r from-amber-50 to-orange-50 border border-orange-200 px-3 py-1.5 rounded-lg cursor-pointer hover:shadow-md transition"
>
<div class="flex flex-col items-end leading-none">
<span
class="text-[10px] text-orange-400 font-semibold uppercase tracking-wider"
>AI 算力</span
>
<span
class="text-sm font-bold text-gray-800 font-mono"
id="aiBalance"
>12,450</span
>
</div>
<div
class="w-8 h-8 bg-gradient-to-br from-orange-400 to-amber-500 rounded-full flex items-center justify-center text-white shadow-sm group-hover:scale-110 transition"
>
<i class="fa-solid fa-bolt"></i>
</div>
<!-- Dropdown for Recharge -->
<div
class="absolute top-full right-0 mt-2 w-48 bg-white rounded-lg shadow-xl border border-gray-100 p-3 hidden group-hover:block animate-fade-in z-50"
>
<div class="text-xs text-gray-500 mb-2">预计可用: 3天</div>
<button
class="w-full bg-orange-500 text-white text-xs font-bold py-2 rounded hover:bg-orange-600 transition"
>
立即充值
</button>
</div>
</div>
<!-- Toggle Sidebar Info -->
<button
onclick="toggleRightPanel()"
class="text-gray-400 hover:text-brand transition ml-2"
>
<i class="fa-solid fa-circle-info text-xl"></i>
</button>
</div>
</header>
<!-- Message Stream -->
<div
class="flex-1 overflow-y-auto p-6 space-y-6 custom-scroll"
id="messageContainer"
>
<div class="text-center text-xs text-gray-400 my-4">今天 10:23</div>
<!-- Customer Message -->
<div class="flex gap-4 items-end">
<img
src="https://i.pravatar.cc/150?u=8"
class="w-8 h-8 rounded-full shadow-sm mb-1"
/>
<div class="flex flex-col gap-1 max-w-[70%]">
<span class="text-xs text-gray-400 ml-1">大野</span>
<div
class="bg-white border border-gray-200 px-4 py-3 rounded-2xl rounded-bl-none shadow-sm text-sm text-gray-700 leading-relaxed"
>
老板,上次说的那个魔兽代练套餐,如果是老客户的话,还能再送一次幻化吗?
</div>
</div>
</div>
<!-- AI Copilot Suggestion (Intervention) -->
<div
class="flex justify-end pr-12 animate-fade-in-up"
id="aiSuggestionBox"
>
<div
class="bg-purple-50 border border-purple-200 rounded-xl p-4 max-w-md shadow-sm relative"
>
<div
class="absolute -top-3 -left-3 w-6 h-6 bg-purple-600 rounded-full flex items-center justify-center text-white text-xs border-2 border-white shadow-sm"
>
<i class="fa-solid fa-wand-magic-sparkles"></i>
</div>
<div class="flex justify-between items-center mb-2">
<span class="text-xs font-bold text-purple-700"
>AI 建议回复 (消耗 5 算力)</span
>
<div class="flex gap-2">
<button
onclick="useSuggestion()"
class="text-xs bg-white hover:bg-purple-600 hover:text-white text-purple-600 px-3 py-1 rounded border border-purple-200 transition font-medium"
>
一键发送
</button>
</div>
</div>
<p class="text-sm text-gray-600 italic">
"亲,没问题的!既然是老客户,这次直接帮您申请赠送幻化服务。现在下单的话,我这就去安排打手准备。"
</p>
</div>
</div>
<!-- My Message (Human) -->
<!-- Populated via JS -->
</div>
<!-- Input Area -->
<footer class="bg-white p-4 border-t border-gray-200 z-20">
<!-- Toolbar -->
<div class="flex justify-between items-center mb-3">
<div class="flex gap-4 text-gray-400">
<i
class="fa-regular fa-face-smile text-lg hover:text-brand cursor-pointer transition"
></i>
<i
class="fa-regular fa-image text-lg hover:text-brand cursor-pointer transition"
></i>
<i
class="fa-solid fa-folder-open text-lg hover:text-brand cursor-pointer transition"
title="话术库"
></i>
</div>
<!-- AI Switch -->
<div
class="flex items-center gap-2 bg-gray-100 rounded-full px-3 py-1 cursor-pointer"
onclick="toggleAI(this)"
>
<span class="text-xs font-medium text-gray-500 select-none"
>AI 辅助</span
>
<div
class="w-8 h-4 bg-brand rounded-full relative transition-colors"
id="aiToggleBg"
>
<div
class="w-3 h-3 bg-white rounded-full absolute top-0.5 right-0.5 shadow-sm transition-all"
id="aiToggleDot"
></div>
</div>
</div>
</div>
<!-- Textarea -->
<div
class="relative bg-gray-50 rounded-xl border border-gray-200 focus-within:border-brand focus-within:ring-1 focus-within:ring-brand transition"
>
<textarea
id="msgInput"
class="w-full bg-transparent h-24 p-3 text-sm resize-none focus:outline-none custom-scroll"
placeholder="输入消息... (Shift+Enter 换行)"
></textarea>
<button
onclick="sendMessage()"
class="absolute bottom-3 right-3 bg-brand hover:bg-brandHover text-white px-4 py-1.5 rounded-lg text-sm font-medium transition shadow-md flex items-center gap-2"
>
发送 <i class="fa-regular fa-paper-plane text-xs"></i>
</button>
</div>
</footer>
</div>
<!-- 4. 右侧CRM/详情面板 (Right Panel) -->
<div
id="rightPanel"
class="w-72 bg-white border-l border-gray-200 flex flex-col flex-shrink-0 transition-width overflow-hidden"
>
<!-- Tabs -->
<div class="flex border-b border-gray-200">
<button
onclick="switchTab('profile')"
id="tab-profile"
class="flex-1 py-3 text-sm font-medium text-brand border-b-2 border-brand bg-indigo-50/30 transition"
>
客户资料
</button>
<button
onclick="switchTab('scripts')"
id="tab-scripts"
class="flex-1 py-3 text-sm font-medium text-gray-500 hover:text-gray-700 transition"
>
话术/快捷
</button>
</div>
<!-- Content Area -->
<div class="flex-1 overflow-y-auto p-5 custom-scroll relative">
<!-- Tab: Profile -->
<div id="content-profile" class="space-y-6 fade-in">
<div class="text-center">
<div class="relative inline-block">
<img
src="https://i.pravatar.cc/150?u=8"
class="w-20 h-20 rounded-full border-4 border-white shadow-lg object-cover"
/>
<div
class="absolute bottom-0 right-0 bg-white p-1 rounded-full shadow-md cursor-pointer text-gray-500 hover:text-brand"
>
<i class="fa-solid fa-pen-to-square text-xs"></i>
</div>
</div>
<h3 class="mt-3 font-bold text-gray-800 text-lg">大野</h3>
<p class="text-xs text-gray-400">ID: ys666wow</p>
</div>
<!-- Tags -->
<div>
<h4
class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2"
>
用户标签
</h4>
<div class="flex flex-wrap gap-2">
<span
class="tag px-2 py-1 bg-red-50 text-red-600 text-xs rounded border border-red-100"
>高意向</span
>
<span
class="tag px-2 py-1 bg-blue-50 text-blue-600 text-xs rounded border border-blue-100"
>魔兽玩家</span
>
<button
class="px-2 py-1 border border-dashed border-gray-300 text-gray-400 text-xs rounded hover:border-brand hover:text-brand transition"
>
+
</button>
</div>
</div>
<!-- Info Grid -->
<div class="space-y-4">
<div class="group">
<label class="text-xs text-gray-400 block mb-1">备注名</label>
<input
type="text"
value="大野(朋友圈业务)"
class="w-full text-sm border-b border-gray-200 bg-transparent py-1 focus:border-brand focus:outline-none transition"
/>
</div>
<div class="group">
<label class="text-xs text-gray-400 block mb-1">手机号</label>
<div class="flex justify-between items-center">
<span class="text-sm text-gray-600 font-mono">138****8888</span>
<i
class="fa-regular fa-copy text-gray-300 hover:text-brand cursor-pointer"
></i>
</div>
</div>
<div class="group">
<label class="text-xs text-gray-400 block mb-1">跟进记录</label>
<div
class="bg-yellow-50 border border-yellow-100 p-2 rounded text-xs text-yellow-800 leading-snug"
>
上次提到需要发票,记得确认公司抬头。
</div>
</div>
</div>
</div>
<!-- Tab: Scripts (Hidden by default) -->
<div id="content-scripts" class="hidden space-y-4 fade-in">
<div class="relative">
<input
type="text"
placeholder="搜索话术..."
class="w-full bg-gray-100 text-xs p-2 rounded border-none focus:ring-1 focus:ring-brand"
/>
<i
class="fa-solid fa-magnifying-glass absolute right-2 top-2.5 text-gray-400 text-xs"
></i>
</div>
<div class="space-y-2">
<div
class="p-3 bg-gray-50 hover:bg-indigo-50 border border-gray-100 hover:border-indigo-100 rounded cursor-pointer group transition"
onclick="insertScript(this)"
>
<h5
class="text-xs font-bold text-gray-700 mb-1 group-hover:text-brand"
>
开场白 - 通用
</h5>
<p class="text-xs text-gray-500 line-clamp-2">
您好,很高兴为您服务!请问有什么可以帮您的吗?我们目前正在进行...
</p>
</div>
<div
class="p-3 bg-gray-50 hover:bg-indigo-50 border border-gray-100 hover:border-indigo-100 rounded cursor-pointer group transition"
onclick="insertScript(this)"
>
<h5
class="text-xs font-bold text-gray-700 mb-1 group-hover:text-brand"
>
价格异议处理
</h5>
<p class="text-xs text-gray-500 line-clamp-2">
亲,这个价格已经是我们的底价了,包含了一年的售后服务,性价比非常高...
</p>
</div>
</div>
</div>
</div>
</div>
<!-- JS Logic -->
<script>
// --- 1. 右侧栏折叠逻辑 ---
let isPanelOpen = true
function toggleRightPanel() {
const panel = document.getElementById('rightPanel')
if (isPanelOpen) {
panel.style.width = '0px'
panel.classList.add('opacity-0')
} else {
panel.style.width = '18rem' // 72 tailwind spacing
panel.classList.remove('opacity-0')
}
isPanelOpen = !isPanelOpen
}
// --- 2. 账号切换逻辑 ---
function switchAccount(el) {
// 简单的视觉切换,实际应用会加载数据
document.querySelectorAll('.border-green-500').forEach((img) => {
img.classList.remove('border-green-500')
img.classList.add('border-transparent')
img.parentElement.classList.add('opacity-60')
})
const img = el.querySelector('img')
img.classList.remove('border-transparent')
img.classList.add('border-green-500')
el.classList.remove('opacity-60')
}
// --- 3. 聊天列表选中逻辑 ---
function selectChat(el, title, isAI) {
// 清除旧选中状态
document.querySelectorAll('.chat-item').forEach((item) => {
item.classList.remove('bg-blue-50/50', 'border-blue-100')
item.classList.add('border-transparent')
})
// 添加新选中状态
el.classList.add('bg-blue-50/50', 'border-blue-100')
el.classList.remove('border-transparent')
// 更新顶部标题
document.getElementById('chatTitle').innerText = title
}
// --- 4. Tab 切换逻辑 ---
function switchTab(tabName) {
// Reset Tabs
document.getElementById('tab-profile').className =
'flex-1 py-3 text-sm font-medium text-gray-500 hover:text-gray-700 transition cursor-pointer'
document.getElementById('tab-scripts').className =
'flex-1 py-3 text-sm font-medium text-gray-500 hover:text-gray-700 transition cursor-pointer'
// Set Active Tab
document.getElementById(`tab-${tabName}`).className =
'flex-1 py-3 text-sm font-medium text-brand border-b-2 border-brand bg-indigo-50/30 transition cursor-pointer'
// Toggle Content
document.getElementById('content-profile').classList.add('hidden')
document.getElementById('content-scripts').classList.add('hidden')
document.getElementById(`content-${tabName}`).classList.remove('hidden')
}
// --- 5. AI 开关动画 ---
let aiEnabled = true
function toggleAI(btn) {
aiEnabled = !aiEnabled
const bg = document.getElementById('aiToggleBg')
const dot = document.getElementById('aiToggleDot')
if (aiEnabled) {
bg.classList.replace('bg-gray-300', 'bg-brand')
dot.classList.replace('right-5', 'right-0.5') // Move right
dot.style.transform = 'translateX(0)'
} else {
bg.classList.replace('bg-brand', 'bg-gray-300')
// Tailwind doesn't have right-auto easily, using transform for smoother toggle
dot.style.transform = 'translateX(-16px)'
}
}
// --- 6. 发送消息模拟 & 算力扣除 ---
function sendMessage() {
const input = document.getElementById('msgInput')
const text = input.value.trim()
if (!text) return
const container = document.getElementById('messageContainer')
// 构建HTML字符串
const html = `
<div class="flex gap-4 items-end justify-end animate-fade-in-up">
<div class="flex flex-col gap-1 items-end max-w-[70%]">
<div class="bg-brand text-white px-4 py-2.5 rounded-2xl rounded-br-none shadow-md text-sm leading-relaxed">
${text}
</div>
</div>
<img src="https://i.pravatar.cc/150?u=wx1" class="w-8 h-8 rounded-full shadow-sm mb-1">
</div>
`
container.insertAdjacentHTML('beforeend', html)
input.value = ''
container.scrollTop = container.scrollHeight
}
// --- 7. 使用AI建议 ---
function useSuggestion() {
const suggestion =
'亲,没问题的!既然是老客户,这次直接帮您申请赠送幻化服务。现在下单的话,我这就去安排打手准备。'
// 扣除算力动画
const balanceEl = document.getElementById('aiBalance')
let current = parseInt(balanceEl.innerText.replace(',', ''))
balanceEl.innerText = (current - 5).toLocaleString()
balanceEl.classList.add('text-red-500') // Flash red
setTimeout(() => balanceEl.classList.remove('text-red-500'), 500)
// 隐藏建议框
document.getElementById('aiSuggestionBox').style.display = 'none'
// 发送消息
const container = document.getElementById('messageContainer')
const html = `
<div class="flex gap-4 items-end justify-end animate-fade-in-up">
<div class="flex flex-col gap-1 items-end max-w-[70%]">
<div class="flex items-center gap-2 mb-1">
<span class="text-[10px] bg-purple-100 text-purple-600 px-1 rounded border border-purple-200 flex items-center gap-1">
<i class="fa-solid fa-bolt"></i> AI生成
</span>
</div>
<div class="bg-brand text-white px-4 py-3 rounded-2xl rounded-br-none shadow-md text-sm leading-relaxed">
${suggestion}
</div>
</div>
<img src="https://i.pravatar.cc/150?u=wx1" class="w-8 h-8 rounded-full shadow-sm mb-1">
</div>
`
container.insertAdjacentHTML('beforeend', html)
container.scrollTop = container.scrollHeight
}
// --- 8. 插入话术 ---
function insertScript(el) {
const text = el.querySelector('p').innerText
document.getElementById('msgInput').value = text
}
// Add custom animation styles
const style = document.createElement('style')
style.innerHTML = `
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in-up { animation: fadeInUp 0.3s ease-out forwards; }
.animate-fade-in { animation: fadeInUp 0.2s ease-out forwards; }
`
document.head.appendChild(style)
</script>
</body>
</html>