c !== 'page-container');
+
+ // If root doesn't have page-container, add it
+ if (!rootClass.includes('page-container')) {
+ const oldLine = `${indent}
"page-container ${rootClass}"`);
+ }
+
+ // Remove padding: 0 from the page-specific root class in scoped style
+ if (pageSpecificClass) {
+ // Match something like: .order-list { padding: 0; } or .order-list {\n padding: 0;\n}
+ const paddingZeroRegex = new RegExp(
+ `(\\.${pageSpecificClass}\\s*\\{[^}]*?)padding:\\s*0\\s*;?\\s*([^}]*\\})`,
+ 's'
+ );
+ if (paddingZeroRegex.test(content)) {
+ content = content.replace(paddingZeroRegex, (match, before, after) => {
+ let result = before + after;
+ // If the rule is now empty like .class { }, remove it entirely
+ result = result.replace(
+ new RegExp(`\\.${pageSpecificClass}\\s*\\{\\s*\\}`, 's'),
+ ''
+ );
+ return result;
+ });
+ modified = true;
+ console.log(`[REMOVE padding:0] ${path.relative(viewsDir, filePath)} : .${pageSpecificClass}`);
+ }
+ }
+
+ if (modified) {
+ fs.writeFileSync(filePath, content, 'utf8');
+ fixedFiles.push(filePath);
+ } else {
+ console.log(`[SKIP] ${path.relative(viewsDir, filePath)} : already has page-container`);
+ }
+}
+
+console.log(`\n=== Summary ===`);
+console.log(`Total files: ${files.length}`);
+console.log(`Fixed files: ${fixedFiles.length}`);
+fixedFiles.forEach(f => console.log(` - ${path.relative(viewsDir, f)}`));
diff --git a/fix-quote.cjs b/fix-quote.cjs
new file mode 100644
index 0000000..2ce4606
--- /dev/null
+++ b/fix-quote.cjs
@@ -0,0 +1,639 @@
+const fs = require('fs');
+const fixedContent = `
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 草稿
+ 已发送
+ 已接受
+ 已拒绝
+ 已过期
+
+
+ 全部方案
+ 方案A (标准型)
+ 方案B (升级型)
+
+
+
+
+
+
+
+
+
+ 报价单号
+ 客户信息
+ 产品数量
+ 方案类型
+ 报价金额
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+
+
+ {{ item.quoteNumber }}
+
+
+ {{ item.customerName }}
+ {{ item.customerPhone }}
+
+ {{ item.productCount }} 套
+
+
+ 方案{{ item.scheme }}
+
+
+ ¥{{ item.totalAmount.toFixed(2) }}
+
+
+ {{ getStatusText(item.status) }}
+
+
+ {{ item.createdAt }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
基本信息
+
+
+ 报价单号
+ {{ currentItem.quoteNumber }}
+
+
+ 客户名称
+ {{ currentItem.customerName }}
+
+
+ 联系电话
+ {{ currentItem.customerPhone }}
+
+
+ 邮箱
+ {{ currentItem.customerEmail || '-' }}
+
+
+ 产品数量
+ {{ currentItem.productCount }} 套
+
+
+ 方案类型
+ 方案{{ currentItem.scheme }}
+
+
+ 报价金额
+ ¥{{ currentItem.totalAmount.toFixed(2) }}
+
+
+ 状态
+ {{ getStatusText(currentItem.status) }}
+
+
+ 有效期至
+ {{ currentItem.validUntil || '-' }}
+
+
+ 创建时间
+ {{ currentItem.createdAt }}
+
+
+ 更新时间
+ {{ currentItem.updatedAt }}
+
+
+ 备注
+ {{ currentItem.remark || '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
收件人: {{ emailRecipient }}
+
主题: {{ emailSubject }}
+
附件: 报价表_{{ currentDate.value }}.pdf
+
邮件内容:
+
尊敬的客户,您好!
+
根据您的需求,我们已为您准备了详细的产品报价方案,请查看附件中的报价表PDF文件。如果您有任何疑问或需要进一步调整,请随时与我们联系。
+
感谢您对我们产品的关注!
+
此致 销售团队
+
+
+
+
+
+
+
+
+ 返回测量报价
+
+
+ 下一步:跟单看板
+
+
+
+
+
+
+
+`;
+fs.writeFileSync('./src/views/quote/QuoteGenerate.vue', fixedContent, 'utf8');
+console.log('✅ Successfully wrote QuoteGenerate.vue!');
diff --git a/fix-tags.cjs b/fix-tags.cjs
new file mode 100644
index 0000000..806b3bd
--- /dev/null
+++ b/fix-tags.cjs
@@ -0,0 +1,85 @@
+const fs = require('fs');
+
+// 修复破碎的 HTML 标签
+const files = [
+ 'src/views/order/OrderList.vue',
+ 'src/views/order/OrderFollow.vue',
+ 'src/views/order/OrderRecheck.vue',
+ 'src/views/order/OrderTracking.vue',
+ 'src/views/quote/QuoteGenerate.vue'
+];
+
+let fixed = 0;
+files.forEach(file => {
+ try {
+ let content = fs.readFileSync(file, 'utf8');
+ let modified = false;
+
+ // 修复破碎的 option 标签
+ const optionMatches = content.match(/>[^<]*\/option>/g);
+ if (optionMatches) {
+ optionMatches.forEach(match => {
+ const text = match.substring(1, match.length - 9); // 提取文本内容
+ const fixed = '>' + text + '';
+ content = content.split(match).join(fixed);
+ modified = true;
+ });
+ }
+
+ // 修复破碎的 th 标签
+ const thMatches = content.match(/>[^<]*\/th>/g);
+ if (thMatches) {
+ thMatches.forEach(match => {
+ const text = match.substring(1, match.length - 5);
+ const fixed = '>' + text + '';
+ content = content.split(match).join(fixed);
+ modified = true;
+ });
+ }
+
+ // 修复破碎的 td 标签
+ const tdMatches = content.match(/>[^<]*\/td>/g);
+ if (tdMatches) {
+ tdMatches.forEach(match => {
+ const text = match.substring(1, match.length - 5);
+ const fixed = '>' + text + '';
+ content = content.split(match).join(fixed);
+ modified = true;
+ });
+ }
+
+ // 修复破碎的 button 标签
+ const buttonMatches = content.match(/>[^<]*\/button>/g);
+ if (buttonMatches) {
+ buttonMatches.forEach(match => {
+ const text = match.substring(1, match.length - 10);
+ const fixed = '>' + text + '';
+ content = content.split(match).join(fixed);
+ modified = true;
+ });
+ }
+
+ // 修复破碎的 label 标签
+ const labelMatches = content.match(/>[^<]*\/label>/g);
+ if (labelMatches) {
+ labelMatches.forEach(match => {
+ const text = match.substring(1, match.length - 8);
+ const fixed = '>' + text + '';
+ content = content.split(match).join(fixed);
+ modified = true;
+ });
+ }
+
+ if (modified) {
+ fs.writeFileSync(file, content, 'utf8');
+ console.log('Fixed: ' + file);
+ fixed++;
+ } else {
+ console.log('No changes: ' + file);
+ }
+ } catch(e) {
+ console.log('Error fixing ' + file + ': ' + e.message);
+ }
+});
+
+console.log('\nTotal fixed: ' + fixed + ' files');
diff --git a/html-vue3/.gitignore b/html-vue3/.gitignore
new file mode 100644
index 0000000..62a2e53
--- /dev/null
+++ b/html-vue3/.gitignore
@@ -0,0 +1,7 @@
+node_modules
+dist
+.DS_Store
+*.local
+*.log
+.vscode
+.idea
diff --git a/html-vue3/README.md b/html-vue3/README.md
new file mode 100644
index 0000000..cd06beb
--- /dev/null
+++ b/html-vue3/README.md
@@ -0,0 +1,221 @@
+# 窗帘工厂管理系统 - Vue3版本
+
+## 项目简介
+
+这是一个基于 Vue 3 + Vite + Pinia 构建的窗帘工厂管理系统,提供完整的客户管理、订单管理、生产管理、安装管理、财务管理等功能模块。
+
+## 技术栈
+
+- **前端框架**: Vue 3.3+ (Composition API + script setup)
+- **构建工具**: Vite 4.4+
+- **状态管理**: Pinia 2.1+
+- **路由管理**: Vue Router 4.6+
+- **图表库**: Chart.js 4.4+
+- **UI设计**: 自定义CSS变量系统
+
+## 项目结构
+
+```
+html-vue3/
+├── public/ # 静态资源
+├── src/
+│ ├── assets/ # 资源文件
+│ │ └── css/
+│ │ └── global.css # 全局样式
+│ │
+│ ├── components/ # 组件目录
+│ │ ├── common/ # 通用组件
+│ │ │ └── PagePlaceholder.vue
+│ │ └── layout/ # 布局组件
+│ │ ├── MainLayout.vue # 主布局
+│ │ ├── Sidebar.vue # 侧边栏
+│ │ └── TopBar.vue # 顶部栏
+│ │
+│ ├── composables/ # 组合式函数
+│ │ ├── useToast.js # Toast提示
+│ │ ├── usePagination.js # 分页功能
+│ │ ├── useSearch.js # 搜索过滤
+│ │ └── useModal.js # 模态框管理
+│ │
+│ ├── stores/ # Pinia状态管理
+│ │ ├── userStore.js # 用户状态
+│ │ ├── customerStore.js # 客户状态
+│ │ └── orderStore.js # 订单状态
+│ │
+│ ├── views/ # 页面视图
+│ │ ├── customer/ # 客户管理
+│ │ ├── quote/ # 测量报价
+│ │ ├── order/ # 订单管理
+│ │ ├── production/ # 生产管理
+│ │ ├── install/ # 安装管理
+│ │ ├── aftersale/ # 售后管理
+│ │ ├── finance/ # 财务管理
+│ │ ├── purchase/ # 采购库存
+│ │ ├── product/ # 产品管理
+│ │ ├── staff/ # 人员管理
+│ │ ├── system/ # 系统设置
+│ │ └── analysis/ # 数据分析
+│ │
+│ ├── router/ # 路由配置
+│ │ └── index.js
+│ │
+│ ├── App.vue # 根组件
+│ └── main.js # 入口文件
+│
+├── index.html # HTML模板
+├── vite.config.js # Vite配置
+├── package.json # 项目配置
+└── start.bat # 启动脚本
+```
+
+## 功能模块
+
+### 1. 首页与总览
+- 经营仪表盘 - 实时KPI展示
+- 功能流程图 - 业务流程展示
+
+### 2. 客户管理
+- 客户管理 - 客户信息CRUD
+- 预约测量 - 预约管理
+- 测量管理 - 测量记录
+
+### 3. 测量报价
+- 测量报价 - 产品测量和报价
+- 报价表生成 - 生成专业报价单
+
+### 4. 订单管理
+- 订单管理 - 订单全流程管理
+- 订单跟踪 - 物流跟踪
+- 跟单页 - 跟单处理
+- 定单明细 - 订单详情
+- 成交定单 - 成交记录
+- 复尺确认 - 尺寸确认
+
+### 5. 生产管理
+- 生产任务板 - 任务分配和跟踪
+- 生产采购 - 物料采购
+- 生产采购明细 - 采购记录
+
+### 6. 安装管理
+- 新增安装 - 创建安装任务
+- 安装任务板 - 任务管理
+- 安装人员排班表 - 排班管理
+- 帘窗安装完成 - 完成记录
+
+### 7. 售后管理
+- 售后原因 - 问题记录分析
+- 维修安排 - 维修任务
+
+### 8. 财务管理
+- 收尾款 - 尾款管理
+- 发票管理 - 发票开具
+
+### 9. 采购库存
+- 采购订单 - 采购管理
+- 供应商管理 - 供应商信息
+- 库存管理 - 库存盘点
+
+### 10. 产品管理
+- 产品库管理 - 产品信息
+- 面料配件库 - 面料配件
+
+### 11. 人员管理
+- 测量师管理 - 测量师信息
+- 安装师傅管理 - 安装师傅信息
+
+### 12. 系统设置
+- 角色权限 - 权限配置
+- 系统用户 - 用户管理
+- 通知模板 - 消息模板
+- 工作时间 - 时间设置
+- 消息中心 - 消息管理
+
+### 13. 数据分析
+- 数据分析中心 - 综合分析
+- 报表分析 - 图表报表
+- 满意度调查 - 客户满意度
+- 渠道管理 - 渠道统计
+
+## 快速开始
+
+### 安装依赖
+```bash
+npm install
+```
+
+### 启动开发服务器
+```bash
+npm run dev
+```
+或双击 `start.bat` 文件
+
+### 构建生产版本
+```bash
+npm run build
+```
+
+### 预览生产版本
+```bash
+npm run preview
+```
+
+## 开发规范
+
+### 组件命名
+- 组件文件: PascalCase (如 `CustomerList.vue`)
+- 组合式函数: camelCase with use prefix (如 `useToast.js`)
+
+### 代码组织
+```vue
+
+```
+
+### 样式规范
+- 使用 scoped 样式
+- 使用 CSS 变量
+- 遵循 BEM 命名约定
+
+## 特性
+
+- ✅ Vue 3 Composition API
+- ✅ Pinia 状态管理
+- ✅ Vue Router 路由守卫
+- ✅ 响应式设计
+- ✅ 组件化开发
+- ✅ 组合式函数复用
+- ✅ 模块化路由配置
+- ✅ 全局样式系统
+
+## 浏览器支持
+
+- Chrome >= 87
+- Firefox >= 78
+- Safari >= 14
+- Edge >= 88
+
+## 许可证
+
+MIT License
+
+## 联系方式
+
+如有问题或建议,请联系开发团队。
diff --git a/html-vue3/index.html b/html-vue3/index.html
new file mode 100644
index 0000000..afa446b
--- /dev/null
+++ b/html-vue3/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
窗帘工厂管理系统
+
+
+
+
+
+
+
+
diff --git a/html-vue3/package-lock.json b/html-vue3/package-lock.json
new file mode 100644
index 0000000..4ab4d33
--- /dev/null
+++ b/html-vue3/package-lock.json
@@ -0,0 +1,834 @@
+{
+ "name": "curtain-factory-management",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "curtain-factory-management",
+ "version": "1.0.0",
+ "dependencies": {
+ "chart.js": "^4.4.0",
+ "pinia": "^2.1.7",
+ "vue": "^3.3.4",
+ "vue-router": "^4.6.4"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^4.2.3",
+ "vite": "^4.4.5"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
+ "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz",
+ "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==",
+ "dev": true,
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.0.0 || ^5.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.33.tgz",
+ "integrity": "sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==",
+ "dependencies": {
+ "@babel/parser": "^7.29.2",
+ "@vue/shared": "3.5.33",
+ "entities": "^7.0.1",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.33.tgz",
+ "integrity": "sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.33",
+ "@vue/shared": "3.5.33"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.33.tgz",
+ "integrity": "sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==",
+ "dependencies": {
+ "@babel/parser": "^7.29.2",
+ "@vue/compiler-core": "3.5.33",
+ "@vue/compiler-dom": "3.5.33",
+ "@vue/compiler-ssr": "3.5.33",
+ "@vue/shared": "3.5.33",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.10",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.33.tgz",
+ "integrity": "sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.33",
+ "@vue/shared": "3.5.33"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.33.tgz",
+ "integrity": "sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A==",
+ "dependencies": {
+ "@vue/shared": "3.5.33"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.33.tgz",
+ "integrity": "sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.33",
+ "@vue/shared": "3.5.33"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.33.tgz",
+ "integrity": "sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.33",
+ "@vue/runtime-core": "3.5.33",
+ "@vue/shared": "3.5.33",
+ "csstype": "^3.2.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.33.tgz",
+ "integrity": "sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw==",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.33",
+ "@vue/shared": "3.5.33"
+ },
+ "peerDependencies": {
+ "vue": "3.5.33"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.33.tgz",
+ "integrity": "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ=="
+ },
+ "node_modules/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
+ },
+ "node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "node_modules/pinia": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
+ "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.3",
+ "vue-demi": "^0.14.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "3.30.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.30.0.tgz",
+ "integrity": "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=14.18.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "4.5.14",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz",
+ "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.18.10",
+ "postcss": "^8.4.27",
+ "rollup": "^3.27.1"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.33.tgz",
+ "integrity": "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.33",
+ "@vue/compiler-sfc": "3.5.33",
+ "@vue/runtime-dom": "3.5.33",
+ "@vue/server-renderer": "3.5.33",
+ "@vue/shared": "3.5.33"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.6.4",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
+ "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ }
+ }
+}
diff --git a/html-vue3/package.json b/html-vue3/package.json
new file mode 100644
index 0000000..7dfd636
--- /dev/null
+++ b/html-vue3/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "curtain-factory-management",
+ "version": "1.0.0",
+ "description": "窗帘工厂管理系统 - Vue3版本",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "vue": "^3.3.4",
+ "vue-router": "^4.6.4",
+ "pinia": "^2.1.7",
+ "chart.js": "^4.4.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^4.2.3",
+ "vite": "^4.4.5"
+ }
+}
diff --git a/html-vue3/src/App.vue b/html-vue3/src/App.vue
new file mode 100644
index 0000000..e8aaf4d
--- /dev/null
+++ b/html-vue3/src/App.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/html-vue3/src/assets/css/global.css b/html-vue3/src/assets/css/global.css
new file mode 100644
index 0000000..56deb33
--- /dev/null
+++ b/html-vue3/src/assets/css/global.css
@@ -0,0 +1,685 @@
+/* global.css - 窗帘工厂管理系统统一样式 */
+:root {
+ /* 主色调 */
+ --primary: #2ea2cc;
+ --primary-dark: #1f7fa3;
+ --primary-light: #b0e0f0;
+ --primary-bg-light: #f0f9ff;
+
+ /* 中性色 */
+ --text-primary: #1e2b3c;
+ --text-secondary: #2d4059;
+ --text-muted: #64748b;
+ --border-color: #dee7f0;
+ --card-bg: #ffffff;
+ --shadow-color: rgba(0, 20, 40, 0.06);
+ --focus-ring: rgba(46, 162, 204, 0.1);
+
+ /* 背景色 */
+ --bg-primary: #f5f9ff;
+ --bg-secondary: #f8fafc;
+ --bg-hover: #f1f5f9;
+
+ /* 间距 */
+ --spacing-xs: 4px;
+ --spacing-sm: 8px;
+ --spacing-md: 16px;
+ --spacing-lg: 24px;
+ --spacing-xl: 32px;
+
+ /* 圆角 */
+ --radius-sm: 8px;
+ --radius-md: 12px;
+ --radius-lg: 16px;
+ --radius-xl: 24px;
+ --radius-card: 20px;
+ --radius-btn: 40px;
+ --radius-input: 12px;
+
+ /* 功能色 */
+ --success: #2d6e46;
+ --success-dark: #1f5936;
+ --danger: #e53e3c;
+ --danger-dark: #c53030;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: 'Segoe UI', Roboto, 'Microsoft YaHei', sans-serif;
+}
+
+body {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ line-height: 1.5;
+}
+
+/* 卡片容器 */
+.card {
+ background: var(--card-bg);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 8px 20px var(--shadow-color);
+ padding: var(--spacing-lg);
+ margin-bottom: var(--spacing-lg);
+ border: 1px solid var(--border-color);
+ transition: all 0.3s ease;
+}
+
+.card:hover {
+ box-shadow: 0 12px 28px rgba(0, 20, 40, 0.1);
+ transform: translateY(-2px);
+}
+
+/* 页面主标题 */
+.page-title {
+ font-size: 1.8rem;
+ font-weight: 700;
+ color: #0f2a44;
+ margin-bottom: var(--spacing-lg);
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-md);
+ border-left: 5px solid var(--primary);
+ padding: var(--spacing-md) var(--spacing-lg);
+ background: linear-gradient(90deg, var(--primary-bg-light) 0%, transparent 100%);
+ border-radius: 0 var(--radius-md) var(--radius-md) 0;
+}
+
+.page-title i {
+ color: var(--primary);
+ font-size: 1.6rem;
+}
+
+/* 标题区域容器 */
+.page-header {
+ margin-bottom: var(--spacing-xl);
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-wrap: wrap;
+ gap: var(--spacing-md);
+}
+
+.page-header-left {
+ flex: 1;
+}
+
+.page-header .page-title {
+ margin-bottom: 8px;
+}
+
+.page-header .text-secondary {
+ font-size: 1rem;
+ color: var(--text-secondary);
+ padding-left: 28px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.page-header-right {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+/* 表格容器 */
+.table-container {
+ overflow-x: auto;
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--border-color);
+ background: var(--card-bg);
+ box-shadow: 0 2px 8px var(--shadow-color);
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+th {
+ background: var(--primary-bg-light);
+ color: #1e3a5f;
+ font-weight: 600;
+ padding: 14px 12px;
+ text-align: left;
+ border-bottom: 2px solid #cbdbee;
+}
+
+td {
+ padding: 12px;
+ border-bottom: 1px solid #eef2f6;
+ color: #1f2c3f;
+}
+
+tr:last-child td {
+ border-bottom: none;
+}
+
+tr:hover td {
+ background: #f8fbfe;
+ transition: background 0.3s ease;
+}
+
+tr {
+ transition: all 0.3s ease;
+}
+
+/* 按钮 */
+.btn {
+ padding: 10px 22px;
+ border-radius: var(--radius-btn);
+ font-weight: 500;
+ font-size: 0.95rem;
+ border: none;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ background: var(--card-bg);
+ color: #1e3a5f;
+ border: 1px solid #cbdae4;
+ touch-action: manipulation;
+ -webkit-tap-highlight-color: transparent;
+ min-height: 44px;
+ position: relative;
+ overflow: hidden;
+}
+
+.btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
+ transition: left 0.5s ease;
+}
+
+.btn:hover::before {
+ left: 100%;
+}
+
+.btn:active {
+ transform: scale(0.98);
+}
+
+.btn-primary {
+ background: var(--primary);
+ color: white;
+ border: none;
+ box-shadow: 0 4px 10px var(--focus-ring);
+}
+
+.btn-primary:hover {
+ background: var(--primary-dark);
+ transform: translateY(-2px);
+}
+
+.btn-success {
+ background: var(--success);
+ color: white;
+}
+
+.btn-success:hover {
+ background: var(--success-dark);
+}
+
+.btn-danger {
+ background: var(--danger);
+ color: white;
+}
+
+.btn-danger:hover {
+ background: var(--danger-dark);
+}
+
+.btn-warning {
+ background: #f59e0b;
+ color: white;
+}
+
+.btn-warning:hover {
+ background: #d97706;
+}
+
+.btn-secondary {
+ background: #f1f5f9;
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+}
+
+.btn-secondary:hover {
+ background: #e2e8f0;
+}
+
+.btn-outline {
+ background: transparent;
+ border: 1px solid var(--primary);
+ color: var(--primary);
+}
+
+.btn-outline:hover {
+ background: var(--primary-bg-light);
+}
+
+.btn-link {
+ background: transparent;
+ border: none;
+ color: var(--primary);
+ text-decoration: none;
+ padding: 8px 12px;
+}
+
+.btn-link:hover {
+ text-decoration: underline;
+}
+
+/* 表单 */
+.form-group {
+ margin-bottom: 20px;
+ position: relative;
+}
+
+label {
+ display: block;
+ margin-bottom: 6px;
+ font-weight: 500;
+ color: var(--text-secondary);
+}
+
+label.required::after {
+ content: ' *';
+ color: var(--danger);
+}
+
+input, select, textarea {
+ width: 100%;
+ padding: 12px 16px;
+ border: 1px solid #d1d9e6;
+ border-radius: var(--radius-input);
+ font-size: 0.95rem;
+ transition: all 0.3s ease;
+ background: var(--card-bg);
+}
+
+input:focus, select:focus, textarea:focus {
+ outline: none;
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px var(--focus-ring);
+ transform: translateY(-1px);
+}
+
+.form-group.has-error input,
+.form-group.has-error select,
+.form-group.has-error textarea {
+ border-color: var(--danger);
+ box-shadow: 0 0 0 3px rgba(229, 62, 60, 0.1);
+}
+
+.form-error-message {
+ color: var(--danger);
+ font-size: 0.8rem;
+ margin-top: 4px;
+ display: none;
+}
+
+.form-group.has-error .form-error-message {
+ display: block;
+}
+
+/* 徽章 */
+.badge {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 40px;
+ font-size: 0.8rem;
+ font-weight: 500;
+}
+
+.badge-new { background: #e6fffa; color: #234e52; }
+.badge-measured { background: #f0fff4; color: #22543d; }
+.badge-quoted { background: #ebf8ff; color: #2c5282; }
+.badge-ordered { background: #faf5ff; color: #553c9a; }
+.badge-completed { background: #f0f4ff; color: #3c366b; }
+
+/* 工具条 */
+.toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 20px;
+ gap: 15px;
+}
+
+/* 模态框 */
+.modal {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.modal.active {
+ display: flex;
+}
+
+.modal-content {
+ background: white;
+ width: 90%;
+ max-width: 600px;
+ min-width: 400px;
+ max-height: 90vh;
+ border-radius: 28px;
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+
+.modal-header {
+ padding: 24px 32px;
+ border-bottom: 1px solid #edf2f7;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: linear-gradient(135deg, var(--primary-bg-light) 0%, white 100%);
+ border-radius: 28px 28px 0 0;
+}
+
+.modal-title {
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: #0f2a44;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.modal-title::before {
+ content: '';
+ width: 4px;
+ height: 24px;
+ background: var(--primary);
+ border-radius: 2px;
+}
+
+.modal-close {
+ background: none;
+ border: none;
+ font-size: 28px;
+ cursor: pointer;
+ color: #7b8a9b;
+ line-height: 1;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ transition: all 0.2s;
+}
+
+.modal-close:hover {
+ color: #1e2b3c;
+ background: rgba(0, 0, 0, 0.05);
+}
+
+.modal-body {
+ padding: 32px;
+ overflow-y: auto;
+ flex: 1;
+}
+
+.modal-footer {
+ padding: 20px 32px;
+ border-top: 1px solid #edf2f7;
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+ background: #fafbfc;
+ border-radius: 0 0 28px 28px;
+}
+
+.modal.modal-large .modal-content {
+ max-width: 800px;
+}
+
+/* Toast 通知 */
+.toast {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ background: #333;
+ color: #fff;
+ padding: 14px 28px;
+ border-radius: 40px;
+ font-size: 14px;
+ z-index: 2000;
+ opacity: 0;
+ transition: opacity 0.3s;
+ pointer-events: none;
+}
+
+.toast.show {
+ opacity: 1;
+}
+
+.toast.success { background: var(--success); }
+.toast.error { background: var(--danger); }
+.toast.info { background: var(--primary); }
+
+/* 面包屑导航 */
+.breadcrumb {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ margin-bottom: var(--spacing-lg);
+ font-size: 0.9rem;
+ color: var(--text-muted);
+}
+
+.breadcrumb-item {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.breadcrumb-item a {
+ color: var(--primary);
+ text-decoration: none;
+ transition: color 0.3s ease;
+}
+
+.breadcrumb-item a:hover {
+ color: var(--primary-dark);
+ text-decoration: underline;
+}
+
+.breadcrumb-separator {
+ color: var(--text-muted);
+}
+
+.breadcrumb-item.active {
+ color: var(--text-primary);
+ font-weight: 500;
+}
+
+/* 页面容器 */
+.page-container {
+ width: 100%;
+ max-width: 1600px;
+ margin: 0 auto;
+ padding: 24px;
+}
+
+/* 按钮组 */
+.btn-group {
+ display: flex;
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.btn-group .btn {
+ flex: 1;
+}
+
+/* 表单操作 */
+.form-actions {
+ margin: 24px 0;
+ display: flex;
+ gap: 16px;
+}
+
+/* 文本样式 */
+.text-left { text-align: left; }
+.text-center { text-align: center; }
+.text-secondary { color: var(--text-secondary); }
+.font-medium { font-weight: 500; }
+
+/* 产品类型标签样式 */
+.type-tag {
+ cursor: pointer;
+ transition: all 0.2s;
+ padding: 10px 16px;
+ border: 1px solid var(--border-color);
+ background: white;
+ border-radius: var(--radius-btn);
+ font-weight: 500;
+ color: var(--text-secondary);
+}
+
+.type-tag:hover {
+ background: var(--primary-bg-light);
+ border-color: var(--primary);
+ color: var(--primary);
+ transform: translateY(-1px);
+}
+
+.type-tag.active {
+ background: var(--primary);
+ border: 1px solid var(--primary) !important;
+ font-weight: 600;
+ color: white;
+ box-shadow: 0 2px 8px rgba(46, 162, 204, 0.3);
+}
+
+/* 图表标题 */
+.chart-title {
+ font-size: 1.2rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 20px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+/* 表格操作区 */
+.table-actions {
+ display: flex;
+ gap: 10px;
+}
+
+/* 页脚注释 */
+.footer-note {
+ text-align: center;
+ color: var(--text-secondary);
+ font-size: 0.9rem;
+ margin-top: 30px;
+ padding: 16px;
+}
+
+/* 加载动画 */
+.loading {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ border: 3px solid rgba(46, 162, 204, 0.3);
+ border-radius: 50%;
+ border-top-color: var(--primary);
+ animation: spin 1s ease-in-out infinite;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+/* 淡入动画 */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.fade-in {
+ animation: fadeIn 0.6s ease-out forwards;
+}
+
+/* 平滑滚动 */
+html {
+ scroll-behavior: smooth;
+}
+
+/* 响应式设计 */
+@media (max-width: 767px) {
+ .btn {
+ min-height: 48px;
+ padding: 12px 20px;
+ }
+
+ input, select, textarea {
+ min-height: 48px;
+ }
+
+ .page-container {
+ padding: 16px;
+ }
+
+ .page-title {
+ font-size: 1.4rem;
+ padding: 10px 12px;
+ }
+
+ .card {
+ padding: 16px;
+ }
+
+ .modal-content {
+ max-width: 95% !important;
+ min-width: 280px;
+ margin: 10px;
+ }
+}
+
+@media (max-width: 480px) {
+ .page-title {
+ font-size: 1.2rem;
+ padding: 8px 10px;
+ }
+
+ .card {
+ padding: 12px;
+ }
+
+ .btn {
+ padding: 10px 14px;
+ font-size: 0.85rem;
+ }
+}
diff --git a/html-vue3/src/components/common/PagePlaceholder.vue b/html-vue3/src/components/common/PagePlaceholder.vue
new file mode 100644
index 0000000..053f50e
--- /dev/null
+++ b/html-vue3/src/components/common/PagePlaceholder.vue
@@ -0,0 +1,74 @@
+
+
+
+
+ 首页
+
+ /
+ {{ title }}
+
+
+
+
+
+
+
+
{{ title }}
+
{{ description }}
+
+ 功能开发中,敬请期待...
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/components/layout/MainLayout.vue b/html-vue3/src/components/layout/MainLayout.vue
new file mode 100644
index 0000000..abfff3e
--- /dev/null
+++ b/html-vue3/src/components/layout/MainLayout.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
diff --git a/html-vue3/src/components/layout/Sidebar.vue b/html-vue3/src/components/layout/Sidebar.vue
new file mode 100644
index 0000000..446e963
--- /dev/null
+++ b/html-vue3/src/components/layout/Sidebar.vue
@@ -0,0 +1,297 @@
+
+
+
+
+
+
+
diff --git a/html-vue3/src/components/layout/TopBar.vue b/html-vue3/src/components/layout/TopBar.vue
new file mode 100644
index 0000000..26d8fa8
--- /dev/null
+++ b/html-vue3/src/components/layout/TopBar.vue
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+
+
+ 首页
+
+ /
+ {{ currentPageTitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ result.name }}
+
+
{{ result.group }}
+
+
+
+
+
+
+
+ 管理员
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/composables/useModal.js b/html-vue3/src/composables/useModal.js
new file mode 100644
index 0000000..25c6cb1
--- /dev/null
+++ b/html-vue3/src/composables/useModal.js
@@ -0,0 +1,28 @@
+import { ref } from 'vue'
+
+export function useModal() {
+ const isVisible = ref(false)
+ const modalData = ref(null)
+
+ const open = (data = null) => {
+ modalData.value = data
+ isVisible.value = true
+ }
+
+ const close = () => {
+ isVisible.value = false
+ modalData.value = null
+ }
+
+ const toggle = () => {
+ isVisible.value = !isVisible.value
+ }
+
+ return {
+ isVisible,
+ modalData,
+ open,
+ close,
+ toggle
+ }
+}
diff --git a/html-vue3/src/composables/usePagination.js b/html-vue3/src/composables/usePagination.js
new file mode 100644
index 0000000..30d26d2
--- /dev/null
+++ b/html-vue3/src/composables/usePagination.js
@@ -0,0 +1,47 @@
+import { ref, computed } from 'vue'
+
+export function usePagination(items, pageSize = 10) {
+ const currentPage = ref(1)
+ const itemsPerPage = ref(pageSize)
+
+ const totalPages = computed(() => Math.ceil(items.value.length / itemsPerPage.value))
+
+ const paginatedItems = computed(() => {
+ const start = (currentPage.value - 1) * itemsPerPage.value
+ const end = start + itemsPerPage.value
+ return items.value.slice(start, end)
+ })
+
+ const goToPage = (page) => {
+ if (page >= 1 && page <= totalPages.value) {
+ currentPage.value = page
+ }
+ }
+
+ const nextPage = () => {
+ if (currentPage.value < totalPages.value) {
+ currentPage.value++
+ }
+ }
+
+ const prevPage = () => {
+ if (currentPage.value > 1) {
+ currentPage.value--
+ }
+ }
+
+ const resetPage = () => {
+ currentPage.value = 1
+ }
+
+ return {
+ currentPage,
+ itemsPerPage,
+ totalPages,
+ paginatedItems,
+ goToPage,
+ nextPage,
+ prevPage,
+ resetPage
+ }
+}
diff --git a/html-vue3/src/composables/useSearch.js b/html-vue3/src/composables/useSearch.js
new file mode 100644
index 0000000..dfb382d
--- /dev/null
+++ b/html-vue3/src/composables/useSearch.js
@@ -0,0 +1,53 @@
+import { ref, computed } from 'vue'
+
+export function useSearch(items, searchFields = []) {
+ const searchQuery = ref('')
+ const statusFilter = ref('')
+ const sourceFilter = ref('')
+
+ const filteredItems = computed(() => {
+ let result = items.value
+
+ // 文本搜索
+ if (searchQuery.value.trim()) {
+ const query = searchQuery.value.toLowerCase()
+ result = result.filter(item => {
+ if (searchFields.length === 0) {
+ // 默认搜索所有字符串字段
+ return Object.values(item).some(val =>
+ String(val).toLowerCase().includes(query)
+ )
+ }
+ return searchFields.some(field =>
+ String(item[field]).toLowerCase().includes(query)
+ )
+ })
+ }
+
+ // 状态过滤
+ if (statusFilter.value) {
+ result = result.filter(item => item.status === statusFilter.value)
+ }
+
+ // 来源过滤
+ if (sourceFilter.value) {
+ result = result.filter(item => item.source === sourceFilter.value)
+ }
+
+ return result
+ })
+
+ const resetFilters = () => {
+ searchQuery.value = ''
+ statusFilter.value = ''
+ sourceFilter.value = ''
+ }
+
+ return {
+ searchQuery,
+ statusFilter,
+ sourceFilter,
+ filteredItems,
+ resetFilters
+ }
+}
diff --git a/html-vue3/src/composables/useToast.js b/html-vue3/src/composables/useToast.js
new file mode 100644
index 0000000..649388b
--- /dev/null
+++ b/html-vue3/src/composables/useToast.js
@@ -0,0 +1,31 @@
+import { ref, readonly } from 'vue'
+
+const toastMsg = ref('')
+const toastClass = ref('')
+let timer = null
+
+export function useToast() {
+ function showToast(msg, type = 'info') {
+ if (timer) clearTimeout(timer)
+ toastMsg.value = msg
+ toastClass.value = 'show ' + type
+ timer = setTimeout(() => {
+ toastClass.value = ''
+ }, 3000)
+ }
+
+ function toastSuccess(msg) { showToast(msg, 'success') }
+ function toastError(msg) { showToast(msg, 'error') }
+ function toastInfo(msg) { showToast(msg, 'info') }
+ function toastWarning(msg) { showToast(msg, 'warning') }
+
+ return {
+ toastMsg: readonly(toastMsg),
+ toastClass: readonly(toastClass),
+ showToast,
+ toastSuccess,
+ toastError,
+ toastInfo,
+ toastWarning
+ }
+}
diff --git a/html-vue3/src/main.js b/html-vue3/src/main.js
new file mode 100644
index 0000000..6643b4e
--- /dev/null
+++ b/html-vue3/src/main.js
@@ -0,0 +1,12 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import App from './App.vue'
+import router from './router'
+import './assets/css/global.css'
+
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(router)
+
+app.mount('#app')
diff --git a/html-vue3/src/router/index.js b/html-vue3/src/router/index.js
new file mode 100644
index 0000000..43f060b
--- /dev/null
+++ b/html-vue3/src/router/index.js
@@ -0,0 +1,320 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+
+// 路由配置
+const routes = [
+ // 登录页
+ {
+ path: '/login',
+ name: 'Login',
+ component: () => import('@/views/Login.vue'),
+ meta: { title: '登录', requiresAuth: false }
+ },
+
+ // 主框架
+ {
+ path: '/',
+ component: () => import('@/components/layout/MainLayout.vue'),
+ redirect: '/dashboard',
+ meta: { requiresAuth: true },
+ children: [
+ // 经营仪表盘
+ {
+ path: 'dashboard',
+ name: 'Dashboard',
+ component: () => import('@/views/Dashboard.vue'),
+ meta: { title: '经营仪表盘', icon: 'fa-chart-line' }
+ },
+
+ // 功能流程图
+ {
+ path: 'workflow',
+ name: 'Workflow',
+ component: () => import('@/views/Workflow.vue'),
+ meta: { title: '功能流程图', icon: 'fa-project-diagram' }
+ },
+
+ // ========== 客户管理 ==========
+ {
+ path: 'customer',
+ name: 'CustomerList',
+ component: () => import('@/views/customer/CustomerList.vue'),
+ meta: { title: '客户管理', icon: 'fa-users' }
+ },
+ {
+ path: 'customer/appointment',
+ name: 'Appointment',
+ component: () => import('@/views/customer/Appointment.vue'),
+ meta: { title: '预约测量', icon: 'fa-calendar-check' }
+ },
+ {
+ path: 'customer/measurement',
+ name: 'MeasurementManage',
+ component: () => import('@/views/customer/MeasurementManage.vue'),
+ meta: { title: '测量管理', icon: 'fa-ruler-combined' }
+ },
+
+ // ========== 测量报价 ==========
+ {
+ path: 'quote',
+ name: 'QuoteMeasure',
+ component: () => import('@/views/quote/QuoteMeasure.vue'),
+ meta: { title: '测量报价', icon: 'fa-calculator' }
+ },
+ {
+ path: 'quote/generate',
+ name: 'QuoteGenerate',
+ component: () => import('@/views/quote/QuoteGenerate.vue'),
+ meta: { title: '报价表生成', icon: 'fa-file-invoice' }
+ },
+
+ // ========== 订单管理 ==========
+ {
+ path: 'order',
+ name: 'OrderList',
+ component: () => import('@/views/order/OrderList.vue'),
+ meta: { title: '订单管理', icon: 'fa-shopping-cart' }
+ },
+ {
+ path: 'order/tracking',
+ name: 'OrderTracking',
+ component: () => import('@/views/order/OrderTracking.vue'),
+ meta: { title: '订单跟踪', icon: 'fa-truck' }
+ },
+ {
+ path: 'order/follow',
+ name: 'OrderFollow',
+ component: () => import('@/views/order/OrderFollow.vue'),
+ meta: { title: '跟单页', icon: 'fa-clipboard-list' }
+ },
+ {
+ path: 'order/detail',
+ name: 'OrderDetail',
+ component: () => import('@/views/order/OrderDetail.vue'),
+ meta: { title: '定单明细', icon: 'fa-list-alt' }
+ },
+ {
+ path: 'order/deal',
+ name: 'OrderDeal',
+ component: () => import('@/views/order/OrderDeal.vue'),
+ meta: { title: '成交定单', icon: 'fa-handshake' }
+ },
+ {
+ path: 'order/recheck',
+ name: 'OrderRecheck',
+ component: () => import('@/views/order/OrderRecheck.vue'),
+ meta: { title: '复尺确认', icon: 'fa-check-double' }
+ },
+
+ // ========== 生产管理 ==========
+ {
+ path: 'production/task',
+ name: 'ProductionTask',
+ component: () => import('@/views/production/ProductionTask.vue'),
+ meta: { title: '生产任务板', icon: 'fa-tasks' }
+ },
+ {
+ path: 'production/purchase',
+ name: 'ProductionPurchase',
+ component: () => import('@/views/production/ProductionPurchase.vue'),
+ meta: { title: '生产采购', icon: 'fa-shopping-basket' }
+ },
+ {
+ path: 'production/purchase-detail',
+ name: 'ProductionPurchaseDetail',
+ component: () => import('@/views/production/ProductionPurchaseDetail.vue'),
+ meta: { title: '生产采购明细', icon: 'fa-list' }
+ },
+
+ // ========== 安装管理 ==========
+ {
+ path: 'install/add',
+ name: 'InstallAdd',
+ component: () => import('@/views/install/InstallAdd.vue'),
+ meta: { title: '新增安装', icon: 'fa-plus-circle' }
+ },
+ {
+ path: 'install/task',
+ name: 'InstallTask',
+ component: () => import('@/views/install/InstallTask.vue'),
+ meta: { title: '安装任务板', icon: 'fa-clipboard-check' }
+ },
+ {
+ path: 'install/schedule',
+ name: 'InstallSchedule',
+ component: () => import('@/views/install/InstallSchedule.vue'),
+ meta: { title: '安装人员排班表', icon: 'fa-calendar-alt' }
+ },
+ {
+ path: 'install/complete',
+ name: 'InstallComplete',
+ component: () => import('@/views/install/InstallComplete.vue'),
+ meta: { title: '帘窗安装完成', icon: 'fa-check-circle' }
+ },
+
+ // ========== 售后管理 ==========
+ {
+ path: 'after-sale/reason',
+ name: 'AfterSaleReason',
+ component: () => import('@/views/aftersale/AfterSaleReason.vue'),
+ meta: { title: '售后原因', icon: 'fa-question-circle' }
+ },
+ {
+ path: 'after-sale/arrange',
+ name: 'AfterSaleArrange',
+ component: () => import('@/views/aftersale/AfterSaleArrange.vue'),
+ meta: { title: '维修安排', icon: 'fa-tools' }
+ },
+
+ // ========== 财务管理 ==========
+ {
+ path: 'finance/final-payment',
+ name: 'FinalPayment',
+ component: () => import('@/views/finance/FinalPayment.vue'),
+ meta: { title: '收尾款', icon: 'fa-money-bill-wave' }
+ },
+ {
+ path: 'finance/invoice',
+ name: 'Invoice',
+ component: () => import('@/views/finance/Invoice.vue'),
+ meta: { title: '发票管理', icon: 'fa-file-invoice-dollar' }
+ },
+
+ // ========== 采购库存 ==========
+ {
+ path: 'purchase/order',
+ name: 'PurchaseOrder',
+ component: () => import('@/views/purchase/PurchaseOrder.vue'),
+ meta: { title: '采购订单', icon: 'fa-cart-plus' }
+ },
+ {
+ path: 'purchase/supplier',
+ name: 'Supplier',
+ component: () => import('@/views/purchase/Supplier.vue'),
+ meta: { title: '供应商管理', icon: 'fa-truck-loading' }
+ },
+ {
+ path: 'purchase/inventory',
+ name: 'Inventory',
+ component: () => import('@/views/purchase/Inventory.vue'),
+ meta: { title: '库存管理', icon: 'fa-boxes' }
+ },
+
+ // ========== 产品管理 ==========
+ {
+ path: 'product/library',
+ name: 'ProductLibrary',
+ component: () => import('@/views/product/ProductLibrary.vue'),
+ meta: { title: '产品库管理', icon: 'fa-cube' }
+ },
+ {
+ path: 'product/material',
+ name: 'ProductMaterial',
+ component: () => import('@/views/product/ProductMaterial.vue'),
+ meta: { title: '面料配件库', icon: 'fa-layer-group' }
+ },
+
+ // ========== 人员管理 ==========
+ {
+ path: 'staff/measurer',
+ name: 'MeasurerManage',
+ component: () => import('@/views/staff/MeasurerManage.vue'),
+ meta: { title: '测量师管理', icon: 'fa-user-tie' }
+ },
+ {
+ path: 'staff/installer',
+ name: 'InstallerManage',
+ component: () => import('@/views/staff/InstallerManage.vue'),
+ meta: { title: '安装师傅管理', icon: 'fa-hard-hat' }
+ },
+
+ // ========== 系统设置 ==========
+ {
+ path: 'system/role',
+ name: 'RolePermission',
+ component: () => import('@/views/system/RolePermission.vue'),
+ meta: { title: '角色权限', icon: 'fa-user-shield' }
+ },
+ {
+ path: 'system/user',
+ name: 'SystemUser',
+ component: () => import('@/views/system/SystemUser.vue'),
+ meta: { title: '系统用户', icon: 'fa-users-cog' }
+ },
+ {
+ path: 'system/notification-template',
+ name: 'NotificationTemplate',
+ component: () => import('@/views/system/NotificationTemplate.vue'),
+ meta: { title: '通知模板', icon: 'fa-bell' }
+ },
+ {
+ path: 'system/work-time',
+ name: 'WorkTime',
+ component: () => import('@/views/system/WorkTime.vue'),
+ meta: { title: '工作时间', icon: 'fa-clock' }
+ },
+ {
+ path: 'system/message',
+ name: 'MessageCenter',
+ component: () => import('@/views/system/MessageCenter.vue'),
+ meta: { title: '消息中心', icon: 'fa-envelope' }
+ },
+
+ // ========== 数据分析 ==========
+ {
+ path: 'analysis/center',
+ name: 'AnalysisCenter',
+ component: () => import('@/views/analysis/AnalysisCenter.vue'),
+ meta: { title: '数据分析中心', icon: 'fa-chart-bar' }
+ },
+ {
+ path: 'analysis/report',
+ name: 'ReportAnalysis',
+ component: () => import('@/views/analysis/ReportAnalysis.vue'),
+ meta: { title: '报表分析', icon: 'fa-chart-pie' }
+ },
+ {
+ path: 'analysis/satisfaction',
+ name: 'SatisfactionSurvey',
+ component: () => import('@/views/analysis/SatisfactionSurvey.vue'),
+ meta: { title: '满意度调查', icon: 'fa-smile' }
+ },
+ {
+ path: 'analysis/channel',
+ name: 'ChannelManage',
+ component: () => import('@/views/analysis/ChannelManage.vue'),
+ meta: { title: '渠道管理', icon: 'fa-network-wired' }
+ }
+ ]
+ },
+
+ // 404
+ {
+ path: '/:pathMatch(.*)*',
+ name: 'NotFound',
+ component: () => import('@/views/NotFound.vue')
+ }
+]
+
+const router = createRouter({
+ history: createWebHashHistory(),
+ routes
+})
+
+// 路由守卫
+router.beforeEach((to, from, next) => {
+ // 设置页面标题
+ document.title = to.meta.title ? `${to.meta.title} · 窗帘工厂` : '窗帘工厂管理系统'
+
+ // 检查登录状态
+ const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'
+
+ if (to.meta.requiresAuth !== false && !isLoggedIn && to.path !== '/login') {
+ next('/login')
+ } else if (to.path === '/login' && isLoggedIn) {
+ next('/dashboard')
+ } else {
+ next()
+ }
+})
+
+export default router
diff --git a/html-vue3/src/stores/customerStore.js b/html-vue3/src/stores/customerStore.js
new file mode 100644
index 0000000..b159013
--- /dev/null
+++ b/html-vue3/src/stores/customerStore.js
@@ -0,0 +1,81 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+
+export const useCustomerStore = defineStore('customer', () => {
+ const customers = ref([
+ {
+ id: 'C001',
+ name: '张三',
+ phone: '13800001111',
+ address: '北京市朝阳区建国路88号',
+ product: '卷帘',
+ source: '线上咨询',
+ status: 'new',
+ createTime: '2026-05-01 10:30',
+ remark: '需要测量三个窗户'
+ },
+ {
+ id: 'C002',
+ name: '李四',
+ phone: '13900002222',
+ address: '上海市浦东新区陆家嘴金融中心',
+ product: '遮阳窗帘',
+ source: '电话咨询',
+ status: 'measured',
+ createTime: '2026-05-02 14:20',
+ remark: '已预约测量'
+ },
+ {
+ id: 'C003',
+ name: '王五',
+ phone: '13700003333',
+ address: '广州市天河区珠江新城',
+ product: '纱窗',
+ source: '老客户介绍',
+ status: 'quoted',
+ createTime: '2026-05-03 09:15',
+ remark: '等待客户确认报价'
+ }
+ ])
+
+ const totalCustomers = computed(() => customers.value.length)
+ const newCustomers = computed(() => customers.value.filter(c => c.status === 'new').length)
+
+ function addCustomer(customer) {
+ const newCustomer = {
+ ...customer,
+ id: 'C' + String(customers.value.length + 1).padStart(3, '0'),
+ createTime: new Date().toLocaleString('zh-CN')
+ }
+ customers.value.unshift(newCustomer)
+ return newCustomer
+ }
+
+ function updateCustomer(id, data) {
+ const index = customers.value.findIndex(c => c.id === id)
+ if (index !== -1) {
+ customers.value[index] = { ...customers.value[index], ...data }
+ }
+ }
+
+ function deleteCustomer(id) {
+ const index = customers.value.findIndex(c => c.id === id)
+ if (index !== -1) {
+ customers.value.splice(index, 1)
+ }
+ }
+
+ function getCustomerById(id) {
+ return customers.value.find(c => c.id === id)
+ }
+
+ return {
+ customers,
+ totalCustomers,
+ newCustomers,
+ addCustomer,
+ updateCustomer,
+ deleteCustomer,
+ getCustomerById
+ }
+})
diff --git a/html-vue3/src/stores/orderStore.js b/html-vue3/src/stores/orderStore.js
new file mode 100644
index 0000000..d3cc612
--- /dev/null
+++ b/html-vue3/src/stores/orderStore.js
@@ -0,0 +1,66 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+
+export const useOrderStore = defineStore('order', () => {
+ const orders = ref([
+ {
+ id: 'O20260501001',
+ customer: '张三',
+ amount: 3500,
+ status: 'pending',
+ createTime: '2026-05-01 14:30',
+ products: ['卷帘 x 2', '纱窗 x 1']
+ },
+ {
+ id: 'O20260502002',
+ customer: '李四',
+ amount: 8900,
+ status: 'production',
+ createTime: '2026-05-02 10:15',
+ products: ['遮阳窗帘 x 3']
+ },
+ {
+ id: 'O20260503003',
+ customer: '王五',
+ amount: 2200,
+ status: 'completed',
+ createTime: '2026-05-03 16:45',
+ products: ['蜂巢帘 x 1']
+ }
+ ])
+
+ const totalOrders = computed(() => orders.value.length)
+ const pendingOrders = computed(() => orders.value.filter(o => o.status === 'pending').length)
+ const totalAmount = computed(() => orders.value.reduce((sum, o) => sum + o.amount, 0))
+
+ function addOrder(order) {
+ const newOrder = {
+ ...order,
+ id: 'O' + new Date().getTime(),
+ createTime: new Date().toLocaleString('zh-CN')
+ }
+ orders.value.unshift(newOrder)
+ return newOrder
+ }
+
+ function updateOrderStatus(id, status) {
+ const order = orders.value.find(o => o.id === id)
+ if (order) {
+ order.status = status
+ }
+ }
+
+ function getOrderById(id) {
+ return orders.value.find(o => o.id === id)
+ }
+
+ return {
+ orders,
+ totalOrders,
+ pendingOrders,
+ totalAmount,
+ addOrder,
+ updateOrderStatus,
+ getOrderById
+ }
+})
diff --git a/html-vue3/src/stores/userStore.js b/html-vue3/src/stores/userStore.js
new file mode 100644
index 0000000..3cd3bb3
--- /dev/null
+++ b/html-vue3/src/stores/userStore.js
@@ -0,0 +1,43 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+
+export const useUserStore = defineStore('user', () => {
+ const user = ref(null)
+ const isLoggedIn = ref(false)
+
+ const userName = computed(() => user.value?.name || '未登录')
+ const userRole = computed(() => user.value?.role || 'guest')
+
+ function login(userData) {
+ user.value = userData
+ isLoggedIn.value = true
+ localStorage.setItem('isLoggedIn', 'true')
+ localStorage.setItem('user', JSON.stringify(userData))
+ }
+
+ function logout() {
+ user.value = null
+ isLoggedIn.value = false
+ localStorage.removeItem('isLoggedIn')
+ localStorage.removeItem('user')
+ }
+
+ function initUser() {
+ const savedUser = localStorage.getItem('user')
+ const savedLogin = localStorage.getItem('isLoggedIn')
+ if (savedLogin === 'true' && savedUser) {
+ user.value = JSON.parse(savedUser)
+ isLoggedIn.value = true
+ }
+ }
+
+ return {
+ user,
+ isLoggedIn,
+ userName,
+ userRole,
+ login,
+ logout,
+ initUser
+ }
+})
diff --git a/html-vue3/src/utils/dataService.js b/html-vue3/src/utils/dataService.js
new file mode 100644
index 0000000..ba8f7c3
--- /dev/null
+++ b/html-vue3/src/utils/dataService.js
@@ -0,0 +1,261 @@
+// 数据服务 - 模拟后端API
+const AppData = {
+ // 客户数据
+ getCustomers() {
+ return JSON.parse(localStorage.getItem('customers') || '[]') || this.getDefaultCustomers()
+ },
+
+ getDefaultCustomers() {
+ return [
+ { id: 'C001', name: '张三', phone: '13800001111', address: '北京市朝阳区建国路88号', product: '卷帘', source: '线上咨询', status: 'new', createTime: '2026-05-01 10:30', remark: '需要测量三个窗户', province: '北京市' },
+ { id: 'C002', name: '李四', phone: '13900002222', address: '上海市浦东新区陆家嘴金融中心', product: '遮阳窗帘', source: '电话咨询', status: 'measured', createTime: '2026-05-02 14:20', remark: '已预约测量', province: '上海市' },
+ { id: 'C003', name: '王五', phone: '13700003333', address: '广州市天河区珠江新城', product: '纱窗', source: '老客户介绍', status: 'quoted', createTime: '2026-05-03 09:15', remark: '等待客户确认报价', province: '广东省' },
+ { id: 'C004', name: '赵六', phone: '13600004444', address: '深圳市南山区科技园', product: '蜂巢帘', source: '线下门店', status: 'ordered', createTime: '2026-05-04 11:40', remark: '已下单生产', province: '广东省' },
+ { id: 'C005', name: '钱七', phone: '13500005555', address: '杭州市西湖区文三路', product: '梦幻帘', source: '小区推广', status: 'completed', createTime: '2026-05-05 16:25', remark: '安装完成', province: '浙江省' }
+ ]
+ },
+
+ saveCustomers(data) {
+ localStorage.setItem('customers', JSON.stringify(data))
+ },
+
+ getCustomerById(id) {
+ const customers = this.getCustomers()
+ return customers.find(c => c.id === id)
+ },
+
+ // 订单数据
+ getOrder() {
+ return JSON.parse(localStorage.getItem('orders') || '[]') || this.getDefaultOrders()
+ },
+
+ getDefaultOrders() {
+ return [
+ { id: 'O20260501001', customerId: 'C001', customer: '张三', products: [{ productName: '卷帘', model: 'JL-001', quantity: 2 }], amount: 3500, status: 'pending', rep: 'rep1', timeCycle: 7, createTime: '2026-05-01 14:30' },
+ { id: 'O20260502002', customerId: 'C002', customer: '李四', products: [{ productName: '遮阳窗帘', model: 'ZY-002', quantity: 3 }], amount: 8900, status: 'processing', rep: 'rep2', timeCycle: 10, createTime: '2026-05-02 10:15' },
+ { id: 'O20260503003', customerId: 'C003', customer: '王五', products: [{ productName: '蜂巢帘', model: 'FC-001', quantity: 1 }], amount: 2200, status: 'completed', rep: 'rep3', timeCycle: 5, createTime: '2026-05-03 16:45' },
+ { id: 'O20260504004', customerId: 'C004', customer: '赵六', products: [{ productName: '梦幻帘', model: 'MH-001', quantity: 2 }], amount: 15600, status: 'shipped', rep: 'rep4', timeCycle: 12, createTime: '2026-05-04 09:20' },
+ { id: 'O20260505005', customerId: 'C005', customer: '钱七', products: [{ productName: '安全门窗', model: 'AQ-001', quantity: 1 }], amount: 4500, status: 'pending', rep: 'rep1', timeCycle: 8, createTime: '2026-05-05 11:30' }
+ ]
+ },
+
+ saveOrders(data) {
+ localStorage.setItem('orders', JSON.stringify(data))
+ },
+
+ getOrderById(id) {
+ const orders = this.getOrder()
+ return orders.find(o => o.id === id)
+ },
+
+ // 生产任务数据
+ getProductionTask() {
+ return JSON.parse(localStorage.getItem('productionTasks') || '[]') || this.getDefaultProductionTasks()
+ },
+
+ getDefaultProductionTasks() {
+ return [
+ { id: 'PT001', orderId: 'O20260501001', taskName: '卷帘制作', assignee: '张师傅', planStart: '2026-05-06', planEnd: '2026-05-08', status: 'pending', progress: 0 },
+ { id: 'PT002', orderId: 'O20260502002', taskName: '遮阳窗帘制作', assignee: '李师傅', planStart: '2026-05-06', planEnd: '2026-05-10', status: 'processing', progress: 60 },
+ { id: 'PT003', orderId: 'O20260503003', taskName: '蜂巢帘制作', assignee: '王师傅', planStart: '2026-05-04', planEnd: '2026-05-06', status: 'completed', progress: 100 }
+ ]
+ },
+
+ saveProductionTasks(data) {
+ localStorage.setItem('productionTasks', JSON.stringify(data))
+ },
+
+ // 安装任务数据
+ getInstallation() {
+ return JSON.parse(localStorage.getItem('installations') || '[]') || this.getDefaultInstallations()
+ },
+
+ getDefaultInstallations() {
+ return [
+ { id: 'IN001', orderId: 'O20260501001', duration: '2天', installer: '张师傅', remarks: '需要梯子', status: 'scheduled' },
+ { id: 'IN002', orderId: 'O20260502002', duration: '3天', installer: '李师傅', remarks: '高层建筑', status: 'scheduled' },
+ { id: 'IN003', orderId: 'O20260503003', duration: '1天', installer: '王师傅', remarks: '', status: 'completed' }
+ ]
+ },
+
+ saveInstallations(data) {
+ localStorage.setItem('installations', JSON.stringify(data))
+ },
+
+ // 测量师数据
+ getSurveyors() {
+ return [
+ { id: 'S001', name: '张测量', phone: '13800001111', status: 'active' },
+ { id: 'S002', name: '李测量', phone: '13900002222', status: 'active' },
+ { id: 'S003', name: '王测量', phone: '13700003333', status: 'inactive' }
+ ]
+ },
+
+ // 安装师傅数据
+ getInstallers() {
+ return [
+ { id: 'I001', name: '张师傅', phone: '13600001111', status: 'active' },
+ { id: 'I002', name: '李师傅', phone: '13500002222', status: 'active' },
+ { id: 'I003', name: '王师傅', phone: '13400003333', status: 'active' }
+ ]
+ },
+
+ // 产品数据
+ getProducts() {
+ return [
+ { id: 'P001', name: '卷帘', category: '窗帘', price: 350, unit: '平方米' },
+ { id: 'P002', name: '遮阳窗帘', category: '窗帘', price: 580, unit: '平方米' },
+ { id: 'P003', name: '蜂巢帘', category: '窗帘', price: 420, unit: '平方米' },
+ { id: 'P004', name: '梦幻帘', category: '窗帘', price: 680, unit: '平方米' },
+ { id: 'P005', name: '纱窗', category: '窗纱', price: 280, unit: '平方米' }
+ ]
+ },
+
+ // 预约数据
+ getAppointments() {
+ return JSON.parse(localStorage.getItem('appointments') || '[]') || []
+ },
+
+ saveAppointments(data) {
+ localStorage.setItem('appointments', JSON.stringify(data))
+ },
+
+ // 测量任务数据
+ getMeasurementTasks() {
+ return JSON.parse(localStorage.getItem('measurementTasks') || '[]') || this.getDefaultMeasurementTasks()
+ },
+
+ getDefaultMeasurementTasks() {
+ return [
+ { id: 'M001', customerId: 'C001', customerName: '张三', address: '北京市朝阳区建国路88号', surveyor: '张测量', appointmentTime: '2026-05-06 10:00', status: 'pending', createTime: '2026-05-05 09:00' },
+ { id: 'M002', customerId: 'C002', customerName: '李四', address: '上海市浦东新区陆家嘴金融中心', surveyor: '李测量', appointmentTime: '2026-05-06 14:00', status: 'in-progress', createTime: '2026-05-05 10:00' },
+ { id: 'M003', customerId: 'C003', customerName: '王五', address: '广州市天河区珠江新城', surveyor: '张测量', appointmentTime: '2026-05-05 09:00', status: 'completed', createTime: '2026-05-04 15:00' }
+ ]
+ },
+
+ saveMeasurementTasks(data) {
+ localStorage.setItem('measurementTasks', JSON.stringify(data))
+ },
+
+ // 渠道数据
+ getChannels() {
+ return JSON.parse(localStorage.getItem('channels') || '[]') || this.getDefaultChannels()
+ },
+
+ getDefaultChannels() {
+ return [
+ { id: 1, name: '线上推广', cost: 15000, leads: 120, conversion: 25 },
+ { id: 2, name: '线下门店', cost: 8000, leads: 85, conversion: 32 },
+ { id: 3, name: '老客户介绍', cost: 2000, leads: 45, conversion: 45 },
+ { id: 4, name: '小区推广', cost: 5000, leads: 60, conversion: 18 },
+ { id: 5, name: '电话营销', cost: 3000, leads: 90, conversion: 12 }
+ ]
+ },
+
+ saveChannels(data) {
+ localStorage.setItem('channels', JSON.stringify(data))
+ },
+
+ getChannelById(id) {
+ const channels = this.getChannels()
+ return channels.find(c => c.id === id)
+ },
+
+ addChannel(channel) {
+ const channels = this.getChannels()
+ const maxId = channels.length > 0 ? Math.max(...channels.map(c => c.id)) : 0
+ const newChannel = { ...channel, id: maxId + 1 }
+ channels.push(newChannel)
+ this.saveChannels(channels)
+ return newChannel
+ },
+
+ updateChannel(id, data) {
+ const channels = this.getChannels()
+ const index = channels.findIndex(c => c.id === id)
+ if (index !== -1) {
+ channels[index] = { ...channels[index], ...data }
+ this.saveChannels(channels)
+ return channels[index]
+ }
+ return null
+ },
+
+ deleteChannel(id) {
+ const channels = this.getChannels()
+ const filtered = channels.filter(c => c.id !== id)
+ this.saveChannels(filtered)
+ return true
+ },
+
+ // 满意度调查数据
+ getSurveys() {
+ return JSON.parse(localStorage.getItem('surveys') || '[]') || this.getDefaultSurveys()
+ },
+
+ getDefaultSurveys() {
+ return [
+ { id: 1, customerName: '张伟', orderId: 'O20260501001', sendDate: '2026-05-01', rating: 5, feedback: '安装师傅很专业,窗帘效果很好!', status: '已反馈' },
+ { id: 2, customerName: '李娜', orderId: 'O20260502002', sendDate: '2026-05-02', rating: 4, feedback: '颜色稍微有点偏差,整体满意。', status: '已反馈' },
+ { id: 3, customerName: '王强', orderId: 'O20260503003', sendDate: '2026-05-03', rating: null, feedback: null, status: '未反馈' },
+ { id: 4, customerName: '赵敏', orderId: 'O20260504004', sendDate: '2026-05-04', rating: 5, feedback: '非常满意,已推荐朋友。', status: '已反馈' },
+ { id: 5, customerName: '陈磊', orderId: 'O20260505005', sendDate: '2026-05-05', rating: 3, feedback: '轨道安装有点松动,后来修好了。', status: '已反馈' }
+ ]
+ },
+
+ saveSurveys(data) {
+ localStorage.setItem('surveys', JSON.stringify(data))
+ },
+
+ // 售后数据
+ getAfterSales() {
+ return JSON.parse(localStorage.getItem('afterSales') || '[]') || this.getDefaultAfterSales()
+ },
+
+ getDefaultAfterSales() {
+ return [
+ { id: 'AS001', orderId: 'O20260501001', customer: '张三', issue: '安装尺寸问题', status: 'done', person: '张师傅', createTime: '2026-05-02' },
+ { id: 'AS002', orderId: 'O20260502002', customer: '李四', issue: '面料色差', status: 'processing', person: '李师傅', createTime: '2026-05-03' },
+ { id: 'AS003', orderId: 'O20260503003', customer: '王五', issue: '轨道松动', status: 'pending', person: '王师傅', createTime: '2026-05-04' }
+ ]
+ },
+
+ saveAfterSales(data) {
+ localStorage.setItem('afterSales', JSON.stringify(data))
+ },
+
+ // 报价数据
+ getQuotes() {
+ return JSON.parse(localStorage.getItem('quotes') || '[]') || this.getDefaultQuotes()
+ },
+
+ getDefaultQuotes() {
+ return [
+ { id: 'Q001', customerId: 'C001', customerName: '张三', amount: 3500, status: 'pending', createTime: '2026-05-01' },
+ { id: 'Q002', customerId: 'C002', customerName: '李四', amount: 8900, status: 'accepted', createTime: '2026-05-02' },
+ { id: 'Q003', customerId: 'C003', customerName: '王五', amount: 2200, status: 'rejected', createTime: '2026-05-03' }
+ ]
+ },
+
+ saveQuotes(data) {
+ localStorage.setItem('quotes', JSON.stringify(data))
+ },
+
+ // 采购订单数据
+ getPurchaseOrders() {
+ return JSON.parse(localStorage.getItem('purchaseOrders') || '[]') || this.getDefaultPurchaseOrders()
+ },
+
+ getDefaultPurchaseOrders() {
+ return [
+ { id: 'PO001', material: '窗帘布料', quantity: 100, price: 50, supplier: '供应商A', status: 'completed' },
+ { id: 'PO002', material: '轨道配件', quantity: 200, price: 15, supplier: '供应商B', status: 'pending' }
+ ]
+ },
+
+ savePurchaseOrders(data) {
+ localStorage.setItem('purchaseOrders', JSON.stringify(data))
+ }
+}
+
+export default AppData
diff --git a/html-vue3/src/views/Dashboard.vue b/html-vue3/src/views/Dashboard.vue
new file mode 100644
index 0000000..ba35ead
--- /dev/null
+++ b/html-vue3/src/views/Dashboard.vue
@@ -0,0 +1,908 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+ 首页
+
+ /
+ 经营仪表盘
+
+
+
+
+
+
+
+
+
+
{{ kpi.title }}
+
+ {{ kpi.value }}
+ {{ kpi.unit }}
+
+
+
+ {{ Math.abs(kpi.trend) }}% 较上期
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 订单号
+ 客户
+ 金额(¥)
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+ {{ order.id }}
+ {{ order.customer }}
+ {{ order.amount.toLocaleString() }}
+
+
+ {{ getStatusText(order.status) }}
+
+
+ {{ order.createTime }}
+
+ 查看
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ todo.title }}
+
+ {{ todo.type }}
+ {{ todo.time }}
+
+
+
紧急
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建订单
+
+
+
+
+
+ 添加客户
+
+
+
+
+
+ 产品管理
+
+
+
+
+
+ 报表中心
+
+
+
+
+
+ 库存管理
+
+
+
+
+
+ 系统设置
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/Login.vue b/html-vue3/src/views/Login.vue
new file mode 100644
index 0000000..992e675
--- /dev/null
+++ b/html-vue3/src/views/Login.vue
@@ -0,0 +1,870 @@
+
+
+
+
+
+
+
+
+
+ 用户名
+
+
+
+ {{ errors.username }}
+
+
+
+
+
+
+
+
+
+
+
+ 登录
+
+
+ 登录中...
+
+
+
+
+
+
+
+ 演示账号
+
+
+
+
+ 管理员
+
+ admin / admin123
+
+
+
+ 普通用户
+
+ user / user123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/NotFound.vue b/html-vue3/src/views/NotFound.vue
new file mode 100644
index 0000000..75231b4
--- /dev/null
+++ b/html-vue3/src/views/NotFound.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
404
+
页面未找到
+
+ 返回首页
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/Workflow.vue b/html-vue3/src/views/Workflow.vue
new file mode 100644
index 0000000..51e6ad3
--- /dev/null
+++ b/html-vue3/src/views/Workflow.vue
@@ -0,0 +1,645 @@
+
+
+
+
+
+
+
+
+
特别说明:
+
1. "客户满意度调查表"在安装完成一周后自动发送给客户
+
2. "复尺"和"售后维修"为条件分支,根据实际需要执行
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
安装后流程
+
+
+
+ 安装完成一周后自动发送给客户
+
+
+
+
+
+
+
+
+
+
+ 播放流程动画
+
+
+ 重置动画
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/aftersale/AfterSaleArrange.vue b/html-vue3/src/views/aftersale/AfterSaleArrange.vue
new file mode 100644
index 0000000..e20d9f6
--- /dev/null
+++ b/html-vue3/src/views/aftersale/AfterSaleArrange.vue
@@ -0,0 +1,1292 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ stats.total }}
+
总维修任务
+
+
+
+
+
+
+
+
{{ stats.pending }}
+
待处理
+
+
+
+
+
+
+
+
{{ stats.inProgress }}
+
进行中
+
+
+
+
+
+
+
+
{{ stats.completed }}
+
已完成
+
+
+
+
+
+
+
+
+
+
+
+
+ 任务编号
+ 客户姓名
+ 联系电话
+ 安装地址
+ 问题描述
+ 维修师傅
+ 预约时间
+ 状态
+ 操作
+
+
+
+
+ {{ task.id }}
+ {{ task.customerName }}
+ {{ task.phone }}
+ {{ task.address }}
+ {{ task.issue }}
+
+
+ {{ task.installer }}
+
+
+ {{ task.appointmentTime }}
+
+
+ {{ getStatusText(task.status) }}
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无维修任务
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 安装地址
+
+
+
+ 问题描述
+
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/aftersale/AfterSaleReason.vue b/html-vue3/src/views/aftersale/AfterSaleReason.vue
new file mode 100644
index 0000000..713b2f7
--- /dev/null
+++ b/html-vue3/src/views/aftersale/AfterSaleReason.vue
@@ -0,0 +1,1285 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ stats.weekCount }}
+
本周售后
+
+
+
+
+
+
+
+
{{ stats.staffCount }}
+
责任人(在岗)
+
+
+
+
+
+
+
+
{{ stats.pendingCount }}
+
待定责
+
+
+
+
+
+
+
+
{{ stats.closeRate }}%
+
闭环率
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 返回收尾款
+
+
+ 下一步:数据分析中心
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/analysis/AnalysisCenter.vue b/html-vue3/src/views/analysis/AnalysisCenter.vue
new file mode 100644
index 0000000..82a47fa
--- /dev/null
+++ b/html-vue3/src/views/analysis/AnalysisCenter.vue
@@ -0,0 +1,997 @@
+
+
+
+
+
+
+
+
+
+
+
+
{{ kpi.title }}
+
+ {{ kpi.value }}
+ {{ kpi.unit }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 业务员
+ 营业额(元)
+ 测量数
+ 成交单
+ 转化率
+ 错误率
+ 金额区间订单分布
+
+
+
+
+
+
+
+ {{ staff.name }}
+
+ {{ staff.revenue.toLocaleString() }}
+ {{ staff.measurements }}
+ {{ staff.orders }}
+
+ {{ staff.conversionRate }}%
+
+
+
+ {{ staff.errorRate }}%
+
+
+ {{ staff.amountRange.lt1k }}
+ {{ staff.amountRange.range1k5k }}
+ {{ staff.amountRange.gt5k }}
+
+
+ 暂无业务员数据
+
+
+
+
+
+ 金额区间分布:订单按最终成交金额划分,反映各业务员接单能力。
+
+
+
+
+
+
+
+
+
+
+
+
+ 业务员
+ 布艺窗帘
+ 卷帘
+ 罗马帘
+ 百叶窗
+
+
+
+
+ {{ item.name }}
+ {{ item.curtain }}%
+ {{ item.roller }}%
+ {{ item.roman }}%
+ {{ item.blind }}%
+
+
+
+
+
+ {{ topPerformer }} 在布艺窗帘上转化率突出,可分享经验。
+
+
+
+
+
+
+
+
+
+
+ 渠道
+ 测量数
+ 成交数
+ 渠道成交额
+
+
+
+
+ {{ channel.name }}
+ {{ channel.measurements }}
+ {{ channel.deals }}
+ ¥{{ channel.revenue.toLocaleString() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 产品
+ 销量(件)
+ 营业额(元)
+
+
+
+
+ {{ product.name }}
+ {{ product.quantity }}
+ ¥{{ product.revenue.toLocaleString() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 市场活动线索: {{ marketStats.leads }}
+ 成交额: {{ marketStats.revenue }}万
+ ROI (市场): {{ marketStats.roi }}
+ 新客占比: {{ marketStats.newCustomerRate }}%
+
+
+
+
+
+ 本月利润: {{ companyStats.profit }}万
+ 利润率: {{ companyStats.profitRate }}%
+ 完成目标 {{ companyStats.targetRate }}%
+ 工厂产能: {{ companyStats.capacity }}%
+
+
+
+
+
+
+
+
+
+
+
{{ stat.label }}
+
{{ stat.value }}
+
+
+
+
+ 主要售后问题:{{ topAfterSalesIssues }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/analysis/ChannelManage.vue b/html-vue3/src/views/analysis/ChannelManage.vue
new file mode 100644
index 0000000..6322170
--- /dev/null
+++ b/html-vue3/src/views/analysis/ChannelManage.vue
@@ -0,0 +1,938 @@
+
+
+
+
+
+
+
+
+
+
+
{{ stats.totalChannels }}
+
渠道总数
+
+
+
+
+
+
{{ stats.totalLeads }}
+
总线索数
+
+
+
+
+
+
{{ stats.avgConversion }}%
+
平均转化率
+
+
+
+
+
+
¥{{ stats.totalCost.toLocaleString() }}
+
总投入成本
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 渠道名称
+ 成本(¥)
+ 线索数
+ 转化率
+ 预计成交数
+ ROI
+ 操作
+
+
+
+
+ {{ channel.id }}
+
+
+
+ {{ channel.name }}
+
+
+ ¥{{ channel.cost.toLocaleString() }}
+ {{ channel.leads }}
+
+
+ {{ channel.conversion }}%
+
+
+ {{ Math.floor(channel.leads * channel.conversion / 100) }}
+
+
+ {{ calculateRoi(channel) }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无渠道数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/analysis/ReportAnalysis.vue b/html-vue3/src/views/analysis/ReportAnalysis.vue
new file mode 100644
index 0000000..f453854
--- /dev/null
+++ b/html-vue3/src/views/analysis/ReportAnalysis.vue
@@ -0,0 +1,886 @@
+
+
+
+
+
+
+
+
+
+
+
+ 业务员绩效
+
+
+
+
+
+ 业务员
+ 成交订单数
+ 总销售额(¥)
+ 平均客单价(¥)
+ 售后数
+
+
+
+
+
+
+ {{ staff.name }}
+
+ {{ staff.orders }}
+ ¥{{ staff.revenue.toLocaleString() }}
+ ¥{{ staff.avgOrderValue }}
+
+
+ {{ staff.afterSales }}
+
+
+
+
+ 暂无业务员数据
+
+
+
+
+
+
+
+
+
+
+
+ 售后概况
+
+
+
+
待处理
+
{{ afterSalesStats.pending }}
+
+
+
处理中
+
{{ afterSalesStats.processing }}
+
+
+
已完成
+
{{ afterSalesStats.completed }}
+
+
+
+ 主要问题:
+ {{ afterSalesStats.topIssue }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 财务报表
+
+
+
+
+
+ 财务指标
+
+
+
+
总收入
+
¥{{ financeStats.revenue.toLocaleString() }}
+
+
+
总支出
+
¥{{ financeStats.expense.toLocaleString() }}
+
+
+
利润
+
¥{{ financeStats.profit.toLocaleString() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/analysis/SatisfactionSurvey.vue b/html-vue3/src/views/analysis/SatisfactionSurvey.vue
new file mode 100644
index 0000000..a21298e
--- /dev/null
+++ b/html-vue3/src/views/analysis/SatisfactionSurvey.vue
@@ -0,0 +1,931 @@
+
+
+
+
+
+
+
+
+
+
+
{{ stats.total }}
+
总调查数
+
+
+
+
+
+
{{ stats.responded }}
+
已反馈
+
+
+
+
+
+
{{ stats.pending }}
+
待反馈
+
+
+
+
+
+
{{ stats.avgRating }}
+
平均评分
+
+
+
+
+
+
+
+ 全部状态
+ 已反馈
+ 未反馈
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+ 暂无评分
+
+
+ {{ survey.feedback }}
+
+
+ 客户尚未反馈
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择客户
+
+ 请选择客户
+
+ {{ c.name }} - {{ c.phone }}
+
+
+
+
+ 关联订单
+
+ 请选择订单
+
+ {{ o.id }} - {{ o.customer }} (¥{{ o.amount }})
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 客户姓名:
+ {{ currentSurvey.customerName }}
+
+
+ 订单编号:
+ {{ currentSurvey.orderId }}
+
+
+ 发送时间:
+ {{ currentSurvey.sendDate }}
+
+
+ 状态:
+
+ {{ currentSurvey.status }}
+
+
+
+
+
评分
+
+
+ {{ currentSurvey.rating }} 分
+
+
+
+
客户反馈
+
{{ currentSurvey.feedback }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/customer/Appointment.vue b/html-vue3/src/views/customer/Appointment.vue
new file mode 100644
index 0000000..1f0f135
--- /dev/null
+++ b/html-vue3/src/views/customer/Appointment.vue
@@ -0,0 +1,1699 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
📅
+
+
{{ stats.total }}
+
总预约
+
+
+
+
📌
+
+
{{ stats.today }}
+
今日
+
+
+
+
⏰
+
+
{{ stats.pending }}
+
待测量
+
+
+
+
✅
+
+
{{ stats.completed }}
+
已完成
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 预约编号
+ 客户姓名
+ 联系电话
+ 地址
+ 测量师
+ 预约时间
+ 业务类型
+ 状态
+ 操作
+
+
+
+
+ {{ appointment.id }}
+ {{ appointment.customerName }}
+ {{ appointment.customerPhone }}
+ {{ appointment.address }}
+ {{ getSurveyorName(appointment.personId) }}
+ {{ appointment.date }} {{ appointment.timeSlot }}
+
+
+ {{ getBusinessTypeText(appointment.businessType) }}
+
+
+
+
+ {{ getStatusText(appointment.status) }}
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ surveyor.name }}
+
+
+
+ 住宅
+ 办公
+ 酒店
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ slot.display }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 备注
+
+
+
+
+
+
+ 提醒设置
+
+
+ 内部提醒
+
+ 提前15分钟
+ 提前30分钟
+ 提前1小时
+ 提前2小时
+
+
+
+ 客户短信提醒
+
+ 提前1天
+ 提前12小时
+ 提前6小时
+ 提前2小时
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确定要删除预约 {{ deleteTarget?.customerName }} 吗?
+
+
+ 此操作不可恢复
+
+
+
+
+
+
+
+
+
+ 下一步:测量报价
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/customer/CustomerList.vue b/html-vue3/src/views/customer/CustomerList.vue
new file mode 100644
index 0000000..5e036ee
--- /dev/null
+++ b/html-vue3/src/views/customer/CustomerList.vue
@@ -0,0 +1,997 @@
+
+
+
+
+
+ 首页
+
+ /
+ 客户管理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 客户编号
+ 客户姓名
+ 联系电话
+ 地址
+ 需求产品
+ 来源
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+ {{ customer.id }}
+ {{ customer.name }}
+ {{ customer.phone }}
+ {{ customer.address }}
+ {{ customer.product }}
+ {{ customer.source }}
+
+
+ {{ getStatusText(customer.status) }}
+
+
+ {{ customer.createTime }}
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ customers.length }}
+
总客户数
+
+
+
+
+
+
+
+
{{ stats.newCount }}
+
新客户
+
+
+
+
+
+
+
+
{{ stats.measuredCount }}
+
已测量
+
+
+
+
+
+
+
+
{{ stats.quotedCount }}
+
已报价
+
+
+
+
+
+
+
+
{{ stats.orderedCount }}
+
已下单
+
+
+
+
+
+
+
+
{{ stats.completedCount }}
+
已完成
+
+
+
+
+
+
+ 客户状态分布
+
+
+
+ 新客户 {{ stats.newPercent }}%
+ 已测量 {{ stats.measuredPercent }}%
+ 已报价 {{ stats.quotedPercent }}%
+ 已下单 {{ stats.orderedPercent }}%
+ 已完成 {{ stats.completedPercent }}%
+
+
+
+
+
+ 客户来源分布
+
+
+
+
{{ s.name }}
+
+
{{ s.count }}人 ({{ s.percent }}%)
+
+
+
+
+
+
+
+
+
+
+
+ 客户姓名
+
+
+
+ 联系电话
+
+
+
+ 地址
+
+
+
+ 需求产品
+
+ 请选择产品
+ 卷帘
+ 遮阳窗帘
+ 纱窗
+ 蜂巢帘
+ 梦幻帘
+
+
+
+ 客户来源
+
+ 请选择来源
+ 线上咨询
+ 电话咨询
+ 老客户介绍
+ 线下门店
+ 小区推广
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确定要删除客户 {{ deleteTarget?.name }} 吗?
+
+
+ 此操作不可恢复
+
+
+
+
+
+
+
+
{{ toastMsg }}
+
+
+
+
+
+
diff --git a/html-vue3/src/views/customer/MeasurementManage.vue b/html-vue3/src/views/customer/MeasurementManage.vue
new file mode 100644
index 0000000..fe8caa1
--- /dev/null
+++ b/html-vue3/src/views/customer/MeasurementManage.vue
@@ -0,0 +1,1851 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 任务编号
+ 客户姓名
+ 联系电话
+ 地址
+ 测量师
+ 预约时间
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+ {{ task.id }}
+ {{ task.customerName }}
+ {{ task.customerPhone }}
+ {{ task.address }}
+ {{ task.surveyor }}
+ {{ task.appointmentTime }}
+
+
+ {{ getStatusText(task.status) }}
+
+
+ {{ task.createdAt }}
+
+
+
+ 查看
+
+
+ 开始
+
+
+ 录入数据
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 数据编号
+ 关联任务
+ 客户
+ 测量项目
+ 宽度(cm)
+ 高度(cm)
+ 数量
+ 测量师
+ 测量时间
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.taskId }}
+ {{ item.customer }}
+ {{ item.item }}
+ {{ item.width }}
+ {{ item.height }}
+ {{ item.quantity }}
+ {{ item.surveyor }}
+ {{ item.measuredAt }}
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ stats.total }}
+
总测量任务
+
+
+
{{ stats.pending }}
+
待测量
+
+
+
{{ stats.inProgress }}
+
测量中
+
+
+
{{ stats.completed }}
+
已完成
+
+
+
+
+
+
测量师工作量
+
+
+
{{ item.name }}
+
+
{{ item.count }} 单
+
+
+
+
+
近期测量任务
+
+
+
+
+ 任务编号
+ 客户
+ 测量师
+ 状态
+ 时间
+
+
+
+
+ {{ task.id }}
+ {{ task.customerName }}
+ {{ task.surveyor }}
+
+
+ {{ getStatusText(task.status) }}
+
+
+ {{ task.appointmentTime }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择客户
+
+ 请选择客户
+
+ {{ c.name }} - {{ c.address }}
+
+
+
+
+ 测量师
+
+ 请选择测量师
+
+ {{ s.name }}
+
+
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 客户:
+ {{ currentTask?.customerName || '-' }}
+
+
+ 地址:
+ {{ currentTask?.address || '-' }}
+
+
+ 测量师:
+ {{ currentTask?.surveyor || '-' }}
+
+
+
+
+ 测量项目
+
+
+
+
+
+
+ 项目类型
+
+ 请选择
+ 窗帘
+ 窗纱
+ 百叶帘
+ 卷帘
+
+
+
+ 宽度 (cm)
+
+
+
+ 高度 (cm)
+
+
+
+ 数量
+
+
+
+ 备注
+
+
+
+
+
+
+ 添加窗户
+
+
+
+
+
+
+
+
+
+
+
+
+ 确定要删除{{ deleteType === 'task' ? '任务' : '数据' }} {{ deleteTarget?.customerName || deleteTarget?.id }} 吗?
+
+
+ 此操作不可恢复
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/finance/FinalPayment.vue b/html-vue3/src/views/finance/FinalPayment.vue
new file mode 100644
index 0000000..a76ea8d
--- /dev/null
+++ b/html-vue3/src/views/finance/FinalPayment.vue
@@ -0,0 +1,800 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+ 待收尾款总额 {{ formatMoney(stats.totalPending) }}
+
+
+
+ 已完成订单 {{ stats.completedCount }} 笔
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编号
+ 金额 (¥)
+ 订单明细
+ 定金 (¥)
+ 尾款 (¥)
+
+
+
+
+
+ {{ order.orderNo }}
+
+ {{ order.amount.toFixed(2) }}
+ {{ order.detail }}
+ {{ order.deposit.toFixed(2) }}
+
+
+ 已付清
+
+
+ ¥{{ order.final.toFixed(2) }}
+
+ 收款
+
+
+
+
+
+ 暂无已完成订单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 下一步:售后原因
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/finance/Invoice.vue b/html-vue3/src/views/finance/Invoice.vue
new file mode 100644
index 0000000..5b4dbfa
--- /dev/null
+++ b/html-vue3/src/views/finance/Invoice.vue
@@ -0,0 +1,771 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 发票号
+ 订单号
+ 金额(¥)
+ 状态
+ 开票日期
+ 到期日
+ 付款日期
+ 操作
+
+
+
+
+ {{ invoice.id }}
+ {{ invoice.invoiceNo }}
+ {{ invoice.orderId }}
+ {{ invoice.amount.toFixed(2) }}
+
+
+ {{ getStatusText(invoice.status) }}
+
+
+ {{ invoice.createDate }}
+ {{ invoice.dueDate || '-' }}
+ {{ invoice.paidDate || '-' }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无发票数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/install/InstallAdd.vue b/html-vue3/src/views/install/InstallAdd.vue
new file mode 100644
index 0000000..b98bdb2
--- /dev/null
+++ b/html-vue3/src/views/install/InstallAdd.vue
@@ -0,0 +1,619 @@
+
+
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 创建安装任务
+
+
+ 返回
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/install/InstallComplete.vue b/html-vue3/src/views/install/InstallComplete.vue
new file mode 100644
index 0000000..aeb4daf
--- /dev/null
+++ b/html-vue3/src/views/install/InstallComplete.vue
@@ -0,0 +1,932 @@
+
+
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
+
+ 安装任务板
+
+
+ 安装人员排班
+
+
+ 安装完成
+
+
+
+
+
+
+
+
+
+
+
+
+ 工单号
+ 客户名称
+ 产品信息
+ 安装日期
+ 安装师傅
+ 状态
+ 操作
+
+
+
+
+ {{ task.id }}
+ {{ task.customerName }}
+ {{ task.product }}
+ {{ task.installDate }}
+ {{ task.installer }}
+
+
+ {{ getStatusText(task.status) }}
+
+
+
+
+
+ 详情
+
+
+ 完成
+
+
+ 发送账单
+
+
+
+
+
+ 暂无安装任务
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 工单号
+ {{ currentTask.id }}
+
+ 客户名称
+ {{ currentTask.customerName }}
+
+ 联系电话
+ {{ currentTask.customerPhone }}
+
+ 安装地址
+ {{ currentTask.customerAddress }}
+
+ 产品信息
+ {{ currentTask.product }}
+
+ 安装日期
+ {{ currentTask.installDate }}
+
+ 安装时间
+ {{ currentTask.installTime }}
+
+ 安装师傅
+ {{ currentTask.installer }}
+
+ 状态
+
+
+ {{ getStatusText(currentTask.status) }}
+
+
+
+ 备注
+ {{ currentTask.remarks || '无' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 售后问题
+
+
+ 下一步:收尾款
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/install/InstallSchedule.vue b/html-vue3/src/views/install/InstallSchedule.vue
new file mode 100644
index 0000000..e272134
--- /dev/null
+++ b/html-vue3/src/views/install/InstallSchedule.vue
@@ -0,0 +1,1202 @@
+
+
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
+
+ 安装任务板
+
+
+ 安装人员排班
+
+
+ 安装完成
+
+
+
+
+
+
+
+
+
+
+
+
+ 时段
+
+ {{ day.dayName }}
+ {{ formatShortDate(day.dateObj) }}
+
+
+
+
+
+ {{ time }}
+
+
+ {{ getInstallation(rowIdx, colIdx).id }}
+ {{ getProduct(rowIdx, colIdx) }}
+ {{ getInstallation(rowIdx, colIdx).installer }}
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 返回任务板
+
+
+
+
+
+
+
+
+
+
+ 工单号:
+ {{ selectedInstallation.id }}
+
+
+ 客户:
+ {{ selectedInstallation.customerName }}
+
+
+ 产品:
+ {{ selectedProduct }}
+
+
+ 地址:
+ {{ selectedInstallation.customerAddress }}
+
+
+ 安装师傅:
+ {{ selectedInstallation.installer }}
+
+
+ 备注:
+ {{ selectedInstallation.remarks || '无' }}
+
+
+
+
+
该时段暂无安装任务安排
+
+ 添加新任务
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/install/InstallTask.vue b/html-vue3/src/views/install/InstallTask.vue
new file mode 100644
index 0000000..b86036f
--- /dev/null
+++ b/html-vue3/src/views/install/InstallTask.vue
@@ -0,0 +1,1086 @@
+
+
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
+
+ 安装任务板
+
+
+ 安装人员排班
+
+
+ 安装完成
+
+
+
+
+
+
+
+
+
+
+
+
+ 工单号
+ 客户名称
+ 窗帘产品
+ 安装日期
+ 安装时间
+ 安装师傅
+ 状态
+ 操作
+
+
+
+
+ {{ inst.id }}
+ {{ inst.customerName }}
+ {{ inst.product }}
+ {{ inst.installDate }}
+ {{ inst.installTime }}
+ {{ inst.installer }}
+
+
+ {{ getStatusText(inst.status) }}
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+ 暂无安装工单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 工单号
+ {{ detailData.id }}
+
+ 客户名称
+ {{ detailData.customerName }}
+
+ 联系电话
+ {{ detailData.customerPhone }}
+
+ 安装地址
+ {{ detailData.customerAddress }}
+
+ 窗帘产品
+ {{ detailData.product }}
+
+ 安装日期
+ {{ detailData.installDate }}
+
+ 安装时间
+ {{ detailData.installTime }}
+
+ 安装师傅
+ {{ detailData.installer }}
+
+ 状态
+
+
+ {{ getStatusText(detailData.status) }}
+
+
+
+ 备注
+ {{ detailData.remarks || '无' }}
+
+
+
+
+
+
+
+
+
+
+ 安装完成(去收尾款)
+
+
+ 售后问题
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/order/OrderDeal.vue b/html-vue3/src/views/order/OrderDeal.vue
new file mode 100644
index 0000000..7b41217
--- /dev/null
+++ b/html-vue3/src/views/order/OrderDeal.vue
@@ -0,0 +1,731 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编号
+ 金额 (¥)
+ 窗帘类型
+ 布料 / 规格
+ 定金 (¥)
+ 邮编 / 地区
+ 计时 (天)
+ 复尺天数
+ 操作
+ 复尺
+
+
+
+
+ {{ order.orderNo }}
+ {{ order.amount }}
+ {{ order.type }}
+ {{ order.fabric }}
+ {{ order.deposit }}
+ {{ order.postcode }}
+
+ {{ order.timing }}
+ 点击
+
+ {{ order.remeasure }}
+
+
+ 确认复尺
+
+
+ {{ order.remeasure }}
+
+
+
+ 暂无{{ activeTab === 'completed' ? '成交' : '待复尺' }}订单
+
+
+
+
+
+
+
+
+
+
+ 按邮编分类 (同地区聚合)
+
+
+ 按计时从多到少排序
+
+
+
+
+
+
+
+
+
+
+ 下一步:生产采购
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/order/OrderDetail.vue b/html-vue3/src/views/order/OrderDetail.vue
new file mode 100644
index 0000000..3c882ed
--- /dev/null
+++ b/html-vue3/src/views/order/OrderDetail.vue
@@ -0,0 +1,716 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+ 客户 & 安装信息
+
+
+
+ 客户名称
+ {{ order.customerName }}
+
+
+ 联系电话
+ {{ order.phone }}
+
+
+ 安装地址
+ {{ order.address }}
+
+
+ 窗户数量
+ {{ order.windows }}
+
+
+ 订单金额
+ ¥ {{ order.amount?.toLocaleString() }}
+
+
+ 下单时间
+ {{ order.createTime }}
+
+
+ 跟单员
+ {{ order.follower }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 产品/型号
+ 布料编号
+ 颜色
+ 尺寸 (宽×高) cm
+ 轨道类型
+ 数量
+ 单价(¥)
+ 小计(¥)
+
+
+
+
+ {{ product.name }}
+ {{ product.fabric }}
+ {{ product.color }}
+ {{ product.width }} × {{ product.height }}
+ {{ product.track }}
+ {{ product.quantity }}
+ ¥{{ product.price.toFixed(2) }}
+ ¥{{ (product.quantity * product.price).toFixed(2) }}
+
+
+ 暂无产品明细
+
+
+
+
+
+
+ 📦 共{{ productCount }}件产品 · 合计 ¥ {{ productTotal.toFixed(2) }}
+
+
+
+
+
+
+
+ 跟进记录 / 安装节点
+
+
+
+ 📝
+ 新增跟进记录
+
+
+
+
+
+
+
+
+
+ 返回跟单看板
+
+
+ 下一步:成交订单
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/order/OrderFollow.vue b/html-vue3/src/views/order/OrderFollow.vue
new file mode 100644
index 0000000..7b31065
--- /dev/null
+++ b/html-vue3/src/views/order/OrderFollow.vue
@@ -0,0 +1,586 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+ 下一步:成交订单
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/order/OrderList.vue b/html-vue3/src/views/order/OrderList.vue
new file mode 100644
index 0000000..f8aea05
--- /dev/null
+++ b/html-vue3/src/views/order/OrderList.vue
@@ -0,0 +1,787 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 订单号
+ 客户
+ 产品
+ 金额(¥)
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+ {{ order.id }}
+ {{ order.customer }}
+ {{ order.products.join(', ') }}
+ {{ order.amount.toLocaleString() }}
+
+
+ {{ getStatusText(order.status) }}
+
+
+ {{ order.createTime }}
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 客户名称
+
+
+
+ 产品信息
+
+
+
+
+ 订单金额 (¥)
+
+
+
+ 订单状态
+
+ 待处理
+ 生产中
+ 已安装
+ 已完成
+ 已取消
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/order/OrderRecheck.vue b/html-vue3/src/views/order/OrderRecheck.vue
new file mode 100644
index 0000000..36c0eff
--- /dev/null
+++ b/html-vue3/src/views/order/OrderRecheck.vue
@@ -0,0 +1,960 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+ 收定金起计时 · 点击「计时」按钮查看详情
+
+
+
+
+
+
+
+
+
+
+ 编号
+ 金额 (¥)
+ 窗帘类型
+ 布料 / 规格
+ 定金 (¥)
+ 邮编 / 地区
+ 计时 (天)
+ 复尺
+ 操作
+
+
+
+
+ {{ order.orderNo }}
+ {{ order.amount }}
+ {{ order.type }}
+ {{ order.fabric }}
+ {{ order.deposit }}
+ {{ order.postcode }}
+
+ {{ order.timing }}
+ 点击
+
+ {{ order.remeasure }}
+
+
+
+ 编辑
+
+
+ 确认
+
+
+ 删除
+
+
+
+
+
+ 暂无成交订单
+
+
+
+
+
+
+
+
+
+ 按邮编分类 (同地区聚合)
+
+
+ 按计时从多到少排序
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 下一步:生产采购
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/order/OrderTracking.vue b/html-vue3/src/views/order/OrderTracking.vue
new file mode 100644
index 0000000..2ee2985
--- /dev/null
+++ b/html-vue3/src/views/order/OrderTracking.vue
@@ -0,0 +1,1182 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 订单号
+ 客户姓名
+ 产品名称
+ 订单金额
+ 下单日期
+ 当前状态
+ 预计完成
+ 进度
+ 操作
+
+
+
+
+ {{ order.id }}
+ {{ order.customerName }}
+ {{ order.productName }}
+ ¥{{ order.amount?.toFixed(2) }}
+ {{ order.createDate }}
+
+
+ {{ getStatusText(order.status) }}
+
+
+ {{ order.expectedDate }}
+
+
+
+
{{ order.progress }}%
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无符合条件的订单
+
+
+
+
+
+
+
+
+
+
+
重要提醒
+
以下订单即将到期,请及时跟进:
+
+
+ 订单 #{{ reminder.id }} ({{ reminder.days }}天后到期)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 订单金额 (¥)
+
+
+
+ 订单状态
+
+ 待审批
+ 生产中
+ 已发货
+ 已完成
+ 已取消
+
+
+
+
+
+ 订单备注
+
+
+
+ 配送说明
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/product/ProductLibrary.vue b/html-vue3/src/views/product/ProductLibrary.vue
new file mode 100644
index 0000000..93f2227
--- /dev/null
+++ b/html-vue3/src/views/product/ProductLibrary.vue
@@ -0,0 +1,975 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 图片
+ 产品名称
+ 分类
+ 默认价格(¥)
+ 单位
+ 状态
+ 操作
+
+
+
+
+ {{ product.id }}
+
+
+
+ {{ product.name }}
+
+ {{ product.category }}
+
+ {{ product.defaultPrice.toFixed(2) }}
+ {{ product.unit }}
+
+
+ {{ product.active ? '启用' : '停用' }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无产品数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 产品名称
+
+
+
+
+ 分类
+
+ 窗帘
+ 配件
+ 电动
+
+
+
+ 默认价格 (¥)
+
+
+
+
+
+ 单位
+
+
+
+ 状态
+
+ 启用
+ 停用
+
+
+
+
+ 图片URL
+
+
+
+ 描述
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/product/ProductMaterial.vue b/html-vue3/src/views/product/ProductMaterial.vue
new file mode 100644
index 0000000..befe53e
--- /dev/null
+++ b/html-vue3/src/views/product/ProductMaterial.vue
@@ -0,0 +1,933 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 名称
+ 类型
+ 规格
+ 单位
+ 单价(¥)
+ 供应商
+ 操作
+
+
+
+
+ {{ material.id }}
+ {{ material.name }}
+
+ {{ material.type }}
+
+ {{ material.spec || '-' }}
+ {{ material.unit || '-' }}
+ {{ material.price.toFixed(2) }}
+ {{ material.supplierName || '-' }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无物料数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 名称
+
+
+
+
+ 类型
+
+ 面料
+ 配件
+ 电机
+
+
+
+ 规格
+
+
+
+
+
+ 供应商
+
+ 请选择供应商
+
+ {{ s.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/production/ProductionPurchase.vue b/html-vue3/src/views/production/ProductionPurchase.vue
new file mode 100644
index 0000000..ed63078
--- /dev/null
+++ b/html-vue3/src/views/production/ProductionPurchase.vue
@@ -0,0 +1,1064 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 待处理
+ 生产中
+ 采购中
+ 已完成
+
+
+
+
+
+
+
+
+
+
+ 编号
+ 面料 / 组件
+ 颜色 / 型号
+ 幅宽 / 库存
+ 状态
+ 操作
+
+
+
+
+ {{ comp.id }}
+ {{ comp.fabric }}
+ {{ comp.color }}
+ {{ comp.stock }}
+
+
+ {{ getStatusText(comp.status) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无生产采购数据
+
+
+
+
+
+
+
+
+
+
+
+ 👆 点击行选中
+
+ {{ selectedIndex >= 0 ? `#${filteredComponents[selectedIndex]?.id} ${filteredComponents[selectedIndex]?.fabric} / ${filteredComponents[selectedIndex]?.color}` : '未选中任何组件' }}
+
+
+
双击行可查看详细
+
+
+
+
+
+ 启动生产
+
+
+ 采购申请
+
+
+ 打印看板
+
+
+
+
+
+ 📋 订单号 / 款式
+
+
+
+
+
+
+
+ 下一步:生产采购明细
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/production/ProductionPurchaseDetail.vue b/html-vue3/src/views/production/ProductionPurchaseDetail.vue
new file mode 100644
index 0000000..3d54c8b
--- /dev/null
+++ b/html-vue3/src/views/production/ProductionPurchaseDetail.vue
@@ -0,0 +1,1175 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部类型
+ 生产 (自制)
+ 采购 (外购)
+
+
+
+
+
+
+
+
+
+
+
{{ kpiData.producing }}
+
生产中
+
+
+
+
+
+
{{ kpiData.purchasing }}
+
采购在途
+
+
+
+
+
+
{{ kpiData.warning }}
+
库存预警
+
+
+
+
+
+
{{ kpiData.completion }}%
+
完工率
+
+
+
+
+
+
+ 点击任意物料格 → 查看明细
+ 下方按钮模拟工厂操作
+ 今日计划执行中
+
+
+
+
+
+
+
+ 生产 (自制)
+ 采购 (外购)
+ 操作
+
+
+
+
+
+
+ {{ item.produceName }}
+ {{ item.produceQty }} {{ item.produceUnit }}
+
+
+
+
+ {{ item.purchaseName }}
+ {{ item.purchaseQty }} {{ item.purchaseUnit }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无生产采购数据
+
+
+
+
+
+
+
+
+
+
+
+ 生产完成
+
+
+ 采购入库
+
+
+ 计算物料表
+
+
+ 生成采购
+
+
+
+
+
+ 点击任何物料单元格均显示详细
+ 数据更新至 {{ updateTime }}
+ 下个批次计划: 13:00
+
+
+
+
+
+
+ 下一步:安装任务板
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/production/ProductionTask.vue b/html-vue3/src/views/production/ProductionTask.vue
new file mode 100644
index 0000000..77c5273
--- /dev/null
+++ b/html-vue3/src/views/production/ProductionTask.vue
@@ -0,0 +1,1359 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+ 生产任务板
+
+
+ 生产采购
+
+
+ 生产采购明细
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 待处理
+ 生产中
+ 已完成
+
+
+ 全部订单
+
+ 订单 #{{ order.id }}
+
+
+
+
+
+
+
+
+ 总任务
+ {{ filteredTasks.length }}
+
+
+ 待处理
+ {{ getColumnTasks('pending').length }}
+
+
+ 生产中
+ {{ getColumnTasks('processing').length }}
+
+
+ 已完成
+ {{ getColumnTasks('completed').length }}
+
+
+
+
+
+
+
+
+ {{ column.title }}
+
+ {{ getColumnTasks(column.status).length }}
+
+
+
+
+
+
+
+
{{ task.taskName }}
+
+ {{ getOrderInfo(task.orderId) }} · 责任人: {{ task.assignee || '未分配' }}
+
+
+ 计划: {{ task.planStart }} 至 {{ task.planEnd }}
+
+
+
+
+
+
+
+
+
+
+
暂无任务
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/purchase/Inventory.vue b/html-vue3/src/views/purchase/Inventory.vue
new file mode 100644
index 0000000..fd70fc3
--- /dev/null
+++ b/html-vue3/src/views/purchase/Inventory.vue
@@ -0,0 +1,1050 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
{{ stats.totalItems }}
+
库存种类
+
+
+
+
+
+
{{ stats.lowStock }}
+
库存预警
+
+
+
+
+
+
{{ stats.normalStock }}
+
库存正常
+
+
+
+
+
+
{{ stats.totalQuantity }}
+
总库存量
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 物料名称
+ 当前库存
+ 预警阈值
+ 状态
+ 库位
+ 最后更新
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.materialName }}
+ {{ item.quantity }}
+ {{ item.threshold }}
+
+
+
+ {{ item.quantity < item.threshold ? '低于预警' : '正常' }}
+
+
+ {{ item.location || '-' }}
+ {{ item.lastUpdated }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无库存数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 物料
+
+ 请选择物料
+
+ {{ m.name }} ({{ m.type }})
+
+
+
+
+
+ 库位
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/purchase/PurchaseOrder.vue b/html-vue3/src/views/purchase/PurchaseOrder.vue
new file mode 100644
index 0000000..b4d6827
--- /dev/null
+++ b/html-vue3/src/views/purchase/PurchaseOrder.vue
@@ -0,0 +1,1039 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 采购单号
+ 供应商
+ 物料
+ 数量
+ 单价(¥)
+ 总价(¥)
+ 预计到货
+ 状态
+ 操作
+
+
+
+
+ {{ order.orderNo }}
+ {{ order.supplierName }}
+ {{ order.materialName }}
+ {{ order.quantity }}
+ {{ order.price.toFixed(2) }}
+ {{ (order.quantity * order.price).toFixed(2) }}
+ {{ order.expectedDate }}
+
+
+ {{ getStatusText(order.status) }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+ 到货
+
+
+
+
+
+ 暂无采购订单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 供应商
+
+ 请选择供应商
+
+ {{ s.name }}
+
+
+
+
+ 物料
+
+ 请选择物料
+
+ {{ m.name }} ({{ m.spec || '无规格' }})
+
+
+
+
+
+
+ 预计到货日期
+
+
+
+ 状态
+
+ 草稿
+ 已下单
+ 部分到货
+ 已完成
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/purchase/Supplier.vue b/html-vue3/src/views/purchase/Supplier.vue
new file mode 100644
index 0000000..b62b3b6
--- /dev/null
+++ b/html-vue3/src/views/purchase/Supplier.vue
@@ -0,0 +1,892 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 名称
+ 联系人
+ 电话
+ 地址
+ 供应产品
+ 操作
+
+
+
+
+ {{ supplier.id }}
+ {{ supplier.name }}
+ {{ supplier.contact || '-' }}
+ {{ supplier.phone || '-' }}
+ {{ supplier.address || '-' }}
+
+
+
+ {{ product }}
+
+ -
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无供应商数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/quote/QuoteGenerate.vue b/html-vue3/src/views/quote/QuoteGenerate.vue
new file mode 100644
index 0000000..4240870
--- /dev/null
+++ b/html-vue3/src/views/quote/QuoteGenerate.vue
@@ -0,0 +1,1549 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 草稿
+ 已发送
+ 已接受
+ 已拒绝
+ 已过期
+
+
+ 全部方案
+ 方案A (标准型)
+ 方案B (升级型)
+
+
+
+
+
+
+
+
+
+
+ 报价单号
+ 客户信息
+ 产品数量
+ 方案类型
+ 报价金额
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+
+
+ {{ item.quoteNumber }}
+
+
+ {{ item.customerName }}
+ {{ item.customerPhone }}
+
+ {{ item.productCount }} 项
+
+
+ 方案{{ item.scheme }}
+
+
+ ¥{{ item.totalAmount.toFixed(2) }}
+
+
+ {{ getStatusText(item.status) }}
+
+
+ {{ item.createdAt }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
基本信息
+
+
+ 报价单号
+ {{ currentItem.quoteNumber }}
+
+
+ 客户名称
+ {{ currentItem.customerName }}
+
+
+ 联系电话
+ {{ currentItem.customerPhone }}
+
+
+ 邮箱
+ {{ currentItem.customerEmail || '-' }}
+
+
+ 产品数量
+ {{ currentItem.productCount }} 项
+
+
+ 方案类型
+
+ 方案{{ currentItem.scheme }}
+
+
+
+ 报价金额
+ ¥{{ currentItem.totalAmount.toFixed(2) }}
+
+
+ 状态
+
+ {{ getStatusText(currentItem.status) }}
+
+
+
+ 有效期至
+ {{ currentItem.validUntil || '-' }}
+
+
+ 创建时间
+ {{ currentItem.createdAt }}
+
+
+ 更新时间
+ {{ currentItem.updatedAt }}
+
+
+ 备注
+ {{ currentItem.remark || '无' }}
+
+
+
+
+
+
+
产品明细
+
+
+
+
+ 数量: {{ product.qty }}
+ 小计: ¥{{ (product.price * product.qty).toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
收件人: {{ emailRecipient }}
+
主题: {{ emailSubject }}
+
附件: 报价表_{{ currentDate }}.pdf
+
邮件内容:
+
尊敬的客户,您好!
+
根据您的需求,我们已为您准备了详细的产品报价方案,请查看附件中的报价表PDF文件。如果您有任何疑问或需要进一步调整,请随时与我们联系。
+
感谢您对我们产品的关注!
+
此致 销售团队
+
+
+
+
+
+
+
+
+
+ 返回测量报价
+
+
+ 下一步:跟单看板
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/quote/QuoteMeasure.vue b/html-vue3/src/views/quote/QuoteMeasure.vue
new file mode 100644
index 0000000..36f1e01
--- /dev/null
+++ b/html-vue3/src/views/quote/QuoteMeasure.vue
@@ -0,0 +1,1310 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 待处理
+ 测量中
+ 已报价
+ 已确认
+
+
+ 全部产品类型
+
+ {{ name }}
+
+
+
+
+
+
+
+
+
+
+
+ 报价单号
+ 客户信息
+ 产品类型
+ 测量地址
+ 测量日期
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+
+
+ {{ item.quoteNumber }}
+
+
+ {{ item.customerName }}
+ {{ item.customerPhone }}
+
+
+ {{ item.productType }}
+
+ {{ item.address }}
+ {{ item.measureDate }}
+
+
+ {{ getStatusText(item.status) }}
+
+
+ {{ item.createdAt }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 测量地址 *
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
基本信息
+
+
+ 报价单号
+ {{ currentItem.quoteNumber }}
+
+
+ 客户名称
+ {{ currentItem.customerName }}
+
+
+ 联系电话
+ {{ currentItem.customerPhone }}
+
+
+ 产品类型
+ {{ currentItem.productType }}
+
+
+ 测量地址
+ {{ currentItem.address }}
+
+
+ 测量日期
+ {{ currentItem.measureDate }}
+
+
+ 测量尺寸
+ {{ currentItem.measureSize || '-' }}
+
+
+ 状态
+
+ {{ getStatusText(currentItem.status) }}
+
+
+
+ 备注
+ {{ currentItem.remark || '无' }}
+
+
+
+
+
时间信息
+
+
+ 创建时间
+ {{ currentItem.createdAt }}
+
+
+ 更新时间
+ {{ currentItem.updatedAt }}
+
+
+
+
+
+
+
+
+
+
+
+ 返回预约测量
+
+
+ 下一步:生成报价表
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/staff/InstallerManage.vue b/html-vue3/src/views/staff/InstallerManage.vue
new file mode 100644
index 0000000..92e6cd4
--- /dev/null
+++ b/html-vue3/src/views/staff/InstallerManage.vue
@@ -0,0 +1,813 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 姓名
+ 电话
+ 技能标签
+ 最大任务数
+ 当前任务数
+ 负荷
+ 状态
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.name }}
+ {{ item.phone || '-' }}
+
+
+ {{ skill }}
+
+ -
+
+ {{ item.maxTasks }}
+ {{ item.currentTasks }}
+
+
+
{{ item.currentTasks }}/{{ item.maxTasks }}
+
+
+
+
+
+ {{ item.status === 'active' ? '启用' : '停用' }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无安装师傅数据,请点击"新增师傅"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/staff/MeasurerManage.vue b/html-vue3/src/views/staff/MeasurerManage.vue
new file mode 100644
index 0000000..290e027
--- /dev/null
+++ b/html-vue3/src/views/staff/MeasurerManage.vue
@@ -0,0 +1,772 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 姓名
+ 电话
+ 技能标签
+ 工作负荷
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.name }}
+ {{ item.phone || '-' }}
+
+
+ {{ skill }}
+
+ -
+
+
+
+
{{ item.load }}/{{ item.maxLoad }}
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无测量师数据,请点击"新增测量师"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/system/MessageCenter.vue b/html-vue3/src/views/system/MessageCenter.vue
new file mode 100644
index 0000000..adf6403
--- /dev/null
+++ b/html-vue3/src/views/system/MessageCenter.vue
@@ -0,0 +1,780 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 类型
+ 标题
+ 内容
+ 状态
+ 时间
+ 操作
+
+
+
+
+
+
+ {{ getTypeText(item.type) }}
+
+
+ {{ item.title }}
+ {{ item.content }}
+
+
+ {{ item.status === 'unread' ? '未读' : '已读' }}
+
+
+ {{ item.createTime }}
+
+
+
+ 查看
+
+
+ 删除
+
+
+
+
+
+ 暂无通知
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 类型:
+ {{ currentNotification ? getTypeText(currentNotification.type) : '' }}
+
+
+ 标题:
+ {{ currentNotification?.title }}
+
+
+ 内容:
+ {{ currentNotification?.content }}
+
+
+ 时间:
+ {{ currentNotification?.createTime }}
+
+
+ 状态:
+ {{ currentNotification?.status === 'unread' ? '未读' : '已读' }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/system/NotificationTemplate.vue b/html-vue3/src/views/system/NotificationTemplate.vue
new file mode 100644
index 0000000..5cab6fa
--- /dev/null
+++ b/html-vue3/src/views/system/NotificationTemplate.vue
@@ -0,0 +1,742 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 类型
+ 名称
+ 内容预览
+ 变量
+ 操作
+
+
+
+
+ {{ item.id }}
+
+
+ {{ item.type === 'sms' ? '短信' : '邮件' }}
+
+
+ {{ item.name }}
+
+
+ {{ item.content.substring(0, 30) }}{{ item.content.length > 30 ? '…' : '' }}
+
+
+ {{ item.variables && item.variables.length > 0 ? item.variables.join(', ') : '-' }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无模板数据,请点击"新增模板"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 类型 *
+
+ 短信
+ 邮件
+
+
+
+ 名称 *
+
+
+
+ 内容 *
+
+
+
+ 变量(逗号分隔)
+
+ 在内容中用 {变量名} 引用
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/system/RolePermission.vue b/html-vue3/src/views/system/RolePermission.vue
new file mode 100644
index 0000000..830287d
--- /dev/null
+++ b/html-vue3/src/views/system/RolePermission.vue
@@ -0,0 +1,759 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 角色名称
+ 描述
+ 权限列表
+ 操作
+
+
+
+
+ {{ role.id }}
+ {{ role.name }}
+ {{ role.description || '-' }}
+
+
+
+ {{ group.name }}: {{ group.permissions.join(',') }}
+
+
-
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无角色数据,请点击"新增角色"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/system/SystemUser.vue b/html-vue3/src/views/system/SystemUser.vue
new file mode 100644
index 0000000..77a387f
--- /dev/null
+++ b/html-vue3/src/views/system/SystemUser.vue
@@ -0,0 +1,771 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 用户名
+ 姓名
+ 角色
+ 电话
+ 邮箱
+ 状态
+ 操作
+
+
+
+
+ {{ user.id }}
+ {{ user.username }}
+ {{ user.realName || '-' }}
+
+ {{ getRoleName(user.roleId) }}
+
+ {{ user.phone || '-' }}
+ {{ user.email || '-' }}
+
+
+ {{ user.status === 'active' ? '启用' : '停用' }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无用户数据,请点击"新增用户"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/src/views/system/WorkTime.vue b/html-vue3/src/views/system/WorkTime.vue
new file mode 100644
index 0000000..7dde92a
--- /dev/null
+++ b/html-vue3/src/views/system/WorkTime.vue
@@ -0,0 +1,742 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 星期
+ 开始时间
+ 结束时间
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ getDayName(item.dayOfWeek) }}
+ {{ item.startTime }}
+ {{ item.endTime }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无工作时间数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 日期
+ 名称
+ 类型
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.date }}
+ {{ item.name }}
+
+ {{ item.type === 'public' ? '法定节假日' : '公司假日' }}
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无节假日数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html-vue3/start.bat b/html-vue3/start.bat
new file mode 100644
index 0000000..42f7f76
--- /dev/null
+++ b/html-vue3/start.bat
@@ -0,0 +1,20 @@
+@echo off
+echo ================================
+echo 窗帘工厂管理系统 - Vue3版本
+echo ================================
+echo.
+echo 正在启动开发服务器...
+echo.
+
+cd /d "%~dp0"
+
+:: 检查 node_modules 是否存在
+if not exist "node_modules" (
+ echo 首次运行,正在安装依赖...
+ npm install
+ echo.
+)
+
+npm run dev
+
+pause
diff --git a/html-vue3/vite.config.js b/html-vue3/vite.config.js
new file mode 100644
index 0000000..6211ea9
--- /dev/null
+++ b/html-vue3/vite.config.js
@@ -0,0 +1,16 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import { resolve } from 'path'
+
+export default defineConfig({
+ plugins: [vue()],
+ resolve: {
+ alias: {
+ '@': resolve(__dirname, 'src')
+ }
+ },
+ server: {
+ port: 3001,
+ open: true
+ }
+})
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..afa446b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
窗帘工厂管理系统
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..4ab4d33
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,834 @@
+{
+ "name": "curtain-factory-management",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "curtain-factory-management",
+ "version": "1.0.0",
+ "dependencies": {
+ "chart.js": "^4.4.0",
+ "pinia": "^2.1.7",
+ "vue": "^3.3.4",
+ "vue-router": "^4.6.4"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^4.2.3",
+ "vite": "^4.4.5"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
+ "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz",
+ "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==",
+ "dev": true,
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.0.0 || ^5.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.33.tgz",
+ "integrity": "sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==",
+ "dependencies": {
+ "@babel/parser": "^7.29.2",
+ "@vue/shared": "3.5.33",
+ "entities": "^7.0.1",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.33.tgz",
+ "integrity": "sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.33",
+ "@vue/shared": "3.5.33"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.33.tgz",
+ "integrity": "sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==",
+ "dependencies": {
+ "@babel/parser": "^7.29.2",
+ "@vue/compiler-core": "3.5.33",
+ "@vue/compiler-dom": "3.5.33",
+ "@vue/compiler-ssr": "3.5.33",
+ "@vue/shared": "3.5.33",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.10",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.33.tgz",
+ "integrity": "sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.33",
+ "@vue/shared": "3.5.33"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.33.tgz",
+ "integrity": "sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A==",
+ "dependencies": {
+ "@vue/shared": "3.5.33"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.33.tgz",
+ "integrity": "sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.33",
+ "@vue/shared": "3.5.33"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.33.tgz",
+ "integrity": "sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.33",
+ "@vue/runtime-core": "3.5.33",
+ "@vue/shared": "3.5.33",
+ "csstype": "^3.2.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.33.tgz",
+ "integrity": "sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw==",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.33",
+ "@vue/shared": "3.5.33"
+ },
+ "peerDependencies": {
+ "vue": "3.5.33"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.33.tgz",
+ "integrity": "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ=="
+ },
+ "node_modules/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
+ },
+ "node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "node_modules/pinia": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
+ "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.3",
+ "vue-demi": "^0.14.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "3.30.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.30.0.tgz",
+ "integrity": "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=14.18.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "4.5.14",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz",
+ "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.18.10",
+ "postcss": "^8.4.27",
+ "rollup": "^3.27.1"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.33",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.33.tgz",
+ "integrity": "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.33",
+ "@vue/compiler-sfc": "3.5.33",
+ "@vue/runtime-dom": "3.5.33",
+ "@vue/server-renderer": "3.5.33",
+ "@vue/shared": "3.5.33"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.6.4",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
+ "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..7dfd636
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "curtain-factory-management",
+ "version": "1.0.0",
+ "description": "窗帘工厂管理系统 - Vue3版本",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "vue": "^3.3.4",
+ "vue-router": "^4.6.4",
+ "pinia": "^2.1.7",
+ "chart.js": "^4.4.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^4.2.3",
+ "vite": "^4.4.5"
+ }
+}
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..e8aaf4d
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/src/api/index.js b/src/api/index.js
new file mode 100644
index 0000000..78d38cd
--- /dev/null
+++ b/src/api/index.js
@@ -0,0 +1,150 @@
+import request from './request'
+import mock from './mock'
+
+const useMock = import.meta.env.VITE_API_MOCK === 'true'
+
+export const customerApi = {
+ list(params) {
+ return useMock ? mock.customers.list(params) : request.get('/api/customers', { params })
+ },
+ getById(id) {
+ return useMock ? mock.customers.getById(id) : request.get(`/api/customers/${id}`)
+ },
+ create(data) {
+ return useMock ? mock.customers.create(data) : request.post('/api/customers', data)
+ },
+ update(id, data) {
+ return useMock ? mock.customers.update(id, data) : request.put(`/api/customers/${id}`, data)
+ },
+ remove(id) {
+ return useMock ? mock.customers.remove(id) : request.delete(`/api/customers/${id}`)
+ }
+}
+
+export const orderApi = {
+ list(params) {
+ return useMock ? mock.orders.list(params) : request.get('/api/orders', { params })
+ },
+ getById(id) {
+ return useMock ? mock.orders.getById(id) : request.get(`/api/orders/${id}`)
+ },
+ create(data) {
+ return useMock ? mock.orders.create(data) : request.post('/api/orders', data)
+ },
+ update(id, data) {
+ return useMock ? mock.orders.update(id, data) : request.put(`/api/orders/${id}`, data)
+ },
+ remove(id) {
+ return useMock ? mock.orders.remove(id) : request.delete(`/api/orders/${id}`)
+ }
+}
+
+export const productApi = {
+ list() {
+ return useMock ? mock.products.list() : request.get('/api/products')
+ }
+}
+
+export const appointmentApi = {
+ list() {
+ return useMock ? mock.appointments.list() : request.get('/api/appointments')
+ },
+ create(data) {
+ return useMock ? mock.appointments.create(data) : request.post('/api/appointments', data)
+ }
+}
+
+export const measurementApi = {
+ list() {
+ return useMock ? mock.measurementTasks.list() : request.get('/api/measurement-tasks')
+ },
+ create(data) {
+ return useMock ? mock.measurementTasks.create(data) : request.post('/api/measurement-tasks', data)
+ },
+ update(id, data) {
+ return useMock ? mock.measurementTasks.update(id, data) : request.put(`/api/measurement-tasks/${id}`, data)
+ }
+}
+
+export const productionApi = {
+ list() {
+ return useMock ? mock.productionTasks.list() : request.get('/api/production-tasks')
+ },
+ create(data) {
+ return useMock ? mock.productionTasks.create(data) : request.post('/api/production-tasks', data)
+ }
+}
+
+export const installationApi = {
+ list() {
+ return useMock ? mock.installations.list() : request.get('/api/installations')
+ },
+ create(data) {
+ return useMock ? mock.installations.create(data) : request.post('/api/installations', data)
+ }
+}
+
+export const quoteApi = {
+ list() {
+ return useMock ? mock.quotes.list() : request.get('/api/quotes')
+ },
+ create(data) {
+ return useMock ? mock.quotes.create(data) : request.post('/api/quotes', data)
+ }
+}
+
+export const purchaseApi = {
+ list() {
+ return useMock ? mock.purchaseOrders.list() : request.get('/api/purchase-orders')
+ },
+ create(data) {
+ return useMock ? mock.purchaseOrders.create(data) : request.post('/api/purchase-orders', data)
+ }
+}
+
+export const afterSaleApi = {
+ list() {
+ return useMock ? mock.afterSales.list() : request.get('/api/after-sales')
+ },
+ create(data) {
+ return useMock ? mock.afterSales.create(data) : request.post('/api/after-sales', data)
+ }
+}
+
+export const channelApi = {
+ list() {
+ return useMock ? mock.channels.list() : request.get('/api/channels')
+ },
+ create(data) {
+ return useMock ? mock.channels.create(data) : request.post('/api/channels', data)
+ },
+ update(id, data) {
+ return useMock ? mock.channels.update(id, data) : request.put(`/api/channels/${id}`, data)
+ },
+ remove(id) {
+ return useMock ? mock.channels.remove(id) : request.delete(`/api/channels/${id}`)
+ }
+}
+
+export const surveyApi = {
+ list() {
+ return useMock ? mock.surveys.list() : request.get('/api/surveys')
+ }
+}
+
+export const staffApi = {
+ measurers() {
+ return useMock ? mock.staff.measurers() : request.get('/api/staff/measurers')
+ },
+ installers() {
+ return useMock ? mock.staff.installers() : request.get('/api/staff/installers')
+ }
+}
+
+export const authApi = {
+ login(data) {
+ return useMock
+ ? Promise.resolve({ token: 'mock-jwt-token', user: { name: data.username, role: data.username === 'admin' ? 'admin' : 'user' } })
+ : request.post('/api/auth/login', data)
+ }
+}
diff --git a/src/api/mock.js b/src/api/mock.js
new file mode 100644
index 0000000..209b05f
--- /dev/null
+++ b/src/api/mock.js
@@ -0,0 +1,239 @@
+import AppData from '@/utils/dataService'
+
+const delay = (ms = 200) => new Promise(r => setTimeout(r, ms))
+
+const mock = {
+ customers: {
+ list(params = {}) {
+ return delay().then(() => {
+ let list = AppData.getCustomers()
+ if (params.keyword) {
+ const kw = params.keyword.toLowerCase()
+ list = list.filter(c => c.name.includes(kw) || c.phone.includes(kw) || (c.address && c.address.includes(kw)))
+ }
+ if (params.status) {
+ list = list.filter(c => c.status === params.status)
+ }
+ if (params.source) {
+ list = list.filter(c => c.source === params.source)
+ }
+ const page = params.page || 1
+ const size = params.size || 10
+ const total = list.length
+ const start = (page - 1) * size
+ return { total, page, size, list: list.slice(start, start + size) }
+ })
+ },
+ getById(id) {
+ return delay().then(() => AppData.getCustomerById(id) || null)
+ },
+ create(data) {
+ return delay().then(() => {
+ const customers = AppData.getCustomers()
+ const maxId = customers.length > 0 ? Math.max(...customers.map(c => parseInt(c.id.replace('C', '')))) : 0
+ const record = { ...data, id: 'C' + String(maxId + 1).padStart(3, '0'), createTime: new Date().toLocaleString('zh-CN') }
+ customers.unshift(record)
+ AppData.saveCustomers(customers)
+ return record
+ })
+ },
+ update(id, data) {
+ return delay().then(() => {
+ const customers = AppData.getCustomers()
+ const idx = customers.findIndex(c => c.id === id)
+ if (idx === -1) throw new Error('客户不存在')
+ customers[idx] = { ...customers[idx], ...data }
+ AppData.saveCustomers(customers)
+ return customers[idx]
+ })
+ },
+ remove(id) {
+ return delay().then(() => {
+ const customers = AppData.getCustomers()
+ AppData.saveCustomers(customers.filter(c => c.id !== id))
+ return true
+ })
+ }
+ },
+ orders: {
+ list(params = {}) {
+ return delay().then(() => {
+ let list = AppData.getOrder()
+ if (params.keyword) {
+ const kw = params.keyword.toLowerCase()
+ list = list.filter(o => o.id.toLowerCase().includes(kw) || o.customer.toLowerCase().includes(kw))
+ }
+ if (params.status) list = list.filter(o => o.status === params.status)
+ const page = params.page || 1
+ const size = params.size || 10
+ const total = list.length
+ const start = (page - 1) * size
+ return { total, page, size, list: list.slice(start, start + size) }
+ })
+ },
+ getById(id) {
+ return delay().then(() => AppData.getOrderById(id) || null)
+ },
+ create(data) {
+ return delay().then(() => {
+ const orders = AppData.getOrder()
+ const record = { ...data, id: 'O' + Date.now(), createTime: new Date().toLocaleString('zh-CN') }
+ orders.unshift(record)
+ AppData.saveOrders(orders)
+ return record
+ })
+ },
+ update(id, data) {
+ return delay().then(() => {
+ const orders = AppData.getOrder()
+ const idx = orders.findIndex(o => o.id === id)
+ if (idx === -1) throw new Error('订单不存在')
+ orders[idx] = { ...orders[idx], ...data }
+ AppData.saveOrders(orders)
+ return orders[idx]
+ })
+ },
+ remove(id) {
+ return delay().then(() => {
+ const orders = AppData.getOrder()
+ AppData.saveOrders(orders.filter(o => o.id !== id))
+ return true
+ })
+ }
+ },
+ products: {
+ list() {
+ return delay().then(() => AppData.getProducts())
+ }
+ },
+ appointments: {
+ list() {
+ return delay().then(() => AppData.getAppointments())
+ },
+ create(data) {
+ return delay().then(() => {
+ const list = AppData.getAppointments()
+ list.push({ ...data, id: 'A' + Date.now() })
+ AppData.saveAppointments(list)
+ return data
+ })
+ }
+ },
+ measurementTasks: {
+ list() {
+ return delay().then(() => AppData.getMeasurementTasks())
+ },
+ create(data) {
+ return delay().then(() => {
+ const list = AppData.getMeasurementTasks()
+ list.push({ ...data, id: 'M' + (list.length + 1).toString().padStart(3, '0') })
+ AppData.saveMeasurementTasks(list)
+ return data
+ })
+ },
+ update(id, data) {
+ return delay().then(() => {
+ const list = AppData.getMeasurementTasks()
+ const idx = list.findIndex(t => t.id === id)
+ if (idx === -1) throw new Error('任务不存在')
+ list[idx] = { ...list[idx], ...data }
+ AppData.saveMeasurementTasks(list)
+ return list[idx]
+ })
+ }
+ },
+ productionTasks: {
+ list() {
+ return delay().then(() => AppData.getProductionTask())
+ },
+ create(data) {
+ return delay().then(() => {
+ const list = AppData.getProductionTask()
+ list.push({ ...data, id: 'PT' + (list.length + 1).toString().padStart(3, '0') })
+ AppData.saveProductionTasks(list)
+ return data
+ })
+ }
+ },
+ installations: {
+ list() {
+ return delay().then(() => AppData.getInstallation())
+ },
+ create(data) {
+ return delay().then(() => {
+ const list = AppData.getInstallation()
+ list.push({ ...data, id: 'IN' + (list.length + 1).toString().padStart(3, '0') })
+ AppData.saveInstallations(list)
+ return data
+ })
+ }
+ },
+ quotes: {
+ list() {
+ return delay().then(() => AppData.getQuotes())
+ },
+ create(data) {
+ return delay().then(() => {
+ const list = AppData.getQuotes()
+ list.push({ ...data, id: 'Q' + (list.length + 1).toString().padStart(3, '0') })
+ AppData.saveQuotes(list)
+ return data
+ })
+ }
+ },
+ purchaseOrders: {
+ list() {
+ return delay().then(() => AppData.getPurchaseOrders())
+ },
+ create(data) {
+ return delay().then(() => {
+ const list = AppData.getPurchaseOrders()
+ list.push({ ...data, id: 'PO' + (list.length + 1).toString().padStart(3, '0') })
+ AppData.savePurchaseOrders(list)
+ return data
+ })
+ }
+ },
+ afterSales: {
+ list() {
+ return delay().then(() => AppData.getAfterSales())
+ },
+ create(data) {
+ return delay().then(() => {
+ const list = AppData.getAfterSales()
+ list.push({ ...data, id: 'AS' + (list.length + 1).toString().padStart(3, '0') })
+ AppData.saveAfterSales(list)
+ return data
+ })
+ }
+ },
+ channels: {
+ list() {
+ return delay().then(() => AppData.getChannels())
+ },
+ create(data) {
+ return delay().then(() => AppData.addChannel(data))
+ },
+ update(id, data) {
+ return delay().then(() => AppData.updateChannel(id, data))
+ },
+ remove(id) {
+ return delay().then(() => AppData.deleteChannel(id))
+ }
+ },
+ surveys: {
+ list() {
+ return delay().then(() => AppData.getSurveys())
+ }
+ },
+ staff: {
+ measurers() {
+ return delay().then(() => AppData.getSurveyors())
+ },
+ installers() {
+ return delay().then(() => AppData.getInstallers())
+ }
+ }
+}
+
+export default mock
diff --git a/src/api/request.js b/src/api/request.js
new file mode 100644
index 0000000..33402ab
--- /dev/null
+++ b/src/api/request.js
@@ -0,0 +1,57 @@
+import axios from 'axios'
+import { useUserStore } from '@/stores/userStore'
+
+const request = axios.create({
+ baseURL: import.meta.env.VITE_API_BASE_URL,
+ timeout: 30000,
+ headers: { 'Content-Type': 'application/json' }
+})
+
+request.interceptors.request.use(
+ (config) => {
+ const token = localStorage.getItem('accessToken')
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`
+ }
+ return config
+ },
+ (error) => Promise.reject(error)
+)
+
+request.interceptors.response.use(
+ (res) => {
+ const body = res.data
+ if (body && typeof body === 'object' && 'code' in body) {
+ if (body.code === 200) {
+ return body.data
+ }
+ if (body.code === 401) {
+ const userStore = useUserStore()
+ userStore.logout()
+ window.location.hash = '#/login'
+ return Promise.reject(new Error(body.msg || '认证已过期'))
+ }
+ return Promise.reject(new Error(body.msg || '请求失败'))
+ }
+ return body
+ },
+ (error) => {
+ if (error.response) {
+ const { status, data } = error.response
+ if (status === 401) {
+ const userStore = useUserStore()
+ userStore.logout()
+ window.location.hash = '#/login'
+ return Promise.reject(new Error('认证已过期,请重新登录'))
+ }
+ const msg = data?.msg || data?.message || `服务器错误 (${status})`
+ return Promise.reject(new Error(msg))
+ }
+ if (error.code === 'ECONNABORTED') {
+ return Promise.reject(new Error('请求超时,请重试'))
+ }
+ return Promise.reject(new Error('网络异常,请检查网络连接'))
+ }
+)
+
+export default request
diff --git a/src/assets/css/global.css b/src/assets/css/global.css
new file mode 100644
index 0000000..ae57edd
--- /dev/null
+++ b/src/assets/css/global.css
@@ -0,0 +1,1279 @@
+/* global.css - 窗帘工厂管理系统统一样式 (基于 QuoteGenerate 标准) */
+:root {
+ --primary: #2ea2cc;
+ --primary-dark: #1f7fa3;
+ --primary-light: #b0e0f0;
+ --primary-bg-light: #f0f9ff;
+ --text-primary: #1e2b3c;
+ --text-secondary: #2d4059;
+ --text-muted: #64748b;
+ --border-color: #d4dfec;
+ --card-bg: #ffffff;
+ --shadow-color: rgba(0, 20, 40, 0.06);
+ --focus-ring: rgba(46, 162, 204, 0.1);
+ --bg-primary: #f5f9ff;
+ --bg-secondary: #f8fafc;
+ --bg-hover: #f1f5f9;
+ --spacing-xs: 4px;
+ --spacing-sm: 8px;
+ --spacing-md: 16px;
+ --spacing-lg: 24px;
+ --spacing-xl: 32px;
+ --radius-sm: 8px;
+ --radius-md: 12px;
+ --radius-lg: 16px;
+ --radius-xl: 24px;
+ --radius-btn: 10px;
+ --radius-input: 8px;
+ --success: #2d6e46;
+ --success-dark: #1f5936;
+ --danger: #e53e3c;
+ --danger-dark: #c53030;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: 'Segoe UI', Roboto, 'Microsoft YaHei', sans-serif;
+}
+
+body {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ line-height: 1.5;
+}
+
+/* ========== 页面容器 ========== */
+.page-container {
+ width: 100%;
+ max-width: 1600px;
+ margin: 0 auto;
+ padding: 24px;
+}
+
+/* ========== 页面头部 ========== */
+.page-header {
+ margin-bottom: 24px;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-wrap: wrap;
+ gap: 16px;
+}
+
+.page-header-left {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.page-header-right {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+/* ========== 页面标题 ========== */
+.page-title {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: #2d4059;
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.page-title i {
+ color: #2ea2cc;
+}
+
+.text-secondary {
+ color: #6c757d;
+ font-size: 0.9rem;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+/* ========== 卡片容器 ========== */
+.card {
+ background: white;
+ border-radius: 16px;
+ border: 2px solid #d4dfec;
+ overflow: hidden;
+ padding: 24px;
+ margin-bottom: 24px;
+}
+
+/* ========== 搜索栏 ========== */
+.search-bar {
+ display: flex;
+ gap: 16px;
+ margin-bottom: 20px;
+ flex-wrap: wrap;
+}
+
+.search-box {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.search-box input {
+ padding: 12px 16px;
+ border: 2px solid #d4dfec;
+ border-radius: 40px;
+ font-size: 14px;
+ transition: all 0.2s;
+ background: white;
+ min-width: 240px;
+}
+
+.search-box input:focus {
+ outline: none;
+ border-color: #2ea2cc;
+ box-shadow: 0 0 0 3px rgba(46, 162, 204, 0.1);
+}
+
+.search-input-group {
+ flex: 1;
+ min-width: 280px;
+ position: relative;
+}
+
+.search-input-group i {
+ position: absolute;
+ left: 16px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #6c757d;
+}
+
+.search-input-group input {
+ width: 100%;
+ padding: 12px 16px 12px 44px;
+ border: 2px solid #d4dfec;
+ border-radius: 40px;
+ font-size: 14px;
+ transition: all 0.2s;
+}
+
+.search-input-group input:focus {
+ outline: none;
+ border-color: #2ea2cc;
+ box-shadow: 0 0 0 3px rgba(46, 162, 204, 0.1);
+}
+
+.filter-group {
+ display: flex;
+ gap: 12px;
+}
+
+.search-input {
+ padding: 12px 16px;
+ border: 2px solid #d4dfec;
+ border-radius: 40px;
+ font-size: 14px;
+ transition: all 0.2s;
+ background: white;
+}
+
+.search-input:focus {
+ outline: none;
+ border-color: #2ea2cc;
+ box-shadow: 0 0 0 3px rgba(46, 162, 204, 0.1);
+}
+
+.search-input.status-select {
+ min-width: 150px;
+ padding-right: 36px;
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%234a5f73' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 14px center;
+ background-size: 12px;
+}
+
+/* 工具栏中的 select(搜索/筛选区域) */
+.toolbar select,
+.search-bar select,
+.role-selector select {
+ padding: 12px 36px 12px 16px;
+ border: 2px solid #d4dfec;
+ border-radius: 40px;
+ font-size: 14px;
+ background: white;
+ cursor: pointer;
+ min-width: 140px;
+ transition: all 0.2s;
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%234a5f73' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 14px center;
+ background-size: 12px;
+}
+
+.toolbar select:focus,
+.search-bar select:focus,
+.role-selector select:focus {
+ outline: none;
+ border-color: #2ea2cc;
+ box-shadow: 0 0 0 3px rgba(46, 162, 204, 0.1);
+}
+
+.filter-group select {
+ padding: 12px 36px 12px 16px;
+ border: 2px solid #d4dfec;
+ border-radius: 40px;
+ font-size: 14px;
+ background: white;
+ cursor: pointer;
+ min-width: 140px;
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%234a5f73' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 14px center;
+ background-size: 12px;
+}
+
+.filter-group select:focus {
+ outline: none;
+ border-color: #2ea2cc;
+}
+
+/* ========== 工具栏 ========== */
+.toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 20px;
+ gap: 12px;
+}
+
+/* ========== 导航标签页 ========== */
+.nav-tabs {
+ display: flex;
+ border-bottom: 2px solid var(--border-color);
+ margin-bottom: 24px;
+ gap: 4px;
+}
+
+.nav-tab {
+ padding: 10px 20px;
+ font-weight: 600;
+ color: #4a5f73;
+ border-bottom: 3px solid transparent;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.nav-tab:hover {
+ color: var(--primary);
+}
+
+.nav-tab.active {
+ color: var(--primary-dark);
+ border-bottom-color: var(--primary);
+}
+
+/* ========== 标签页(tabs) ========== */
+.tabs {
+ display: flex;
+ border-bottom: 2px solid #d4dfec;
+ margin-bottom: 20px;
+}
+
+.tab {
+ padding: 10px 24px;
+ font-weight: 600;
+ color: #4a5f73;
+ border-bottom: 3px solid transparent;
+ cursor: pointer;
+ transition: 0.2s;
+}
+
+.tab:hover {
+ color: #2ea2cc;
+}
+
+.tab.active {
+ color: #2ea2cc;
+ border-bottom-color: #2ea2cc;
+}
+
+/* ========== 表格容器 ========== */
+.table-container {
+ overflow-x: auto;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+th, td {
+ padding: 14px 16px;
+ text-align: left;
+ border-bottom: 1px solid #eef2f7;
+}
+
+th {
+ background: #f8fafc;
+ font-weight: 600;
+ color: #4a5f73;
+ font-size: 0.9rem;
+ white-space: nowrap;
+}
+
+td {
+ color: #2d4059;
+}
+
+/* ========== 按钮 ========== */
+.btn {
+ padding: 12px 20px;
+ border-radius: 10px;
+ font-size: 0.95rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s;
+ border: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.btn-primary {
+ background: var(--primary);
+ color: white;
+}
+
+.btn-primary:hover {
+ background: var(--primary-dark);
+}
+
+.btn-success {
+ background: var(--success);
+ color: white;
+}
+
+.btn-success:hover {
+ background: var(--success-dark);
+}
+
+.btn-danger {
+ background: var(--danger);
+ color: white;
+}
+
+.btn-danger:hover {
+ background: var(--danger-dark);
+}
+
+.btn-warning {
+ background: #f59e0b;
+ color: white;
+}
+
+.btn-warning:hover {
+ background: #d97706;
+}
+
+.btn-secondary {
+ background: #f1f5f9;
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+}
+
+.btn-secondary:hover {
+ background: #e2e8f0;
+}
+
+.btn-outline {
+ background: transparent;
+ border: 1px solid var(--primary);
+ color: var(--primary);
+}
+
+.btn-outline:hover {
+ background: var(--primary-bg-light);
+}
+
+.btn-link {
+ background: transparent;
+ border: none;
+ color: var(--primary);
+ text-decoration: none;
+ padding: 8px 12px;
+}
+
+.btn-link:hover {
+ text-decoration: underline;
+}
+
+.btn-sm {
+ padding: 6px 14px;
+ font-size: 0.85rem;
+ border-radius: 8px;
+}
+
+.btn-group {
+ display: flex;
+ gap: 8px;
+}
+
+/* ========== 分页 ========== */
+.pagination-container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px;
+ border-top: 1px solid #eef2f7;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+.pagination-info {
+ color: #6c757d;
+ font-size: 14px;
+}
+
+.pagination {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.page-btn {
+ width: 36px;
+ height: 36px;
+ border: 2px solid #d4dfec;
+ background: white;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.page-btn:hover:not(:disabled) {
+ border-color: #2ea2cc;
+ color: #2ea2cc;
+}
+
+.page-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.page-info {
+ padding: 0 12px;
+ font-weight: 600;
+ color: #2d4059;
+}
+
+/* ========== 模态框 ========== */
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 0.3s;
+}
+
+.modal.show,
+.modal.active {
+ opacity: 1;
+ visibility: visible;
+}
+
+.modal-content {
+ background: white;
+ border-radius: 16px;
+ width: 90%;
+ max-width: 600px;
+ max-height: 90vh;
+ overflow-y: auto;
+ transform: translateY(-20px);
+ transition: transform 0.3s;
+}
+
+.modal.show .modal-content,
+.modal.active .modal-content {
+ transform: translateY(0);
+}
+
+.modal-form {
+ max-width: 700px;
+}
+
+.modal-detail {
+ max-width: 800px;
+}
+
+.modal-email {
+ max-width: 600px;
+}
+
+.modal-header {
+ padding: 20px 24px;
+ border-bottom: 1px solid #eef2f7;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.modal-title {
+ margin: 0;
+ font-size: 1.25rem;
+ color: #2d4059;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.modal-title i {
+ color: #2ea2cc;
+}
+
+.modal-close {
+ background: none;
+ border: none;
+ font-size: 1.5rem;
+ color: #6c757d;
+ cursor: pointer;
+ padding: 0;
+ line-height: 1;
+}
+
+.modal-close:hover {
+ color: #2d4059;
+}
+
+.modal-body {
+ padding: 24px;
+}
+
+.modal-footer {
+ padding: 16px 24px;
+ border-top: 1px solid #eef2f7;
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+}
+
+/* ========== 表单 ========== */
+.form-group {
+ margin-bottom: 20px;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 600;
+ color: #2d4059;
+ font-size: 0.9rem;
+}
+
+.form-group label i {
+ margin-right: 6px;
+ color: #2ea2cc;
+}
+
+.form-group input,
+.form-group select,
+.form-group textarea {
+ width: 100%;
+ padding: 10px 14px;
+ border: 2px solid #d4dfec;
+ border-radius: 8px;
+ font-size: 14px;
+ transition: all 0.2s;
+ background: white;
+}
+
+.form-group input:focus,
+.form-group select:focus,
+.form-group textarea:focus {
+ outline: none;
+ border-color: #2ea2cc;
+ box-shadow: 0 0 0 3px rgba(46, 162, 204, 0.1);
+}
+
+.form-group textarea {
+ resize: vertical;
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+}
+
+.form-actions {
+ margin: 24px 0;
+ display: flex;
+ gap: 16px;
+}
+
+/* ========== 操作按钮组 ========== */
+.action-btns {
+ display: flex;
+ gap: 6px;
+ flex-wrap: wrap;
+}
+
+/* ========== 流程导航 ========== */
+.flow-nav {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 24px;
+ gap: 16px;
+}
+
+/* ========== 分页 ========== */
+.pagination-bar,
+.pagination-container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 0;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+.pagination-info {
+ font-size: 0.9rem;
+ color: #4a5f73;
+}
+
+.pages {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.page-item {
+ padding: 8px 14px;
+ border: 1px solid #d4dfec;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ transition: all 0.2s;
+}
+
+.page-item:hover {
+ border-color: #2ea2cc;
+ color: #2ea2cc;
+}
+
+.page-item.active {
+ background: #2ea2cc;
+ color: white;
+ border-color: #2ea2cc;
+}
+
+.page-arrow {
+ padding: 8px 12px;
+ border: 1px solid #d4dfec;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.page-arrow:hover:not(.disabled) {
+ border-color: #2ea2cc;
+ color: #2ea2cc;
+}
+
+.page-arrow.disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.page-btn {
+ padding: 8px 14px;
+ border: 1px solid #d4dfec;
+ border-radius: 8px;
+ background: white;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.page-btn:hover:not(:disabled) {
+ border-color: #2ea2cc;
+ color: #2ea2cc;
+}
+
+.page-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.page-info {
+ font-size: 0.9rem;
+ color: #4a5f73;
+}
+
+/* ========== 按钮面板 ========== */
+.button-panel {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+ justify-content: center;
+ margin: 32px 0 16px;
+}
+
+/* ========== 统计卡片 ========== */
+.stats-section {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 1.5rem;
+ margin-bottom: 2rem;
+}
+
+.stat-badge {
+ background: #e8f4f8;
+ padding: 0.6rem 1.5rem;
+ border-radius: 40px;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ font-size: 1rem;
+ border: 1px solid #d4dfec;
+}
+
+.stat-badge i {
+ color: #2ea2cc;
+ font-size: 1.3rem;
+}
+
+.stat-badge strong {
+ font-size: 1.35rem;
+ font-weight: 700;
+ color: #2d4059;
+ margin-right: 0.25rem;
+}
+
+/* ========== 统计网格卡片 ========== */
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 16px;
+ margin-bottom: 24px;
+}
+
+.stat-card {
+ background: white;
+ border-radius: 12px;
+ padding: 20px;
+ border: 2px solid #d4dfec;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ transition: transform 0.2s;
+}
+
+.stat-card:hover {
+ transform: translateY(-2px);
+}
+
+.stat-icon {
+ width: 48px;
+ height: 48px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.4rem;
+ background: #e8f4f8;
+ color: #2ea2cc;
+}
+
+.stat-icon i {
+ color: inherit;
+}
+
+.stat-content h3 {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: #2d4059;
+ margin: 0;
+}
+
+.stat-content p {
+ font-size: 0.85rem;
+ color: #6c757d;
+ margin: 4px 0 0;
+}
+
+/* ========== 主卡片 ========== */
+.main-card {
+ background: white;
+ border-radius: 20px;
+ padding: 24px;
+ border: 2px solid #d4dfec;
+}
+
+/* ========== 空状态 ========== */
+.empty-text {
+ text-align: center;
+ color: #9ca3af;
+ padding: 40px 20px;
+ font-size: 0.95rem;
+}
+
+/* ========== 加载遮罩 ========== */
+.loading-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 2;
+ border-radius: 20px;
+}
+
+.spinner {
+ border: 4px solid #f3f3f3;
+ border-top: 4px solid #2ea2cc;
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* ========== 提醒区域 ========== */
+.notes-section {
+ margin-top: 24px;
+ padding: 20px;
+ background: #fff8e1;
+ border-radius: 16px;
+ border: 1px solid #ffe082;
+}
+
+.notes-section h3 {
+ font-size: 1rem;
+ color: #b46917;
+ margin-bottom: 12px;
+}
+
+.notes-section p {
+ font-size: 0.9rem;
+ color: #92400e;
+ margin-bottom: 8px;
+}
+
+.reminder-list p {
+ font-size: 0.85rem;
+ color: #b46917;
+ padding: 4px 0;
+}
+
+/* ========== Toast 通知 ========== */
+.toast {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ background: #333;
+ color: #fff;
+ padding: 14px 28px;
+ border-radius: 40px;
+ font-size: 14px;
+ z-index: 2000;
+ opacity: 0;
+ transition: opacity 0.3s;
+ pointer-events: none;
+}
+
+.toast.show {
+ opacity: 1;
+}
+
+.toast.success { background: var(--success); }
+.toast.error { background: var(--danger); }
+.toast.info { background: var(--primary); }
+
+/* ========== 徽章 ========== */
+.badge {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 40px;
+ font-size: 0.8rem;
+ font-weight: 500;
+}
+
+.badge-new { background: #e6fffa; color: #234e52; }
+.badge-measured { background: #f0fff4; color: #22543d; }
+.badge-quoted { background: #ebf8ff; color: #2c5282; }
+.badge-ordered { background: #faf5ff; color: #553c9a; }
+.badge-completed { background: #f0f4ff; color: #3c366b; }
+
+/* ========== 面包屑 ========== */
+.breadcrumb {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 24px;
+ font-size: 0.9rem;
+ color: var(--text-muted);
+}
+
+.breadcrumb-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.breadcrumb-item a {
+ color: var(--primary);
+ text-decoration: none;
+}
+
+.breadcrumb-item a:hover {
+ color: var(--primary-dark);
+ text-decoration: underline;
+}
+
+.breadcrumb-separator {
+ color: var(--text-muted);
+}
+
+.breadcrumb-item.active {
+ color: var(--text-primary);
+ font-weight: 500;
+}
+
+/* ========== 卡片标题 ========== */
+.card-title {
+ color: #2d4059;
+ margin: 0 0 16px;
+ font-size: 1rem;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.card-title i {
+ color: #2ea2cc;
+}
+
+/* ========== 区域头部 ========== */
+.section-header {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+/* ========== 下拉菜单 ========== */
+.dropdown {
+ position: relative;
+ display: inline-block;
+}
+
+.dropdown-trigger {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ background-color: #eef3fc;
+ border: 1px solid #d4dfec;
+ border-radius: 30px;
+ padding: 8px 16px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ color: #2d4059;
+ cursor: pointer;
+ transition: background 0.15s;
+}
+
+.dropdown-trigger span:last-child {
+ font-size: 0.7rem;
+ margin-left: 2px;
+}
+
+.dropdown-trigger:hover {
+ background-color: #dfebf7;
+}
+
+.dropdown-content {
+ display: none;
+ position: absolute;
+ top: calc(100% + 8px);
+ right: 0;
+ background-color: #fff;
+ min-width: 190px;
+ box-shadow: 0 16px 32px rgba(22, 45, 70, 0.15);
+ border: 1px solid #d4dfec;
+ border-radius: 16px;
+ z-index: 15;
+ overflow: hidden;
+ padding: 6px 0;
+}
+
+.dropdown:hover .dropdown-content {
+ display: block;
+}
+
+.dropdown-content a {
+ color: #2d4059;
+ padding: 11px 20px;
+ text-decoration: none;
+ display: block;
+ font-size: 0.9rem;
+ font-weight: 500;
+ border-bottom: 1px solid #edf2f9;
+ cursor: pointer;
+}
+
+.dropdown-content a:last-child {
+ border-bottom: none;
+}
+
+.dropdown-content a:hover {
+ background-color: #e7f0fd;
+ color: #0b2a41;
+}
+
+/* ========== 客户信息网格 ========== */
+.customer-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 18px 28px;
+ background-color: #f8fafc;
+ padding: 22px 24px;
+ border-radius: 16px;
+ border: 1px solid #d4dfec;
+}
+
+.customer-item {
+ display: flex;
+ align-items: baseline;
+ font-size: 0.98rem;
+}
+
+.customer-item .label {
+ font-weight: 500;
+ color: #496f92;
+ width: 90px;
+ flex-shrink: 0;
+}
+
+.customer-item .value {
+ color: #2d4059;
+ font-weight: 500;
+ word-break: break-word;
+}
+
+/* ========== 产品汇总 ========== */
+.product-summary {
+ margin-top: 16px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+ align-items: center;
+}
+
+.summary-tag {
+ background: #f8fafc;
+ padding: 6px 18px;
+ border-radius: 30px;
+ font-size: 0.9rem;
+}
+
+/* ========== 状态标签 ========== */
+.status-tag {
+ background: #dff0d8;
+ color: #2e6b3e;
+ font-size: 0.75rem;
+ padding: 3px 12px;
+ border-radius: 50px;
+ display: inline-block;
+ margin-right: 8px;
+}
+
+/* ========== 日历复选框 ========== */
+.calendar-checkbox {
+ width: 18px;
+ height: 18px;
+ accent-color: #2ea2cc;
+ transform: scale(1.1);
+ cursor: pointer;
+ margin: 0 4px;
+}
+
+/* ========== 新增跟进按钮 ========== */
+.add-followup-btn {
+ background: #d8e5f5;
+ border-radius: 40px;
+ padding: 14px 22px;
+ border: 1px solid #ccddef;
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ cursor: pointer;
+ transition: background 0.2s;
+ width: fit-content;
+ margin-top: 16px;
+}
+
+.add-followup-btn:hover {
+ background: #c5d6ea;
+}
+
+.icon-wrapper {
+ background: #d8e5f5;
+ border-radius: 50%;
+ width: 38px;
+ height: 38px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.3rem;
+}
+
+.btn-text {
+ color: #1f4870;
+}
+
+/* ========== 底部卡片 ========== */
+.footer-card {
+ padding: 20px 24px;
+}
+
+.footer-content {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 0.9rem;
+}
+
+.production-status {
+ display: flex;
+ gap: 25px;
+}
+
+.version-info {
+ color: #5f7fa0;
+}
+
+/* ========== 产品明细 ========== */
+.dimension {
+ font-family: monospace;
+ background-color: #f4f9ff;
+ padding: 4px 8px;
+ border-radius: 12px;
+ font-size: 0.85rem;
+}
+
+.amount {
+ font-weight: 700;
+ color: #153e6f;
+}
+
+/* ========== 文本样式 ========== */
+.text-left { text-align: left; }
+.text-center { text-align: center; }
+.font-medium { font-weight: 500; }
+
+/* ========== 产品类型标签 ========== */
+.type-tag {
+ cursor: pointer;
+ transition: all 0.2s;
+ padding: 10px 16px;
+ border: 1px solid var(--border-color);
+ background: white;
+ border-radius: 10px;
+ font-weight: 500;
+ color: var(--text-secondary);
+}
+
+.type-tag:hover {
+ border-color: var(--primary);
+ color: var(--primary);
+}
+
+.type-tag.active {
+ background: var(--primary);
+ color: white;
+ border-color: var(--primary);
+}
+
+/* ========== 平滑滚动 ========== */
+html {
+ scroll-behavior: smooth;
+}
+
+/* ========== 响应式 ========== */
+@media (max-width: 767px) {
+ .btn {
+ padding: 12px 20px;
+ }
+
+ input, select, textarea {
+ min-height: 48px;
+ }
+
+ .page-container {
+ padding: 16px;
+ }
+
+ .page-title {
+ font-size: 1.4rem;
+ }
+
+ .card {
+ border-radius: 12px;
+ }
+
+ .modal-content {
+ max-width: 95% !important;
+ min-width: 280px;
+ margin: 10px;
+ }
+
+ .form-row {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 480px) {
+ .page-title {
+ font-size: 1.2rem;
+ }
+
+ .btn {
+ padding: 10px 14px;
+ font-size: 0.85rem;
+ }
+}
diff --git a/src/components/common/GoogleCalendar.vue b/src/components/common/GoogleCalendar.vue
new file mode 100644
index 0000000..a40a221
--- /dev/null
+++ b/src/components/common/GoogleCalendar.vue
@@ -0,0 +1,832 @@
+
+
+
+
+
+
+
+
+
+ {{ formatHour(h - 1) }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ getEventField(event, titleKey) }}
+
+ {{ getEventField(event, workerKey) }}
+ {{ getEventField(event, customerKey) }}
+
+
{{ formatTime(getEventField(event, startKey)) }} – {{ formatTime(getEventField(event, endKey)) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/common/PagePlaceholder.vue b/src/components/common/PagePlaceholder.vue
new file mode 100644
index 0000000..053f50e
--- /dev/null
+++ b/src/components/common/PagePlaceholder.vue
@@ -0,0 +1,74 @@
+
+
+
+
+ 首页
+
+ /
+ {{ title }}
+
+
+
+
+
+
+
+
{{ title }}
+
{{ description }}
+
+ 功能开发中,敬请期待...
+
+
+
+
+
+
+
+
+
diff --git a/src/components/layout/MainLayout.vue b/src/components/layout/MainLayout.vue
new file mode 100644
index 0000000..0e64f0e
--- /dev/null
+++ b/src/components/layout/MainLayout.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
diff --git a/src/components/layout/Sidebar.vue b/src/components/layout/Sidebar.vue
new file mode 100644
index 0000000..c5cdecd
--- /dev/null
+++ b/src/components/layout/Sidebar.vue
@@ -0,0 +1,303 @@
+
+
+
+
+
+
+
diff --git a/src/components/layout/TopBar.vue b/src/components/layout/TopBar.vue
new file mode 100644
index 0000000..26d8fa8
--- /dev/null
+++ b/src/components/layout/TopBar.vue
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+
+
+ 首页
+
+ /
+ {{ currentPageTitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ result.name }}
+
+
{{ result.group }}
+
+
+
+
+
+
+
+ 管理员
+
+
+
+
+
+
+
+
diff --git a/src/composables/useModal.js b/src/composables/useModal.js
new file mode 100644
index 0000000..25c6cb1
--- /dev/null
+++ b/src/composables/useModal.js
@@ -0,0 +1,28 @@
+import { ref } from 'vue'
+
+export function useModal() {
+ const isVisible = ref(false)
+ const modalData = ref(null)
+
+ const open = (data = null) => {
+ modalData.value = data
+ isVisible.value = true
+ }
+
+ const close = () => {
+ isVisible.value = false
+ modalData.value = null
+ }
+
+ const toggle = () => {
+ isVisible.value = !isVisible.value
+ }
+
+ return {
+ isVisible,
+ modalData,
+ open,
+ close,
+ toggle
+ }
+}
diff --git a/src/composables/usePagination.js b/src/composables/usePagination.js
new file mode 100644
index 0000000..30d26d2
--- /dev/null
+++ b/src/composables/usePagination.js
@@ -0,0 +1,47 @@
+import { ref, computed } from 'vue'
+
+export function usePagination(items, pageSize = 10) {
+ const currentPage = ref(1)
+ const itemsPerPage = ref(pageSize)
+
+ const totalPages = computed(() => Math.ceil(items.value.length / itemsPerPage.value))
+
+ const paginatedItems = computed(() => {
+ const start = (currentPage.value - 1) * itemsPerPage.value
+ const end = start + itemsPerPage.value
+ return items.value.slice(start, end)
+ })
+
+ const goToPage = (page) => {
+ if (page >= 1 && page <= totalPages.value) {
+ currentPage.value = page
+ }
+ }
+
+ const nextPage = () => {
+ if (currentPage.value < totalPages.value) {
+ currentPage.value++
+ }
+ }
+
+ const prevPage = () => {
+ if (currentPage.value > 1) {
+ currentPage.value--
+ }
+ }
+
+ const resetPage = () => {
+ currentPage.value = 1
+ }
+
+ return {
+ currentPage,
+ itemsPerPage,
+ totalPages,
+ paginatedItems,
+ goToPage,
+ nextPage,
+ prevPage,
+ resetPage
+ }
+}
diff --git a/src/composables/useSearch.js b/src/composables/useSearch.js
new file mode 100644
index 0000000..dfb382d
--- /dev/null
+++ b/src/composables/useSearch.js
@@ -0,0 +1,53 @@
+import { ref, computed } from 'vue'
+
+export function useSearch(items, searchFields = []) {
+ const searchQuery = ref('')
+ const statusFilter = ref('')
+ const sourceFilter = ref('')
+
+ const filteredItems = computed(() => {
+ let result = items.value
+
+ // 文本搜索
+ if (searchQuery.value.trim()) {
+ const query = searchQuery.value.toLowerCase()
+ result = result.filter(item => {
+ if (searchFields.length === 0) {
+ // 默认搜索所有字符串字段
+ return Object.values(item).some(val =>
+ String(val).toLowerCase().includes(query)
+ )
+ }
+ return searchFields.some(field =>
+ String(item[field]).toLowerCase().includes(query)
+ )
+ })
+ }
+
+ // 状态过滤
+ if (statusFilter.value) {
+ result = result.filter(item => item.status === statusFilter.value)
+ }
+
+ // 来源过滤
+ if (sourceFilter.value) {
+ result = result.filter(item => item.source === sourceFilter.value)
+ }
+
+ return result
+ })
+
+ const resetFilters = () => {
+ searchQuery.value = ''
+ statusFilter.value = ''
+ sourceFilter.value = ''
+ }
+
+ return {
+ searchQuery,
+ statusFilter,
+ sourceFilter,
+ filteredItems,
+ resetFilters
+ }
+}
diff --git a/src/composables/useToast.js b/src/composables/useToast.js
new file mode 100644
index 0000000..649388b
--- /dev/null
+++ b/src/composables/useToast.js
@@ -0,0 +1,31 @@
+import { ref, readonly } from 'vue'
+
+const toastMsg = ref('')
+const toastClass = ref('')
+let timer = null
+
+export function useToast() {
+ function showToast(msg, type = 'info') {
+ if (timer) clearTimeout(timer)
+ toastMsg.value = msg
+ toastClass.value = 'show ' + type
+ timer = setTimeout(() => {
+ toastClass.value = ''
+ }, 3000)
+ }
+
+ function toastSuccess(msg) { showToast(msg, 'success') }
+ function toastError(msg) { showToast(msg, 'error') }
+ function toastInfo(msg) { showToast(msg, 'info') }
+ function toastWarning(msg) { showToast(msg, 'warning') }
+
+ return {
+ toastMsg: readonly(toastMsg),
+ toastClass: readonly(toastClass),
+ showToast,
+ toastSuccess,
+ toastError,
+ toastInfo,
+ toastWarning
+ }
+}
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..6643b4e
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,12 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import App from './App.vue'
+import router from './router'
+import './assets/css/global.css'
+
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(router)
+
+app.mount('#app')
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 0000000..5fab568
--- /dev/null
+++ b/src/router/index.js
@@ -0,0 +1,328 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+
+// 路由配置
+const routes = [
+ // 登录页
+ {
+ path: '/login',
+ name: 'Login',
+ component: () => import('@/views/Login.vue'),
+ meta: { title: '登录', requiresAuth: false }
+ },
+
+ // 主框架
+ {
+ path: '/',
+ component: () => import('@/components/layout/MainLayout.vue'),
+ redirect: '/dashboard',
+ meta: { requiresAuth: true },
+ children: [
+ // 经营仪表盘
+ {
+ path: 'dashboard',
+ name: 'Dashboard',
+ component: () => import('@/views/Dashboard.vue'),
+ meta: { title: '经营仪表盘', icon: 'fa-chart-line' }
+ },
+
+ // 功能流程图
+ {
+ path: 'workflow',
+ name: 'Workflow',
+ component: () => import('@/views/Workflow.vue'),
+ meta: { title: '功能流程图', icon: 'fa-project-diagram' }
+ },
+
+ // ========== 日程安排 ==========
+ {
+ path: 'calendar',
+ name: 'ScheduleCalendar',
+ component: () => import('@/views/calendar/ScheduleCalendar.vue'),
+ meta: { title: 'Schedule Calendar', icon: 'fa-calendar-week' }
+ },
+
+ // ========== 客户管理 ==========
+ {
+ path: 'customer',
+ name: 'CustomerList',
+ component: () => import('@/views/customer/CustomerList.vue'),
+ meta: { title: '客户管理', icon: 'fa-users' }
+ },
+ {
+ path: 'customer/appointment',
+ name: 'Appointment',
+ component: () => import('@/views/customer/Appointment.vue'),
+ meta: { title: '预约测量', icon: 'fa-calendar-check' }
+ },
+ {
+ path: 'customer/measurement',
+ name: 'MeasurementManage',
+ component: () => import('@/views/customer/MeasurementManage.vue'),
+ meta: { title: '测量管理', icon: 'fa-ruler-combined' }
+ },
+
+ // ========== 测量报价 ==========
+ {
+ path: 'quote',
+ name: 'QuoteMeasure',
+ component: () => import('@/views/quote/QuoteMeasure.vue'),
+ meta: { title: '测量报价', icon: 'fa-calculator' }
+ },
+ {
+ path: 'quote/generate',
+ name: 'QuoteGenerate',
+ component: () => import('@/views/quote/QuoteGenerate.vue'),
+ meta: { title: '报价表生成', icon: 'fa-file-invoice' }
+ },
+
+ // ========== 订单管理 ==========
+ {
+ path: 'order',
+ name: 'OrderList',
+ component: () => import('@/views/order/OrderList.vue'),
+ meta: { title: '订单管理', icon: 'fa-shopping-cart' }
+ },
+ {
+ path: 'order/tracking',
+ name: 'OrderTracking',
+ component: () => import('@/views/order/OrderTracking.vue'),
+ meta: { title: '订单跟踪', icon: 'fa-truck' }
+ },
+ {
+ path: 'order/follow',
+ name: 'OrderFollow',
+ component: () => import('@/views/order/OrderFollow.vue'),
+ meta: { title: '跟单页', icon: 'fa-clipboard-list' }
+ },
+ {
+ path: 'order/detail',
+ name: 'OrderDetail',
+ component: () => import('@/views/order/OrderDetail.vue'),
+ meta: { title: '定单明细', icon: 'fa-list-alt' }
+ },
+ {
+ path: 'order/deal',
+ name: 'OrderDeal',
+ component: () => import('@/views/order/OrderDeal.vue'),
+ meta: { title: '成交定单', icon: 'fa-handshake' }
+ },
+ {
+ path: 'order/recheck',
+ name: 'OrderRecheck',
+ component: () => import('@/views/order/OrderRecheck.vue'),
+ meta: { title: '复尺确认', icon: 'fa-check-double' }
+ },
+
+ // ========== 生产管理 ==========
+ {
+ path: 'production/task',
+ name: 'ProductionTask',
+ component: () => import('@/views/production/ProductionTask.vue'),
+ meta: { title: '生产任务板', icon: 'fa-tasks' }
+ },
+ {
+ path: 'production/purchase',
+ name: 'ProductionPurchase',
+ component: () => import('@/views/production/ProductionPurchase.vue'),
+ meta: { title: '生产采购', icon: 'fa-shopping-basket' }
+ },
+ {
+ path: 'production/purchase-detail',
+ name: 'ProductionPurchaseDetail',
+ component: () => import('@/views/production/ProductionPurchaseDetail.vue'),
+ meta: { title: '生产采购明细', icon: 'fa-list' }
+ },
+
+ // ========== 安装管理 ==========
+ {
+ path: 'install/add',
+ name: 'InstallAdd',
+ component: () => import('@/views/install/InstallAdd.vue'),
+ meta: { title: '新增安装', icon: 'fa-plus-circle' }
+ },
+ {
+ path: 'install/task',
+ name: 'InstallTask',
+ component: () => import('@/views/install/InstallTask.vue'),
+ meta: { title: '安装任务板', icon: 'fa-clipboard-check' }
+ },
+ {
+ path: 'install/schedule',
+ name: 'InstallSchedule',
+ component: () => import('@/views/install/InstallSchedule.vue'),
+ meta: { title: '安装人员排班表', icon: 'fa-calendar-alt' }
+ },
+ {
+ path: 'install/complete',
+ name: 'InstallComplete',
+ component: () => import('@/views/install/InstallComplete.vue'),
+ meta: { title: '帘窗安装完成', icon: 'fa-check-circle' }
+ },
+
+ // ========== 售后管理 ==========
+ {
+ path: 'after-sale/reason',
+ name: 'AfterSaleReason',
+ component: () => import('@/views/aftersale/AfterSaleReason.vue'),
+ meta: { title: '售后原因', icon: 'fa-question-circle' }
+ },
+ {
+ path: 'after-sale/arrange',
+ name: 'AfterSaleArrange',
+ component: () => import('@/views/aftersale/AfterSaleArrange.vue'),
+ meta: { title: '维修安排', icon: 'fa-tools' }
+ },
+
+ // ========== 财务管理 ==========
+ {
+ path: 'finance/final-payment',
+ name: 'FinalPayment',
+ component: () => import('@/views/finance/FinalPayment.vue'),
+ meta: { title: '收尾款', icon: 'fa-money-bill-wave' }
+ },
+ {
+ path: 'finance/invoice',
+ name: 'Invoice',
+ component: () => import('@/views/finance/Invoice.vue'),
+ meta: { title: '发票管理', icon: 'fa-file-invoice-dollar' }
+ },
+
+ // ========== 采购库存 ==========
+ {
+ path: 'purchase/order',
+ name: 'PurchaseOrder',
+ component: () => import('@/views/purchase/PurchaseOrder.vue'),
+ meta: { title: '采购订单', icon: 'fa-cart-plus' }
+ },
+ {
+ path: 'purchase/supplier',
+ name: 'Supplier',
+ component: () => import('@/views/purchase/Supplier.vue'),
+ meta: { title: '供应商管理', icon: 'fa-truck-loading' }
+ },
+ {
+ path: 'purchase/inventory',
+ name: 'Inventory',
+ component: () => import('@/views/purchase/Inventory.vue'),
+ meta: { title: '库存管理', icon: 'fa-boxes' }
+ },
+
+ // ========== 产品管理 ==========
+ {
+ path: 'product/library',
+ name: 'ProductLibrary',
+ component: () => import('@/views/product/ProductLibrary.vue'),
+ meta: { title: '产品库管理', icon: 'fa-cube' }
+ },
+ {
+ path: 'product/material',
+ name: 'ProductMaterial',
+ component: () => import('@/views/product/ProductMaterial.vue'),
+ meta: { title: '面料配件库', icon: 'fa-layer-group' }
+ },
+
+ // ========== 人员管理 ==========
+ {
+ path: 'staff/measurer',
+ name: 'MeasurerManage',
+ component: () => import('@/views/staff/MeasurerManage.vue'),
+ meta: { title: '测量师管理', icon: 'fa-user-tie' }
+ },
+ {
+ path: 'staff/installer',
+ name: 'InstallerManage',
+ component: () => import('@/views/staff/InstallerManage.vue'),
+ meta: { title: '安装师傅管理', icon: 'fa-hard-hat' }
+ },
+
+ // ========== 系统设置 ==========
+ {
+ path: 'system/role',
+ name: 'RolePermission',
+ component: () => import('@/views/system/RolePermission.vue'),
+ meta: { title: '角色权限', icon: 'fa-user-shield' }
+ },
+ {
+ path: 'system/user',
+ name: 'SystemUser',
+ component: () => import('@/views/system/SystemUser.vue'),
+ meta: { title: '系统用户', icon: 'fa-users-cog' }
+ },
+ {
+ path: 'system/notification-template',
+ name: 'NotificationTemplate',
+ component: () => import('@/views/system/NotificationTemplate.vue'),
+ meta: { title: '通知模板', icon: 'fa-bell' }
+ },
+ {
+ path: 'system/work-time',
+ name: 'WorkTime',
+ component: () => import('@/views/system/WorkTime.vue'),
+ meta: { title: '工作时间', icon: 'fa-clock' }
+ },
+ {
+ path: 'system/message',
+ name: 'MessageCenter',
+ component: () => import('@/views/system/MessageCenter.vue'),
+ meta: { title: '消息中心', icon: 'fa-envelope' }
+ },
+
+ // ========== 数据分析 ==========
+ {
+ path: 'analysis/center',
+ name: 'AnalysisCenter',
+ component: () => import('@/views/analysis/AnalysisCenter.vue'),
+ meta: { title: '数据分析中心', icon: 'fa-chart-bar' }
+ },
+ {
+ path: 'analysis/report',
+ name: 'ReportAnalysis',
+ component: () => import('@/views/analysis/ReportAnalysis.vue'),
+ meta: { title: '报表分析', icon: 'fa-chart-pie' }
+ },
+ {
+ path: 'analysis/satisfaction',
+ name: 'SatisfactionSurvey',
+ component: () => import('@/views/analysis/SatisfactionSurvey.vue'),
+ meta: { title: '满意度调查', icon: 'fa-smile' }
+ },
+ {
+ path: 'analysis/channel',
+ name: 'ChannelManage',
+ component: () => import('@/views/analysis/ChannelManage.vue'),
+ meta: { title: '渠道管理', icon: 'fa-network-wired' }
+ }
+ ]
+ },
+
+ // 404
+ {
+ path: '/:pathMatch(.*)*',
+ name: 'NotFound',
+ component: () => import('@/views/NotFound.vue')
+ }
+]
+
+const router = createRouter({
+ history: createWebHashHistory(),
+ routes
+})
+
+// 路由守卫
+router.beforeEach((to, from, next) => {
+ // 设置页面标题
+ document.title = to.meta.title ? `${to.meta.title} · 窗帘工厂` : '窗帘工厂管理系统'
+
+ // 检查登录状态
+ const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'
+
+ if (to.meta.requiresAuth !== false && !isLoggedIn && to.path !== '/login') {
+ next('/login')
+ } else if (to.path === '/login' && isLoggedIn) {
+ next('/dashboard')
+ } else {
+ next()
+ }
+})
+
+export default router
diff --git a/src/stores/customerStore.js b/src/stores/customerStore.js
new file mode 100644
index 0000000..b159013
--- /dev/null
+++ b/src/stores/customerStore.js
@@ -0,0 +1,81 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+
+export const useCustomerStore = defineStore('customer', () => {
+ const customers = ref([
+ {
+ id: 'C001',
+ name: '张三',
+ phone: '13800001111',
+ address: '北京市朝阳区建国路88号',
+ product: '卷帘',
+ source: '线上咨询',
+ status: 'new',
+ createTime: '2026-05-01 10:30',
+ remark: '需要测量三个窗户'
+ },
+ {
+ id: 'C002',
+ name: '李四',
+ phone: '13900002222',
+ address: '上海市浦东新区陆家嘴金融中心',
+ product: '遮阳窗帘',
+ source: '电话咨询',
+ status: 'measured',
+ createTime: '2026-05-02 14:20',
+ remark: '已预约测量'
+ },
+ {
+ id: 'C003',
+ name: '王五',
+ phone: '13700003333',
+ address: '广州市天河区珠江新城',
+ product: '纱窗',
+ source: '老客户介绍',
+ status: 'quoted',
+ createTime: '2026-05-03 09:15',
+ remark: '等待客户确认报价'
+ }
+ ])
+
+ const totalCustomers = computed(() => customers.value.length)
+ const newCustomers = computed(() => customers.value.filter(c => c.status === 'new').length)
+
+ function addCustomer(customer) {
+ const newCustomer = {
+ ...customer,
+ id: 'C' + String(customers.value.length + 1).padStart(3, '0'),
+ createTime: new Date().toLocaleString('zh-CN')
+ }
+ customers.value.unshift(newCustomer)
+ return newCustomer
+ }
+
+ function updateCustomer(id, data) {
+ const index = customers.value.findIndex(c => c.id === id)
+ if (index !== -1) {
+ customers.value[index] = { ...customers.value[index], ...data }
+ }
+ }
+
+ function deleteCustomer(id) {
+ const index = customers.value.findIndex(c => c.id === id)
+ if (index !== -1) {
+ customers.value.splice(index, 1)
+ }
+ }
+
+ function getCustomerById(id) {
+ return customers.value.find(c => c.id === id)
+ }
+
+ return {
+ customers,
+ totalCustomers,
+ newCustomers,
+ addCustomer,
+ updateCustomer,
+ deleteCustomer,
+ getCustomerById
+ }
+})
diff --git a/src/stores/orderStore.js b/src/stores/orderStore.js
new file mode 100644
index 0000000..d3cc612
--- /dev/null
+++ b/src/stores/orderStore.js
@@ -0,0 +1,66 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+
+export const useOrderStore = defineStore('order', () => {
+ const orders = ref([
+ {
+ id: 'O20260501001',
+ customer: '张三',
+ amount: 3500,
+ status: 'pending',
+ createTime: '2026-05-01 14:30',
+ products: ['卷帘 x 2', '纱窗 x 1']
+ },
+ {
+ id: 'O20260502002',
+ customer: '李四',
+ amount: 8900,
+ status: 'production',
+ createTime: '2026-05-02 10:15',
+ products: ['遮阳窗帘 x 3']
+ },
+ {
+ id: 'O20260503003',
+ customer: '王五',
+ amount: 2200,
+ status: 'completed',
+ createTime: '2026-05-03 16:45',
+ products: ['蜂巢帘 x 1']
+ }
+ ])
+
+ const totalOrders = computed(() => orders.value.length)
+ const pendingOrders = computed(() => orders.value.filter(o => o.status === 'pending').length)
+ const totalAmount = computed(() => orders.value.reduce((sum, o) => sum + o.amount, 0))
+
+ function addOrder(order) {
+ const newOrder = {
+ ...order,
+ id: 'O' + new Date().getTime(),
+ createTime: new Date().toLocaleString('zh-CN')
+ }
+ orders.value.unshift(newOrder)
+ return newOrder
+ }
+
+ function updateOrderStatus(id, status) {
+ const order = orders.value.find(o => o.id === id)
+ if (order) {
+ order.status = status
+ }
+ }
+
+ function getOrderById(id) {
+ return orders.value.find(o => o.id === id)
+ }
+
+ return {
+ orders,
+ totalOrders,
+ pendingOrders,
+ totalAmount,
+ addOrder,
+ updateOrderStatus,
+ getOrderById
+ }
+})
diff --git a/src/stores/userStore.js b/src/stores/userStore.js
new file mode 100644
index 0000000..3cd3bb3
--- /dev/null
+++ b/src/stores/userStore.js
@@ -0,0 +1,43 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+
+export const useUserStore = defineStore('user', () => {
+ const user = ref(null)
+ const isLoggedIn = ref(false)
+
+ const userName = computed(() => user.value?.name || '未登录')
+ const userRole = computed(() => user.value?.role || 'guest')
+
+ function login(userData) {
+ user.value = userData
+ isLoggedIn.value = true
+ localStorage.setItem('isLoggedIn', 'true')
+ localStorage.setItem('user', JSON.stringify(userData))
+ }
+
+ function logout() {
+ user.value = null
+ isLoggedIn.value = false
+ localStorage.removeItem('isLoggedIn')
+ localStorage.removeItem('user')
+ }
+
+ function initUser() {
+ const savedUser = localStorage.getItem('user')
+ const savedLogin = localStorage.getItem('isLoggedIn')
+ if (savedLogin === 'true' && savedUser) {
+ user.value = JSON.parse(savedUser)
+ isLoggedIn.value = true
+ }
+ }
+
+ return {
+ user,
+ isLoggedIn,
+ userName,
+ userRole,
+ login,
+ logout,
+ initUser
+ }
+})
diff --git a/src/utils/dataService.js b/src/utils/dataService.js
new file mode 100644
index 0000000..ba8f7c3
--- /dev/null
+++ b/src/utils/dataService.js
@@ -0,0 +1,261 @@
+// 数据服务 - 模拟后端API
+const AppData = {
+ // 客户数据
+ getCustomers() {
+ return JSON.parse(localStorage.getItem('customers') || '[]') || this.getDefaultCustomers()
+ },
+
+ getDefaultCustomers() {
+ return [
+ { id: 'C001', name: '张三', phone: '13800001111', address: '北京市朝阳区建国路88号', product: '卷帘', source: '线上咨询', status: 'new', createTime: '2026-05-01 10:30', remark: '需要测量三个窗户', province: '北京市' },
+ { id: 'C002', name: '李四', phone: '13900002222', address: '上海市浦东新区陆家嘴金融中心', product: '遮阳窗帘', source: '电话咨询', status: 'measured', createTime: '2026-05-02 14:20', remark: '已预约测量', province: '上海市' },
+ { id: 'C003', name: '王五', phone: '13700003333', address: '广州市天河区珠江新城', product: '纱窗', source: '老客户介绍', status: 'quoted', createTime: '2026-05-03 09:15', remark: '等待客户确认报价', province: '广东省' },
+ { id: 'C004', name: '赵六', phone: '13600004444', address: '深圳市南山区科技园', product: '蜂巢帘', source: '线下门店', status: 'ordered', createTime: '2026-05-04 11:40', remark: '已下单生产', province: '广东省' },
+ { id: 'C005', name: '钱七', phone: '13500005555', address: '杭州市西湖区文三路', product: '梦幻帘', source: '小区推广', status: 'completed', createTime: '2026-05-05 16:25', remark: '安装完成', province: '浙江省' }
+ ]
+ },
+
+ saveCustomers(data) {
+ localStorage.setItem('customers', JSON.stringify(data))
+ },
+
+ getCustomerById(id) {
+ const customers = this.getCustomers()
+ return customers.find(c => c.id === id)
+ },
+
+ // 订单数据
+ getOrder() {
+ return JSON.parse(localStorage.getItem('orders') || '[]') || this.getDefaultOrders()
+ },
+
+ getDefaultOrders() {
+ return [
+ { id: 'O20260501001', customerId: 'C001', customer: '张三', products: [{ productName: '卷帘', model: 'JL-001', quantity: 2 }], amount: 3500, status: 'pending', rep: 'rep1', timeCycle: 7, createTime: '2026-05-01 14:30' },
+ { id: 'O20260502002', customerId: 'C002', customer: '李四', products: [{ productName: '遮阳窗帘', model: 'ZY-002', quantity: 3 }], amount: 8900, status: 'processing', rep: 'rep2', timeCycle: 10, createTime: '2026-05-02 10:15' },
+ { id: 'O20260503003', customerId: 'C003', customer: '王五', products: [{ productName: '蜂巢帘', model: 'FC-001', quantity: 1 }], amount: 2200, status: 'completed', rep: 'rep3', timeCycle: 5, createTime: '2026-05-03 16:45' },
+ { id: 'O20260504004', customerId: 'C004', customer: '赵六', products: [{ productName: '梦幻帘', model: 'MH-001', quantity: 2 }], amount: 15600, status: 'shipped', rep: 'rep4', timeCycle: 12, createTime: '2026-05-04 09:20' },
+ { id: 'O20260505005', customerId: 'C005', customer: '钱七', products: [{ productName: '安全门窗', model: 'AQ-001', quantity: 1 }], amount: 4500, status: 'pending', rep: 'rep1', timeCycle: 8, createTime: '2026-05-05 11:30' }
+ ]
+ },
+
+ saveOrders(data) {
+ localStorage.setItem('orders', JSON.stringify(data))
+ },
+
+ getOrderById(id) {
+ const orders = this.getOrder()
+ return orders.find(o => o.id === id)
+ },
+
+ // 生产任务数据
+ getProductionTask() {
+ return JSON.parse(localStorage.getItem('productionTasks') || '[]') || this.getDefaultProductionTasks()
+ },
+
+ getDefaultProductionTasks() {
+ return [
+ { id: 'PT001', orderId: 'O20260501001', taskName: '卷帘制作', assignee: '张师傅', planStart: '2026-05-06', planEnd: '2026-05-08', status: 'pending', progress: 0 },
+ { id: 'PT002', orderId: 'O20260502002', taskName: '遮阳窗帘制作', assignee: '李师傅', planStart: '2026-05-06', planEnd: '2026-05-10', status: 'processing', progress: 60 },
+ { id: 'PT003', orderId: 'O20260503003', taskName: '蜂巢帘制作', assignee: '王师傅', planStart: '2026-05-04', planEnd: '2026-05-06', status: 'completed', progress: 100 }
+ ]
+ },
+
+ saveProductionTasks(data) {
+ localStorage.setItem('productionTasks', JSON.stringify(data))
+ },
+
+ // 安装任务数据
+ getInstallation() {
+ return JSON.parse(localStorage.getItem('installations') || '[]') || this.getDefaultInstallations()
+ },
+
+ getDefaultInstallations() {
+ return [
+ { id: 'IN001', orderId: 'O20260501001', duration: '2天', installer: '张师傅', remarks: '需要梯子', status: 'scheduled' },
+ { id: 'IN002', orderId: 'O20260502002', duration: '3天', installer: '李师傅', remarks: '高层建筑', status: 'scheduled' },
+ { id: 'IN003', orderId: 'O20260503003', duration: '1天', installer: '王师傅', remarks: '', status: 'completed' }
+ ]
+ },
+
+ saveInstallations(data) {
+ localStorage.setItem('installations', JSON.stringify(data))
+ },
+
+ // 测量师数据
+ getSurveyors() {
+ return [
+ { id: 'S001', name: '张测量', phone: '13800001111', status: 'active' },
+ { id: 'S002', name: '李测量', phone: '13900002222', status: 'active' },
+ { id: 'S003', name: '王测量', phone: '13700003333', status: 'inactive' }
+ ]
+ },
+
+ // 安装师傅数据
+ getInstallers() {
+ return [
+ { id: 'I001', name: '张师傅', phone: '13600001111', status: 'active' },
+ { id: 'I002', name: '李师傅', phone: '13500002222', status: 'active' },
+ { id: 'I003', name: '王师傅', phone: '13400003333', status: 'active' }
+ ]
+ },
+
+ // 产品数据
+ getProducts() {
+ return [
+ { id: 'P001', name: '卷帘', category: '窗帘', price: 350, unit: '平方米' },
+ { id: 'P002', name: '遮阳窗帘', category: '窗帘', price: 580, unit: '平方米' },
+ { id: 'P003', name: '蜂巢帘', category: '窗帘', price: 420, unit: '平方米' },
+ { id: 'P004', name: '梦幻帘', category: '窗帘', price: 680, unit: '平方米' },
+ { id: 'P005', name: '纱窗', category: '窗纱', price: 280, unit: '平方米' }
+ ]
+ },
+
+ // 预约数据
+ getAppointments() {
+ return JSON.parse(localStorage.getItem('appointments') || '[]') || []
+ },
+
+ saveAppointments(data) {
+ localStorage.setItem('appointments', JSON.stringify(data))
+ },
+
+ // 测量任务数据
+ getMeasurementTasks() {
+ return JSON.parse(localStorage.getItem('measurementTasks') || '[]') || this.getDefaultMeasurementTasks()
+ },
+
+ getDefaultMeasurementTasks() {
+ return [
+ { id: 'M001', customerId: 'C001', customerName: '张三', address: '北京市朝阳区建国路88号', surveyor: '张测量', appointmentTime: '2026-05-06 10:00', status: 'pending', createTime: '2026-05-05 09:00' },
+ { id: 'M002', customerId: 'C002', customerName: '李四', address: '上海市浦东新区陆家嘴金融中心', surveyor: '李测量', appointmentTime: '2026-05-06 14:00', status: 'in-progress', createTime: '2026-05-05 10:00' },
+ { id: 'M003', customerId: 'C003', customerName: '王五', address: '广州市天河区珠江新城', surveyor: '张测量', appointmentTime: '2026-05-05 09:00', status: 'completed', createTime: '2026-05-04 15:00' }
+ ]
+ },
+
+ saveMeasurementTasks(data) {
+ localStorage.setItem('measurementTasks', JSON.stringify(data))
+ },
+
+ // 渠道数据
+ getChannels() {
+ return JSON.parse(localStorage.getItem('channels') || '[]') || this.getDefaultChannels()
+ },
+
+ getDefaultChannels() {
+ return [
+ { id: 1, name: '线上推广', cost: 15000, leads: 120, conversion: 25 },
+ { id: 2, name: '线下门店', cost: 8000, leads: 85, conversion: 32 },
+ { id: 3, name: '老客户介绍', cost: 2000, leads: 45, conversion: 45 },
+ { id: 4, name: '小区推广', cost: 5000, leads: 60, conversion: 18 },
+ { id: 5, name: '电话营销', cost: 3000, leads: 90, conversion: 12 }
+ ]
+ },
+
+ saveChannels(data) {
+ localStorage.setItem('channels', JSON.stringify(data))
+ },
+
+ getChannelById(id) {
+ const channels = this.getChannels()
+ return channels.find(c => c.id === id)
+ },
+
+ addChannel(channel) {
+ const channels = this.getChannels()
+ const maxId = channels.length > 0 ? Math.max(...channels.map(c => c.id)) : 0
+ const newChannel = { ...channel, id: maxId + 1 }
+ channels.push(newChannel)
+ this.saveChannels(channels)
+ return newChannel
+ },
+
+ updateChannel(id, data) {
+ const channels = this.getChannels()
+ const index = channels.findIndex(c => c.id === id)
+ if (index !== -1) {
+ channels[index] = { ...channels[index], ...data }
+ this.saveChannels(channels)
+ return channels[index]
+ }
+ return null
+ },
+
+ deleteChannel(id) {
+ const channels = this.getChannels()
+ const filtered = channels.filter(c => c.id !== id)
+ this.saveChannels(filtered)
+ return true
+ },
+
+ // 满意度调查数据
+ getSurveys() {
+ return JSON.parse(localStorage.getItem('surveys') || '[]') || this.getDefaultSurveys()
+ },
+
+ getDefaultSurveys() {
+ return [
+ { id: 1, customerName: '张伟', orderId: 'O20260501001', sendDate: '2026-05-01', rating: 5, feedback: '安装师傅很专业,窗帘效果很好!', status: '已反馈' },
+ { id: 2, customerName: '李娜', orderId: 'O20260502002', sendDate: '2026-05-02', rating: 4, feedback: '颜色稍微有点偏差,整体满意。', status: '已反馈' },
+ { id: 3, customerName: '王强', orderId: 'O20260503003', sendDate: '2026-05-03', rating: null, feedback: null, status: '未反馈' },
+ { id: 4, customerName: '赵敏', orderId: 'O20260504004', sendDate: '2026-05-04', rating: 5, feedback: '非常满意,已推荐朋友。', status: '已反馈' },
+ { id: 5, customerName: '陈磊', orderId: 'O20260505005', sendDate: '2026-05-05', rating: 3, feedback: '轨道安装有点松动,后来修好了。', status: '已反馈' }
+ ]
+ },
+
+ saveSurveys(data) {
+ localStorage.setItem('surveys', JSON.stringify(data))
+ },
+
+ // 售后数据
+ getAfterSales() {
+ return JSON.parse(localStorage.getItem('afterSales') || '[]') || this.getDefaultAfterSales()
+ },
+
+ getDefaultAfterSales() {
+ return [
+ { id: 'AS001', orderId: 'O20260501001', customer: '张三', issue: '安装尺寸问题', status: 'done', person: '张师傅', createTime: '2026-05-02' },
+ { id: 'AS002', orderId: 'O20260502002', customer: '李四', issue: '面料色差', status: 'processing', person: '李师傅', createTime: '2026-05-03' },
+ { id: 'AS003', orderId: 'O20260503003', customer: '王五', issue: '轨道松动', status: 'pending', person: '王师傅', createTime: '2026-05-04' }
+ ]
+ },
+
+ saveAfterSales(data) {
+ localStorage.setItem('afterSales', JSON.stringify(data))
+ },
+
+ // 报价数据
+ getQuotes() {
+ return JSON.parse(localStorage.getItem('quotes') || '[]') || this.getDefaultQuotes()
+ },
+
+ getDefaultQuotes() {
+ return [
+ { id: 'Q001', customerId: 'C001', customerName: '张三', amount: 3500, status: 'pending', createTime: '2026-05-01' },
+ { id: 'Q002', customerId: 'C002', customerName: '李四', amount: 8900, status: 'accepted', createTime: '2026-05-02' },
+ { id: 'Q003', customerId: 'C003', customerName: '王五', amount: 2200, status: 'rejected', createTime: '2026-05-03' }
+ ]
+ },
+
+ saveQuotes(data) {
+ localStorage.setItem('quotes', JSON.stringify(data))
+ },
+
+ // 采购订单数据
+ getPurchaseOrders() {
+ return JSON.parse(localStorage.getItem('purchaseOrders') || '[]') || this.getDefaultPurchaseOrders()
+ },
+
+ getDefaultPurchaseOrders() {
+ return [
+ { id: 'PO001', material: '窗帘布料', quantity: 100, price: 50, supplier: '供应商A', status: 'completed' },
+ { id: 'PO002', material: '轨道配件', quantity: 200, price: 15, supplier: '供应商B', status: 'pending' }
+ ]
+ },
+
+ savePurchaseOrders(data) {
+ localStorage.setItem('purchaseOrders', JSON.stringify(data))
+ }
+}
+
+export default AppData
diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue
new file mode 100644
index 0000000..31e536f
--- /dev/null
+++ b/src/views/Dashboard.vue
@@ -0,0 +1,868 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+ 首页
+
+ /
+ 经营仪表盘
+
+
+
+
+
+
+
+
+
+
{{ kpi.title }}
+
+ {{ kpi.value }}
+ {{ kpi.unit }}
+
+
+
+ {{ Math.abs(kpi.trend) }}% 较上期
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 订单号
+ 客户
+ 金额(¥)
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+ {{ order.id }}
+ {{ order.customer }}
+ {{ order.amount.toLocaleString() }}
+
+
+ {{ getStatusText(order.status) }}
+
+
+ {{ order.createTime }}
+
+ 查看
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ todo.title }}
+
+ {{ todo.type }}
+ {{ todo.time }}
+
+
+
紧急
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建订单
+
+
+
+
+
+ 添加客户
+
+
+
+
+
+ 产品管理
+
+
+
+
+
+ 报表中心
+
+
+
+
+
+ 库存管理
+
+
+
+
+
+ 系统设置
+
+
+
+
+
+
+
+
+
diff --git a/src/views/Login.vue b/src/views/Login.vue
new file mode 100644
index 0000000..a5ee6e9
--- /dev/null
+++ b/src/views/Login.vue
@@ -0,0 +1,766 @@
+
+
+
+
+
+
+
+
+
+ 用户名
+
+
+
+ {{ errors.username }}
+
+
+
+
+
+
+
+
+
+
+
+ 登录
+
+
+ 登录中...
+
+
+
+
+
+
+
+ 演示账号
+
+
+
+
+ 管理员
+
+ admin / admin123
+
+
+
+ 普通用户
+
+ user / user123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
diff --git a/src/views/NotFound.vue b/src/views/NotFound.vue
new file mode 100644
index 0000000..75231b4
--- /dev/null
+++ b/src/views/NotFound.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
404
+
页面未找到
+
+ 返回首页
+
+
+
+
+
+
+
+
diff --git a/src/views/Workflow.vue b/src/views/Workflow.vue
new file mode 100644
index 0000000..88c845b
--- /dev/null
+++ b/src/views/Workflow.vue
@@ -0,0 +1,583 @@
+
+
+
+
+
+
+
+
+
特别说明:
+
1. "客户满意度调查表"在安装完成一周后自动发送给客户
+
2. "复尺"和"售后维修"为条件分支,根据实际需要执行
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
安装后流程
+
+
+
+ 安装完成一周后自动发送给客户
+
+
+
+
+
+
+
+
+
+
+ 播放流程动画
+
+
+ 重置动画
+
+
+
+
+
+
+
+
diff --git a/src/views/aftersale/AfterSaleArrange.vue b/src/views/aftersale/AfterSaleArrange.vue
new file mode 100644
index 0000000..820d73c
--- /dev/null
+++ b/src/views/aftersale/AfterSaleArrange.vue
@@ -0,0 +1,871 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ stats.total }}
+
总维修任务
+
+
+
+
+
+
+
+
{{ stats.pending }}
+
待处理
+
+
+
+
+
+
+
+
{{ stats.inProgress }}
+
进行中
+
+
+
+
+
+
+
+
{{ stats.completed }}
+
已完成
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 任务编号
+ 客户姓名
+ 联系电话
+ 安装地址
+ 问题描述
+ 维修师傅
+ 预约时间
+ 状态
+ 操作
+
+
+
+
+ {{ task.id }}
+ {{ task.customerName }}
+ {{ task.phone }}
+ {{ task.address }}
+ {{ task.issue }}
+
+
+ {{ task.installer }}
+
+
+ {{ task.appointmentTime }}
+
+
+ {{ getStatusText(task.status) }}
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无维修任务
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 安装地址
+
+
+
+ 问题描述
+
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/aftersale/AfterSaleReason.vue b/src/views/aftersale/AfterSaleReason.vue
new file mode 100644
index 0000000..1e57f27
--- /dev/null
+++ b/src/views/aftersale/AfterSaleReason.vue
@@ -0,0 +1,833 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ stats.weekCount }}
+
本周售后
+
+
+
+
+
+
+
+
{{ stats.staffCount }}
+
责任人(在岗)
+
+
+
+
+
+
+
+
{{ stats.pendingCount }}
+
待定责
+
+
+
+
+
+
+
+
{{ stats.closeRate }}%
+
闭环率
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 返回收尾款
+
+
+ 下一步:数据分析中心
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/analysis/AnalysisCenter.vue b/src/views/analysis/AnalysisCenter.vue
new file mode 100644
index 0000000..797a099
--- /dev/null
+++ b/src/views/analysis/AnalysisCenter.vue
@@ -0,0 +1,895 @@
+
+
+
+
+
+
+
+
+
+
+
+
{{ kpi.title }}
+
+ {{ kpi.value }}
+ {{ kpi.unit }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 业务员
+ 营业额(元)
+ 测量数
+ 成交单
+ 转化率
+ 错误率
+ 金额区间订单分布
+
+
+
+
+
+
+
+ {{ staff.name }}
+
+ {{ staff.revenue.toLocaleString() }}
+ {{ staff.measurements }}
+ {{ staff.orders }}
+
+ {{ staff.conversionRate }}%
+
+
+
+ {{ staff.errorRate }}%
+
+
+ {{ staff.amountRange.lt1k }}
+ {{ staff.amountRange.range1k5k }}
+ {{ staff.amountRange.gt5k }}
+
+
+ 暂无业务员数据
+
+
+
+
+
+ 金额区间分布:订单按最终成交金额划分,反映各业务员接单能力。
+
+
+
+
+
+
+
+
+
+
+
+
+ 业务员
+ 布艺窗帘
+ 卷帘
+ 罗马帘
+ 百叶窗
+
+
+
+
+ {{ item.name }}
+ {{ item.curtain }}%
+ {{ item.roller }}%
+ {{ item.roman }}%
+ {{ item.blind }}%
+
+
+
+
+
+ {{ topPerformer }} 在布艺窗帘上转化率突出,可分享经验。
+
+
+
+
+
+
+
+
+
+
+ 渠道
+ 测量数
+ 成交数
+ 渠道成交额
+
+
+
+
+ {{ channel.name }}
+ {{ channel.measurements }}
+ {{ channel.deals }}
+ ¥{{ channel.revenue.toLocaleString() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 产品
+ 销量(件)
+ 营业额(元)
+
+
+
+
+ {{ product.name }}
+ {{ product.quantity }}
+ ¥{{ product.revenue.toLocaleString() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 市场活动线索: {{ marketStats.leads }}
+ 成交额: {{ marketStats.revenue }}万
+ ROI (市场): {{ marketStats.roi }}
+ 新客占比: {{ marketStats.newCustomerRate }}%
+
+
+
+
+
+ 本月利润: {{ companyStats.profit }}万
+ 利润率: {{ companyStats.profitRate }}%
+ 完成目标 {{ companyStats.targetRate }}%
+ 工厂产能: {{ companyStats.capacity }}%
+
+
+
+
+
+
+
+
+
+
+
{{ stat.label }}
+
{{ stat.value }}
+
+
+
+
+ 主要售后问题:{{ topAfterSalesIssues }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/analysis/ChannelManage.vue b/src/views/analysis/ChannelManage.vue
new file mode 100644
index 0000000..182b74c
--- /dev/null
+++ b/src/views/analysis/ChannelManage.vue
@@ -0,0 +1,611 @@
+
+
+
+
+
+
+
+
+
+
+
{{ stats.totalChannels }}
+
渠道总数
+
+
+
+
+
+
{{ stats.totalLeads }}
+
总线索数
+
+
+
+
+
+
{{ stats.avgConversion }}%
+
平均转化率
+
+
+
+
+
+
¥{{ stats.totalCost.toLocaleString() }}
+
总投入成本
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 渠道名称
+ 成本(¥)
+ 线索数
+ 转化率
+ 预计成交数
+ ROI
+ 操作
+
+
+
+
+ {{ channel.id }}
+
+
+
+ {{ channel.name }}
+
+
+ ¥{{ channel.cost.toLocaleString() }}
+ {{ channel.leads }}
+
+
+ {{ channel.conversion }}%
+
+
+ {{ Math.floor(channel.leads * channel.conversion / 100) }}
+
+
+ {{ calculateRoi(channel) }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无渠道数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/analysis/ReportAnalysis.vue b/src/views/analysis/ReportAnalysis.vue
new file mode 100644
index 0000000..cae4335
--- /dev/null
+++ b/src/views/analysis/ReportAnalysis.vue
@@ -0,0 +1,770 @@
+
+
+
+
+
+
+
+
+
+
+
+ 业务员绩效
+
+
+
+
+
+ 业务员
+ 成交订单数
+ 总销售额(¥)
+ 平均客单价(¥)
+ 售后数
+
+
+
+
+
+
+ {{ staff.name }}
+
+ {{ staff.orders }}
+ ¥{{ staff.revenue.toLocaleString() }}
+ ¥{{ staff.avgOrderValue }}
+
+
+ {{ staff.afterSales }}
+
+
+
+
+ 暂无业务员数据
+
+
+
+
+
+
+
+
+
+
+
+ 售后概况
+
+
+
+
待处理
+
{{ afterSalesStats.pending }}
+
+
+
处理中
+
{{ afterSalesStats.processing }}
+
+
+
已完成
+
{{ afterSalesStats.completed }}
+
+
+
+ 主要问题:
+ {{ afterSalesStats.topIssue }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 财务报表
+
+
+
+
+
+ 财务指标
+
+
+
+
总收入
+
¥{{ financeStats.revenue.toLocaleString() }}
+
+
+
总支出
+
¥{{ financeStats.expense.toLocaleString() }}
+
+
+
利润
+
¥{{ financeStats.profit.toLocaleString() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/analysis/SatisfactionSurvey.vue b/src/views/analysis/SatisfactionSurvey.vue
new file mode 100644
index 0000000..25fafb7
--- /dev/null
+++ b/src/views/analysis/SatisfactionSurvey.vue
@@ -0,0 +1,661 @@
+
+
+
+
+
+
+
+
+
+
+
{{ stats.total }}
+
总调查数
+
+
+
+
+
+
{{ stats.responded }}
+
已反馈
+
+
+
+
+
+
{{ stats.pending }}
+
待反馈
+
+
+
+
+
+
{{ stats.avgRating }}
+
平均评分
+
+
+
+
+
+
+
+ 全部状态
+ 已反馈
+ 未反馈
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+ 暂无评分
+
+
+ {{ survey.feedback }}
+
+
+ 客户尚未反馈
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择客户
+
+ 请选择客户
+
+ {{ c.name }} - {{ c.phone }}
+
+
+
+
+ 关联订单
+
+ 请选择订单
+
+ {{ o.id }} - {{ o.customer }} (¥{{ o.amount }})
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 客户姓名:
+ {{ currentSurvey.customerName }}
+
+
+ 订单编号:
+ {{ currentSurvey.orderId }}
+
+
+ 发送时间:
+ {{ currentSurvey.sendDate }}
+
+
+ 状态:
+
+ {{ currentSurvey.status }}
+
+
+
+
+
评分
+
+
+ {{ currentSurvey.rating }} 分
+
+
+
+
客户反馈
+
{{ currentSurvey.feedback }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/calendar/ScheduleCalendar.vue b/src/views/calendar/ScheduleCalendar.vue
new file mode 100644
index 0000000..b2b4093
--- /dev/null
+++ b/src/views/calendar/ScheduleCalendar.vue
@@ -0,0 +1,2304 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formatHour(h - 1) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ event.title }}
+
{{ formatTime(event.start) }} – {{ formatTime(event.end) }}
+
+ {{ event.worker }}
+ ·
+ {{ event.customer }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formatTime(event.start) }}
+ {{ event.title }}
+
+
+ +{{ day.events.length - 4 }} 更多
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ day.dayName }}
+ {{ day.dayNum }}
+ {{ day.month }}
+
+
+
+
+
+
{{ event.title }}
+
{{ formatTime(event.start) }} – {{ formatTime(event.end) }}
+
+ {{ event.worker }}
+ · {{ event.customer }}
+
+
+
+
暂无日程
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标题
+
+
+
+
+
+
+ 全天事件
+
+
+
+ 重复
+
+ 不重复
+ 每天
+ 每周
+ 每两周
+ 每月
+ 每年
+
+
+
+
+ 地点
+
+
+
+ 描述
+
+
+
+ 提醒
+
+ 无提醒
+ 5分钟前
+ 10分钟前
+ 15分钟前
+ 30分钟前
+ 1小时前
+ 1天前
+
+
+
+ 冲突: {{ conflictWarning }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formatDateRange(detailEvent) }}
+
+
+
+ {{ detailEvent.worker }}
+
+
+
+ {{ detailEvent.customer }}
+
+
+
+ {{ detailEvent.location }}
+
+
+
{{ detailEvent.description }}
+
+
+ 检测到时间冲突
+
+
+
+
+
+
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
diff --git a/src/views/customer/Appointment.vue b/src/views/customer/Appointment.vue
new file mode 100644
index 0000000..1d5bebe
--- /dev/null
+++ b/src/views/customer/Appointment.vue
@@ -0,0 +1,1059 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
📅
+
+
{{ stats.total }}
+
总预约
+
+
+
+
📌
+
+
{{ stats.today }}
+
今日
+
+
+
+
⏰
+
+
{{ stats.pending }}
+
待测量
+
+
+
+
✅
+
+
{{ stats.completed }}
+
已完成
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 预约编号
+ 客户姓名
+ 联系电话
+ 地址
+ 测量师
+ 预约时间
+ 业务类型
+ 状态
+ 操作
+
+
+
+
+ {{ appointment.id }}
+ {{ appointment.customerName }}
+ {{ appointment.customerPhone }}
+ {{ appointment.address }}
+ {{ getSurveyorName(appointment.personId) }}
+ {{ appointment.date }} {{ appointment.timeSlot }}
+
+
+ {{ getBusinessTypeText(appointment.businessType) }}
+
+
+
+
+ {{ getStatusText(appointment.status) }}
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 备注
+
+
+
+
+
+
+ 提醒设置
+
+
+ 内部提醒
+
+ 提前15分钟
+ 提前30分钟
+ 提前1小时
+ 提前2小时
+
+
+
+ 客户短信提醒
+
+ 提前1天
+ 提前12小时
+ 提前6小时
+ 提前2小时
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确定要删除预约 {{ deleteTarget?.customerName }} 吗?
+
+
+ 此操作不可恢复
+
+
+
+
+
+
+
+
+
+ 下一步:测量报价
+
+
+
+
+
+
+
+
diff --git a/src/views/customer/CustomerList.vue b/src/views/customer/CustomerList.vue
new file mode 100644
index 0000000..a46712b
--- /dev/null
+++ b/src/views/customer/CustomerList.vue
@@ -0,0 +1,812 @@
+
+
+
+
+
+ 首页
+
+ /
+ 客户管理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 客户编号
+ 客户姓名
+ 联系电话
+ 地址
+ 需求产品
+ 来源
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+ {{ customer.id }}
+ {{ customer.name }}
+ {{ customer.phone }}
+ {{ customer.address }}
+ {{ customer.product }}
+ {{ customer.source }}
+
+
+ {{ getStatusText(customer.status) }}
+
+
+ {{ customer.createTime }}
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ customers.length }}
+
总客户数
+
+
+
+
+
+
+
+
{{ stats.newCount }}
+
新客户
+
+
+
+
+
+
+
+
{{ stats.measuredCount }}
+
已测量
+
+
+
+
+
+
+
+
{{ stats.quotedCount }}
+
已报价
+
+
+
+
+
+
+
+
{{ stats.orderedCount }}
+
已下单
+
+
+
+
+
+
+
+
{{ stats.completedCount }}
+
已完成
+
+
+
+
+
+
+ 客户状态分布
+
+
+
+ 新客户 {{ stats.newPercent }}%
+ 已测量 {{ stats.measuredPercent }}%
+ 已报价 {{ stats.quotedPercent }}%
+ 已下单 {{ stats.orderedPercent }}%
+ 已完成 {{ stats.completedPercent }}%
+
+
+
+
+
+ 客户来源分布
+
+
+
+
{{ s.name }}
+
+
{{ s.count }}人 ({{ s.percent }}%)
+
+
+
+
+
+
+
+
+
+
+
+ 客户姓名
+
+
+
+ 联系电话
+
+
+
+ 地址
+
+
+
+ 需求产品
+
+ 请选择产品
+ 卷帘
+ 遮阳窗帘
+ 纱窗
+ 蜂巢帘
+ 梦幻帘
+
+
+
+ 客户来源
+
+ 请选择来源
+ 线上咨询
+ 电话咨询
+ 老客户介绍
+ 线下门店
+ 小区推广
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确定要删除客户 {{ deleteTarget?.name }} 吗?
+
+
+ 此操作不可恢复
+
+
+
+
+
+
+
+
{{ toastMsg }}
+
+
+
+
+
+
diff --git a/src/views/customer/MeasurementManage.vue b/src/views/customer/MeasurementManage.vue
new file mode 100644
index 0000000..440841e
--- /dev/null
+++ b/src/views/customer/MeasurementManage.vue
@@ -0,0 +1,1431 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 任务编号
+ 客户姓名
+ 联系电话
+ 地址
+ 测量师
+ 预约时间
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+ {{ task.id }}
+ {{ task.customerName }}
+ {{ task.customerPhone }}
+ {{ task.address }}
+ {{ task.surveyor }}
+ {{ task.appointmentTime }}
+
+
+ {{ getStatusText(task.status) }}
+
+
+ {{ task.createdAt }}
+
+
+
+ 查看
+
+
+ 开始
+
+
+ 录入数据
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 数据编号
+ 关联任务
+ 客户
+ 测量项目
+ 宽度(cm)
+ 高度(cm)
+ 数量
+ 测量师
+ 测量时间
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.taskId }}
+ {{ item.customer }}
+ {{ item.item }}
+ {{ item.width }}
+ {{ item.height }}
+ {{ item.quantity }}
+ {{ item.surveyor }}
+ {{ item.measuredAt }}
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ stats.total }}
+
总测量任务
+
+
+
{{ stats.pending }}
+
待测量
+
+
+
{{ stats.inProgress }}
+
测量中
+
+
+
{{ stats.completed }}
+
已完成
+
+
+
+
+
+
测量师工作量
+
+
+
{{ item.name }}
+
+
{{ item.count }} 单
+
+
+
+
+
近期测量任务
+
+
+
+
+ 任务编号
+ 客户
+ 测量师
+ 状态
+ 时间
+
+
+
+
+ {{ task.id }}
+ {{ task.customerName }}
+ {{ task.surveyor }}
+
+
+ {{ getStatusText(task.status) }}
+
+
+ {{ task.appointmentTime }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择客户
+
+ 请选择客户
+
+ {{ c.name }} - {{ c.address }}
+
+
+
+
+ 测量师
+
+ 请选择测量师
+
+ {{ s.name }}
+
+
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 客户:
+ {{ currentTask?.customerName || '-' }}
+
+
+ 地址:
+ {{ currentTask?.address || '-' }}
+
+
+ 测量师:
+ {{ currentTask?.surveyor || '-' }}
+
+
+
+
+ 测量项目
+
+
+
+
+
+
+ 项目类型
+
+ 请选择
+ 窗帘
+ 窗纱
+ 百叶帘
+ 卷帘
+
+
+
+ 宽度 (cm)
+
+
+
+ 高度 (cm)
+
+
+
+ 数量
+
+
+
+ 备注
+
+
+
+
+
+
+ 添加窗户
+
+
+
+
+
+
+
+
+
+
+
+
+ 确定要删除{{ deleteType === 'task' ? '任务' : '数据' }} {{ deleteTarget?.customerName || deleteTarget?.id }} 吗?
+
+
+ 此操作不可恢复
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/finance/FinalPayment.vue b/src/views/finance/FinalPayment.vue
new file mode 100644
index 0000000..e719619
--- /dev/null
+++ b/src/views/finance/FinalPayment.vue
@@ -0,0 +1,645 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+ 待收尾款总额 {{ formatMoney(stats.totalPending) }}
+
+
+
+ 已完成订单 {{ stats.completedCount }} 笔
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编号
+ 金额 (¥)
+ 订单明细
+ 定金 (¥)
+ 尾款 (¥)
+
+
+
+
+
+ {{ order.orderNo }}
+
+ {{ order.amount.toFixed(2) }}
+ {{ order.detail }}
+ {{ order.deposit.toFixed(2) }}
+
+
+ 已付清
+
+
+ ¥{{ order.final.toFixed(2) }}
+
+ 收款
+
+
+
+
+
+ 暂无已完成订单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 下一步:售后原因
+
+
+
+
+
+
+
+
diff --git a/src/views/finance/Invoice.vue b/src/views/finance/Invoice.vue
new file mode 100644
index 0000000..baa6122
--- /dev/null
+++ b/src/views/finance/Invoice.vue
@@ -0,0 +1,413 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 发票号
+ 订单号
+ 金额(¥)
+ 状态
+ 开票日期
+ 到期日
+ 付款日期
+ 操作
+
+
+
+
+ {{ invoice.id }}
+ {{ invoice.invoiceNo }}
+ {{ invoice.orderId }}
+ {{ invoice.amount.toFixed(2) }}
+
+
+ {{ getStatusText(invoice.status) }}
+
+
+ {{ invoice.createDate }}
+ {{ invoice.dueDate || '-' }}
+ {{ invoice.paidDate || '-' }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无发票数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/install/InstallAdd.vue b/src/views/install/InstallAdd.vue
new file mode 100644
index 0000000..3edb986
--- /dev/null
+++ b/src/views/install/InstallAdd.vue
@@ -0,0 +1,473 @@
+
+
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 创建安装任务
+
+
+ 返回
+
+
+
+
+
+
+
+
+
diff --git a/src/views/install/InstallComplete.vue b/src/views/install/InstallComplete.vue
new file mode 100644
index 0000000..2e99f98
--- /dev/null
+++ b/src/views/install/InstallComplete.vue
@@ -0,0 +1,589 @@
+
+
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
+
+ 安装任务板
+
+
+ 安装人员排班
+
+
+ 安装完成
+
+
+
+
+
+
+
+
+
+
+
+
+ 工单号
+ 客户名称
+ 产品信息
+ 安装日期
+ 安装师傅
+ 状态
+ 操作
+
+
+
+
+ {{ task.id }}
+ {{ task.customerName }}
+ {{ task.product }}
+ {{ task.installDate }}
+ {{ task.installer }}
+
+
+ {{ getStatusText(task.status) }}
+
+
+
+
+
+ 详情
+
+
+ 完成
+
+
+ 发送账单
+
+
+
+
+
+ 暂无安装任务
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 工单号
+ {{ currentTask.id }}
+
+ 客户名称
+ {{ currentTask.customerName }}
+
+ 联系电话
+ {{ currentTask.customerPhone }}
+
+ 安装地址
+ {{ currentTask.customerAddress }}
+
+ 产品信息
+ {{ currentTask.product }}
+
+ 安装日期
+ {{ currentTask.installDate }}
+
+ 安装时间
+ {{ currentTask.installTime }}
+
+ 安装师傅
+ {{ currentTask.installer }}
+
+ 状态
+
+
+ {{ getStatusText(currentTask.status) }}
+
+
+
+ 备注
+ {{ currentTask.remarks || '无' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 售后问题
+
+
+ 下一步:收尾款
+
+
+
+
+
+
+
+
diff --git a/src/views/install/InstallSchedule.vue b/src/views/install/InstallSchedule.vue
new file mode 100644
index 0000000..e3053de
--- /dev/null
+++ b/src/views/install/InstallSchedule.vue
@@ -0,0 +1,585 @@
+
+
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
+
+ 安装任务板
+
+
+ 安装人员排班
+
+
+ 安装完成
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 返回任务板
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/install/InstallTask.vue b/src/views/install/InstallTask.vue
new file mode 100644
index 0000000..283c098
--- /dev/null
+++ b/src/views/install/InstallTask.vue
@@ -0,0 +1,701 @@
+
+
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
+
+ 安装任务板
+
+
+ 安装人员排班
+
+
+ 安装完成
+
+
+
+
+
+
+
+
+
+
+
+
+ 工单号
+ 客户名称
+ 窗帘产品
+ 安装日期
+ 安装时间
+ 安装师傅
+ 状态
+ 操作
+
+
+
+
+ {{ inst.id }}
+ {{ inst.customerName }}
+ {{ inst.product }}
+ {{ inst.installDate }}
+ {{ inst.installTime }}
+ {{ inst.installer }}
+
+
+ {{ getStatusText(inst.status) }}
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+ 暂无安装工单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 工单号
+ {{ detailData.id }}
+
+ 客户名称
+ {{ detailData.customerName }}
+
+ 联系电话
+ {{ detailData.customerPhone }}
+
+ 安装地址
+ {{ detailData.customerAddress }}
+
+ 窗帘产品
+ {{ detailData.product }}
+
+ 安装日期
+ {{ detailData.installDate }}
+
+ 安装时间
+ {{ detailData.installTime }}
+
+ 安装师傅
+ {{ detailData.installer }}
+
+ 状态
+
+
+ {{ getStatusText(detailData.status) }}
+
+
+
+ 备注
+ {{ detailData.remarks || '无' }}
+
+
+
+
+
+
+
+
+
+
+ 安装完成(去收尾款)
+
+
+ 售后问题
+
+
+
+
+
+
+
+
diff --git a/src/views/order/OrderDeal.vue b/src/views/order/OrderDeal.vue
new file mode 100644
index 0000000..9ccfaf2
--- /dev/null
+++ b/src/views/order/OrderDeal.vue
@@ -0,0 +1,514 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编号
+ 金额 (¥)
+ 窗帘类型
+ 布料 / 规格
+ 定金 (¥)
+ 邮编 / 地区
+ 计时 (天)
+ 复尺天数
+ 操作
+ 复尺
+
+
+
+
+ {{ order.orderNo }}
+ {{ order.amount }}
+ {{ order.type }}
+ {{ order.fabric }}
+ {{ order.deposit }}
+ {{ order.postcode }}
+
+ {{ order.timing }}
+ 点击
+
+ {{ order.remeasure }}
+
+
+ 确认复尺
+
+
+ {{ order.remeasure }}
+
+
+
+ 暂无{{ activeTab === 'completed' ? '成交' : '待复尺' }}订单
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 按邮编分类 (同地区聚合)
+
+
+ 按计时从多到少排序
+
+
+
+
+
+
+
+
+
+
+ 下一步:生产采购
+
+
+
+
+
+
+
+
diff --git a/src/views/order/OrderDetail.vue b/src/views/order/OrderDetail.vue
new file mode 100644
index 0000000..d6173d5
--- /dev/null
+++ b/src/views/order/OrderDetail.vue
@@ -0,0 +1,580 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+ 客户 & 安装信息
+
+
+
+ 客户名称
+ {{ order.customerName }}
+
+
+ 联系电话
+ {{ order.phone }}
+
+
+ 安装地址
+ {{ order.address }}
+
+
+ 窗户数量
+ {{ order.windows }}
+
+
+ 订单金额
+ ¥ {{ order.amount?.toLocaleString() }}
+
+
+ 下单时间
+ {{ order.createTime }}
+
+
+ 跟单员
+ {{ order.follower }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 产品/型号
+ 布料编号
+ 颜色
+ 尺寸 (宽×高) cm
+ 轨道类型
+ 数量
+ 单价(¥)
+ 小计(¥)
+
+
+
+
+ {{ product.name }}
+ {{ product.fabric }}
+ {{ product.color }}
+ {{ product.width }} × {{ product.height }}
+ {{ product.track }}
+ {{ product.quantity }}
+ ¥{{ product.price.toFixed(2) }}
+ ¥{{ (product.quantity * product.price).toFixed(2) }}
+
+
+ 暂无产品明细
+
+
+
+
+
+
+ 📦 共{{ productCount }}件产品 · 合计 ¥ {{ productTotal.toFixed(2) }}
+
+
+
+
+
+
+
+ 跟进记录 / 安装节点
+
+
+
+ 📝
+ 新增跟进记录
+
+
+
+
+
+
+
+
+
+ 返回跟单看板
+
+
+ 下一步:成交订单
+
+
+
+
+
+
+
+
diff --git a/src/views/order/OrderFollow.vue b/src/views/order/OrderFollow.vue
new file mode 100644
index 0000000..f25b5fd
--- /dev/null
+++ b/src/views/order/OrderFollow.vue
@@ -0,0 +1,415 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 下一步:成交订单
+
+
+
+
+
+
+
+
diff --git a/src/views/order/OrderList.vue b/src/views/order/OrderList.vue
new file mode 100644
index 0000000..b64bb98
--- /dev/null
+++ b/src/views/order/OrderList.vue
@@ -0,0 +1,408 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 订单号
+ 客户
+ 产品
+ 金额(¥)
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+ {{ order.id }}
+ {{ order.customer }}
+ {{ order.products.join(', ') }}
+ {{ order.amount.toLocaleString() }}
+
+
+ {{ getStatusText(order.status) }}
+
+
+ {{ order.createTime }}
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 客户名称
+
+
+
+ 产品信息
+
+
+
+
+ 订单金额 (¥)
+
+
+
+ 订单状态
+
+ 待处理
+ 生产中
+ 已安装
+ 已完成
+ 已取消
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/order/OrderRecheck.vue b/src/views/order/OrderRecheck.vue
new file mode 100644
index 0000000..f277ffa
--- /dev/null
+++ b/src/views/order/OrderRecheck.vue
@@ -0,0 +1,619 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+ 收定金起计时 · 点击「计时」按钮查看详情
+
+
+
+
+
+
+
+ 编号
+ 金额 (¥)
+ 窗帘类型
+ 布料 / 规格
+ 定金 (¥)
+ 邮编 / 地区
+ 计时 (天)
+ 复尺
+ 操作
+
+
+
+
+ {{ order.orderNo }}
+ {{ order.amount }}
+ {{ order.type }}
+ {{ order.fabric }}
+ {{ order.deposit }}
+ {{ order.postcode }}
+
+ {{ order.timing }}
+ 点击
+
+ {{ order.remeasure }}
+
+
+
+ 编辑
+
+
+ 确认
+
+
+ 删除
+
+
+
+
+
+ 暂无成交订单
+
+
+
+
+
+
+
+
+
+
+
+
+ 按邮编分类 (同地区聚合)
+
+
+ 按计时从多到少排序
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 下一步:生产采购
+
+
+
+
+
+
+
+
diff --git a/src/views/order/OrderTracking.vue b/src/views/order/OrderTracking.vue
new file mode 100644
index 0000000..5c2caf1
--- /dev/null
+++ b/src/views/order/OrderTracking.vue
@@ -0,0 +1,793 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 订单号
+ 客户姓名
+ 产品名称
+ 订单金额
+ 下单日期
+ 当前状态
+ 预计完成
+ 进度
+ 操作
+
+
+
+
+ {{ order.id }}
+ {{ order.customerName }}
+ {{ order.productName }}
+ ¥{{ order.amount?.toFixed(2) }}
+ {{ order.createDate }}
+
+
+ {{ getStatusText(order.status) }}
+
+
+ {{ order.expectedDate }}
+
+
+
+
{{ order.progress }}%
+
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无符合条件的订单
+
+
+
+
+
+
+
+
+
+
+
+
重要提醒
+
以下订单即将到期,请及时跟进:
+
+
+ 订单 #{{ reminder.id }} ({{ reminder.days }}天后到期)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 订单金额 (¥)
+
+
+
+ 订单状态
+
+ 待审批
+ 生产中
+ 已发货
+ 已完成
+ 已取消
+
+
+
+
+
+ 订单备注
+
+
+
+ 配送说明
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/product/ProductLibrary.vue b/src/views/product/ProductLibrary.vue
new file mode 100644
index 0000000..1aee302
--- /dev/null
+++ b/src/views/product/ProductLibrary.vue
@@ -0,0 +1,592 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 图片
+ 产品名称
+ 分类
+ 默认价格(¥)
+ 单位
+ 状态
+ 操作
+
+
+
+
+ {{ product.id }}
+
+
+
+ {{ product.name }}
+
+ {{ product.category }}
+
+ {{ product.defaultPrice.toFixed(2) }}
+ {{ product.unit }}
+
+
+ {{ product.active ? '启用' : '停用' }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无产品数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 产品名称
+
+
+
+
+ 分类
+
+ 窗帘
+ 配件
+ 电动
+
+
+
+ 默认价格 (¥)
+
+
+
+
+
+ 单位
+
+
+
+ 状态
+
+ 启用
+ 停用
+
+
+
+
+ 图片URL
+
+
+
+ 描述
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/product/ProductMaterial.vue b/src/views/product/ProductMaterial.vue
new file mode 100644
index 0000000..c8a362a
--- /dev/null
+++ b/src/views/product/ProductMaterial.vue
@@ -0,0 +1,554 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 名称
+ 类型
+ 规格
+ 单位
+ 单价(¥)
+ 供应商
+ 操作
+
+
+
+
+ {{ material.id }}
+ {{ material.name }}
+
+ {{ material.type }}
+
+ {{ material.spec || '-' }}
+ {{ material.unit || '-' }}
+ {{ material.price.toFixed(2) }}
+ {{ material.supplierName || '-' }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无物料数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 名称
+
+
+
+
+ 类型
+
+ 面料
+ 配件
+ 电机
+
+
+
+ 规格
+
+
+
+
+
+ 供应商
+
+ 请选择供应商
+
+ {{ s.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/production/ProductionPurchase.vue b/src/views/production/ProductionPurchase.vue
new file mode 100644
index 0000000..621d129
--- /dev/null
+++ b/src/views/production/ProductionPurchase.vue
@@ -0,0 +1,658 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 待处理
+ 生产中
+ 采购中
+ 已完成
+
+
+
+
+
+
+
+
+
+
+ 编号
+ 面料 / 组件
+ 颜色 / 型号
+ 幅宽 / 库存
+ 状态
+ 操作
+
+
+
+
+ {{ comp.id }}
+ {{ comp.fabric }}
+ {{ comp.color }}
+ {{ comp.stock }}
+
+
+ {{ getStatusText(comp.status) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无生产采购数据
+
+
+
+
+
+
+
+
+
+
+
+ 👆 点击行选中
+
+ {{ selectedIndex >= 0 ? `#${filteredComponents[selectedIndex]?.id} ${filteredComponents[selectedIndex]?.fabric} / ${filteredComponents[selectedIndex]?.color}` : '未选中任何组件' }}
+
+
+
双击行可查看详细
+
+
+
+
+
+ 启动生产
+
+
+ 采购申请
+
+
+ 打印看板
+
+
+
+
+
+ 📋 订单号 / 款式
+
+
+
+
+
+
+
+ 下一步:生产采购明细
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/production/ProductionPurchaseDetail.vue b/src/views/production/ProductionPurchaseDetail.vue
new file mode 100644
index 0000000..189dc23
--- /dev/null
+++ b/src/views/production/ProductionPurchaseDetail.vue
@@ -0,0 +1,761 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部类型
+ 生产 (自制)
+ 采购 (外购)
+
+
+
+
+
+
+
+
+
+
+
{{ kpiData.producing }}
+
生产中
+
+
+
+
+
+
{{ kpiData.purchasing }}
+
采购在途
+
+
+
+
+
+
{{ kpiData.warning }}
+
库存预警
+
+
+
+
+
+
{{ kpiData.completion }}%
+
完工率
+
+
+
+
+
+
+ 点击任意物料格 → 查看明细
+ 下方按钮模拟工厂操作
+ 今日计划执行中
+
+
+
+
+
+
+
+ 生产 (自制)
+ 采购 (外购)
+ 操作
+
+
+
+
+
+
+ {{ item.produceName }}
+ {{ item.produceQty }} {{ item.produceUnit }}
+
+
+
+
+ {{ item.purchaseName }}
+ {{ item.purchaseQty }} {{ item.purchaseUnit }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无生产采购数据
+
+
+
+
+
+
+
+
+
+
+
+ 生产完成
+
+
+ 采购入库
+
+
+ 计算物料表
+
+
+ 生成采购
+
+
+
+
+
+ 点击任何物料单元格均显示详细
+ 数据更新至 {{ updateTime }}
+ 下个批次计划: 13:00
+
+
+
+
+
+
+ 下一步:安装任务板
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/production/ProductionTask.vue b/src/views/production/ProductionTask.vue
new file mode 100644
index 0000000..695d47e
--- /dev/null
+++ b/src/views/production/ProductionTask.vue
@@ -0,0 +1,977 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+ 生产任务板
+
+
+ 生产采购
+
+
+ 生产采购明细
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 待处理
+ 生产中
+ 已完成
+
+
+ 全部订单
+
+ 订单 #{{ order.id }}
+
+
+
+
+
+
+
+
+ 总任务
+ {{ filteredTasks.length }}
+
+
+ 待处理
+ {{ getColumnTasks('pending').length }}
+
+
+ 生产中
+ {{ getColumnTasks('processing').length }}
+
+
+ 已完成
+ {{ getColumnTasks('completed').length }}
+
+
+
+
+
+
+
+
+ {{ column.title }}
+
+ {{ getColumnTasks(column.status).length }}
+
+
+
+
+
+
+
+
{{ task.taskName }}
+
+ {{ getOrderInfo(task.orderId) }} · 责任人: {{ task.assignee || '未分配' }}
+
+
+ 计划: {{ task.planStart }} 至 {{ task.planEnd }}
+
+
+
+
+
+
+
+
+
+
+
暂无任务
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/purchase/Inventory.vue b/src/views/purchase/Inventory.vue
new file mode 100644
index 0000000..5e6b321
--- /dev/null
+++ b/src/views/purchase/Inventory.vue
@@ -0,0 +1,698 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
{{ stats.totalItems }}
+
库存种类
+
+
+
+
+
+
{{ stats.lowStock }}
+
库存预警
+
+
+
+
+
+
{{ stats.normalStock }}
+
库存正常
+
+
+
+
+
+
{{ stats.totalQuantity }}
+
总库存量
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 物料名称
+ 当前库存
+ 预警阈值
+ 状态
+ 库位
+ 最后更新
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.materialName }}
+ {{ item.quantity }}
+ {{ item.threshold }}
+
+
+
+ {{ item.quantity < item.threshold ? '低于预警' : '正常' }}
+
+
+ {{ item.location || '-' }}
+ {{ item.lastUpdated }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无库存数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 物料
+
+ 请选择物料
+
+ {{ m.name }} ({{ m.type }})
+
+
+
+
+
+ 库位
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/purchase/PurchaseOrder.vue b/src/views/purchase/PurchaseOrder.vue
new file mode 100644
index 0000000..30242a6
--- /dev/null
+++ b/src/views/purchase/PurchaseOrder.vue
@@ -0,0 +1,684 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 采购单号
+ 供应商
+ 物料
+ 数量
+ 单价(¥)
+ 总价(¥)
+ 预计到货
+ 状态
+ 操作
+
+
+
+
+ {{ order.orderNo }}
+ {{ order.supplierName }}
+ {{ order.materialName }}
+ {{ order.quantity }}
+ {{ order.price.toFixed(2) }}
+ {{ (order.quantity * order.price).toFixed(2) }}
+ {{ order.expectedDate }}
+
+
+ {{ getStatusText(order.status) }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+ 到货
+
+
+
+
+
+ 暂无采购订单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 供应商
+
+ 请选择供应商
+
+ {{ s.name }}
+
+
+
+
+ 物料
+
+ 请选择物料
+
+ {{ m.name }} ({{ m.spec || '无规格' }})
+
+
+
+
+
+
+ 预计到货日期
+
+
+
+ 状态
+
+ 草稿
+ 已下单
+ 部分到货
+ 已完成
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/purchase/Supplier.vue b/src/views/purchase/Supplier.vue
new file mode 100644
index 0000000..f4f8e49
--- /dev/null
+++ b/src/views/purchase/Supplier.vue
@@ -0,0 +1,544 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 名称
+ 联系人
+ 电话
+ 地址
+ 供应产品
+ 操作
+
+
+
+
+ {{ supplier.id }}
+ {{ supplier.name }}
+ {{ supplier.contact || '-' }}
+ {{ supplier.phone || '-' }}
+ {{ supplier.address || '-' }}
+
+
+
+ {{ product }}
+
+ -
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无供应商数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/quote/QuoteGenerate.vue b/src/views/quote/QuoteGenerate.vue
new file mode 100644
index 0000000..5cb704c
--- /dev/null
+++ b/src/views/quote/QuoteGenerate.vue
@@ -0,0 +1,1113 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 草稿
+ 已发送
+ 已接受
+ 已拒绝
+ 已过期
+
+
+ 全部方案
+ 方案A (标准型)
+ 方案B (升级型)
+
+
+
+
+
+
+
+
+
+
+ 报价单号
+ 客户信息
+ 产品数量
+ 方案类型
+ 报价金额
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+
+
+ {{ item.quoteNumber }}
+
+
+ {{ item.customerName }}
+ {{ item.customerPhone }}
+
+ {{ item.productCount }} 项
+
+
+ 方案{{ item.scheme }}
+
+
+ ¥{{ item.totalAmount.toFixed(2) }}
+
+
+ {{ getStatusText(item.status) }}
+
+
+ {{ item.createdAt }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
基本信息
+
+
+ 报价单号
+ {{ currentItem.quoteNumber }}
+
+
+ 客户名称
+ {{ currentItem.customerName }}
+
+
+ 联系电话
+ {{ currentItem.customerPhone }}
+
+
+ 邮箱
+ {{ currentItem.customerEmail || '-' }}
+
+
+ 产品数量
+ {{ currentItem.productCount }} 项
+
+
+ 方案类型
+
+ 方案{{ currentItem.scheme }}
+
+
+
+ 报价金额
+ ¥{{ currentItem.totalAmount.toFixed(2) }}
+
+
+ 状态
+
+ {{ getStatusText(currentItem.status) }}
+
+
+
+ 有效期至
+ {{ currentItem.validUntil || '-' }}
+
+
+ 创建时间
+ {{ currentItem.createdAt }}
+
+
+ 更新时间
+ {{ currentItem.updatedAt }}
+
+
+ 备注
+ {{ currentItem.remark || '无' }}
+
+
+
+
+
+
+
产品明细
+
+
+
+
+ 数量: {{ product.qty }}
+ 小计: ¥{{ (product.price * product.qty).toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
收件人: {{ emailRecipient }}
+
主题: {{ emailSubject }}
+
附件: 报价表_{{ currentDate }}.pdf
+
邮件内容:
+
尊敬的客户,您好!
+
根据您的需求,我们已为您准备了详细的产品报价方案,请查看附件中的报价表PDF文件。如果您有任何疑问或需要进一步调整,请随时与我们联系。
+
感谢您对我们产品的关注!
+
此致 销售团队
+
+
+
+
+
+
+
+
+
+ 返回测量报价
+
+
+ 下一步:跟单看板
+
+
+
+
+
+
+
+
diff --git a/src/views/quote/QuoteGenerate_fixed.vue b/src/views/quote/QuoteGenerate_fixed.vue
new file mode 100644
index 0000000..8ca3fd7
--- /dev/null
+++ b/src/views/quote/QuoteGenerate_fixed.vue
@@ -0,0 +1,1099 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+ ��������
+
+
+ ���۱���??
+
+
+
+
+
+
+
+
+
+
+ ȫ��״?
+ �ݸ�
+ �ѷ�?
+ �ѽ�?
+ �Ѿ�?
+ �ѹ�?
+
+
+ ȫ������
+ ����A (��??
+ ����B (����??
+
+
+
+
+
+
+
+
+
+
+ ���۵���
+ �ͻ���Ϣ
+ ��Ʒ����
+ ��������
+ ���۽��
+ ״?
+ ����ʱ��
+ ����
+
+
+
+
+
+
+ {{ item.quoteNumber }}
+
+
+ {{ item.customerName }}
+ {{ item.customerPhone }}
+
+ {{ item.productCount }} ?
+
+
+ ����{{ item.scheme }}
+
+
+ ��{{ item.totalAmount.toFixed(2) }}
+
+
+ {{ getStatusText(item.status) }}
+
+
+ {{ item.createdAt }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ��������
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
������Ϣ
+
+
+ ���۵���
+ {{ currentItem.quoteNumber }}
+
+
+ �ͻ�����
+ {{ currentItem.customerName }}
+
+
+ ��ϵ�绰
+ {{ currentItem.customerPhone }}
+
+
+ ����
+ {{ currentItem.customerEmail || '-' }}
+
+
+ ��Ʒ����
+ {{ currentItem.productCount }} ?
+
+
+ ��������
+
+ ����{{ currentItem.scheme }}
+
+
+
+ ���۽��
+ ��{{ currentItem.totalAmount.toFixed(2) }}
+
+
+ ״?
+
+ {{ getStatusText(currentItem.status) }}
+
+
+
+ ������
+ {{ currentItem.validUntil || '-' }}
+
+
+ ����ʱ��
+ {{ currentItem.createdAt }}
+
+
+ ����ʱ��
+ {{ currentItem.updatedAt }}
+
+
+ ��ע
+ {{ currentItem.remark || '?? }}
+
+
+
+
+
+
+
��Ʒ��ϸ
+
+
+
+
+ ����: {{ product.qty }}
+ ��: ��{{ (product.price * product.qty).toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
�ռ��ˣ� {{ emailRecipient }}
+
����? {{ emailSubject }}
+
����? ���۱�_{{ currentDate }}.pdf
+
�ʼ�����?
+
�Ŀͻ�������?
+
������������������Ϊ��������ϸ�IJ�Ʒ���۷�������鿴�����еı��۱�PDF�ļ�����������κ����ʻ���Ҫ��һ������������ʱ��������ϵ?
+
��л�������Dz�Ʒ�Ĺ�ע��
+
���� ������?
+
+
+
+
+
+
+
+
+
+ ���ز�������
+
+
+ ��һ������������
+
+
+
+
+
+
+
+
diff --git a/src/views/quote/QuoteMeasure.vue b/src/views/quote/QuoteMeasure.vue
new file mode 100644
index 0000000..1e4af56
--- /dev/null
+++ b/src/views/quote/QuoteMeasure.vue
@@ -0,0 +1,881 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 待处理
+ 测量中
+ 已报价
+ 已确认
+
+
+ 全部产品类型
+
+ {{ name }}
+
+
+
+
+
+
+
+
+
+
+
+ 报价单号
+ 客户信息
+ 产品类型
+ 测量地址
+ 测量日期
+ 状态
+ 创建时间
+ 操作
+
+
+
+
+
+
+ {{ item.quoteNumber }}
+
+
+ {{ item.customerName }}
+ {{ item.customerPhone }}
+
+
+ {{ item.productType }}
+
+ {{ item.address }}
+ {{ item.measureDate }}
+
+
+ {{ getStatusText(item.status) }}
+
+
+ {{ item.createdAt }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 测量地址 *
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
基本信息
+
+
+ 报价单号
+ {{ currentItem.quoteNumber }}
+
+
+ 客户名称
+ {{ currentItem.customerName }}
+
+
+ 联系电话
+ {{ currentItem.customerPhone }}
+
+
+ 产品类型
+ {{ currentItem.productType }}
+
+
+ 测量地址
+ {{ currentItem.address }}
+
+
+ 测量日期
+ {{ currentItem.measureDate }}
+
+
+ 测量尺寸
+ {{ currentItem.measureSize || '-' }}
+
+
+ 状态
+
+ {{ getStatusText(currentItem.status) }}
+
+
+
+ 备注
+ {{ currentItem.remark || '无' }}
+
+
+
+
+
时间信息
+
+
+ 创建时间
+ {{ currentItem.createdAt }}
+
+
+ 更新时间
+ {{ currentItem.updatedAt }}
+
+
+
+
+
+
+
+
+
+
+
+ 返回预约测量
+
+
+ 下一步:生成报价表
+
+
+
+
+
+
+
+
diff --git a/src/views/staff/InstallerManage.vue b/src/views/staff/InstallerManage.vue
new file mode 100644
index 0000000..f92ebc1
--- /dev/null
+++ b/src/views/staff/InstallerManage.vue
@@ -0,0 +1,457 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 姓名
+ 电话
+ 技能标签
+ 最大任务数
+ 当前任务数
+ 负荷
+ 状态
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.name }}
+ {{ item.phone || '-' }}
+
+
+ {{ skill }}
+
+ -
+
+ {{ item.maxTasks }}
+ {{ item.currentTasks }}
+
+
+
{{ item.currentTasks }}/{{ item.maxTasks }}
+
+
+
+
+
+ {{ item.status === 'active' ? '启用' : '停用' }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无安装师傅数据,请点击"新增师傅"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/staff/MeasurerManage.vue b/src/views/staff/MeasurerManage.vue
new file mode 100644
index 0000000..a05220d
--- /dev/null
+++ b/src/views/staff/MeasurerManage.vue
@@ -0,0 +1,416 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 姓名
+ 电话
+ 技能标签
+ 工作负荷
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.name }}
+ {{ item.phone || '-' }}
+
+
+ {{ skill }}
+
+ -
+
+
+
+
{{ item.load }}/{{ item.maxLoad }}
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无测量师数据,请点击"新增测量师"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/MessageCenter.vue b/src/views/system/MessageCenter.vue
new file mode 100644
index 0000000..472d362
--- /dev/null
+++ b/src/views/system/MessageCenter.vue
@@ -0,0 +1,459 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 类型
+ 标题
+ 内容
+ 状态
+ 时间
+ 操作
+
+
+
+
+
+
+ {{ getTypeText(item.type) }}
+
+
+ {{ item.title }}
+ {{ item.content }}
+
+
+ {{ item.status === 'unread' ? '未读' : '已读' }}
+
+
+ {{ item.createTime }}
+
+
+
+ 查看
+
+
+ 删除
+
+
+
+
+
+ 暂无通知
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 类型:
+ {{ currentNotification ? getTypeText(currentNotification.type) : '' }}
+
+
+ 标题:
+ {{ currentNotification?.title }}
+
+
+ 内容:
+ {{ currentNotification?.content }}
+
+
+ 时间:
+ {{ currentNotification?.createTime }}
+
+
+ 状态:
+ {{ currentNotification?.status === 'unread' ? '未读' : '已读' }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/NotificationTemplate.vue b/src/views/system/NotificationTemplate.vue
new file mode 100644
index 0000000..b41d179
--- /dev/null
+++ b/src/views/system/NotificationTemplate.vue
@@ -0,0 +1,395 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 类型
+ 名称
+ 内容预览
+ 变量
+ 操作
+
+
+
+
+ {{ item.id }}
+
+
+ {{ item.type === 'sms' ? '短信' : '邮件' }}
+
+
+ {{ item.name }}
+
+
+ {{ item.content.substring(0, 30) }}{{ item.content.length > 30 ? '…' : '' }}
+
+
+ {{ item.variables && item.variables.length > 0 ? item.variables.join(', ') : '-' }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无模板数据,请点击"新增模板"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 类型 *
+
+ 短信
+ 邮件
+
+
+
+ 名称 *
+
+
+
+ 内容 *
+
+
+
+ 变量(逗号分隔)
+
+ 在内容中用 {变量名} 引用
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/RolePermission.vue b/src/views/system/RolePermission.vue
new file mode 100644
index 0000000..aad1a83
--- /dev/null
+++ b/src/views/system/RolePermission.vue
@@ -0,0 +1,439 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 角色名称
+ 描述
+ 权限列表
+ 操作
+
+
+
+
+ {{ role.id }}
+ {{ role.name }}
+ {{ role.description || '-' }}
+
+
+
+ {{ group.name }}: {{ group.permissions.join(',') }}
+
+
-
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无角色数据,请点击"新增角色"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/SystemUser.vue b/src/views/system/SystemUser.vue
new file mode 100644
index 0000000..efce339
--- /dev/null
+++ b/src/views/system/SystemUser.vue
@@ -0,0 +1,426 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 用户名
+ 姓名
+ 角色
+ 电话
+ 邮箱
+ 状态
+ 操作
+
+
+
+
+ {{ user.id }}
+ {{ user.username }}
+ {{ user.realName || '-' }}
+
+ {{ getRoleName(user.roleId) }}
+
+ {{ user.phone || '-' }}
+ {{ user.email || '-' }}
+
+
+ {{ user.status === 'active' ? '启用' : '停用' }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无用户数据,请点击"新增用户"添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/WorkTime.vue b/src/views/system/WorkTime.vue
new file mode 100644
index 0000000..dfba578
--- /dev/null
+++ b/src/views/system/WorkTime.vue
@@ -0,0 +1,463 @@
+
+
+
+
+ {{ toastMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 星期
+ 开始时间
+ 结束时间
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ getDayName(item.dayOfWeek) }}
+ {{ item.startTime }}
+ {{ item.endTime }}
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无工作时间数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ 日期
+ 名称
+ 类型
+ 操作
+
+
+
+
+ {{ item.id }}
+ {{ item.date }}
+ {{ item.name }}
+
+ {{ item.type === 'public' ? '法定节假日' : '公司假日' }}
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无节假日数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/start.bat b/start.bat
new file mode 100644
index 0000000..42f7f76
--- /dev/null
+++ b/start.bat
@@ -0,0 +1,20 @@
+@echo off
+echo ================================
+echo 窗帘工厂管理系统 - Vue3版本
+echo ================================
+echo.
+echo 正在启动开发服务器...
+echo.
+
+cd /d "%~dp0"
+
+:: 检查 node_modules 是否存在
+if not exist "node_modules" (
+ echo 首次运行,正在安装依赖...
+ npm install
+ echo.
+)
+
+npm run dev
+
+pause
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..6211ea9
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,16 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import { resolve } from 'path'
+
+export default defineConfig({
+ plugins: [vue()],
+ resolve: {
+ alias: {
+ '@': resolve(__dirname, 'src')
+ }
+ },
+ server: {
+ port: 3001,
+ open: true
+ }
+})