Signed-off-by: 吴文峰 <kevin@lmve.net>

This commit is contained in:
2026-06-09 18:56:20 +08:00
parent 4d77943437
commit 33469dc746
7 changed files with 0 additions and 908 deletions
-156
View File
@@ -1,156 +0,0 @@
# 2026-04-24 日志
## 项目梳理
- 完整梳理了 ops2 项目的运行逻辑
- 涉及:后端 Go + Gin 架构、Web 前端 Vue 3 + Vite、移动端 uni-app 基础框架
## 核心模块梳理
### 用户认证 (apiUsers.go)
- Cookie 认证机制,密码加盐哈希
- 各模块独立管理员组(purchase_admin / work_order_admin / schedule_admin / warehouse_admin
- 默认创建 admin 用户
### 文件管理 (apiFiles.go)
- SHA256 哈希去重存储(同一文件只存一份)
- 支持图片/视频/音频/PDF 等类型
- 头像单独存储
### 日程排班 (apiSchedule.go)
- 日程表(软删除)+ 操作日志
- 日期范围查询
### 仓库模块 (apiWarehouse.go)
- 容器(树形,最多5层嵌套)+ 物品管理
- 支持工单关联、物品移动记录、操作日志
### 采购模块 (apiPurchase.go)
- 订单状态:pending → ordered → arrived → received / lost / returned
- 费用明细(多币种)、图片上传、状态记录
- 可关联工单
### 工单模块 (apiWorkOrder.go)
- 工单状态:pending → checked → parts_ordered → repaired → returned / unrepairable
- 可关联仓库物品和采购订单
- 特殊逻辑:送还时自动移除物品容器绑定
## 移动端登录功能开发
### 完成的功能
1. **api/index.js** - API 封装层
- uni.request 统一封装
- 自动注入 userCookieValue
- Cookie 过期自动处理(-44 错误码)
- 支持文件上传
2. **stores/user.js** - 用户状态管理
- userCookie / user / userInfo 状态
- isLoggedIn / cookieValue / username / avatarUrl 计算属性
- login(cookie) - 登录保存
- logout() - 清除状态
- restoreSession() - 启动时恢复会话
- fetchUserInfo() - 获取用户信息
3. **api/user.js** - 用户 API
- login / register / getUserInfo / changePassword / changeEmail / updateInfo / updateAvatar
4. **pages/login/login.vue** - 登录页面(已完成 100%
- 表单验证
- 调用登录 API
- 记住登录选项
- 登录成功跳转用户中心
- 错误提示(密码错误/用户名不存在)
5. **pages/user/user.vue** - 用户中心(已完成 100%
- 未登录状态:显示登录入口
- 已登录状态:显示用户信息卡片 + 功能菜单 + 退出登录
- 快捷入口:订单/工单/仓库/日程
- 退出登录确认对话框
## 移动端工程分析 (ops2_uniapp)
**技术栈**: uni-app + Vue 3 + Pinia + HBuilderX
**已完成**:
- 基础框架搭建
- Pinia 状态管理(config.js, user.js
- 自定义 Toast 组件
- 登录页(✅ 100%)+ 设置页(90%)
- 用户中心(✅ 100%
**API 封装**: 已完善 Cookie 认证机制
**当前总完成度**: ~50%
**待完成**:
- 4个 TabBar 页面功能实现(index/order/message/work-order/schedule/warehouse
- 后端各模块对接
## 下一步
移动端登录和用户中心已完成,可以:
1. ~~开发主页(index)仪表盘~~
2. 开发订单列表(order
3. 继续完善其他功能页面
---
## 下午开发记录
### 1. TabBar 导航修改
- 改为:主页 → 订单 → 仓库 → 用户
- 新增 `pages/warehouse/warehouse.vue`
- 新增 `static/tabbar/warehouse*.png`
### 2. API 层完善
- **api/schedule.js** - 日程管理(getEvents/addEvent/editEvent/deleteEvent
- **api/purchase.js** - 采购订单(getOrders/getOrder/getOrderCount
### 3. 主页功能开发 (pages/index/index.vue)
- 欢迎语 + 今日日期显示
- 今日日程列表(调用 scheduleApi.getEvents
- 订单统计(待处理/已到达/已收件,调用 purchaseApi.getOrderCount
- 快捷入口卡片
### 4. 主页页面交互
- 未登录时显示"欢迎使用 OPS 系统"
- 登录后显示"用户名,您好!"
- 日程按颜色标签显示
- 订单数量有状态提示(待处理>0时橙色高亮)
- 点击统计和快捷入口跳转到对应 TabBar 页面
### 5. TabBar 导航再次修改(增加工单)
- 改为:主页 → 订单 → 工单 → 仓库 → 用户
- 新增 `pages/workorder/workorder.vue` - 工单列表页
- 顶部统计:待处理/已检查/已下单零件/已维修
- 状态下拉筛选
- 工单卡片列表(ID/状态/标题/描述/创建时间)
- 状态颜色标签
- 分页加载
- 新增 `api/work_order.js` - 工单 APIlist/get/getCount/add/update/delete/commit
- 新增 `static/tabbar/workorder*.png` - 工单图标(复制订单图标)
### 6. 仓库页面完善 (pages/warehouse/warehouse.vue)
**新增功能**:
- 容器卡片长按菜单(编辑/删除)
- 新增容器弹窗完善:
- 父容器选择(可选将容器添加到其他目录)
- 颜色标签选择(8种预设颜色)
- 图片上传(最多3张)
- 备注输入
- 容器卡片显示颜色边框和图标背景色
- 删除容器前检查是否有子容器/物品
**后端更新** (apiWarehouse.go):
- TabWarehouseContainer 添加 Color 字段
- add_container / update_container 接口支持 color 参数
**前端关键函数**:
- onContainerLongPress() - 长按显示操作菜单
- showEditModal() / confirmDeleteContainer() - 编辑/删除
- openParentPicker() / selectParent() - 父容器选择
- selectColor() - 颜色选择
- chooseImage() / uploadImage() / removePhoto() - 图片管理
-66
View File
@@ -1,66 +0,0 @@
# 2026-04-27 工作记录
## 移动端仓库 - 物品列表显示数量
- `pages/warehouse/warehouse.vue`:两处物品卡片(全部 Tab / 仅物品 Tab)均加入 `数量: {{ item.Quantity ?? 1 }}` 显示
## 后端 - 编译时注入 Git 版本信息
- `main.go`:添加 `GitVersion``GitCommit``BuildTime` 三个变量(由 `-ldflags -X` 注入)
- `main.go`:添加 `GET /api/version` 接口(无需认证,返回三个版本字段)
- `install.sh`:编译前读取 `git describe --tags``git rev-parse --short HEAD`、当前时间,组装 `LDFLAGS` 传入 `go build`
- 编译命令示例:`CGO_ENABLED=0 GOOS=linux go build -o OPSYS -ldflags="-s -w -X main.GitVersion=... -X main.GitCommit=... -X main.BuildTime=..." main.go`
## 移动端设置页 - 显示版本号 + 自动递增脚本
- 创建 `frontend/ops2_uniapp/bump-version.js`
- 读取 `manifest.json`(支持去除 JS 注释后再 `JSON.parse`
- `versionCode` +1`versionName` 自动递增 patch 位
- 用法:`node bump-version.js`(打包前手动执行)
- `pages/settings/settings.vue`
- 底部新增"关于"分组,显示版本号
- 真机端:`plus.runtime.version`
- H5 端:`fetch('/manifest.json')` + 去注释 + `JSON.parse`
- 样式补全 `cell-label`
## 移动端仓库 - 物品编辑页补充 quantity 字段
- `pages/warehouse/item-edit.vue`:参考 Web 端 `WarehouseItemEdit.vue`
- form 增加 `quantity: 1`
- 编号和备注之间添加 +/− 步进器 UI
- `fetchDetail` 回填 `quantity: item.Quantity ?? 1`
- 提交数据加入 `quantity: form.value.quantity > 0 ? form.value.quantity : 1`
- 删除调试 log,补步进器样式
## 移动端仓库 - 新增物品页面补充 quantity 字段
- `pages/warehouse/add-item.vue`:参考 Web 端 `WarehouseAddItem.vue`,补充 `quantity` 字段
- form 增加 `quantity: 1`(默认值 1
- 模板增加 +/ 步进器 UI
- 提交数据中加入 `quantity: form.value.quantity > 0 ? form.value.quantity : 1`
## 后端 - 根路由增加版本字段
- `routers/api.go`
- 添加包级变量 `GitVersion``GitCommit``BuildTime`
- `GET /api/` 响应新增 `version``gitCommit``buildTime` 三个字段
- `main.go``main()` 启动时将变量赋值给 `routers`
- `install.sh` 修复:`BUILD_TIME` 格式改 `YYYYMMDD_HHMMSS`(去空格,避免 ldflags 解析报错)
## Web 前端 - 版本定义 + Footer 显示
- `vite.config.js`:读取 `package.json``version`,通过 `define: { __APP_VERSION__ }` 注入全局常量
- `components/AppFooter.vue`
- script 中 `const version = __APP_VERSION__`
- 模板中版权行末尾显示 `v{{ version }}`(如 `v0.1.0`
## 移动端 - 全局搜索功能
- `pages/index/index.vue`:欢迎区域右上角添加 🔍 搜索按钮,点击跳转到 `/pages/search/search`
- `pages/search/search.vue`:新建全局搜索页面
- 顶部蓝色搜索栏(输入框 + 返回按钮 + 清空按钮)
- 分类 Tab:全部 / 订单 / 工单 / 物品 / 容器
- 输入防抖 500ms,自动触发搜索
- 结果卡片显示图标、标题、描述,点击跳转详情
- 支持跳转到:订单详情、工单详情、物品详情、容器页
- `pages.json`:注册搜索页面路由
-61
View File
@@ -1,61 +0,0 @@
# 2026-04-28 工作记录
## 搜索页面条码识别 bug 修复
- 修复搜索页面 `res.data.orders``res.data.all_orders`(工单和采购订单列表API返回字段名均为 `all_orders`
- 搜索页添加条码格式识别:`wo:ID`→工单、`item:ID`→物品、`warehouse:ID`→容器、`po:ID`→采购订单
- 容器跳转用 `uni.$emit('barcode-navigate-container')` + `switchTab`tabBar页面不能 navigateTo
- 搜索页添加扫码按钮(📷)
- App端扫码:`uni.getSetting` 仅小程序支持,App端需用 `plus.android` 检查权限
- 条件编译:`#ifdef APP-PLUS``#ifdef MP-WEIXIN``#ifdef H5` 分平台处理
## download_app 改造
- `download.php`:改为扫描 `__DIR__/../` 根目录下的所有 `.apk`,用 `filemtime()` 取最新,不再依赖文件名格式
- `index.php`:版本信息同时显示文件名和修改时间
## 订单详情页打印功能
- `order-detail.vue` 右上角添加打印按钮 🖨
- 点击后用 LcPrinter 插件打印:标题(加粗大字)/ 备注 / 状态 / 创建日期 / 条形码 `po:ID`height=4barcodeType=73
- 条件编译 `#ifdef APP-PLUS`,非 App 端 toast 提示不支持
- 订单详情页补充 `order.Styles`(样式)字段显示,插在"链接"和"备注"之间,与 web 端对齐
- 同步修正打印函数第三行:原打印"状态"→改为打印 `Styles`(样式)
## 打印初始化代码补全
- 在以下四个页面的打印函数中统一加入初始化代码(`initPrinter` + `setConcentration` + `setLineSpacing`),参照 `printer-test.vue` 写法:
- `order-detail.vue``printOrder()`
- `show-workorder.vue``printWorkOrder()`
- `item-detail.vue``printItem()`
- `warehouse.vue``printContainer()`
## 用户登录失败日志表
- `apiUsers.go` 新增 `TabUserLoginFailLog` 表结构:记录 `username``userID``IP``userAgent``reason`password_error / user_not_found)、`count`(连续失败次数)、`created_at``updated_at`
- `InitUsersRouter` 中新增 `AutoMigrate(&TabUserLoginFailLog{})`
- 登录成功时清除该用户失败记录;密码错误和用户不存在时调用 `recordLoginFail()` 记录/更新失败日志(24小时内累计次数)
- `recordLoginFail()``TabUserLoginFailLog` 结构体后定义
## apiWarehouse.go 查重完善
- `add_container` 接口:添加同层级容器 Title 查重(`parent_id` + `title` + `deleted_at IS NULL`),重复返回 `container_title_exist`
- `add_item` 接口:已有查重逻辑(`Name` + `SerialNumber`)保持不变
## 完善 updateSysAdminsCash 函数
- 原函数为空,现实现:
1. 查询 `admins` 用户组 ID
2. 查询该组所有成员的 `TabUserGroupBinds` 记录
3. 提取所有 `UserID` 更新到 `sysAdmins` 缓存切片
4. 组不存在或查询失败时清空缓存
## Web 前端系统管理员入口
- 后端 `getinfo` 接口返回 `isSysAdmin` 布尔值(不再暴露完整管理员列表)
- 新建 `apiSysAdmin.go` 文件,专门处理系统管理员接口
- 后端新增 `POST /admin/sysadmins` 接口(位于 apiSysAdmin.go),仅系统管理员可访问,返回完整 `sysAdmins` 数组
- 前端 `userStore``isSysAdmin` 改为 ref,直接从后端获取
- `AppHeader.vue` 用户菜单:当 `isSysAdmin` 为 true 时显示「系统管理」入口(琥珀色盾牌图标)
- 新建 `SysAdminView.vue`:4 个标签页占位符(用户管理、用户组、登录日志、系统配置),页面内调用 `authApi.sysAdmins()` 获取管理员列表
- 路由 `/admin`:添加 `requireSysAdmin` 元信息,路由守卫拦截非管理员访问
-89
View File
@@ -1,89 +0,0 @@
# 2026-04-29 工作记录
## 完成的任务
### 1. Web 前端优化
- **WorkOrderList.vue** - 工单列表页
- 增宽工单标题列:w-64 → w-80
- 增宽描述列:w-32 → w-48
- 状态气泡添加 `whitespace-nowrap` 防止换行
- **WarehouseOverview.vue** - 仓库概览页
- 操作列按钮添加 `whitespace-nowrap`
- 删除创建日期列
- **WarehouseContainerDetail.vue** - 容器详情页
- 操作列按钮添加 `whitespace-nowrap`
- 删除创建日期列
### 2. 部署脚本
- 创建 `install.sh`(根目录)
- 执行 git pull
- 构建前端:cd frontend/ops_vue_js && npm run build
- 安装后端:cd backend/my_work && sudo bash install.sh
### 3. 移动端开发
- **add-workorder.vue** - 新建工单页
- 添加客户搜索功能(参考 Web 前端实现)
- 修改关联物品为多选模式
- 添加 `selectedItems``linkedItemIds` 数组
- 修改 `submitForm()` 提交 `item_ids` 数组
- **edit-workorder.vue** - 编辑工单页
- 添加关联物品多选功能
- 添加关联客户多选功能
- 加载时已关联的物品和客户
- 修改 `fetchOrder()` 加载 `res.data.items``res.data.customers`
- 修改 `submitForm()` 提交 `item_ids``customer_ids`
### 4. API 文件创建
- **customer.js** (uni-app api)
- 创建客户 API 模块
- 支持 list、get、add、update、delete 操作
## 技术要点
- 使用防抖搜索(setTimeout 300ms
- 多选标签式 UI(参考关联客户实现)
- Vue 3 Composition API (ref, reactive, onMounted)
- uni-app 开发规范
### 5. 样式修改
- **show-workorder.vue** - 工单详情页
- 修改关联客户标签颜色:绿色 → 蓝色
- 背景色: #f6ffed#e6f7ff
- 边框色: #b7eb8f#91d5ff
- 文字色: #52c41a#1890ff
- 与关联物品样式保持一致
### 6. 物品页面添加关联客户
- **add-item.vue** - 新增物品页
- 添加客户搜索功能(防抖 300ms)
- 添加 `selectedCustomers` 数组存储已选客户
- 添加 `customerSearchQuery``customerSearchResults` 等搜索数据
- 修改 `submitForm()` 提交 `customer_ids` 数组
- 添加客户标签样式(蓝色主题)
- **item-edit.vue** - 编辑物品页
- 添加客户搜索功能(防抖 300ms)
- 修改 `fetchDetail()` 加载已关联客户 `res.data.customers`(非 `linkedCustomers`
- 修改 `submitForm()` 提交 `customer_ids` 数组
- 添加客户标签样式(蓝色主题)
- **item-detail.vue** - 物品详情页
- 添加加载 `res.data.customers`
- 添加显示关联客户 UI
- 添加关联客户样式
### 7. 修复关联客户显示问题
- **问题1**: item-edit.vue 没回填关联客户
- 原因:使用了错误的字段名 `linkedCustomers`
- 修复:改为 `res.data.customers`
- **问题2**: item-detail.vue 没显示关联客户
- 原因:`fetchDetail()` 没有加载 `customers` 字段
- 修复:添加 `linkedCustomers.value = res.data.customers || []`
- 添加模板代码显示关联客户
- **后端修复**: apiWarehouse.go
- `CustomerInfo` 结构体添加 `PrimaryPhone` 字段
- 返回客户信息时包含 `primary_phone`
-7
View File
@@ -1,7 +0,0 @@
# 2026-04-30 工作记录
## 修复编译错误:TabCustomer 无 PrimaryPhone 字段
- **问题**: `apiWarehouse.go:1177``c.PrimaryPhone undefined`
- **原因**: `TabCustomer` 结构体没有 `PrimaryPhone` 字段,电话存储在独立的 `TabCustomerPhone` 表(`is_primary=true` 标记主号码)
- **修复**: 改为从 `TabCustomerPhone` 表查询主号码,拼接格式 `+{Prefix} {Phone}`,文件:`routers/apiWarehouse.go`
-147
View File
@@ -1,147 +0,0 @@
# 2026-05-06 日志
## 日历事件日程类型功能
### 修改内容
- **后端** `routers/apiCalendar.go`:
- `TabCalendarEvent` 结构体新增 `ScheduleType string` 字段(默认值 work
- `addevent`/`updateevent` 接口解析并保存 `schedule_type` 参数
- **前端** `CalendarDetail.vue`:
- `eventData` 新增 `scheduleType` 字段
- `openEventModal`/`editEvent` 传递 `scheduleType`
- `selectColor` 函数联动更新 `scheduleType`
- `saveEvent`/`eventDrop` 提交 `schedule_type`
- `getEvents` 返回数据附加 `extendedProps.scheduleType`
- 模态框显示日程类型标签
- **i18n**:
- `zh-CN.json`: 新增 `event_type: "日程类型"`
- `en.json`: 新增 `event_type: "Event Type"`
### 日程类型选项
- work - 工作
- duty - 值班
- exam - 考试
- standby - 备用
- personal_holiday - 个人假期
- public_holiday - 公众假期
### 注意事项
- GORM AutoMigrate 会自动添加新字段
- 前端颜色选择与日程类型联动
## 修复:calendar/events jsonErr
**问题**`fromGetCalendarEvents``start/end``*time.Time` 类型,无法直接解析字符串格式的日期。
**修复**:改为直接用类型断言解析字符串,用 `time.Parse("2006-01-02", ...)` 解析。
## 优化:BgColor 弃用,前端根据 ScheduleType 渲染颜色
**前端**:添加 `getColorByScheduleType()` 函数,`getEvents` 中使用 scheduleType 映射颜色。
**后端**:只存储 ScheduleType,不处理颜色逻辑。颜色完全由前端 `colorOptions` 控制。
## CalendarDetail 滚动标题快照对比
**功能**:每次 getEvents 存快照,数据变化或宽度变化时重新计算标题滚动。
**实现**
- `pageData.lastEventsSnapshot`:存储上一次的 JSON 快照
- `getEvents()` 末尾对比快照,变化则 `setTimeout(recalcScrollTitles, 150)`
- `ResizeObserver` 监听日历容器宽度变化,防抖 150ms 后重算
- `applyScrollToTitle()`:清除旧状态 → 测量 overflow → 设置 `--scroll-distance``data-truncated` 属性
## CalendarList 编辑/删除按钮改用 canEdit
**改动**
- `CalendarList.vue`:编辑/删除按钮 `v-if` 条件从 `calendar.UserID === userStore.userInfo?.ID` 改为 `calendar.canEdit`
- 移除废弃的 `useUserStore` 导入
## CalendarList 删除改用 ConfirmDialog 组件
**改动**
- `CalendarList.vue` 导入并使用 `ConfirmDialog` 组件
- 新增 `showDeleteModal` + `deletingCalendar` 状态
- `deleteCalendar()` 改为打开确认弹窗,`confirmDelete()` 执行实际删除 API
- i18n 新增 `calendar.confirm_delete_message`:zh-CN「确定要删除日历「{name}」吗?此操作不可撤销。」,en 英文版
## 新增日历管理页面 /calendars/admin
**功能**:系统管理员查看所有日历列表,包含日程数量、创建者、创建时间。
**新增文件**
- `src/views/calendar/CalendarAdminList.vue` - 日历管理列表组件
**路由修改** `src/router/index.js`
- 新增 `/calendars/admin` 路由,指向 `CalendarAdminList.vue`
- 设置 `meta: { requireSysAdmin: true }` 要求管理员权限
**SysAdminView.vue**
- `tabs` 数组新增 `{ id: 'calendar', label: t('calendar.admin_title'), to: '/calendars/admin' }`
**i18n 新增**
- `zh-CN.json`: `calendar.admin_title = "日历管理"`, `calendar.event_count = "日程数量"`
- `en.json`: `calendar.admin_title = "Calendar Admin"`, `calendar.event_count = "Event Count"`
## 修复:CalendarAdminList eventCounts 未定义
**问题**:模板访问 `eventCounts[calendar.ID]``eventCounts` 从未声明,且 `fetchEventCounts` 未被调用。
**修复**:后端已直接返回 `event_count` 字段,改为模板直接用 `calendar.event_count ?? 0`,删除多余的 `eventCounts` ref 和 `fetchEventCounts` 函数。
## 新增:后端 TabCalendarEventUserBind 绑定表
**文件**`routers/binds.go`
- 新增 `TabCalendarEventUserBind` 结构体(EventID/UserID/CreatorID/CreatedAt
-`BindsInit` AutoMigrate 中注册
## 新增:日历右键菜单(复制/粘贴日程)
**文件**`CalendarDetail.vue`
**功能**
- 右键日程事件弹出上下文菜单,显示「复制日程」「粘贴日程」
- 复制:将日程数据存入 `clipboard` ref
- 粘贴:以 clipboard 数据调用 `addEvent` API 创建新日程
- 点击任意位置关闭菜单
**实现**
- `contextMenu` ref 管理菜单显示/位置/事件数据
- `clipboard` ref 存储复制的日程
- `eventDidMount` 中为每个事件绑定 `contextmenu` 事件
- `onMounted` 注册全局 click 关闭菜单,`onBeforeUnmount` 移除
- i18n 新增 `calendar.copy_event/paste_event/copy_success/paste_success/no_event_to_paste`
## 修复:粘贴多天日程时结束日期少1天
**问题**:Ctrl+V 粘贴大于1天的日程时,目标结束日期少1天。
**原因**`pasteToDate` 中用 `new Date(targetEndMs).toISOString().split('T')[0]` 计算目标结束日期,`toISOString()` 输出 UTC 时间,在 UTC+8 时区下日期会往前偏移1天。
**修复**`CalendarDetail.vue` `pasteToDate` 函数):
- 改用天数差 `Math.round((origEndDate - origStartDate) / 86400000)` 替代毫秒差
- 用本地日期方法 `targetEndDate.setDate(targetEndDate.getDate() + diffDays)` + 手动拼接 `YYYY-MM-DD`,替代 `toISOString().split('T')[0]`
## 修复:Ctrl+C 无法复制大于1天的日程
**问题**:Ctrl+C 复制多天日程后粘贴无效果。
**根因**`calendarOptions.value.events` 中多天事件的 `end``DateUtils.toCalendarEnd()` 返回的 **Date 对象**(非字符串)。Ctrl+C 直接 `selectedEvent.end || selectedEvent.start` 存入 clipboard,导致 `pasteToDate` 调用 `clipboard.value.end.split('T')[0]` 时 Date 对象没有 `split` 方法而报错。单天事件因走 `isSameDay` 分支不解析 `end`,所以不受影响。
**修复**`CalendarDetail.vue` `handleKeyDown` Ctrl+C 分支):
- 检测 `end` 是否为 Date 实例,如果是则用本地日期方法转为 `YYYY-MM-DD` 字符串再存入 clipboard
## 新增:编辑事件窗口显示创建者
**功能**:编辑事件模态框标题居中显示「xxx 创建」
**实现**(参考 ScheduleView.vue):
- 导入 `useUsersStore`,添加 `usersStore`
- `pageData.eventBindUserID`:数组,每项 `{ eventID, userID }`
- `getUserIdFromEventID(eventID)`:通过事件ID查创建者用户ID
- `getUsernameFromUserID(userID)`:调用 `usersStore.getUsernameFromUserID(userID)`(有缓存)
- `getEvents` 中遍历事件时 `pageData.value.eventBindUserID.push({ eventID: item.ID, userID: item.UserID })`
- 模态框标题新增 `<h5 v-if="eventData.isEditing && getUserIdFromEventID(eventData.id)">` 居中显示
- i18n`calendar.created_by` = "{name} 创建" / "Created by {name}"
-382
View File
@@ -1,382 +0,0 @@
# MEMORY.md - 长效记忆
> 最后更新: 2026-04-29
---
## 项目概览
**项目名称**: OPS 运营管理系统
**技术栈**: Vue 3 + Vite (前端 Web) / uni-app (移动端) / Go + Gin + GORM (后端)
```
ops2/
├── backend/my_work/ # Go 后端(端口由 config.yaml 决定,默认 8080
├── frontend/
│ ├── ops_vue_js/ # Vue 3 Web 前端
│ └── ops2_uniapp/ # uni-app 移动端
└── DOC/
```
---
## 后端架构
### 启动流程 (`main.go`)
1. 检查 `./data/config.yaml`,不存在则复制 `./defConfig/configTemp.yaml`
2. `configed` 必须为 `true` 才能启动
3. 初始化顺序:`ReturnInit``ApiUserInit``ApiFilesInit``ApiScheduleInit``ApiPurchaseInit``ApiWorkOrderInit``ApiWarehouseInit``BindsInit`
4. 静态文件服务 `./dist`,所有非 `/api` 请求转发给前端 HTML
5. 支持 TLS(证书路径在 config 中配置)
6. 版本信息通过 `-ldflags -X` 编译注入(`GitVersion / GitCommit / BuildTime`
### 路由根 (`api.go`)
```
/api
├── /static → ApiStatic
├── /users → ApiUser
├── /files → ApiFiles
├── /purchase → ApiPurchase
├── /schedule → ApiSchedule
├── /work_order → ApiWorkOrder
├── /warehouse → ApiWarehouse
└── /admin → ApiSysAdmin
```
### 请求/响应格式
```json
// 请求体
{ "userCookieValue": "xxx", "data": { ... } }
// 响应体
{ "err_code": 0, "err_msg": "apiOK", "return": { ... } }
```
错误码从 `./defConfig/errorCodes.json` 读取,常用:`apiOK`=0`userNoLogin`=-44`permission_denied``parameErr``dbErr`
### 认证机制
- 登录成功返回 Cookie 对象(存 `TabUserCookie` 表)
- 后续请求通过 JSON body 中 `userCookieValue` 传递
- Cookie 过期后端返回 err_code=-44,前端拦截器自动处理
- `AuthenticationAuthority(ctx)` → 分离 cookie 和 data,验证返回 `(isAuth bool, user TabUser, data map)`
---
## 用户认证模块 (`apiUsers.go`)
### 数据表
| 表 | 说明 |
|---|---|
| `TabUser` | 用户(Name 唯一索引) |
| `TabUserGroups` | 用户组 |
| `TabUserGroupBinds` | 用户-组绑定 |
| `TabUserInfo` | 用户详情(头像/昵称/性别/生日/语言等) |
| `TabUserCookie` | 登录 CookieExpiresAtRemember 字段) |
| `TabUserLoginFailLog` | 登录失败日志(24小时内聚合,累计 Count) |
### 初始化
- 自动创建 `admins` 组 + `admin` 用户(默认密码 `adminpassword`
- 密码:加盐哈希,支持 `text` / `md5` / `md5salt`config 指定)
### 权限缓存(内存)
各模块维护各自的管理员 ID 列表:
| 变量 | 所属模块 | 刷新函数 |
|---|---|---|
| `sysAdmins []uint` | apiUsers.go | `updateSysAdminsCash()` |
| `scheduleAdmins []uint` | apiSchedule.go | `ScheduleUpdateAdminsCash()` |
| `purchaseAdmins []uint` | apiPurchase.go | `PurchaseUpdateAdminsCash()` |
| `workOrderAdmins []uint` | apiWorkOrder.go | `WorkOrderUpdateAdminsCash()` |
| `warehouseAdmins []uint` | apiWarehouse.go | `WarehouseUpdateAdminsCash()` |
**修改用户组时自动刷新缓存**`apiSysAdmin.go``add_group_member``remove_group_member`):
```go
switch group.Name {
case "admins": updateSysAdminsCash()
case "schedule_admin": ScheduleUpdateAdminsCash()
case "purchase_admin": PurchaseUpdateAdminsCash()
case "work_order_admin": WorkOrderUpdateAdminsCash()
case "warehouse_admin": WarehouseUpdateAdminsCash()
}
```
### API 路由 (`/api/users/*`)
| 路由 | 用途 |
|------|------|
| `POST /login` | 登录(返回 Cookie 对象,含 Remember |
| `POST /register` | 注册 |
| `POST /getinfo` | 获取当前用户信息(含 isSysAdmin 字段) |
| `POST /changePassword` | 修改密码(oldpass/newpass |
| `POST /changeEmail` | 修改邮箱 |
| `POST /updateAvatar` | 更新头像(FormData |
| `POST /updateInfo` | 更新详情(firstName/username/birthdate/gender/region/language |
| `GET /getuserinfo/:id` | 获取指定用户信息 |
---
## 系统管理模块 (`apiSysAdmin.go`)
**路由前缀**: `/api/admin/*`,全部要求 `SysAdminCheck`
| 路由 | 用途 |
|------|------|
| `POST /sysadmins` | 获取系统管理员 ID 列表 |
| `POST /users` | 用户列表(分页+搜索,返回含头像路径) |
| `POST /groups` | 用户组列表(含成员数 + 前5个成员ID) |
| `POST /group_members` | 指定组的成员列表(分页) |
| `POST /user_detail` | 用户详情(基本信息 + userinfo |
| `POST /reset_user_password` | 重置密码(同时注销该用户所有 cookie) |
| `POST /add_group_member` | 添加用户到组(含缓存刷新) |
| `POST /remove_group_member` | 从组移除用户(含缓存刷新) |
| `POST /login_fail_logs` | 登录失败日志(分页+搜索 username/IP |
`SysAdminCheck(userID)` 直接查内存 `sysAdmins` 列表。
---
## 文件管理模块 (`api_Files.go`)
### 数据表 `TabFileInfo`
| 字段 | 说明 |
|---|---|
| `Sha256` | 唯一标识,去重键 |
| `Name` | 原始文件名 |
| `Path` | 存储路径 |
| `Mime` | MIME 类型 |
| `Type` | image/video/pdf 等 |
| `Const` | 引用计数 |
### 存储结构
```
data/
├── static/avatar/ # 用户头像(/api/static/avatar/:filename
└── upload/
├── image/ # 以 SHA256 命名
├── video/
├── music/
└── pdf/
```
### API (`/api/files/*`)
| 路由 | 用途 |
|---|---|
| `POST /upload/image` | 上传图片(FormData + SHA256 去重) |
| `GET /:mode/:hash` | `get`=下载(带文件名),`download`=预览 |
---
## 日程模块 (`apiSchedule.go`)
### 数据表
- `TabSchedule`(软删除):Title / StartDate / EndDate / BgColor(默认#3788d9) / Remark
- `TabScheduleLog`:操作日志
### API (`/api/schedule/*`)
| 路由 | 用途 |
|---|---|
| `POST /getevents` | 按日期范围查询(`start_date <= end AND end_date >= start` |
| `POST /addevent` | 新增 |
| `POST /editevent` | 编辑 |
| `POST /deleevent` | 软删除 |
---
## 采购模块 (`apiPurchase.go`)
### 数据表
| 表 | 用途 |
|---|---|
| `TabPurchaseOrder` | 采购订单(软删除) |
| `TabPurchaseCosts` | 费用明细(单价/运费,多币种) |
| `TabPurchaseFileBind` | 图片关联 |
| `TabPurchaseCommit` | 状态变更记录 |
| `TabPurchaseLog` | 操作日志 |
### 状态流
```
pending → ordered → arrived → received
lost / returned
```
### 货币:`1-CNY / 2-MOP / 3-HKD / 4-USD`
### API (`/api/purchase/*`)
| 路由 | 用途 |
|---|---|
| `POST /getorder` | 订单详情(含费用/图片/状态记录/关联工单) |
| `POST /getorders` | 列表(搜索/分页/状态筛选) |
| `POST /addorder` | 新增 |
| `POST /updateorder` | 编辑(费用/图片重建) |
| `POST /deleteorder` | 删除 |
| `POST /updatestatus` | 更新状态(可附评论/图片) |
| `POST /delete_commit` | 删除状态记录 |
| `POST /getordercount` | 各状态数量统计 |
| `POST /search_work_orders` | 搜索工单(用于关联) |
---
## 工单模块 (`apiWorkOrder.go`)
### 数据表
| 表 | 用途 |
|---|---|
| `TabWorkOrder` | 工单(软删除) |
| `TabWorkOrderFileBind` | 工单图片关联 |
| `TabWorkOrderCommit` | 进度记录 |
| `TabWorkOrderCommitFileBind` | 进度关联图片 |
| `TabWorkOrderPurchaseOrderBind` | 工单-采购订单关联(含 CommitID) |
| `TabWorkOrderLog` | 操作日志 |
### 状态流
```
pending → checked → parts_ordered → repaired → returned
unrepairable
```
### 特殊逻辑
- 状态变为 `returned` 时,自动移除物品的容器绑定(`ContainerID = nil`
- 工单 ↔ 物品:`TabWarehouseItemWorkOrderBind`
- 工单 ↔ 采购:`TabWorkOrderPurchaseOrderBind`
### API (`/api/work_order/*`)
| 路由 | 用途 |
|---|---|
| `POST /add` | 新增(可关联物品) |
| `POST /update` | 编辑 |
| `POST /list` | 列表 |
| `POST /get` | 详情(含图片/进度/关联物品/采购) |
| `POST /commit` | 提交进度(更新状态,可关联采购) |
| `POST /delete` | 删除 |
| `POST /delete_commit` | 删除进度 |
| `POST /count` | 各状态统计 |
| `POST /search_purchase_orders` | 搜索采购订单(用于关联) |
---
## 仓库模块 (`apiWarehouse.go`)
### 数据表
| 表 | 用途 |
|---|---|
| `TabWarehouseContainer` | 容器(树形,最多5层,ParentID=nil为顶级) |
| `TabWarehouseItem` | 物品(ContainerID=nil表示未入库) |
| `TabWarehouseItemCommit` | 物品移动记录 |
| `TabWarehouseLog` | 操作日志 |
### 跨模块绑定表 (`binds.go`)
| 表 | 用途 |
|---|---|
| `TabWarehouseItemWorkOrderBind` | 物品-工单 |
| `TabWarehouseItemFileBind` | 物品-图片 |
| `TabWarehouseContainerFileBind` | 容器-图片 |
| `TabPurchaseFileBind` | 采购-图片 |
| `TabWorkOrderFileBind` | 工单-图片 |
| `TabWorkOrderCommitFileBind` | 工单进度-图片 |
| `TabWorkOrderPurchaseOrderBind` | 工单-采购 |
---
## Web 前端架构 (`frontend/ops_vue_js/`)
**技术栈**: Vue 3 + Vite 7 + Pinia + Vue Router (hash 模式) + Vue I18n + Tailwind CSS v4 + Tabler Icons
### 目录结构
```
src/
├── api/
│ ├── index.js # Axios 实例,基础 URL /api,请求拦截注入 cookie,响应拦截处理 -44
│ ├── auth.js # 认证 + sysadmin 管理 API
│ ├── purchase.js # 采购 API
│ ├── warehouse.js # 仓库 API
│ ├── work_order.js # 工单 API
│ ├── schedule.js # 日程 API
│ └── users.js # 其他用户信息 API(按需加载头像/用户名)
├── components/
│ ├── AppHeader.vue # 导航栏(含系统管理入口,权限判断 isSysAdmin
│ ├── AppFooter.vue
│ ├── AppToast.vue
│ ├── ConfirmDialog.vue
│ ├── PurchaseOrderForm.vue
│ ├── SettingNav.vue
│ ├── tagadder.vue
│ ├── useDropzone.vue # 文件拖拽上传
│ ├── imageCropper.vue # 图片裁剪
│ ├── datePicker.vue
│ ├── dateTimePicker.vue
│ └── datatimePickerForFullCalendar.vue
├── composables/
├── i18n/
│ ├── en.json # 英文翻译
│ └── zh-CN.json # 中文翻译
├── layouts/
│ ├── DefaultLayout.vue # 需要登录的页面布局
│ └── AuthLayout.vue # 认证页面全屏布局
├── router/index.js
├── stores/
│ ├── user.js # 当前用户(isLoggedIn / isSysAdmin / cookie / avatarUrl
│ ├── users.js # 其他用户信息缓存(按需拉取,防重复请求)
│ └── toast.js # 全局 Toast
└── views/
├── HomeView.vue
├── ScheduleView.vue # FullCalendar
├── SysAdminView.vue # 系统管理(仅 sysAdmin 可访问,meta.requireSysAdmin
├── AdminView.vue
├── purchase/ # PurchaseList / addorder / ShowOrder / editorder
├── work_order/ # WorkOrderList / AddEditWorkOrder / ShowWorkOrder
├── warehouse/ # WarehouseOverview / ContainerList / ContainerDetail / ItemList / ItemDetail / AddItem / ItemEdit
└── settings/ # AccountView / ContactView / SecurityView
```
### 路由说明
- 公开页:`/` `/login` `/register` `/forgot_password` `/schedule` `/404`
- `/sysadmin` 需要 `meta.requireSysAdmin`,不满足跳回首页
- 未登录跳转 `/login?redirect=原路径`
### 状态管理 (`stores/user.js`)
- Cookie 持久化:`Remember=true` 存 localStorage,否则只存 sessionStorage
- `isSysAdmin``/users/getinfo` 返回的 `isSysAdmin` 字段驱动
- `fetchUserInfo()``login()` 后自动调用
### i18n 翻译节点
`week / errorpage / appname / tagadder / dropzone / cropper / purchase / work_order / warehouse / purchase_addorder / schedule / home / message / settings / button / footer / cost_type / order_status / sysadmin`
### 构建配置
- 输出目录: `../../backend/my_work/dist`(后端直接 serve
- 开发代理: `/api``http://127.0.0.1:8080`
- 路径别名: `@``./src`
---
## 移动端 (`frontend/ops2_uniapp/`)
**技术栈**: uni-app + Vue 3 + Pinia
**当前完成度**: ~40%
| 页面 | 完成度 |
|---|---|
| login | 85%(表单/验证/请求/Toast |
| settings | 90%API 地址配置/连接测试) |
| index/order/message | 5%(占位) |
| user | 20%(登录入口) |
**待完成**:
- 完善 `api/index.js`Cookie 认证,参照 Web 前端)
- 实现各功能页面(仓库/工单/采购等)
---
## 开发注意事项
1. **后端 JSON 字段命名**: 结构体字段为 PascalCase(如 `UserID / AvatarPath`),前端需对应
2. **头像**: `usersStore.getAvatarUrlFromUserID(id)` 返回 `/api/static/avatar/{path}` 或默认 `/ava.svg`
3. **i18n 修改**: 同时修改 `en.json``zh-CN.json` 两个文件
4. **同源部署**: 后端 serve `./dist` 静态资源,前端 build 直接输出到后端目录
5. **API 封装**: `api/index.js` 统一返回 `{ errCode, data, raw }`
---
## 更新记录
- 2026-04-24: 首次梳理
- 2026-04-28: 修复 SysAdminView 中 `<img .../>` 误删 bug;添加 message.sysadmin 英文翻译
- 2026-04-29: 全面重新分析代码,更新并精简记忆文档