commit 8da6567178570837caed22ebf6e8885f9432f4d6 Author: mrpmall Date: Fri May 8 03:06:24 2026 +0800 first commit diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..07d3aaf --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=http://localhost:8080 +VITE_API_MOCK=true diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..f1b73d0 --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=/api +VITE_API_MOCK=false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62a2e53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +dist +.DS_Store +*.local +*.log +.vscode +.idea diff --git a/PROJECT_OVERVIEW.md b/PROJECT_OVERVIEW.md new file mode 100644 index 0000000..d268920 --- /dev/null +++ b/PROJECT_OVERVIEW.md @@ -0,0 +1,610 @@ +# 窗帘工厂管理系统 — 技术框架与功能简介 + +## 一、项目概述 + +窗帘工厂管理系统是一套面向窗帘制造企业的全流程业务管理平台,覆盖从客户预约、测量报价、订单生产、安装交付到售后服务的完整业务闭环,同时整合财务管理、采购库存、人员管理、数据分析等支撑模块。 + +- **项目名称**:窗帘工厂管理系统 (Curtain Factory Management System) +- **版本**:v1.0.0 +- **开发语言**:前端 JavaScript (Vue 3) / 后端 Java (Spring Boot) +- **界面语言**:简体中文 + +--- + +## 二、全栈技术架构 + +### 2.1 架构全景图 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 客户端 (Browser) │ +│ Vue 3 + Vite · Pinia · Chart.js · Font Awesome │ +│ Hash Router · 路由懒加载 · 路由守卫 │ +└──────────────────────────┬──────────────────────────────────────┘ + │ HTTP / JSON (RESTful API) + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 网关层 (Nginx / Gateway) │ +│ 反向代理 · 负载均衡 · HTTPS · 静态资源 │ +└──────────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 业务服务层 (Spring Boot 2.7) │ +│ ┌─────────────┬────────────────┬─────────────────────────────┐ │ +│ │Spring Sec │ AOP 切面 │ 全局异常处理 @RestControllerAdvice │ +│ │JWT 认证 │ 操作日志 │ 统一响应体 R │ +│ │RBAC 鉴权 │ 接口限流 │ 参数校验 JSR-303 │ +│ └─────────────┴────────────────┴─────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Controller → Service → Repository (JPA/Hibernate) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└──────────────────────────┬──────────────────────────────────────┘ + │ + ┌────────────┴────────────┐ + ▼ ▼ +┌──────────────────────┐ ┌──────────────────────┐ +│ MySQL 8.0 (主库) │ │ Redis (缓存/Session) │ +│ · 读写分离 │ │ · JWT Token 黑名单 │ +│ · 慢查询日志 │ │ · 验证码存储 │ +│ · 索引优化 │ │ · 热点数据缓存 │ +└──────────────────────┘ └──────────────────────┘ +``` + +### 2.2 技术栈明细 + +| 层级 | 技术选型 | 版本 | 用途 | +|------|----------|------|------| +| **前端框架** | Vue 3 (Composition API) | ^3.3.4 | 组件化 UI 构建 | +| **构建工具** | Vite | ^4.4.5 | 开发服务器 / HMR / 生产构建 | +| **路由管理** | Vue Router | ^4.6.4 | Hash 模式 SPA 路由 | +| **状态管理** | Pinia | ^2.1.7 | 全局状态与跨组件通信 | +| **图表可视化** | Chart.js | ^4.4.0 | KPI 仪表盘与数据分析图表 | +| **图标库** | Font Awesome 6 | CDN | 2000+ 矢量图标 | +| **样式方案** | CSS Variables + Scoped CSS | — | 设计令牌 + 组件隔离 | +| **后端框架** | Spring Boot | 2.7.x | 约定优于配置的微服务基座 | +| **安全框架** | Spring Security + JWT | — | 无状态认证 + RBAC 鉴权 | +| **ORM** | Spring Data JPA (Hibernate) | — | 对象关系映射 | +| **缓存** | Redis | 7.x | Token 黑名单 / 验证码 / 热点缓存 | +| **数据库** | MySQL | 8.0 | 主数据存储 | +| **API 文档** | Knife4j (Swagger) | — | 自动生成 RESTful API 文档 | +| **构建工具** | Maven | 3.8+ | 依赖管理与打包 | + +--- + +## 三、前端项目架构 + +### 3.1 目录结构 + +``` +html-vue3/ +├── index.html # 入口 HTML(挂载 #app + Font Awesome CDN) +├── package.json # 项目依赖与 pnpm/npm 脚本 +├── vite.config.js # Vite 配置(@别名 / 端口3001) +│ +└── src/ + ├── main.js # 应用入口:createApp → Pinia → Router + ├── App.vue # 根组件( 路由出口) + │ + ├── assets/css/ + │ └── global.css # 全局样式:CSS变量 / 卡片 / 按钮 / 表格 / 模态框 / Toast + │ + ├── components/ + │ ├── common/ + │ │ └── PagePlaceholder.vue + │ └── layout/ + │ ├── MainLayout.vue # 主布局:Sidebar + TopBar + + │ ├── Sidebar.vue # 左侧导航(260px / 可折叠 / 13组菜单) + │ └── TopBar.vue # 顶部栏(侧栏切换 / 面包屑 / 全局搜索 / 用户信息) + │ + ├── composables/ # 可复用逻辑 + │ ├── useModal.js # 模态框 open/close/loading + │ ├── usePagination.js # 分页:page/size/total/pageChange + │ ├── useSearch.js # 搜索:keyword/loading/search + │ └── useToast.js # Toast:message/type/show/autoHide + │ + ├── router/index.js # 路由配置(Hash模式 / 42条路由 / 路由守卫) + │ + ├── stores/ # Pinia 状态管理 + │ ├── customerStore.js # 客户 CRUD + 统计 + │ ├── orderStore.js # 订单 CRUD + 状态流转 + │ └── userStore.js # 登录/登出/持久化 + │ + ├── utils/ + │ └── dataService.js # 演示数据生成器 + │ + └── views/ # 42 个业务页面(按模块分目录) + ├── Login.vue + ├── Dashboard.vue + ├── Workflow.vue + ├── NotFound.vue + ├── customer/ # 3页 + ├── quote/ # 2页 + ├── order/ # 6页 + ├── production/ # 3页 + ├── install/ # 4页 + ├── aftersale/ # 2页 + ├── finance/ # 2页 + ├── purchase/ # 3页 + ├── product/ # 2页 + ├── staff/ # 2页 + ├── system/ # 5页 + └── analysis/ # 4页 +``` + +### 3.2 前端技术亮点 + +| 特性 | 实现方式 | +|------|----------| +| **Composition API** | 全部页面采用 ` +``` + +### 样式规范 +- 使用 scoped 样式 +- 使用 CSS 变量 +- 遵循 BEM 命名约定 + +## 特性 + +- ✅ Vue 3 Composition API +- ✅ Pinia 状态管理 +- ✅ Vue Router 路由守卫 +- ✅ 响应式设计 +- ✅ 组件化开发 +- ✅ 组合式函数复用 +- ✅ 模块化路由配置 +- ✅ 全局样式系统 + +## 浏览器支持 + +- Chrome >= 87 +- Firefox >= 78 +- Safari >= 14 +- Edge >= 88 + +## 许可证 + +MIT License + +## 联系方式 + +如有问题或建议,请联系开发团队。 diff --git a/fix-all.cjs b/fix-all.cjs new file mode 100644 index 0000000..f289638 --- /dev/null +++ b/fix-all.cjs @@ -0,0 +1,99 @@ +const fs = require('fs'); + +// 所有需要修复的文件 +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' +]; + +// 中文文本修复映射 +const textReplacements = { + '报价表管': '报价表管理', + '新建报价': '新建报价表', + '标签页导': '标签页导航', + '报价表生': '报价表生成', + '搜索报价单号、客户名': '搜索报价单号、客户名称', + '全部状': '全部状态', + '已发': '已发送', + '已接': '已接受', + '已拒': '已拒绝', + '已过': '已过期', + '方案A (标准': '方案A (标准型)', + '方案B (升级': '方案B (升级型)' +}; + +let totalFixed = 0; + +files.forEach(file => { + try { + let content = fs.readFileSync(file, 'utf8'); + let modified = false; + + // 修复破碎的 HTML 标签 + const tagPatterns = [ + { regex: />([^<]+)\/option>/g, replace: '>$1' }, + { regex: />([^<]+)\/th>/g, replace: '>$1' }, + { regex: />([^<]+)\/td>/g, replace: '>$1' }, + { regex: />([^<]+)\/button>/g, replace: '>$1' }, + { regex: />([^<]+)\/label>/g, replace: '>$1' }, + { regex: />([^<]+)\/span>/g, replace: '>$1' }, + { regex: />([^<]+)\/p>/g, replace: '>$1

