diff --git a/.npmrc b/.npmrc index 614e2bc..cbd1d05 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,2 @@ @saber:registry=https://center.javablade.com/api/packages/blade/npm/ -//center.javablade.com/api/packages/blade/npm/:_authToken=如何配置请见BladeX开发手册 1.3.0配置资源令牌 章节 +//center.javablade.com/api/packages/blade/npm/:_authToken=aafaf70d1edf6ac66f7eddce9a57c67199b274a3 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1187987 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,244 @@ +# AGENTS.md +本文件用于指导 Codex (Codex.ai/code) 在 Saber3 代码仓库中工作时的行为规范。 + +> 本规范适用于 Saber3 前端工程的所有开发任务,为强制性条款。除非用户显式豁免,任何条目都不得忽视或删减。 +> +> 作为 AI 助手参与本项目开发时,你必须: +> - 每次输出前深度理解 BladeX 微服务架构体系、Saber3 前端工程结构和 Vue 3 技术栈特征 +> - 当回答依赖外部知识时,先查询 Vue 3、Element Plus、Avue、Vite 等官方文档 +> - 若需求含糊,先复述已知信息并列出关键澄清问题 +> - 面对复杂需求,先拆分为可管理的子任务 +> +> 所有开发内容必须建立在深度思考过的基础之上,禁止机械生成与错误填充。 +> 如果你已了解所有规范,请在用户第一次对话时说明:"我已充分了解 BladeX 微服务平台开发规范。" + +--- + +## 1. 项目概览 + +**Saber3** 是 BladeX 微服务平台的官方前端工程,基于 Vue 3 生态构建的企业级后台管理系统。 + +### 1.1 技术栈 + +| 技术 | 版本 | 用途 | +| ------------------ | -------- | ------------------ | +| Vue | ^3.5.13 | 核心框架 | +| Element Plus | ^2.10.1 | UI 组件库 | +| @smallwei/avue | ^3.7.2 | 增强型 CRUD 组件库 | +| Vue Router | ^4.3.2 | 路由管理 | +| Vuex | ^4.1.0 | 状态管理 | +| Axios | ^1.8.3 | HTTP 客户端 | +| Vite | ^5.4.19 | 构建工具 | +| vue-i18n | ^11.1.3 | 国际化 | +| Sass | ^1.85.1 | CSS 预处理器 | +| crypto-js / sm-crypto | - | 加密(AES / SM2) | + +--- + +## 2. 项目架构 + +``` +Saber3/ +├── src/ +│ ├── main.js # 应用入口(全局组件注册、插件挂载) +│ ├── permission.js # 路由守卫(鉴权、标签页、锁屏) +│ ├── axios.js # HTTP 拦截器(Token 刷新、加密、错误处理) +│ ├── api/ # API 接口层(按业务模块组织) +│ ├── views/ # 业务页面(按模块目录组织) +│ ├── option/ # Avue 表格/表单配置(与 views/api 同构路径) +│ ├── const/ # 常量定义 +│ ├── config/ # 全局配置 +│ │ ├── website.js # 核心配置(认证、菜单、水印、OAuth2、设计器) +│ │ └── env.js # 环境变量(API 基础地址) +│ ├── store/ # Vuex 状态管理(user/common/tags/logs/dict) +│ ├── router/ # 路由系统 +│ │ ├── avue-router.js # 动态路由核心(菜单→路由、keep-alive 扁平化) +│ │ ├── page/ # 页面级路由(登录、锁屏、错误页) +│ │ └── views/ # 视图路由(首页、控制台等) +│ ├── components/ # 全局公共组件 +│ ├── mixins/ # 混入(crud.js 等) +│ ├── utils/ # 工具函数(auth/crypto/validate/util...) +│ ├── lang/ # 国际化(zh/en) +│ ├── styles/ # 全局样式 & 多主题 +│ ├── mac/ # macOS 风格主题 +│ └── page/ # 页面布局框架(主布局、登录、锁屏) +├── vite/ # Vite 插件配置 +├── vite.config.mjs # Vite 主配置 +└── package.json +``` + +### 2.1 路径别名 + +`@` → `./src`、`~` → `./`、`components` → `./src/components`、`styles` → `./src/styles`、`utils` → `./src/utils` + +--- + +## 3. 核心机制 + +### 3.1 Avue 组件代码生成(Skill 调用) + +当涉及 `avue-crud`、`avue-form`、`avue-tree` 等 Avue 组件的代码生成或配置时,**优先调用 `avue-design` Skill**。该 Skill 覆盖 Avue 全部组件类型,支持 Options API 和 Composition API 两种代码风格。 + +**触发场景**:用户要求创建 CRUD 表格、Avue 表单、树组件、数据展示页面,或提到 avue、crud 表格、动态表单、JSON 配置表单等关键词。 + +### 3.2 Avue Option 配置 + +Avue 表格/表单配置独立存放于 `src/option/` 目录,与 `views` 和 `api` 保持同构的模块路径映射。 + +### 3.3 API 接口规范 + +- 所有 API 通过 `src/axios.js` 封装的 Axios 实例发起,按业务模块组织于 `src/api/` +- 命名约定:列表 `getList(current, size, params)`、详情 `getDetail(id)`、新增 `add(row)`、更新 `update(row)`、删除 `remove(ids)`、树形 `getXxxTree()` +- 后端微服务前缀:`/blade-system/`、`/blade-resource/`、`/blade-flow/`、`/blade-desk/`、`/blade-log/`、`/blade-develop/` + +### 3.4 认证机制 + +- OAuth2:`Basic` 头传递 `clientId:clientSecret`(Base64 编码) +- Token:请求头 `Blade-Auth: bearer {token}`,支持 AES 加密模式 +- 存储:`saber3-access-token` / `saber3-refresh-token`(通过 `utils/auth.js` 管理) +- 401 自动刷新 Token,并发请求排队等待;登录密码使用 SM2 国密加密 + +### 3.5 路由系统 + +- 静态路由:`router/page/` + `router/views/` +- 动态路由:`avue-router.js` 将后端菜单数据转换为 Vue Router 路由 +- 多级路由自动扁平化为二级,支持 keep-alive 跨层级缓存 +- 外部链接自动转换为 iframe 路由,支持 Token 透传 + +### 3.6 权限控制 + +- 路由守卫:`permission.js` 控制登录态、锁屏、标签页 +- 按钮权限:`store.getters.permission`,格式 `{module}_{action}`(如 `dict_add`) +- 管理员判断:`userInfo.authority.includes('admin')` + +### 3.7 多租户 + +通过 `website.tenantMode` 控制开关,管理组租户编号 `000000`,后端自动通过请求头传递租户信息。 + +--- + +## 4. 开发规范 + +### 4.1 双 API 风格共存 + +项目同时支持 **Options API**(主流,绝大多数现有页面)和 **Composition API**(新组件可选)。 + +**选择原则**: +- 修改现有页面:保持该页面原有风格,不混用 +- 新建 CRUD 页面:调用 `avue-design` Skill 生成,或参考现有页面手动编写 +- 新建复杂交互页面:可使用 Composition API + ` - - + + diff --git a/public/img/左侧背景放底部.png b/public/img/左侧背景放底部.png new file mode 100644 index 0000000..bef3839 Binary files /dev/null and b/public/img/左侧背景放底部.png differ diff --git a/public/img/系统首页顶部背景图.png b/public/img/系统首页顶部背景图.png new file mode 100644 index 0000000..5832ce6 Binary files /dev/null and b/public/img/系统首页顶部背景图.png differ diff --git a/public/img/运输登录.jpg b/public/img/运输登录.jpg new file mode 100644 index 0000000..1c254bd Binary files /dev/null and b/public/img/运输登录.jpg differ diff --git a/src/api/base/airport-master.js b/src/api/base/airport-master.js new file mode 100644 index 0000000..e95b967 --- /dev/null +++ b/src/api/base/airport-master.js @@ -0,0 +1,52 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-system/airport-master/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-system/airport-master/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-system/airport-master/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-system/airport-master/submit', + method: 'post', + data: row, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-system/airport-master/status', + method: 'post', + params: { + id, + status, + }, + }); +}; diff --git a/src/api/base/common-address.js b/src/api/base/common-address.js new file mode 100644 index 0000000..0b8eab0 --- /dev/null +++ b/src/api/base/common-address.js @@ -0,0 +1,52 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/common-address/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/common-address/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-transport/common-address/submit', + method: 'post', + data: row, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/common-address/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const getSourceOptions = (addressType, keyword) => { + return request({ + url: '/blade-transport/common-address/source-options', + method: 'get', + params: { + addressType, + keyword, + }, + }); +}; diff --git a/src/api/base/currency.js b/src/api/base/currency.js new file mode 100644 index 0000000..adb7555 --- /dev/null +++ b/src/api/base/currency.js @@ -0,0 +1,60 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-system/currency/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-system/currency/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const getRate = params => { + return request({ + url: '/blade-system/currency/rate', + method: 'get', + params, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-system/currency/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-system/currency/submit', + method: 'post', + data: row, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-system/currency/status', + method: 'post', + params: { + id, + status, + }, + }); +}; diff --git a/src/api/base/customer-type.js b/src/api/base/customer-type.js new file mode 100644 index 0000000..0ad91d8 --- /dev/null +++ b/src/api/base/customer-type.js @@ -0,0 +1,52 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-system/customer-type/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-system/customer-type/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-system/customer-type/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-system/customer-type/submit', + method: 'post', + data: row, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-system/customer-type/status', + method: 'post', + params: { + id, + status, + }, + }); +}; diff --git a/src/api/base/fee-item.js b/src/api/base/fee-item.js new file mode 100644 index 0000000..3aeec72 --- /dev/null +++ b/src/api/base/fee-item.js @@ -0,0 +1,52 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-system/fee-item/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-system/fee-item/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-system/fee-item/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-system/fee-item/submit', + method: 'post', + data: row, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-system/fee-item/status', + method: 'post', + params: { + id, + status, + }, + }); +}; diff --git a/src/api/base/port-terminal.js b/src/api/base/port-terminal.js new file mode 100644 index 0000000..6215602 --- /dev/null +++ b/src/api/base/port-terminal.js @@ -0,0 +1,59 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-system/port-terminal/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-system/port-terminal/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-system/port-terminal/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-system/port-terminal/submit', + method: 'post', + data: row, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-system/port-terminal/status', + method: 'post', + params: { + id, + status, + }, + }); +}; + +export const getPortSelect = () => { + return request({ + url: '/blade-system/port-terminal/port-select', + method: 'get', + }); +}; diff --git a/src/api/base/railway-station.js b/src/api/base/railway-station.js new file mode 100644 index 0000000..6af7c68 --- /dev/null +++ b/src/api/base/railway-station.js @@ -0,0 +1,52 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-system/railway-station/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-system/railway-station/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-system/railway-station/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-system/railway-station/submit', + method: 'post', + data: row, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-system/railway-station/status', + method: 'post', + params: { + id, + status, + }, + }); +}; diff --git a/src/api/base/region.js b/src/api/base/region.js index 01bcd16..9a020a0 100644 --- a/src/api/base/region.js +++ b/src/api/base/region.js @@ -50,3 +50,10 @@ export const submit = row => { data: row, }); }; + +export const sync = () => { + return request({ + url: '/blade-system/region/sync', + method: 'post', + }); +}; diff --git a/src/api/system/user.js b/src/api/system/user.js index f62c5ff..87bd534 100644 --- a/src/api/system/user.js +++ b/src/api/system/user.js @@ -88,6 +88,18 @@ export const resetPassword = userIds => { }); }; +export const setPassword = (userId, password, password2) => { + return request({ + url: '/blade-system/user/set-password', + method: 'post', + params: { + userId, + password, + password2, + }, + }); +}; + export const updatePassword = (oldPassword, newPassword, newPassword1) => { return request({ url: '/blade-system/user/update-password', @@ -172,4 +184,3 @@ export const getUserPage = (current, size, account, name) => { }, }); }; - diff --git a/src/api/transportCapacity/driver.js b/src/api/transportCapacity/driver.js new file mode 100644 index 0000000..16597ae --- /dev/null +++ b/src/api/transportCapacity/driver.js @@ -0,0 +1,60 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/driver/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/driver/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-transport/driver/submit', + method: 'post', + data: row, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/driver/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-transport/driver/status', + method: 'post', + params: { + id, + status, + }, + }); +}; + +export const getExpiryStat = params => { + return request({ + url: '/blade-transport/driver/expiry-stat', + method: 'get', + params, + }); +}; diff --git a/src/api/transportCapacity/transport-ship.js b/src/api/transportCapacity/transport-ship.js new file mode 100644 index 0000000..25122d1 --- /dev/null +++ b/src/api/transportCapacity/transport-ship.js @@ -0,0 +1,60 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/transport-ship/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/transport-ship/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-transport/transport-ship/submit', + method: 'post', + data: row, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/transport-ship/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-transport/transport-ship/status', + method: 'post', + params: { + id, + status, + }, + }); +}; + +export const getExpiryStat = params => { + return request({ + url: '/blade-transport/transport-ship/expiry-stat', + method: 'get', + params, + }); +}; diff --git a/src/api/transportCapacity/transport-vehicle.js b/src/api/transportCapacity/transport-vehicle.js new file mode 100644 index 0000000..573d67d --- /dev/null +++ b/src/api/transportCapacity/transport-vehicle.js @@ -0,0 +1,60 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/transport-vehicle/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/transport-vehicle/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-transport/transport-vehicle/submit', + method: 'post', + data: row, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/transport-vehicle/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-transport/transport-vehicle/status', + method: 'post', + params: { + id, + status, + }, + }); +}; + +export const getExpiryStat = params => { + return request({ + url: '/blade-transport/transport-vehicle/expiry-stat', + method: 'get', + params, + }); +}; diff --git a/src/api/user.js b/src/api/user.js index 75f3531..b59b6cc 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -45,17 +45,18 @@ export const loginBySocial = (tenantId, source, code, state) => }, }); -export const loginBySso = (state, code) => +export const loginBySso = (tenantId, state, code) => request({ - url: '/blade-auth/oauth/token', + url: '/blade-auth/iam/sso/token', method: 'post', headers: { - 'Tenant-Id': state, + 'Tenant-Id': tenantId, }, params: { - tenantId: state, + tenant_id: tenantId, code, - grant_type: 'authorization_code', + state, + grant_type: 'iam_sso', scope: 'all', redirect_uri: website.oauth2.redirectUri, }, diff --git a/src/api/vehicle/accident-record.js b/src/api/vehicle/accident-record.js new file mode 100644 index 0000000..2dab765 --- /dev/null +++ b/src/api/vehicle/accident-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/accident-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/accident-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/accident-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/accident-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/accident-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/annual-inspection-record.js b/src/api/vehicle/annual-inspection-record.js new file mode 100644 index 0000000..51cbd4d --- /dev/null +++ b/src/api/vehicle/annual-inspection-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/annual-inspection-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/annual-inspection-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/annual-inspection-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/annual-inspection-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/annual-inspection-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/credit-score-quantification.js b/src/api/vehicle/credit-score-quantification.js new file mode 100644 index 0000000..196ab44 --- /dev/null +++ b/src/api/vehicle/credit-score-quantification.js @@ -0,0 +1,62 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/credit-score-quantification/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/credit-score-quantification/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-transport/credit-score-quantification/submit', + method: 'post', + data: row, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/credit-score-quantification/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const publish = id => { + return request({ + url: '/blade-transport/credit-score-quantification/publish', + method: 'post', + params: { + id, + }, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-transport/credit-score-quantification/status', + method: 'post', + params: { + id, + status, + }, + }); +}; diff --git a/src/api/vehicle/customer-archive.js b/src/api/vehicle/customer-archive.js new file mode 100644 index 0000000..08245bc --- /dev/null +++ b/src/api/vehicle/customer-archive.js @@ -0,0 +1,92 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/customer-archive/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/customer-archive/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const submit = row => { + return request({ + url: '/blade-transport/customer-archive/submit', + method: 'post', + data: row, + }); +}; + +export const submitApproval = id => { + return request({ + url: '/blade-transport/customer-archive/submit-approval', + method: 'post', + params: { + id, + }, + }); +}; + +export const approve = id => { + return request({ + url: '/blade-transport/customer-archive/approve', + method: 'post', + params: { + id, + }, + }); +}; + +export const reject = id => { + return request({ + url: '/blade-transport/customer-archive/reject', + method: 'post', + params: { + id, + }, + }); +}; + +export const changeStatus = (id, status) => { + return request({ + url: '/blade-transport/customer-archive/status', + method: 'post', + params: { + id, + status, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/customer-archive/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const getScoreTemplate = quantificationId => { + return request({ + url: '/blade-transport/customer-archive/score-template', + method: 'get', + params: { + quantificationId, + }, + }); +}; diff --git a/src/api/vehicle/etc-record.js b/src/api/vehicle/etc-record.js new file mode 100644 index 0000000..1b698b9 --- /dev/null +++ b/src/api/vehicle/etc-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/etc-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/etc-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/etc-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/etc-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/etc-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/insurance-record.js b/src/api/vehicle/insurance-record.js new file mode 100644 index 0000000..8bb823d --- /dev/null +++ b/src/api/vehicle/insurance-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/insurance-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/insurance-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/insurance-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/insurance-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/insurance-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/maintenance-plan.js b/src/api/vehicle/maintenance-plan.js new file mode 100644 index 0000000..52db725 --- /dev/null +++ b/src/api/vehicle/maintenance-plan.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/maintenance-plan/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/maintenance-plan/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/maintenance-plan/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/maintenance-plan/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/maintenance-plan/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/maintenance-record.js b/src/api/vehicle/maintenance-record.js new file mode 100644 index 0000000..8773398 --- /dev/null +++ b/src/api/vehicle/maintenance-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/maintenance-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/maintenance-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/maintenance-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/maintenance-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/maintenance-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/mileage-record.js b/src/api/vehicle/mileage-record.js new file mode 100644 index 0000000..033443f --- /dev/null +++ b/src/api/vehicle/mileage-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/mileage-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/mileage-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/mileage-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/mileage-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/mileage-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/oil-electric-record.js b/src/api/vehicle/oil-electric-record.js new file mode 100644 index 0000000..428f1c2 --- /dev/null +++ b/src/api/vehicle/oil-electric-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/oil-electric-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/oil-electric-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/oil-electric-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/oil-electric-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/oil-electric-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/other-expense-record.js b/src/api/vehicle/other-expense-record.js new file mode 100644 index 0000000..58c7819 --- /dev/null +++ b/src/api/vehicle/other-expense-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/other-expense-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/other-expense-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/other-expense-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/other-expense-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/other-expense-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/tire-replacement-record.js b/src/api/vehicle/tire-replacement-record.js new file mode 100644 index 0000000..0446c74 --- /dev/null +++ b/src/api/vehicle/tire-replacement-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/tire-replacement-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/tire-replacement-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/tire-replacement-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/tire-replacement-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/tire-replacement-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/transport-change-record.js b/src/api/vehicle/transport-change-record.js new file mode 100644 index 0000000..fb01f62 --- /dev/null +++ b/src/api/vehicle/transport-change-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/transport-change-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/transport-change-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/transport-change-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/transport-change-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/transport-change-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/api/vehicle/violation-record.js b/src/api/vehicle/violation-record.js new file mode 100644 index 0000000..0575f04 --- /dev/null +++ b/src/api/vehicle/violation-record.js @@ -0,0 +1,49 @@ +import request from '@/axios'; + +export const getList = (current, size, params) => { + return request({ + url: '/blade-transport/violation-record/list', + method: 'get', + params: { + ...params, + current, + size, + }, + }); +}; + +export const getDetail = id => { + return request({ + url: '/blade-transport/violation-record/detail', + method: 'get', + params: { + id, + }, + }); +}; + +export const remove = ids => { + return request({ + url: '/blade-transport/violation-record/remove', + method: 'post', + params: { + ids, + }, + }); +}; + +export const add = row => { + return request({ + url: '/blade-transport/violation-record/submit', + method: 'post', + data: row, + }); +}; + +export const update = row => { + return request({ + url: '/blade-transport/violation-record/submit', + method: 'post', + data: row, + }); +}; diff --git a/src/axios.js b/src/axios.js index a4719cd..cd29fa6 100644 --- a/src/axios.js +++ b/src/axios.js @@ -33,10 +33,14 @@ const isRefreshTokenRequest = config => (config.url || '').includes('/oauth/token') && (config.params || {}).grant_type === REFRESH_GRANT_TYPE; +// 判断是否为IAM统一身份认证登录请求 +const isIamSsoLoginRequest = config => (config.url || '').includes('/iam/sso/token'); + // 判断是否为重新登录请求:会开启新的会话周期 const isLoginRequest = config => { const grantType = (config.params || {}).grant_type; return ( + isIamSsoLoginRequest(config) || (config.url || '').includes('/oauth/token') && !!grantType && grantType !== REFRESH_GRANT_TYPE ); }; diff --git a/src/config/website.js b/src/config/website.js index dcce142..c3d1bcc 100644 --- a/src/config/website.js +++ b/src/config/website.js @@ -2,10 +2,10 @@ * 全局配置文件 */ export default { - title: 'Saber', + title: '运输板块数字化管理平台', logo: 'X', key: 'saber', //配置主键,目前用于存储 - indexTitle: 'BladeX 微服务平台', + indexTitle: 'TMS物流运输管理系统', clientId: 'saber3', // 客户端id clientSecret: 'saber3_secret', // 客户端密钥 tenantMode: true, // 是否开启租户模式 @@ -53,22 +53,24 @@ export default { //水印配置 watermark: { mode: false, - text: 'BladeX', + text: 'TMS物流运输管理系统', }, //oauth2配置 oauth2: { // 是否开启注册功能 registerMode: true, // 使用后端工程 @org.springblade.test.Sm2KeyGenerator 获取 - publicKey: '请配置国密sm2公钥', + publicKey: + '04db3266e1a3bc32d590911a68a1c14f833c2f93edf19f23114bbb6072792d0dd40c1b25316690255d8cf5db1784995f8ade67f66e436d56913cc739c64a780d71', // 第三方系统授权地址 authUrl: 'http://localhost/blade-auth/oauth/render', // 单点登录系统认证 ssoMode: false, // 是否开启单点登录功能 - ssoBaseUrl: 'http://localhost:8100', // 单点登录系统地址(cloud端口为8100,boot端口为80) - ssoAuthUrl: '/oauth/authorize?client_id=saber3&response_type=code&redirect_uri=', // 单点登录授权地址 + ssoClientId: 'f0b52f23f71b1649c468', // IAM统一身份认证客户端ID + ssoBaseUrl: 'https://sso.gxwuzi.com/esc-sso', // IAM统一身份认证地址 + ssoAuthUrl: '/oauth2.0/authorize', // IAM统一身份认证授权地址 ssoLogoutUrl: '/oauth/authorize/logout?redirect_uri=', // 单点登录退出地址 - redirectUri: 'http://localhost:2888/login', // 单点登录回调地址(Saber服务的登录界面地址) + redirectUri: 'http://172.16.203.228:8000/callback', // 单点登录回调地址(Saber服务的登录界面地址) }, //设计器配置 design: { diff --git a/src/lang/zh.js b/src/lang/zh.js index 83af021..a20c5d0 100644 --- a/src/lang/zh.js +++ b/src/lang/zh.js @@ -1,5 +1,5 @@ export default { - title: 'BladeX微服务平台', + title: 'TMS物流运输管理系统', confirmTip: '提示', logoutTip: '退出系统, 是否继续?', submitText: '确定', @@ -79,7 +79,7 @@ export default { }, login: { title: '登录 ', - info: 'BladeX 微服务平台', + info: 'TMS物流运输管理系统', tenantId: '请输入租户ID', name: '请输入姓名', username: '请输入账号', diff --git a/src/mac/login.scss b/src/mac/login.scss index 19e5e87..87b82eb 100644 --- a/src/mac/login.scss +++ b/src/mac/login.scss @@ -132,3 +132,36 @@ .item .iconfont:hover { background-color: rgba(255, 255, 255, 0.5); } + +.iam-button { + min-width: 280px; + height: 45px; + color: #fff; + font-size: 16px; + border: none; + border-radius: 22px; + cursor: pointer; + background-color: #2c77f1; + box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1); +} + +.iam-button:hover { + background-color: #4b8df5; +} + +.account-button { + min-width: 280px; + height: 45px; + margin-top: 16px; + color: #fff; + font-size: 16px; + border: 1px solid rgba(255, 255, 255, 0.6); + border-radius: 22px; + cursor: pointer; + background-color: rgba(255, 255, 255, 0.2); + box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1); +} + +.account-button:hover { + background-color: rgba(255, 255, 255, 0.35); +} diff --git a/src/mac/login.vue b/src/mac/login.vue index 457cb7a..18ddc73 100644 --- a/src/mac/login.vue +++ b/src/mac/login.vue @@ -2,12 +2,10 @@
- +
Login Please
+ +
+
+ +
+
+ + +
+
+
+ + +
diff --git a/src/page/login/codelogin.vue b/src/page/login/codelogin.vue index ba7cf60..ae6b929 100644 --- a/src/page/login/codelogin.vue +++ b/src/page/login/codelogin.vue @@ -170,7 +170,6 @@ export default { if (data.success && data.data.tenantId) { this.tenantMode = false; this.loginForm.tenantId = data.data.tenantId; - this.$parent.$refs.login.style.backgroundImage = `url(${data.data.backgroundUrl})`; } }); }, diff --git a/src/page/login/index.vue b/src/page/login/index.vue index abbb8ee..ac42e2d 100644 --- a/src/page/login/index.vue +++ b/src/page/login/index.vue @@ -4,36 +4,33 @@ {{ time }}
-
- -

{{ $t('login.info') }}

-
@@ -64,7 +61,7 @@ export default { return { website: website, time: '', - activeName: 'user', + activeName: 'iam', socialForm: { tenantId: '000000', source: '', @@ -85,6 +82,17 @@ export default { mounted() {}, computed: { ...mapGetters(['tagWel']), + iamAuthorizeUrl() { + const params = new URLSearchParams({ + client_id: this.website.oauth2.ssoClientId, + response_type: 'code', + redirect_uri: this.website.oauth2.redirectUri, + state: this.website.tenantId, + }); + return `${this.website.oauth2.ssoBaseUrl}${ + this.website.oauth2.ssoAuthUrl + }?${params.toString()}`; + }, }, props: [], methods: { @@ -152,9 +160,25 @@ export default { }); } }, + handleIamLogin() { + window.location.href = this.iamAuthorizeUrl; + }, loadFlowRoutes() { this.$store.dispatch('FlowRoutes').then(() => {}); }, }, }; + diff --git a/src/page/login/registerlogin.vue b/src/page/login/registerlogin.vue index 7e5c6b4..6d66659 100644 --- a/src/page/login/registerlogin.vue +++ b/src/page/login/registerlogin.vue @@ -205,7 +205,6 @@ export default { if (data.success && data.data.tenantId) { this.tenantMode = false; this.loginForm.tenantId = data.data.tenantId; - this.$parent.$refs.login.style.backgroundImage = `url(${data.data.backgroundUrl})`; } }); }, diff --git a/src/page/login/userlogin.vue b/src/page/login/userlogin.vue index 5e3a1d1..5972557 100644 --- a/src/page/login/userlogin.vue +++ b/src/page/login/userlogin.vue @@ -83,7 +83,6 @@ import { mapGetters } from 'vuex'; import { info } from '@/api/system/tenant'; import { getCaptcha } from '@/api/user'; import { getTopUrl } from '@/utils/util'; -import { validatenull } from '@/utils/validate'; import BehaviorCaptcha from '@/components/behavior-captcha/index.vue'; export default { @@ -201,13 +200,6 @@ export default { if (data.success && data.data.tenantId) { this.tenantMode = false; this.loginForm.tenantId = data.data.tenantId; - if (!validatenull(data.data.backgroundUrl)) { - const loginEl = this.$parent.$refs.login; - loginEl.style.backgroundImage = `url(${data.data.backgroundUrl})`; - loginEl.style.backgroundSize = '100% 100%'; - loginEl.style.backgroundRepeat = 'no-repeat'; - loginEl.style.backgroundPosition = 'center center'; - } } }); }, diff --git a/src/router/page/index.js b/src/router/page/index.js index 3a148f7..9110d5c 100644 --- a/src/router/page/index.js +++ b/src/router/page/index.js @@ -23,6 +23,16 @@ export default [ isAuth: false, }, }, + { + path: '/callback', + name: 'IAM统一身份认证回调', + component: () => import('@/page/login/callback.vue'), + meta: { + keepAlive: true, + isTab: false, + isAuth: false, + }, + }, { path: '/lock', name: '锁屏页', diff --git a/src/store/modules/user.js b/src/store/modules/user.js index ae8648a..32a9e22 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -101,7 +101,8 @@ const user = { //根据单点信息登录 LoginBySso({ commit }, userInfo) { return new Promise((resolve, reject) => { - loginBySso(userInfo.state, userInfo.code) + const tenantId = userInfo.tenantId || userInfo.state; + loginBySso(tenantId, userInfo.state, userInfo.code) .then(res => { const data = res.data; if (data.error_description) { diff --git a/src/styles/common.scss b/src/styles/common.scss index 11cce9c..40248c8 100644 --- a/src/styles/common.scss +++ b/src/styles/common.scss @@ -48,6 +48,15 @@ a { height: 100%; overflow: hidden; + &:not(.avue-layout--horizontal) { + .avue-sidebar { + background-image: url('/img/左侧背景放底部.png') !important; + background-repeat: no-repeat !important; + background-position: left bottom !important; + background-size: 100% auto !important; + } + } + &--horizontal { flex-direction: column; diff --git a/src/styles/login.scss b/src/styles/login.scss index 4421443..38d4752 100644 --- a/src/styles/login.scss +++ b/src/styles/login.scss @@ -4,10 +4,12 @@ align-items: center; width: 100%; height: 100%; - // background-color: #fff; - background: linear-gradient(110deg, rgb(2, 40, 85) 0%, rgb(44, 119, 241) 50%, rgb(255,255,255) 50%, rgb(240,240,240) 100%); + background-image: url('/img/运输登录.jpg'); + background-position: center center; + background-repeat: no-repeat; + background-size: cover; overflow: hidden; - &::before{ + &::before { // content:' '; position: absolute; top: 0; @@ -15,7 +17,7 @@ width: 100%; height: 100%; margin-left: -48%; - background-image: url(/img/login-bg.svg); + background-image: url('/img/运输登录.jpg'); background-position: 100%; background-repeat: no-repeat; background-size: auto 100%; @@ -23,29 +25,17 @@ } .login-weaper { margin: 0 auto; - width:100%; + width: 100%; + display: flex; + justify-content: flex-end; } -.login-left, .login-border { position: relative; min-height: 500px; align-items: center; display: flex; } -.login-left { - padding-top: 100px; - justify-content: center; - flex-direction: column; - color: #fff; - float: left; - width: 50%; - position: relative; - box-sizing: border-box; -} -.login-left .img { - width: 200px; -} .login-time { position: absolute; left: 25px; @@ -58,15 +48,6 @@ overflow: hidden; font-weight: bold; } -.login-left .title { - margin-top: 60px; - text-align: center; - color: #fff; - font-weight: 300; - letter-spacing: 2px; - font-size: 28px; - font-weight: bold; -} .login-border { border-left: none; @@ -74,7 +55,6 @@ border-bottom-right-radius: 5px; color: #fff; width: 50%; - float: left; box-sizing: border-box; } .login-main { @@ -83,6 +63,7 @@ width: 70%; //box-shadow: -4px 5px 10px rgba(255, 255, 255, 0.4); box-sizing: border-box; + background: white; } .login-main > h3 { margin-bottom: 20px; @@ -114,7 +95,7 @@ cursor: pointer; margin-top: 30px; transition: 0.25s; - background-color: #2C77F1; + background-color: #2c77f1; } .register-submit { width: 100%; @@ -124,7 +105,7 @@ font-weight: 300; cursor: pointer; margin-top: 30px; - margin-left: 0!important; + margin-left: 0 !important; transition: 0.25s; } .login-form { @@ -136,16 +117,17 @@ input { text-indent: 5px; } - .el-input__wrapper{ - padding:5px 10px; + .el-input__wrapper { + padding: 5px 10px; border-radius: 0; } - .el-input__suffix,.el-input__prefix{ + .el-input__suffix, + .el-input__prefix { display: flex; align-items: center; text-align: center; } - .el-input__prefix{ + .el-input__prefix { margin-left: 8px; } } @@ -153,11 +135,11 @@ .login-code { width: 100%; height: 100%; - .el-input-group__append{ + .el-input-group__append { background-color: #fff; } } -.login-code-box{ +.login-code-box { display: flex; align-items: center; } diff --git a/src/styles/media.scss b/src/styles/media.scss index 0330337..89ce6d3 100644 --- a/src/styles/media.scss +++ b/src/styles/media.scss @@ -27,20 +27,6 @@ width: 98%; } //登录页面 - .login-left { - width: 100%; - min-height: auto; - - .title { - margin-top: 20px; - font-size: 20px; - text-shadow: #000 1px 0 0, #000 0 1px 0, #000 -1px 0 0, #000 0 -1px 0; - } - - .img { - width: 50px; - } - } .login-logo { padding-top: 30px; margin-left: -30px; @@ -55,11 +41,11 @@ width: 100%; background-color: #fff; padding: 10px 20px; - box-shadow: none + box-shadow: none; } .login-container { &::before { - margin-left: 0 + margin-left: 0; } } .top-bar__item { diff --git a/src/styles/theme/cool.scss b/src/styles/theme/cool.scss index 82e7e75..1ae469b 100644 --- a/src/styles/theme/cool.scss +++ b/src/styles/theme/cool.scss @@ -145,25 +145,25 @@ input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { /* WebKit browsers */ - color: #fff; + color: #909399; } input:-moz-placeholder, textarea:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ - color: #fff; + color: #909399; } input::-moz-placeholder, textarea::-moz-placeholder { /* Mozilla Firefox 19+ */ - color: #fff; + color: #909399; } input:-ms-input-placeholder, textarea:-ms-input-placeholder { /* Internet Explorer 10+ */ - color: #fff; + color: #909399; } } diff --git a/src/styles/theme/star.scss b/src/styles/theme/star.scss index 7a8055a..1e91646 100644 --- a/src/styles/theme/star.scss +++ b/src/styles/theme/star.scss @@ -86,25 +86,25 @@ input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { /* WebKit browsers */ - color: #fff; + color: #909399; } input:-moz-placeholder, textarea:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ - color: #fff; + color: #909399; } input::-moz-placeholder, textarea::-moz-placeholder { /* Mozilla Firefox 19+ */ - color: #fff; + color: #909399; } input:-ms-input-placeholder, textarea:-ms-input-placeholder { /* Internet Explorer 10+ */ - color: #fff; + color: #909399; } } diff --git a/src/styles/theme/white.scss b/src/styles/theme/white.scss index 338dba1..cd5db57 100644 --- a/src/styles/theme/white.scss +++ b/src/styles/theme/white.scss @@ -113,25 +113,25 @@ input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { /* WebKit browsers */ - color: #fff; + color: #909399; } input:-moz-placeholder, textarea:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ - color: #fff; + color: #909399; } input::-moz-placeholder, textarea::-moz-placeholder { /* Mozilla Firefox 19+ */ - color: #fff; + color: #909399; } input:-ms-input-placeholder, textarea:-ms-input-placeholder { /* Internet Explorer 10+ */ - color: #fff; + color: #909399; } } diff --git a/src/styles/top.scss b/src/styles/top.scss index ddb4cbb..432aa30 100644 --- a/src/styles/top.scss +++ b/src/styles/top.scss @@ -54,8 +54,13 @@ .el-input__wrapper { font-size: 13px; border: none; - box-shadow: none; - background-color: transparent; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.45); + background-color: #fff; + border-radius: 4px; + } + + .el-input__inner { + color: #303133; } } @@ -90,6 +95,22 @@ right: 10px; display: flex; align-items: center; + color: #fff; + + i, + .el-icon, + .el-dropdown, + .el-dropdown-link { + color: inherit; + } + + .el-color-picker__trigger { + border-color: rgba(255, 255, 255, 0.85); + } + + .el-color-picker__icon { + color: inherit; + } } .top-bar__item { @@ -109,6 +130,7 @@ height: 100%; padding-left: 50px; box-sizing: border-box; + background: url('/img/系统首页顶部背景图.png') center center / cover no-repeat; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/src/utils/audit.js b/src/utils/audit.js new file mode 100644 index 0000000..b09b2fb --- /dev/null +++ b/src/utils/audit.js @@ -0,0 +1,4 @@ +export const formatUpdateUserName = row => { + if (!row) return ''; + return row.updateUserName || row.updateUser || ''; +}; diff --git a/src/utils/crypto.js b/src/utils/crypto.js index 4bbd03a..3b1e677 100644 --- a/src/utils/crypto.js +++ b/src/utils/crypto.js @@ -5,17 +5,17 @@ export default class crypto { * token加密key 使用@org.springblade.test.CryptoKeyGenerator获取,需和后端配置保持一致 * @type {string} */ - static cryptoKey = '请配置cryptoKey'; + static cryptoKey = 'NWmP0cuIWAfL6GPeLJlbY8898b5iPk1y'; /** * 报文加密key 使用@org.springblade.test.CryptoKeyGenerator获取,需和后端配置保持一致 * @type {string} */ - static aesKey = '请配置aesKey'; + static aesKey = 'gLJvoZfX9pOp3nkIdQ9U2DzxlzsrTvJC'; /** * 报文加密key 使用@org.springblade.test.CryptoKeyGenerator获取,需和后端配置保持一致 * @type {string} */ - static desKey = '请配置desKey'; + static desKey = 'hdlxNCD2KZI91Taq'; /** * aes 加密方法 diff --git a/src/views/base/airport-master.vue b/src/views/base/airport-master.vue new file mode 100644 index 0000000..d4c4dba --- /dev/null +++ b/src/views/base/airport-master.vue @@ -0,0 +1,665 @@ + + + + + diff --git a/src/views/base/common-address.vue b/src/views/base/common-address.vue new file mode 100644 index 0000000..9e744dd --- /dev/null +++ b/src/views/base/common-address.vue @@ -0,0 +1,815 @@ + + + + + diff --git a/src/views/base/currency.vue b/src/views/base/currency.vue new file mode 100644 index 0000000..72a7e47 --- /dev/null +++ b/src/views/base/currency.vue @@ -0,0 +1,580 @@ + + + + + diff --git a/src/views/base/customer-type.vue b/src/views/base/customer-type.vue new file mode 100644 index 0000000..30eba90 --- /dev/null +++ b/src/views/base/customer-type.vue @@ -0,0 +1,290 @@ + + + diff --git a/src/views/base/fee-item.vue b/src/views/base/fee-item.vue new file mode 100644 index 0000000..715d755 --- /dev/null +++ b/src/views/base/fee-item.vue @@ -0,0 +1,290 @@ + + + diff --git a/src/views/base/port-terminal.vue b/src/views/base/port-terminal.vue new file mode 100644 index 0000000..4476cd9 --- /dev/null +++ b/src/views/base/port-terminal.vue @@ -0,0 +1,804 @@ + + + + + diff --git a/src/views/base/railway-station.vue b/src/views/base/railway-station.vue new file mode 100644 index 0000000..cf8e20b --- /dev/null +++ b/src/views/base/railway-station.vue @@ -0,0 +1,663 @@ + + + + + diff --git a/src/views/base/region.vue b/src/views/base/region.vue index 332d4cc..60541b2 100644 --- a/src/views/base/region.vue +++ b/src/views/base/region.vue @@ -40,6 +40,14 @@ @click="handleExport" >导出 + 同步 + + + diff --git a/src/views/transportCapacity/ship.vue b/src/views/transportCapacity/ship.vue new file mode 100644 index 0000000..0865d9c --- /dev/null +++ b/src/views/transportCapacity/ship.vue @@ -0,0 +1,603 @@ + + + + + diff --git a/src/views/transportCapacity/vehicle.vue b/src/views/transportCapacity/vehicle.vue new file mode 100644 index 0000000..4937bc1 --- /dev/null +++ b/src/views/transportCapacity/vehicle.vue @@ -0,0 +1,852 @@ + + + + + diff --git a/src/views/vehicle/accident-record.vue b/src/views/vehicle/accident-record.vue new file mode 100644 index 0000000..ab7207a --- /dev/null +++ b/src/views/vehicle/accident-record.vue @@ -0,0 +1,426 @@ + + + + + diff --git a/src/views/vehicle/annual-inspection-record.vue b/src/views/vehicle/annual-inspection-record.vue new file mode 100644 index 0000000..cf26a86 --- /dev/null +++ b/src/views/vehicle/annual-inspection-record.vue @@ -0,0 +1,441 @@ + + + + + diff --git a/src/views/vehicle/credit-score-quantification.vue b/src/views/vehicle/credit-score-quantification.vue new file mode 100644 index 0000000..f4cd009 --- /dev/null +++ b/src/views/vehicle/credit-score-quantification.vue @@ -0,0 +1,862 @@ + + + + + diff --git a/src/views/vehicle/customer-archive.vue b/src/views/vehicle/customer-archive.vue new file mode 100644 index 0000000..2d515f8 --- /dev/null +++ b/src/views/vehicle/customer-archive.vue @@ -0,0 +1,1243 @@ + + + + + diff --git a/src/views/vehicle/etc-record.vue b/src/views/vehicle/etc-record.vue new file mode 100644 index 0000000..af2b818 --- /dev/null +++ b/src/views/vehicle/etc-record.vue @@ -0,0 +1,413 @@ + + + + + diff --git a/src/views/vehicle/insurance-record.vue b/src/views/vehicle/insurance-record.vue new file mode 100644 index 0000000..7a58726 --- /dev/null +++ b/src/views/vehicle/insurance-record.vue @@ -0,0 +1,449 @@ + + + + + diff --git a/src/views/vehicle/maintenance-plan.vue b/src/views/vehicle/maintenance-plan.vue new file mode 100644 index 0000000..f25cc82 --- /dev/null +++ b/src/views/vehicle/maintenance-plan.vue @@ -0,0 +1,602 @@ + + + + + diff --git a/src/views/vehicle/maintenance-record.vue b/src/views/vehicle/maintenance-record.vue new file mode 100644 index 0000000..7424864 --- /dev/null +++ b/src/views/vehicle/maintenance-record.vue @@ -0,0 +1,598 @@ + + + + + diff --git a/src/views/vehicle/mileage-record.vue b/src/views/vehicle/mileage-record.vue new file mode 100644 index 0000000..d2c114f --- /dev/null +++ b/src/views/vehicle/mileage-record.vue @@ -0,0 +1,431 @@ + + + + + diff --git a/src/views/vehicle/oil-electric-record.vue b/src/views/vehicle/oil-electric-record.vue new file mode 100644 index 0000000..8cface0 --- /dev/null +++ b/src/views/vehicle/oil-electric-record.vue @@ -0,0 +1,450 @@ + + + + + diff --git a/src/views/vehicle/other-expense-record.vue b/src/views/vehicle/other-expense-record.vue new file mode 100644 index 0000000..c2c57a3 --- /dev/null +++ b/src/views/vehicle/other-expense-record.vue @@ -0,0 +1,441 @@ + + + + + diff --git a/src/views/vehicle/tire-replacement-record.vue b/src/views/vehicle/tire-replacement-record.vue new file mode 100644 index 0000000..08ce24e --- /dev/null +++ b/src/views/vehicle/tire-replacement-record.vue @@ -0,0 +1,393 @@ + + + + + diff --git a/src/views/vehicle/transport-change-record.vue b/src/views/vehicle/transport-change-record.vue new file mode 100644 index 0000000..3f835d5 --- /dev/null +++ b/src/views/vehicle/transport-change-record.vue @@ -0,0 +1,411 @@ + + + + + diff --git a/src/views/vehicle/violation-record.vue b/src/views/vehicle/violation-record.vue new file mode 100644 index 0000000..ff951ff --- /dev/null +++ b/src/views/vehicle/violation-record.vue @@ -0,0 +1,490 @@ + + + + + diff --git a/常用地址需求卡.md b/常用地址需求卡.md new file mode 100644 index 0000000..6bb05e2 --- /dev/null +++ b/常用地址需求卡.md @@ -0,0 +1,115 @@ +# 常用地址需求卡 + +REQ-ADDR-01 模块入口与访问权限 + +- 功能点:支持具备常用地址功能权限的账号从基础数据进入常用地址列表页面,查看权限范围内的地址主数据。 +- 异常与边界:①账号无该模块权限时不展示入口或禁止进入页面;②账号权限被调整后再次进入页面应按最新权限判断;③进入页面失败时保留在当前可访问页面并给出明确提示。 +- 反例:①不允许无权限账号直接访问常用地址页面;②不允许绕过基础数据菜单进入未授权模块;③不允许权限失效后继续操作历史打开页面中的数据。 + +REQ-ADDR-02 条件查询 + +- 功能点:支持按地址名称、类型、详细地址、行政区划、站点编码、组织、联系人、联系方式筛选常用地址数据。 +- 异常与边界:①未输入查询条件时默认查询权限范围内全部数据;②地址名称、详细地址、行政区划、联系人、联系方式按模糊匹配查询;③类型、组织按下拉选择值精确匹配,站点编码按精确匹配查询。 +- 反例:①不允许类型条件出现常规、港口/码头、铁路车站、空港机场之外的值;②不允许站点编码输入空格后被当作有效编码查询;③不允许普通用户通过组织条件越权修改其他组织数据。 + +REQ-ADDR-03 查询重置 + +- 功能点:支持一键重置查询条件,清空用户已输入的筛选项并恢复默认组织范围。 +- 异常与边界:①点击重置后地址名称、类型、详细地址、行政区划、站点编码、联系人、联系方式恢复为空或全部;②组织条件恢复为当前用户所属组织默认值;③重置后列表按默认条件重新加载。 +- 反例:①不允许重置后残留上一次的文本查询条件;②不允许重置后组织范围变成未授权组织;③不允许只清空界面条件但列表仍按旧条件展示。 + +REQ-ADDR-04 列表展示 + +- 功能点:支持分页展示常用地址列表,展示序号、地址名称、地址编号、类型、站点编码、详细地址、经纬度、行政区划、联系人、联系方式、组织、备注、更新时间、创建时间和操作。 +- 异常与边界:①进入页面默认展示当前组织全部数据,并按更新时间降序排列;②列表默认每页 10 条,支持切换 20、50、100 条每页;③常规类型站点编码显示为“/”,其他类型显示对应主数据编码。 +- 反例:①不允许列表直接展示 createUser、updateUser 等用户 ID 字段;②不允许常规地址展示虚假的站点编码;③不允许分页切换后丢失当前查询条件。 + +REQ-ADDR-05 组织数据范围 + +- 功能点:支持通过组织字段控制数据权限,普通用户默认查看当前组织数据,可按权限切换查看全部组织只读数据。 +- 异常与边界:①当前组织为空或组织权限异常时应阻止查询并提示;②切换组织范围后列表、分页和导出均按新范围生效;③查看全部组织数据时非本组织或无操作权限的数据仅允许查看。 +- 反例:①不允许普通用户编辑或删除无权限组织的数据;②不允许通过手动构造组织条件获取未授权数据;③不允许导出超出当前组织权限范围的数据。 + +REQ-ADDR-06 新增常规地址 + +- 功能点:支持手工新增常规地址,录入地址类型、地址名称、联系人、联系方式、详细地址、行政区划、经纬度和备注等信息。 +- 异常与边界:①类型、地址名称、详细地址为必填项,缺失时阻止提交并提示;②经纬度通过地图选点产生,提交时经度保留 6 位且范围为 -180 到 180,纬度保留 6 位且范围为 -90 到 90;③提交失败时保留用户已录入内容,便于修正后重新提交。 +- 反例:①不允许新增没有地址名称的常规地址;②不允许手工录入超出范围的经纬度,如经度 181 或纬度 -91;③不允许备注超过 200 个字后仍提交。 + +REQ-ADDR-07 新增港口/码头地址 + +- 功能点:支持从港口/码头主数据选择记录生成常用地址,自动带出地址名称、详细地址、行政区划、站点编码和经纬度信息。 +- 异常与边界:①未选择港口/码头主数据时阻止提交;②选择后地址名称默认填充港口/码头名称且允许修改;③站点编码自动带出港口/码头编码且不可修改。 +- 反例:①不允许把港口/码头地址的站点编码改成其他编码;②不允许选择不存在或已失效的港口/码头主数据;③不允许港口/码头类型提交为常规地址的“/”站点编码。 + +REQ-ADDR-08 新增铁路车站地址 + +- 功能点:支持从铁路车站主数据选择记录生成常用地址,自动带出地址名称、详细地址、行政区划、站点编码和经纬度信息。 +- 异常与边界:①未选择铁路车站主数据时阻止提交;②选择后地址名称默认填充车站名称且允许修改;③站点编码自动带出铁路车站编码且不可修改。 +- 反例:①不允许铁路车站类型使用港口码头或机场编码;②不允许手工伪造车站编码完成提交;③不允许已被主数据删除或停用的车站继续作为新增来源。 + +REQ-ADDR-09 新增空港机场地址 + +- 功能点:支持从空港机场主数据选择记录生成常用地址,自动带出地址名称、详细地址、行政区划、站点编码和经纬度信息。 +- 异常与边界:①未选择空港机场主数据时阻止提交;②选择后地址名称默认填充机场名称且允许修改;③站点编码自动带出机场编码且不可修改。 +- 反例:①不允许空港机场类型使用常规地址的“/”站点编码;②不允许将铁路车站编码作为机场站点编码;③不允许选择不存在或已失效的机场主数据。 + +REQ-ADDR-10 地址编号生成 + +- 功能点:系统为每条常用地址自动生成全局唯一地址编号,编号以 DZ 开头并按 5 位流水号递增。 +- 异常与边界:①新增提交时由系统生成地址编号,用户无需录入;②并发新增时应保证地址编号不重复;③编辑地址时保留原地址编号不重新生成。 +- 反例:①不允许用户手工填写地址编号;②不允许出现两个地址共用同一地址编号;③不允许编辑保存后地址编号从 DZ00001 变成新的流水号。 + +REQ-ADDR-11 地图选点与地址回填 + +- 功能点:常规地址支持通过地图组件搜索和手动选点,自动回填详细地址、行政区划、经度和纬度。 +- 异常与边界:①地图搜索无结果时保持当前输入内容并提示无匹配地址;②用户重新选点后应覆盖原经纬度和地址回填信息;③地图组件加载失败时应提示用户稍后重试或重新打开弹窗。 +- 反例:①不允许未选点时生成无效经纬度;②不允许经纬度弹窗字段前台不展示却后台不记录;③不允许详细地址与经纬度明显来自两次不同选点结果。 + +REQ-ADDR-12 查看地址详情 + +- 功能点:支持在操作列查看单条常用地址详情,展示该地址的基础信息、组织信息、地址信息和创建更新时间。 +- 异常与边界:①无查看权限时隐藏查看入口或禁止打开;②记录已被删除时提示数据不存在并刷新列表;③查看全部组织数据时详情页保持只读。 +- 反例:①不允许查看详情时进入可编辑状态;②不允许详情页展示用户 ID 代替组织或人员名称;③不允许已删除记录仍显示为可查看有效数据。 + +REQ-ADDR-13 编辑常用地址 + +- 功能点:支持编辑权限范围内的常用地址,维护地址名称、联系人、联系方式、详细地址、行政区划、经纬度和备注等信息。 +- 异常与边界:①无编辑权限或非可编辑组织数据时禁止编辑;②站点编码按地址类型规则控制,常规地址显示“/”,港口/码头、铁路车站、空港机场自动带出且不可修改;③保存成功后刷新列表并保留当前分页和查询条件。 +- 反例:①不允许编辑时修改地址编号;②不允许把港口/码头地址直接改成机场编码但仍保留港口信息;③不允许备注超过 200 个字后仍保存。 + +REQ-ADDR-14 单条删除 + +- 功能点:支持在操作列删除单条常用地址,删除前进行二次确认并校验是否被业务数据引用。 +- 异常与边界:①点击删除后弹出确认提示,用户取消时不删除数据;②删除前若已被业务数据引用则提示不可删除并保持原数据;③删除成功后刷新列表并提示删除成功。 +- 反例:①不允许未确认直接删除地址;②不允许删除已被业务单据引用的地址;③不允许无删除权限用户看到或触发删除操作。 + +REQ-ADDR-15 批量删除 + +- 功能点:支持勾选多条常用地址后批量删除,删除前进行二次确认并逐一校验业务引用关系。 +- 异常与边界:①未勾选数据时点击批量删除应提示先选择数据;②确认删除前系统逐条校验引用状态,已引用记录从删除队列跳过并提示;③删除完成后刷新列表并提示成功删除条数和因引用跳过条数。 +- 反例:①不允许批量删除时删除未被勾选的数据;②不允许因部分记录被引用导致所有可删除记录都无法删除;③不允许越权批量删除其他组织数据。 + +REQ-ADDR-16 批量导出 + +- 功能点:支持批量导出常用地址数据,勾选多行时导出勾选数据,未勾选时按当前查询条件导出全部匹配数据。 +- 异常与边界:①导出字段与列表展示字段保持一致;②导出范围受当前查询条件和组织权限限制;③导出失败时提示失败原因,不清空当前查询条件和勾选状态。 +- 反例:①不允许导出超出当前权限范围的数据;②不允许导出文件缺少列表中的关键业务字段;③不允许未勾选时忽略当前查询条件导出全库数据。 + +REQ-ADDR-17 操作列权限控制 + +- 功能点:列表操作列固定支持查看、编辑、删除,并根据用户权限和数据范围决定按钮展示或可用状态。 +- 异常与边界:①无查看权限时不展示查看操作;②无编辑或删除权限时隐藏对应操作;③只读组织数据仅保留查看,不允许编辑和删除。 +- 反例:①不允许前端显示了无权限按钮但点击后才提示失败;②不允许只读数据出现编辑入口;③不允许批量按钮绕过行级权限控制。 + +## 通用规则 + +- 常用地址类型固定为常规地址、港口/码头、铁路车站、空港机场,新增和查询不得使用其他类型。 +- 地址编号由系统自动生成,全局唯一,格式为 DZ 开头加 5 位递增流水号,用户不可手工录入或修改。 +- 常规地址通过地图选点生成详细地址、行政区划、经度和纬度;经度、纬度后台记录并在列表展示,保留 6 位小数。 +- 港口/码头、铁路车站、空港机场地址从对应主数据选择生成,站点编码自动带出且不可修改,地址名称可在默认带出后调整。 +- 列表默认按更新时间降序展示,默认每页 10 条,支持 20、50、100 条每页。 +- 组织字段作为数据权限字段,普通用户默认查看当前组织数据,查看全部组织数据时按权限只读展示。 +- 删除前必须校验是否被业务数据引用,已引用的记录不允许删除;批量删除时已引用记录跳过并提示。 +- 备注类字段最多 200 个字,超过限制时前端阻止提交并给出明确提示。 +- 用户可见场景不得直接展示 createUser、updateUser 等用户 ID 字段;涉及人员展示时优先使用姓名字段。 diff --git a/模块需求卡生成规范.md b/模块需求卡生成规范.md new file mode 100644 index 0000000..7fc3dc2 --- /dev/null +++ b/模块需求卡生成规范.md @@ -0,0 +1,183 @@ +# 模块需求卡生成规范 + +本规范用于后续生成各业务模块需求卡,统一采用 `港口码头主数据需求卡.md` 的非表格条目式格式。需求卡应便于产品、前端、后端、测试共同理解,重点描述功能行为、异常边界和反例,不写成测试用例表格。 + +## 一、文件命名 + +- 文件名格式:`{模块名称}需求卡.md` +- 示例:`港口码头主数据需求卡.md`、`币种汇率管理需求卡.md` +- 模块名称应使用业务侧可识别名称,避免仅使用接口名、表名或英文缩写。 + +## 二、文档结构 + +需求卡固定包含以下结构: + +```md +# {模块名称}需求卡 + +REQ-{模块编码}-01 {功能标题} + +- 功能点:{对该功能的简要概括描述} +- 异常与边界:①{异常或边界 1};②{异常或边界 2};③{异常或边界 3}。 +- 反例:①{不允许的规则或错误用法 1};②{不允许的规则或错误用法 2};③{不允许的规则或错误用法 3}。 + +REQ-{模块编码}-02 {功能标题} + +- 功能点:{对该功能的简要概括描述} +- 异常与边界:①{异常或边界 1};②{异常或边界 2}。 +- 反例:①{不允许的规则或错误用法 1};②{不允许的规则或错误用法 2}。 + +## 通用规则 + +- {模块通用规则 1} +- {模块通用规则 2} +- {模块通用规则 3} +``` + +## 三、编号规则 + +- 每个功能描述使用 `REQ-{模块编码}-{两位序号}`。 +- `{模块编码}` 使用 3 到 8 位大写英文,优先取模块英文简称。 +- 序号从 `01` 开始递增,不跳号。 +- 同一模块内编号不得重复。 + +示例: + +- 港口/码头主数据:`REQ-PORT-01` +- 币种汇率管理:`REQ-CURR-01` +- 车辆管理:`REQ-VEH-01` + +## 四、功能拆分规则 + +功能拆分应围绕用户可感知的业务能力展开,一个编号只描述一个清晰功能点。 + +优先拆分以下类型: + +- 列表展示 +- 条件查询 +- 新增或手工录入 +- 编辑或更新 +- 启用 +- 停用 +- 删除或禁止删除 +- 批量导入 +- 模板下载 +- 批量导出 +- 状态流转 +- 编码或唯一性规则 +- 必填和格式校验 +- 业务关联校验 +- 权限控制 +- 审计追踪 + +不得把多个互相独立的操作硬塞进同一个需求项。例如“新增、编辑、删除”应拆成多个 `REQ`,除非它们确实共享同一套规则且业务风险很低。 + +## 五、字段写作规范 + +### 1. 功能点 + +用于描述该功能“做什么”,要求简短、明确、面向业务。 + +写法要求: + +- 使用一句话概括功能。 +- 说明操作对象和业务目的。 +- 可列出关键字段,但不要堆砌全部数据库字段。 +- 不写技术实现细节,例如组件名、接口路径、方法名。 + +推荐写法: + +- `功能点:支持手工新增港口主数据,录入港口编码、港口名称、所属城市、经纬度、备注等信息,默认数据来源为手工导入,默认状态为启用。` + +不推荐写法: + +- `功能点:调用 /submit 接口保存表单。` +- `功能点:点击按钮打开弹窗,然后 avue-crud 触发 rowSave。` + +### 2. 异常与边界 + +用于描述系统遇到异常、极端输入、权限限制、业务冲突时应如何处理。 + +写法要求: + +- 使用 `①②③` 分点描述。 +- 每点应描述“条件 + 处理方式”。 +- 优先覆盖必填、格式、范围、重复、引用、权限、接口失败、文件格式、状态冲突等场景。 +- 不只写“提示错误”,要尽量说明何时提示、是否阻止提交、是否刷新列表、是否保持原状态。 + +推荐写法: + +- `异常与边界:①编码为空时阻止提交并提示;②重复编码时由后端校验并返回明确原因;③接口提交失败时保留用户输入,方便修正后重新提交。` + +不推荐写法: + +- `异常与边界:报错。` +- `异常与边界:需要校验。` + +### 3. 反例 + +用于描述明确不允许发生的行为、错误用法或违反规则的输入。 + +写法要求: + +- 使用 `①②③` 分点描述。 +- 反例应直接表达“不允许什么”。 +- 可以写非法输入示例,如 `CNSH1`、`-1`、`181`。 +- 反例不写系统应如何处理,处理方式放在“异常与边界”。 + +推荐写法: + +- `反例:①录入无上级港口的码头;②将 CNSHG 作为码头编码;③上级港口为 CNSHG 时录入 CNNGB-CT1。` + +不推荐写法: + +- `反例:用户乱填。` +- `反例:后台报错。` + +## 六、通用规则写作规范 + +文末必须保留 `## 通用规则`,用于汇总跨多个功能项重复出现的模块级规则。 + +通用规则适合放: + +- 编码规则 +- 必填规则 +- 金额、经纬度、备注等统一校验 +- 数据来源规则 +- 启停和删除约束 +- 权限和审计约束 +- 新增、更新、导入后刷新规则 + +通用规则不应替代具体功能项。若某规则会影响具体操作,应同时在对应 `REQ` 的“异常与边界”或“反例”中体现。 + +## 七、内容约束 + +- 不使用表格。 +- 不写测试步骤、预期结果、测试数据列。 +- 不写接口路径、组件名、代码方法名,除非用户明确要求。 +- 不写冗长背景介绍,首屏直接进入需求卡内容。 +- 不凭空增加用户未提及且现有业务无法推断的复杂能力。 +- 当需求明确“不支持删除”时,应单独拆出“禁止删除”需求项,并在通用规则中再次强调。 +- 当涉及导入时,通常需要同时拆出“批量导入”和“模板下载”。 +- 当涉及状态时,通常需要分别拆出“启用”和“停用”。 +- 当涉及用户可见审计字段时,优先使用姓名字段,避免直接展示用户 ID。 + +## 八、生成流程 + +1. 先理解用户提供的模块说明和现有参考需求卡。 +2. 梳理模块核心对象、关键字段、数据来源、允许操作、不允许操作。 +3. 按业务能力拆分 `REQ` 编号。 +4. 为每个 `REQ` 编写三行:功能点、异常与边界、反例。 +5. 汇总重复规则到 `## 通用规则`。 +6. 检查是否误用表格、是否遗漏用户强调的禁止项、是否出现互相矛盾描述。 + +## 九、质量检查清单 + +- 每个 `REQ` 是否只有一个明确功能主题。 +- 每个 `REQ` 是否都有功能点、异常与边界、反例。 +- 异常与边界是否包含处理方式,而不只是问题名称。 +- 反例是否使用“不允许”口径。 +- 编号是否连续且无重复。 +- 是否覆盖新增、更新、导入、导出、启停、权限、审计等常见管理功能。 +- 是否把用户明确禁止的能力写成了可用能力。 +- 是否保持非表格格式。