' }, + { regex: />([^<]+)\/h[1-6]>/g, replace: '>$1' }, + { regex: />([^<]+)\/li>/g, replace: '>$1' }, + { regex: />([^<]+)\/a>/g, replace: '>$1' }, + { regex: />([^<]+)\/div>/g, replace: '>$1' }, + { regex: /title="([^"]+)>/g, replace: 'title="$1">' } + ]; + + tagPatterns.forEach(pattern => { + const matches = content.match(pattern.regex); + if (matches) { + matches.forEach(match => { + const fixed = match.replace(pattern.regex, pattern.replace); + if (fixed !== match) { + content = content.split(match).join(fixed); + modified = true; + } + }); + } + }); + + // 修复破碎的 Vue 插值表达式 + const interpolationMatches = content.match(/\{\{[^}]*\?[^}]*'[^']*'/g); + if (interpolationMatches) { + interpolationMatches.forEach(match => { + // 检查是否缺少闭合的引号 + const singleQuotes = (match.match(/'/g) || []).length; + if (singleQuotes % 2 !== 0) { + // 找到缺失的位置并修复 + const fixed = match + "'"; + content = content.split(match).join(fixed); + modified = true; + } + }); + } + + // 修复中文文本 + Object.keys(textReplacements).forEach(broken => { + if (content.includes(broken)) { + content = content.split(broken).join(textReplacements[broken]); + modified = true; + } + }); + + if (modified) { + fs.writeFileSync(file, content, 'utf8'); + console.log('Fixed: ' + file); + totalFixed++; + } else { + console.log('No changes: ' + file); + } + } catch(e) { + console.log('Error fixing ' + file + ': ' + e.message); + } +}); + +console.log('\nTotal fixed: ' + totalFixed + ' files'); diff --git a/fix-complete.cjs b/fix-complete.cjs new file mode 100644 index 0000000..599aef0 --- /dev/null +++ b/fix-complete.cjs @@ -0,0 +1,143 @@ +const fs = require('fs'); + +// 完整的中文文本修复映射 +const textMap = { + // 订单相关 + '订单状': '订单状态', + '待处': '待处理', + '生产中': '生产中', + '已安': '已安装', + '已完': '已完成', + '已取': '已取消', + '全部状': '全部状态', + + // 客户相关 + '客户名': '客户名称', + '请输入客户名': '请输入客户名称', + + // 金额相关 + '请输入金': '请输入金额', + + // 产品相关 + '蜂巢': '蜂巢帘', + '梦幻': '梦幻帘', + '柔纱': '柔纱帘', + '罗马': '罗马帘', + '百叶': '百叶帘', + '香格里拉': '香格里拉帘', + '卷帘': '卷帘', + '纱窗': '纱窗', + + // 姓名相关 + '王十': '王十二', + '冯十': '冯十三', + '陈十': '陈十四', + '褚十': '褚十五', + '卫十': '卫十六', + '蒋十': '蒋十七', + + // 其他 + '工具': '工具栏', + '上一': '上一页', + '下一': '下一页', + '搜索和筛': '搜索和筛选', + '计算属': '计算属性', + '请填写完整信': '请填写完整信息', + '订单已更': '订单已更新', + '订单已创': '订单已创建', + '确定要删除订': '确定要删除订单', + '订单已删': '订单已删除', + '按钮': '按钮组', + '状态徽': '状态徽章', + '响应': '响应式', + '布艺对开': '布艺对开帘', + '日夜蜂巢': '日夜蜂巢帘', + + // 报价相关 + '报价表管': '报价表管理', + '新建报价': '新建报价表', + '标签页导': '标签页导航', + '报价表生': '报价表生成', + '搜索报价单号、客户名': '搜索报价单号、客户名称', + '已发': '已发送', + '已接': '已接受', + '已拒': '已拒绝', + '已过': '已过期', + '方案A (标准': '方案A (标准型)', + '方案B (升级': '方案B (升级型)' +}; + +function fixFile(filePath) { + let content = fs.readFileSync(filePath, 'utf8'); + let modified = false; + + // 修复破碎的 HTML 标签 + const tagFixes = [ + { pattern: />([^<]+)\/option>/g, replace: '>$1' }, + { pattern: />([^<]+)\/th>/g, replace: '>$1' }, + { pattern: />([^<]+)\/td>/g, replace: '>$1' }, + { pattern: />([^<]+)\/button>/g, replace: '>$1' }, + { pattern: />([^<]+)\/label>/g, replace: '>$1' }, + { pattern: />([^<]+)\/span>/g, replace: '>$1' }, + { pattern: />([^<]+)\/p>/g, replace: '>$1

' }, + { pattern: />([^<]+)\/div>/g, replace: '>$1' } + ]; + + tagFixes.forEach(fix => { + const newContent = content.replace(fix.pattern, fix.replace); + if (newContent !== content) { + content = newContent; + modified = true; + } + }); + + // 修复破碎的属性(缺少闭合引号) + const attrFixes = [ + { pattern: /placeholder="([^"]*)/g, check: (m) => !m.endsWith('"') }, + { pattern: /title="([^"]*)/g, check: (m) => !m.endsWith('"') } + ]; + + attrFixes.forEach(fix => { + content = content.replace(fix.pattern, (match) => { + if (fix.check(match)) { + modified = true; + return match + '"'; + } + return match; + }); + }); + + // 修复中文文本 + Object.keys(textMap).forEach(broken => { + if (content.includes(broken)) { + content = content.split(broken).join(textMap[broken]); + modified = true; + } + }); + + if (modified) { + fs.writeFileSync(filePath, content, 'utf8'); + return true; + } + return false; +} + +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 => { + if (fixFile(file)) { + console.log('Fixed: ' + file); + fixed++; + } else { + console.log('No changes: ' + file); + } +}); + +console.log('\nTotal fixed: ' + fixed + ' files'); diff --git a/fix-encoding.cjs b/fix-encoding.cjs new file mode 100644 index 0000000..7b63e67 --- /dev/null +++ b/fix-encoding.cjs @@ -0,0 +1,83 @@ +const fs = require('fs'); +const path = require('path'); + +// 正确的中文文本映射 +const replacements = { + '全部状�?': '全部状态', + '待处�?': '待处理', + '生产�?': '生产中', + '已安�?': '已安装', + '已完�?': '已完成', + '已取�?': '已取消', + '订单�?': '订单号', + '状�?': '状态', + '客户名�?': '客户名称', + '搜索订单号、客户名�?..': '搜索订单号、客户名称...', + '管理所有订单,跟踪订单状�?': '管理所有订单,跟踪订单状态', + '新建订�?': '新建订单', + '工具�?': '工具栏', + '上一�?': '上一页', + '下一�?': '下一页', + '请输入客户名�?': '请输入客户名称', + '订单状�?': '订单状态', + '搜索和筛�?': '搜索和筛选', + '计算属�?': '计算属性', + '请填写完整信�?': '请填写完整信息', + '订单已更�?': '订单已更新', + '订单已创�?': '订单已创建', + '确定要删除订�?': '确定要删除订单', + '订单已删�?': '订单已删除', + '按钮�?': '按钮组', + '状态徽�?': '状态徽章', + '响应�?': '响应式', + '蜂巢�?': '蜂巢帘', + '梦幻�?': '梦幻帘', + '柔纱�?': '柔纱帘', + '罗马�?': '罗马帘', + '百叶�?': '百叶帘', + '香格里拉�?': '香格里拉帘', + '王十�?': '王十二', + '冯十�?': '冯十三', + '陈十�?': '陈十四', + '褚十�?': '褚十五', + '卫十�?': '卫十六', + '蒋十�?': '蒋十七', + '布艺对开�?': '布艺对开帘', + '日夜蜂巢�?': '日夜蜂巢帘' +}; + +const brokenFiles = [ + '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', + 'src/views/system/NotificationTemplate.vue' +]; + +let fixed = 0; +brokenFiles.forEach(file => { + try { + let content = fs.readFileSync(file, 'utf8'); + let modified = false; + + Object.keys(replacements).forEach(broken => { + if (content.includes(broken)) { + content = content.split(broken).join(replacements[broken]); + 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/fix-html.cjs b/fix-html.cjs new file mode 100644 index 0000000..3d4e9bd --- /dev/null +++ b/fix-html.cjs @@ -0,0 +1,65 @@ +const fs = require('fs'); + +// 修复所有破碎的 HTML 属性和标签 +function fixBrokenHtml(content) { + // 修复破碎的属性(缺少闭合引号) + content = content.replace(/placeholder="([^"]*)"/g, (match, p1) => { + if (!match.endsWith('"')) { + return 'placeholder="' + p1 + '"'; + } + return match; + }); + + content = content.replace(/title="([^"]*)"/g, (match, p1) => { + if (!match.endsWith('"')) { + return 'title="' + p1 + '"'; + } + return match; + }); + + // 修复破碎的标签 + content = content.replace(/>([^<]+)\/option>/g, '>$1'); + content = content.replace(/>([^<]+)\/th>/g, '>$1'); + content = content.replace(/>([^<]+)\/td>/g, '>$1'); + content = content.replace(/>([^<]+)\/button>/g, '>$1'); + content = content.replace(/>([^<]+)\/label>/g, '>$1'); + content = content.replace(/>([^<]+)\/span>/g, '>$1'); + content = content.replace(/>([^<]+)\/p>/g, '>$1

'); + content = content.replace(/>([^<]+)\/h([1-6])>/g, '>$1'); + content = content.replace(/>([^<]+)\/li>/g, '>$1'); + content = content.replace(/>([^<]+)\/a>/g, '>$1'); + content = content.replace(/>([^<]+)\/div>/g, '>$1'); + + return content; +} + +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 totalFixed = 0; + +files.forEach(file => { + try { + let content = fs.readFileSync(file, 'utf8'); + const original = content; + + content = fixBrokenHtml(content); + + if (content !== original) { + fs.writeFileSync(file, content, 'utf8'); + console.log('Fixed: ' + file); + totalFixed++; + } else { + console.log('No changes: ' + file); + } + } catch(e) { + console.log('Error fixing ' + file + ': ' + e.message); + } +}); + +console.log('\nTotal fixed: ' + totalFixed + ' files'); diff --git a/fix-padding.cjs b/fix-padding.cjs new file mode 100644 index 0000000..4269a8b --- /dev/null +++ b/fix-padding.cjs @@ -0,0 +1,73 @@ +const fs = require('fs'); +const path = require('path'); + +function walkDir(dir, files = []) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walkDir(full, files); + else if (entry.name.endsWith('.vue') && !entry.name.includes('_fixed')) files.push(full); + } + return files; +} + +const viewsDir = path.join(__dirname, 'src', 'views'); +const files = walkDir(viewsDir); + +let fixedFiles = []; + +for (const filePath of files) { + let content = fs.readFileSync(filePath, 'utf8'); + let modified = false; + + // Find the root div class in template + const templateMatch = content.match(/