截止‘运力管理’模块
This commit is contained in:
2
.npmrc
2
.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
|
||||
|
||||
244
AGENTS.md
Normal file
244
AGENTS.md
Normal file
@@ -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 + `<script setup>`
|
||||
- 同一文件中禁止混用两种风格
|
||||
|
||||
### 4.2 命名规范
|
||||
|
||||
| 类型 | 命名方式 | 示例 |
|
||||
| -------------- | -------------------------- | ----------------------------- |
|
||||
| 页面文件 | kebab-case | `notice.vue` |
|
||||
| 组件文件 | kebab-case 目录 + main.vue | `basic-container/main.vue` |
|
||||
| API / Option / 工具文件 | camelCase | `dict.js`、`dictbiz.js` |
|
||||
| 变量 / 函数 | camelCase | `dictValue`、`handleDelete` |
|
||||
| Vuex mutations | UPPER_SNAKE | `SET_IS_MENU`、`ADD_TAG` |
|
||||
| Vuex actions | PascalCase | `FedLogOut`、`RefreshToken` |
|
||||
|
||||
### 4.3 代码格式(Prettier)
|
||||
|
||||
`printWidth: 100` / `tabWidth: 2` / `semi: true` / `singleQuote: true` / `arrowParens: "avoid"`
|
||||
|
||||
### 4.4 样式规范
|
||||
|
||||
- 全局 SCSS 变量通过 `styles/variables.scss` 定义,Vite 自动注入所有组件
|
||||
- 编写样式优先使用已有变量和 mixin(`styles/mixin.scss`),而非硬编码值
|
||||
|
||||
### 4.5 全局注册组件
|
||||
|
||||
在 `main.js` 中全局注册,可直接在模板中使用:`<basic-container>`、`<basic-block>`、`<highlight>`、`<code-editor>`、`<cron-editor>`、`<flow-design>`、`<flow-design-step>`、`<third-register>`、`<code-setting>`、`<form-setting>`、`<tenant-package>`、`<tenant-datasource>`
|
||||
|
||||
### 4.6 全局属性(Options API 中通过 `this` 访问)
|
||||
|
||||
`this.website`(全局配置)、`this.$dayjs`(日期库)、`this.getScreen`(屏幕尺寸)、`this.findColumn`(Avue 列配置查找)
|
||||
|
||||
### 4.7 表单校验规范
|
||||
|
||||
- 金额类字段必须增加非负校验,禁止提交小于 0 的金额。
|
||||
- 经纬度字段必须增加范围校验:经度范围为 -180 到 180,纬度范围为 -90 到 90。
|
||||
- 备注类字段必须限制最多 200 个字,超过限制时应在前端阻止提交并给出明确提示。
|
||||
|
||||
### 4.8 表单布局规范
|
||||
|
||||
- 新增/编辑弹窗中的表单项,若 `label` 文案超过 4 个字,`label` 必须单独占一行展示,表单控件另起一行,避免长标签挤压输入控件或换行错位。
|
||||
- 使用 Avue 配置、Element Plus 表单或自定义弹窗表单时均需遵守该规则;可通过 `label-position="top"`、自定义 `formslot`、局部样式或单项布局调整实现。
|
||||
|
||||
### 4.9 审计字段展示规范
|
||||
|
||||
- 列表、详情、导出预览等用户可见场景禁止直接展示 `createUser`、`updateUser` 等用户 ID 字段。
|
||||
- 后端 VO 应额外返回 `createUserName`、`updateUserName` 等姓名字段;前端 Avue 列优先绑定姓名字段。
|
||||
- “更新人”列统一使用 `prop: 'updateUserName'`,并通过 `src/utils/audit.js` 中的 `formatUpdateUserName(row)` 做兼容兜底。
|
||||
- 表单提交仍保留后端审计字段机制,前端不得手动提交或覆盖 `createUser`、`updateUser`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 新功能开发流程
|
||||
|
||||
### 5.1 新增标准 CRUD 页面
|
||||
|
||||
1. **调用 `avue-design` Skill** 生成标准 CRUD 页面代码(推荐)
|
||||
2. 或手动创建:API 文件 `src/api/{module}/{name}.js` → Option 配置 `src/option/{module}/{name}.js` → Vue 页面 `src/views/{module}/{name}.vue`
|
||||
3. 后端配置菜单后,动态路由自动生效
|
||||
|
||||
### 5.2 开发前必做
|
||||
|
||||
1. 先看同模块已有实现,模仿其结构与风格
|
||||
2. 优先使用 `src/components/`、`src/utils/` 中的现有实现,禁止重复造轮子
|
||||
3. 若需引入新包,先确认不与已有依赖冲突
|
||||
|
||||
### 5.3 开发后验证
|
||||
|
||||
1. 若引入新依赖:`pnpm install` → `pnpm run build` → 确认构建通过
|
||||
2. 构建通过后:`pnpm run dev` → 确认开发服务器正常启动
|
||||
3. 功能测试交由用户执行,除非用户明确要求,不撰写示例代码或额外文档
|
||||
|
||||
---
|
||||
|
||||
## 6. 常用命令
|
||||
|
||||
```bash
|
||||
pnpm run dev # 启动开发服务器(端口 2888)
|
||||
pnpm run build # 构建(开发环境)
|
||||
pnpm run build:prod # 构建(生产环境,Terser 压缩、删除 console/debugger)
|
||||
pnpm run serve # 预览构建产物
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 自主学习与风格一致性
|
||||
|
||||
1. 风格不确定时,主动查阅当前模块现有代码并模仿,避免破坏一致性
|
||||
2. 新模块参考:`src/views/system/dict.vue`(Options API)、`src/views/desk/notice-composition.vue`(Composition API)
|
||||
3. 若现有模块已满足需求,禁止自写替代实现
|
||||
4. 若确需新实现,须在 commit 信息中说明已检索过的相关组件、现有实现不足的原因、新实现的范围
|
||||
|
||||
---
|
||||
|
||||
## 8. Git 提交规范
|
||||
|
||||
当需要提交代码时,优先使用 **`/blade-commit`** skill,它会自动分析变更内容并生成符合项目规范的 Gitmoji 提交信息。
|
||||
|
||||
项目采用 **Gitmoji** 风格,中文描述:
|
||||
|
||||
| Emoji | 代码 | 场景 |
|
||||
| ---------- | ------------ | ---------------- |
|
||||
| :sparkles: | `:sparkles:` | 新增功能、优化增强 |
|
||||
| :bug: | `:bug:` | 修复 Bug |
|
||||
| :zap: | `:zap:` | 性能优化、问题修复 |
|
||||
| :tada: | `:tada:` | 重大版本发布 |
|
||||
| :lipstick: | `:lipstick:` | 样式调整 |
|
||||
| :recycle: | `:recycle:` | 代码重构 |
|
||||
| :wrench: | `:wrench:` | 配置修改 |
|
||||
| :memo: | `:memo:` | 文档更新 |
|
||||
| :fire: | `:fire:` | 删除代码/文件 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 交流语言
|
||||
|
||||
与用户交互时全程使用中文。若需临时切换语言,须明确告知。
|
||||
74
index.html
74
index.html
@@ -1,43 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<link rel="stylesheet" href="/iconfont/index.css" />
|
||||
<link rel="stylesheet" href="/iconfont/avue/iconfont.css" />
|
||||
<link rel="stylesheet" href="/iconfont/saber/iconfont.css" />
|
||||
<link rel="stylesheet" href="/iconfont/common/iconfont.css" />
|
||||
<link rel="stylesheet" href="/css/loading.css" />
|
||||
<link rel="stylesheet" href="/css/saber.css" />
|
||||
<title>TMS物流运输管理系统</title>
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<meta charset='UTF-8' />
|
||||
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||
<meta http-equiv='X-UA-Compatible' content='chrome=1' />
|
||||
<meta name='renderer' content='webkit'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0'>
|
||||
<meta name='apple-mobile-web-app-capable' content='yes'>
|
||||
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
|
||||
<meta name='format-detection' content='telephone=no'>
|
||||
<link rel='icon' href='/favicon.png' />
|
||||
<link rel='stylesheet' href='/iconfont/index.css'>
|
||||
<link rel='stylesheet' href='/iconfont/avue/iconfont.css'>
|
||||
<link rel='stylesheet' href='/iconfont/saber/iconfont.css'>
|
||||
<link rel='stylesheet' href='/iconfont/common/iconfont.css'>
|
||||
<link rel='stylesheet' href='/css/loading.css'>
|
||||
<link rel='stylesheet' href='/css/saber.css'>
|
||||
<title>BladeX微服务开发平台</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id='app'>
|
||||
<div id='loader-wrapper'>
|
||||
<div class='loader-box'>
|
||||
<span>BladeX</span>
|
||||
<span>微</span>
|
||||
<span>服</span>
|
||||
<span>务</span>
|
||||
<span>开</span>
|
||||
<span>发</span>
|
||||
<span>平</span>
|
||||
<span>台</span>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="loader-wrapper">
|
||||
<div class="loader-box">
|
||||
<span>桂</span>
|
||||
<span>物</span>
|
||||
<span>物</span>
|
||||
<span>流</span>
|
||||
<span>TMS</span>
|
||||
</div>
|
||||
<div class='loader-title'>系统加载中</div>
|
||||
<div class="loader-title">系统加载中</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type='module' src='/src/main.js'></script>
|
||||
</body>
|
||||
|
||||
</div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
BIN
public/img/左侧背景放底部.png
Normal file
BIN
public/img/左侧背景放底部.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
BIN
public/img/系统首页顶部背景图.png
Normal file
BIN
public/img/系统首页顶部背景图.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
BIN
public/img/运输登录.jpg
Normal file
BIN
public/img/运输登录.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 243 KiB |
52
src/api/base/airport-master.js
Normal file
52
src/api/base/airport-master.js
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
52
src/api/base/common-address.js
Normal file
52
src/api/base/common-address.js
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
60
src/api/base/currency.js
Normal file
60
src/api/base/currency.js
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
52
src/api/base/customer-type.js
Normal file
52
src/api/base/customer-type.js
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
52
src/api/base/fee-item.js
Normal file
52
src/api/base/fee-item.js
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
59
src/api/base/port-terminal.js
Normal file
59
src/api/base/port-terminal.js
Normal file
@@ -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',
|
||||
});
|
||||
};
|
||||
52
src/api/base/railway-station.js
Normal file
52
src/api/base/railway-station.js
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -50,3 +50,10 @@ export const submit = row => {
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const sync = () => {
|
||||
return request({
|
||||
url: '/blade-system/region/sync',
|
||||
method: 'post',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
60
src/api/transportCapacity/driver.js
Normal file
60
src/api/transportCapacity/driver.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
60
src/api/transportCapacity/transport-ship.js
Normal file
60
src/api/transportCapacity/transport-ship.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
60
src/api/transportCapacity/transport-vehicle.js
Normal file
60
src/api/transportCapacity/transport-vehicle.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
49
src/api/vehicle/accident-record.js
Normal file
49
src/api/vehicle/accident-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/annual-inspection-record.js
Normal file
49
src/api/vehicle/annual-inspection-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
62
src/api/vehicle/credit-score-quantification.js
Normal file
62
src/api/vehicle/credit-score-quantification.js
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
92
src/api/vehicle/customer-archive.js
Normal file
92
src/api/vehicle/customer-archive.js
Normal file
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/etc-record.js
Normal file
49
src/api/vehicle/etc-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/insurance-record.js
Normal file
49
src/api/vehicle/insurance-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/maintenance-plan.js
Normal file
49
src/api/vehicle/maintenance-plan.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/maintenance-record.js
Normal file
49
src/api/vehicle/maintenance-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/mileage-record.js
Normal file
49
src/api/vehicle/mileage-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/oil-electric-record.js
Normal file
49
src/api/vehicle/oil-electric-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/other-expense-record.js
Normal file
49
src/api/vehicle/other-expense-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/tire-replacement-record.js
Normal file
49
src/api/vehicle/tire-replacement-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/transport-change-record.js
Normal file
49
src/api/vehicle/transport-change-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
49
src/api/vehicle/violation-record.js
Normal file
49
src/api/vehicle/violation-record.js
Normal file
@@ -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,
|
||||
});
|
||||
};
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: '请输入账号',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
<div class="mac_bg"></div>
|
||||
<div class="login animate__animated" :class="{ animate__bounceOut: pass }">
|
||||
<div class="head">
|
||||
<img
|
||||
src="https://bladex.cn/images/logo-small.png"
|
||||
alt=""
|
||||
/>
|
||||
<img src="https://bladex.cn/images/logo-small.png" alt="" />
|
||||
</div>
|
||||
<div class="message">Login Please</div>
|
||||
<!-- 原登录方式先注释保留备用
|
||||
<div class="form">
|
||||
<div class="item" :class="isUserNameError ? 'error' : ''">
|
||||
<input class="account" placeholder="account here..." v-model="form.username" type="email" />
|
||||
@@ -22,27 +20,67 @@
|
||||
<i class="iconfont icon-send" @click="handleLogin"></i>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<div class="form" v-if="activeName === 'user'">
|
||||
<div class="item" :class="isUserNameError ? 'error' : ''">
|
||||
<input class="account" placeholder="account here..." v-model="form.username" type="email" />
|
||||
</div>
|
||||
<div class="item" :class="isUserPasswordError ? 'error' : ''">
|
||||
<input
|
||||
class="password"
|
||||
placeholder="password here..."
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
/>
|
||||
<i class="iconfont icon-send" @click="handleLogin"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form" v-else>
|
||||
<button class="iam-button" @click="handleIamLogin">IAM统一身份认证</button>
|
||||
<button class="account-button" @click="activeName = 'user'">账号密码登录</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import website from '@/config/website';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
website,
|
||||
activeName: 'iam',
|
||||
pass: false,
|
||||
isUserNameError: false,
|
||||
isUserPasswordError: false,
|
||||
form: {
|
||||
tenantId: '000000',
|
||||
deptId: '',
|
||||
roleId: '',
|
||||
username: '',
|
||||
password: '',
|
||||
type: 'account',
|
||||
},
|
||||
};
|
||||
},
|
||||
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()}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleIamLogin() {
|
||||
window.location.href = this.iamAuthorizeUrl;
|
||||
},
|
||||
handleLogin() {
|
||||
if (this.form.username === '') {
|
||||
this.isUserNameError = true;
|
||||
@@ -57,7 +95,7 @@ export default {
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
|
||||
this.$store.dispatch('LoginByUsername', this.form).then(() => {
|
||||
this.pass = true;
|
||||
setTimeout(() => {
|
||||
this.$router.push(this.tagWel);
|
||||
|
||||
172
src/option/base/common-address.js
Normal file
172
src/option/base/common-address.js
Normal file
@@ -0,0 +1,172 @@
|
||||
export const addressTypeOptions = [
|
||||
{ label: '常规地址', value: '常规地址' },
|
||||
{ label: '港口/码头', value: '港口/码头' },
|
||||
{ label: '铁路车站', value: '铁路车站' },
|
||||
{ label: '空港机场', value: '空港机场' },
|
||||
];
|
||||
|
||||
export const createOption = ({ validateLongitude, validateLatitude }) => ({
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
indexLabel: '序号',
|
||||
indexWidth: 70,
|
||||
selection: true,
|
||||
viewBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
addBtn: false,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
column: [
|
||||
{
|
||||
label: '地址名称',
|
||||
prop: 'addressName',
|
||||
search: true,
|
||||
minWidth: 150,
|
||||
rules: [{ required: true, message: '请输入地址名称', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '地址编号',
|
||||
prop: 'addressCode',
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '类型',
|
||||
prop: 'addressType',
|
||||
type: 'select',
|
||||
search: true,
|
||||
dicData: addressTypeOptions,
|
||||
rules: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '来源主数据',
|
||||
prop: 'sourceId',
|
||||
formslot: true,
|
||||
hide: true,
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '站点编码',
|
||||
prop: 'siteCodeDisplay',
|
||||
search: false,
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '站点编码',
|
||||
prop: 'siteCode',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '详细地址',
|
||||
prop: 'detailAddress',
|
||||
search: true,
|
||||
formslot: true,
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
minWidth: 220,
|
||||
rules: [{ required: true, message: '请输入详细地址', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '经度',
|
||||
prop: 'longitude',
|
||||
type: 'number',
|
||||
precision: 6,
|
||||
minWidth: 120,
|
||||
rules: [{ validator: validateLongitude, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '纬度',
|
||||
prop: 'latitude',
|
||||
type: 'number',
|
||||
precision: 6,
|
||||
minWidth: 120,
|
||||
rules: [{ validator: validateLatitude, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '行政区划',
|
||||
prop: 'regionName',
|
||||
search: true,
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'deptId',
|
||||
type: 'select',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
dicData: [],
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'deptName',
|
||||
minWidth: 150,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '联系人',
|
||||
prop: 'contactName',
|
||||
search: true,
|
||||
minWidth: 110,
|
||||
},
|
||||
{
|
||||
label: '联系方式',
|
||||
prop: 'contactPhone',
|
||||
search: true,
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
hide: true,
|
||||
maxlength: 200,
|
||||
showWordLimit: true,
|
||||
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 160,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 160,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
111
src/option/transportCapacity/driver.js
Normal file
111
src/option/transportCapacity/driver.js
Normal file
@@ -0,0 +1,111 @@
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: false,
|
||||
menuWidth: 180,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '姓名',
|
||||
prop: 'driverName',
|
||||
search: true,
|
||||
slot: true,
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
label: '身份证号',
|
||||
prop: 'idCardNo',
|
||||
search: true,
|
||||
width: 190,
|
||||
},
|
||||
{
|
||||
label: '手机号码',
|
||||
prop: 'mobile',
|
||||
search: true,
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
label: '性别',
|
||||
prop: 'gender',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '司机类型',
|
||||
prop: 'driverType',
|
||||
search: true,
|
||||
type: 'select',
|
||||
dicData: [
|
||||
{
|
||||
label: '自有',
|
||||
value: '自有',
|
||||
},
|
||||
{
|
||||
label: '外协',
|
||||
value: '外协',
|
||||
},
|
||||
],
|
||||
width: 100,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '岗位',
|
||||
prop: 'posts',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
label: '准驾车型',
|
||||
prop: 'drivingType',
|
||||
search: true,
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
label: '所属组织',
|
||||
prop: 'organizationName',
|
||||
search: true,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
search: true,
|
||||
type: 'select',
|
||||
slot: true,
|
||||
dicData: [
|
||||
{
|
||||
label: '启用',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '停用',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
width: 90,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '驾驶认证',
|
||||
prop: 'drivingLicenseEndDate',
|
||||
slot: true,
|
||||
width: 130,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '从业资格认证',
|
||||
prop: 'qualificationEndDate',
|
||||
slot: true,
|
||||
width: 140,
|
||||
align: 'center',
|
||||
},
|
||||
],
|
||||
};
|
||||
66
src/option/transportCapacity/transport-ship.js
Normal file
66
src/option/transportCapacity/transport-ship.js
Normal file
@@ -0,0 +1,66 @@
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: false,
|
||||
menuWidth: 210,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{ label: '船舶名', prop: 'shipName', search: true, slot: true, width: 130 },
|
||||
{ label: '船舶识别号', prop: 'shipIdentifierNo', search: true, width: 140 },
|
||||
{ label: '所属组织', prop: 'organizationName', search: true, width: 160 },
|
||||
{ label: '船检登记号', prop: 'shipInspectionNo', search: true, width: 130 },
|
||||
{
|
||||
label: '船舶类型',
|
||||
prop: 'shipType',
|
||||
search: true,
|
||||
type: 'select',
|
||||
dicData: [
|
||||
{ label: '干货船', value: '干货船' },
|
||||
{ label: '集装箱船', value: '集装箱船' },
|
||||
{ label: '散货船', value: '散货船' },
|
||||
{ label: '油船', value: '油船' },
|
||||
{ label: '拖轮', value: '拖轮' },
|
||||
],
|
||||
width: 110,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
search: true,
|
||||
type: 'select',
|
||||
slot: true,
|
||||
dicData: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '停用', value: 2 },
|
||||
],
|
||||
width: 90,
|
||||
align: 'center',
|
||||
},
|
||||
{ label: '国籍证有效期至', prop: 'nationalityCertEndDate', slot: true, width: 150, align: 'center' },
|
||||
{
|
||||
label: '最低安全配员证书有效期至',
|
||||
prop: 'safeManningCertEndDate',
|
||||
slot: true,
|
||||
width: 210,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '营业运输证有效期至',
|
||||
prop: 'businessTransportCertEndDate',
|
||||
slot: true,
|
||||
width: 170,
|
||||
align: 'center',
|
||||
},
|
||||
{ label: '承租有效期至', prop: 'leaseEndDate', slot: true, width: 140, align: 'center' },
|
||||
],
|
||||
};
|
||||
113
src/option/transportCapacity/transport-vehicle.js
Normal file
113
src/option/transportCapacity/transport-vehicle.js
Normal file
@@ -0,0 +1,113 @@
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: false,
|
||||
menuWidth: 180,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '车牌号',
|
||||
prop: 'plateNo',
|
||||
search: true,
|
||||
slot: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
label: '所属组织',
|
||||
prop: 'organizationName',
|
||||
search: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
label: '车辆类型',
|
||||
prop: 'vehicleType',
|
||||
search: true,
|
||||
type: 'select',
|
||||
dicData: [
|
||||
{ label: '厢式', value: '厢式' },
|
||||
{ label: '高栏', value: '高栏' },
|
||||
{ label: '平板', value: '平板' },
|
||||
{ label: '仓栅', value: '仓栅' },
|
||||
{ label: '牵引车', value: '牵引车' },
|
||||
],
|
||||
width: 110,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '业务关系',
|
||||
prop: 'businessRelation',
|
||||
search: true,
|
||||
type: 'select',
|
||||
dicData: [
|
||||
{ label: '自有', value: '自有' },
|
||||
{ label: '业务合作', value: '业务合作' },
|
||||
{ label: '管理挂靠', value: '管理挂靠' },
|
||||
],
|
||||
width: 120,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '能源类型',
|
||||
prop: 'energyType',
|
||||
search: true,
|
||||
type: 'select',
|
||||
dicData: [
|
||||
{ label: '柴油', value: '柴油' },
|
||||
{ label: '汽油', value: '汽油' },
|
||||
{ label: '新能源', value: '新能源' },
|
||||
{ label: '天然气', value: '天然气' },
|
||||
],
|
||||
width: 110,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '核定载质量(KG)',
|
||||
prop: 'approvedLoadKg',
|
||||
width: 140,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
search: true,
|
||||
type: 'select',
|
||||
slot: true,
|
||||
dicData: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '停用', value: 2 },
|
||||
],
|
||||
width: 90,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '行驶证有效期',
|
||||
prop: 'drivingLicenseEndDate',
|
||||
slot: true,
|
||||
width: 140,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '道路运输证有效期',
|
||||
prop: 'roadTransportCertEndDate',
|
||||
slot: true,
|
||||
width: 160,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '年审有效期',
|
||||
prop: 'annualReviewEndDate',
|
||||
slot: true,
|
||||
width: 130,
|
||||
align: 'center',
|
||||
},
|
||||
],
|
||||
};
|
||||
277
src/option/vehicle/accident-record.js
Normal file
277
src/option/vehicle/accident-record.js
Normal file
@@ -0,0 +1,277 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const vehicleTypeDic = [
|
||||
{ label: '车辆', value: '车辆' },
|
||||
{ label: '船舶', value: '船舶' },
|
||||
];
|
||||
|
||||
export const accidentNatureDic = [
|
||||
{ label: '重大事故', value: '重大事故' },
|
||||
{ label: '一般事故', value: '一般事故' },
|
||||
{ label: '轻微事故', value: '轻微事故' },
|
||||
{ label: '其他', value: '其他' },
|
||||
];
|
||||
|
||||
export const accidentResponsibilityDic = [
|
||||
{ label: '全部责任', value: '全部责任' },
|
||||
{ label: '主要责任', value: '主要责任' },
|
||||
{ label: '同等责任', value: '同等责任' },
|
||||
{ label: '次要责任', value: '次要责任' },
|
||||
{ label: '无责任', value: '无责任' },
|
||||
];
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 1050,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
column: [
|
||||
{
|
||||
label: '车船类型',
|
||||
prop: 'vehicleType',
|
||||
type: 'radio',
|
||||
minWidth: 110,
|
||||
dicData: vehicleTypeDic,
|
||||
value: '车辆',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '车牌号/船号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 140,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '事故发生日期',
|
||||
prop: 'accidentDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 140,
|
||||
rules: [{ required: true, message: '请选择事故发生日期', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '事故发生地点',
|
||||
prop: 'accidentLocation',
|
||||
minWidth: 200,
|
||||
overHidden: true,
|
||||
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '事故性质',
|
||||
prop: 'accidentNature',
|
||||
type: 'select',
|
||||
dicData: accidentNatureDic,
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '事故责任',
|
||||
prop: 'accidentResponsibility',
|
||||
type: 'select',
|
||||
dicData: accidentResponsibilityDic,
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '直接经济损失',
|
||||
prop: 'directEconomicLoss',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 140,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '保险理赔金额',
|
||||
prop: 'insuranceClaimAmount',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 140,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '事故原因',
|
||||
prop: 'accidentReasonDamage',
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
search: true,
|
||||
hide: true,
|
||||
rules: [{ max: 500, message: '最多 500 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '事故评定开始日期',
|
||||
prop: 'accidentAssessmentDateStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '事故评定结束日期',
|
||||
prop: 'accidentAssessmentDateEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/accident-record/import-accident-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
284
src/option/vehicle/annual-inspection-record.js
Normal file
284
src/option/vehicle/annual-inspection-record.js
Normal file
@@ -0,0 +1,284 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const vehicleTypeDic = [
|
||||
{ label: '车辆', value: '车辆' },
|
||||
{ label: '船舶', value: '船舶' },
|
||||
];
|
||||
|
||||
export const vehicleTechnicalLevelDic = [
|
||||
{ label: '一级', value: '一级' },
|
||||
{ label: '二级', value: '二级' },
|
||||
{ label: '三级', value: '三级' },
|
||||
{ label: '其他', value: '其他' },
|
||||
];
|
||||
|
||||
export const shipInspectionTypeDic = [
|
||||
{ label: '年检', value: '年检' },
|
||||
{ label: '中检', value: '中检' },
|
||||
{ label: '特检', value: '特检' },
|
||||
{ label: '其他', value: '其他' },
|
||||
];
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 1050,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
column: [
|
||||
{
|
||||
label: '车船类型',
|
||||
prop: 'vehicleType',
|
||||
type: 'radio',
|
||||
minWidth: 110,
|
||||
dicData: vehicleTypeDic,
|
||||
value: '车辆',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '车牌号/船号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 140,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '检测评定日期',
|
||||
prop: 'inspectionAssessmentDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 140,
|
||||
rules: [{ required: true, message: '请选择检测评定日期', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '有效期截止日',
|
||||
prop: 'validUntilDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 140,
|
||||
rules: [{ required: true, message: '请选择有效期截止日', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '车辆技术等级',
|
||||
prop: 'vehicleTechnicalLevel',
|
||||
type: 'select',
|
||||
dicData: vehicleTechnicalLevelDic,
|
||||
minWidth: 130,
|
||||
rules: [{ required: true, message: '请选择车辆技术等级', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '船舶检验类型',
|
||||
prop: 'shipInspectionType',
|
||||
type: 'select',
|
||||
dicData: shipInspectionTypeDic,
|
||||
display: false,
|
||||
minWidth: 130,
|
||||
rules: [{ required: true, message: '请选择船舶检验类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '客车类型及等级',
|
||||
prop: 'passengerTypeLevel',
|
||||
minWidth: 150,
|
||||
hide: true,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '检测评定单位',
|
||||
prop: 'inspectionUnit',
|
||||
minWidth: 160,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '费用',
|
||||
prop: 'fee',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 100,
|
||||
rules: [
|
||||
{ required: true, message: '请输入费用', trigger: 'blur' },
|
||||
{ validator: validateNonNegative, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '评定(复核)单位',
|
||||
prop: 'assessmentUnit',
|
||||
minWidth: 160,
|
||||
hide: true,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '检测评定开始日期',
|
||||
prop: 'inspectionAssessmentDateStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '检测评定结束日期',
|
||||
prop: 'inspectionAssessmentDateEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/annual-inspection-record/import-annual-inspection-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
211
src/option/vehicle/etc-record.js
Normal file
211
src/option/vehicle/etc-record.js
Normal file
@@ -0,0 +1,211 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
const isEmpty = value => value === undefined || value === null || value === '';
|
||||
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (isEmpty(value)) {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const maskEtcCardNo = value => {
|
||||
if (!value) return '';
|
||||
const cardNo = String(value);
|
||||
if (cardNo.length <= 8) return cardNo.replace(/.(?=.{4})/g, '*');
|
||||
return `${cardNo.slice(0, 4)}**** ****${cardNo.slice(-8)}`;
|
||||
};
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
menuFixed: 'right',
|
||||
column: [
|
||||
{
|
||||
label: '车牌号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 140,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ETC卡号',
|
||||
prop: 'etcCardNo',
|
||||
formatter: row => maskEtcCardNo(row.etcCardNo),
|
||||
search: true,
|
||||
minWidth: 220,
|
||||
rules: [
|
||||
{ required: true, message: '请输入ETC卡号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '入口时间',
|
||||
prop: 'entryTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '出口时间',
|
||||
prop: 'exitTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 170,
|
||||
rules: [{ required: true, message: '请选择出口时间', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '入口站',
|
||||
prop: 'entryStation',
|
||||
minWidth: 150,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '出口站',
|
||||
prop: 'exitStation',
|
||||
minWidth: 150,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '数据来源',
|
||||
prop: 'dataSource',
|
||||
value: '手工录入',
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '交易金额',
|
||||
prop: 'transactionAmount',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
append: '元',
|
||||
minWidth: 130,
|
||||
rules: [
|
||||
{ required: true, message: '请输入交易金额', trigger: 'blur' },
|
||||
{ validator: validateNonNegative, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/etc-record/import-etc-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
262
src/option/vehicle/insurance-record.js
Normal file
262
src/option/vehicle/insurance-record.js
Normal file
@@ -0,0 +1,262 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const vehicleTypeDic = [
|
||||
{ label: '车辆', value: '车辆' },
|
||||
{ label: '船舶', value: '船舶' },
|
||||
];
|
||||
|
||||
export const insuranceTypeDic = [
|
||||
{ label: '交强险', value: '交强险' },
|
||||
{ label: '商业险', value: '商业险' },
|
||||
{ label: '承运人责任险', value: '承运人责任险' },
|
||||
{ label: '货运险', value: '货运险' },
|
||||
{ label: '船舶险', value: '船舶险' },
|
||||
];
|
||||
|
||||
export const ocrTemplateDic = [
|
||||
{ label: '紫金-机动车交强险', value: '紫金-机动车交强险' },
|
||||
{ label: '机动车商业险', value: '机动车商业险' },
|
||||
{ label: '船舶保险', value: '船舶保险' },
|
||||
];
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 1100,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
column: [
|
||||
{
|
||||
label: '车船类型',
|
||||
prop: 'vehicleType',
|
||||
type: 'radio',
|
||||
minWidth: 110,
|
||||
dicData: vehicleTypeDic,
|
||||
value: '车辆',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: 'OCR识别模板',
|
||||
prop: 'ocrTemplate',
|
||||
type: 'select',
|
||||
dicData: ocrTemplateDic,
|
||||
value: '紫金-机动车交强险',
|
||||
hide: true,
|
||||
span: 12,
|
||||
},
|
||||
{
|
||||
label: '上传文件',
|
||||
prop: 'policyFile',
|
||||
formslot: true,
|
||||
hide: true,
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '车牌号/船号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 140,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
|
||||
{ max: 50, message: '最多 50 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '保险类型',
|
||||
prop: 'insuranceType',
|
||||
type: 'select',
|
||||
dicData: insuranceTypeDic,
|
||||
search: true,
|
||||
minWidth: 130,
|
||||
rules: [{ required: true, message: '请选择保险类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '保单号',
|
||||
prop: 'policyNo',
|
||||
minWidth: 170,
|
||||
rules: [
|
||||
{ required: true, message: '请输入保单号', trigger: 'blur' },
|
||||
{ max: 80, message: '最多 80 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '开始日期',
|
||||
prop: 'startDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '请选择开始日期', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '结束日期',
|
||||
prop: 'endDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '请选择结束日期', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '保额',
|
||||
prop: 'insuredAmount',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 120,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '保费',
|
||||
prop: 'premium',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 120,
|
||||
rules: [
|
||||
{ required: true, message: '请输入保费', trigger: 'blur' },
|
||||
{ validator: validateNonNegative, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '发票号',
|
||||
prop: 'invoiceNo',
|
||||
minWidth: 150,
|
||||
rules: [{ max: 80, message: '最多 80 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '开票日期',
|
||||
prop: 'invoiceDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [{ max: 200, message: '备注最多 200 个字', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/insurance-record/import-insurance-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
223
src/option/vehicle/mileage-record.js
Normal file
223
src/option/vehicle/mileage-record.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
const isEmpty = value => value === undefined || value === null || value === '';
|
||||
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (isEmpty(value)) {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
menuFixed: 'right',
|
||||
column: [
|
||||
{
|
||||
label: '车牌号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 130,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '上月统计日里程表示值(km)',
|
||||
prop: 'previousMonthMileage',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 210,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '本月统计日里程表示值(km)',
|
||||
prop: 'currentMonthMileage',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 210,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '本月行驶里程(km)',
|
||||
prop: 'monthlyMileage',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 170,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '累计行驶里程(km)',
|
||||
prop: 'totalMileage',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 170,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '累计行驶里程开始值',
|
||||
prop: 'totalMileageStart',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '累计行驶里程结束值',
|
||||
prop: 'totalMileageEnd',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/mileage-record/import-mileage-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
321
src/option/vehicle/oil-electric-record.js
Normal file
321
src/option/vehicle/oil-electric-record.js
Normal file
@@ -0,0 +1,321 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
const isEmpty = value => value === undefined || value === null || value === '';
|
||||
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (isEmpty(value)) {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const vehicleTypeDic = [
|
||||
{ label: '车辆', value: '车辆' },
|
||||
{ label: '船舶', value: '船舶' },
|
||||
];
|
||||
|
||||
export const feeTypeDic = [
|
||||
{ label: '加油', value: '加油' },
|
||||
{ label: '充电', value: '充电' },
|
||||
];
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
menuFixed: 'right',
|
||||
column: [
|
||||
{
|
||||
label: '卡号',
|
||||
prop: 'cardNo',
|
||||
minWidth: 150,
|
||||
rules: [{ max: 30, message: '最多 30 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '交易时间',
|
||||
prop: 'transactionTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 170,
|
||||
rules: [{ required: true, message: '请选择交易时间', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '车船类型',
|
||||
prop: 'vehicleType',
|
||||
type: 'radio',
|
||||
dicData: vehicleTypeDic,
|
||||
value: '车辆',
|
||||
search: true,
|
||||
minWidth: 110,
|
||||
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '费用类型',
|
||||
prop: 'feeType',
|
||||
type: 'select',
|
||||
dicData: feeTypeDic,
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '请选择费用类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '油品',
|
||||
prop: 'oilProduct',
|
||||
minWidth: 160,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '车牌号/船号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 150,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '持卡人',
|
||||
prop: 'cardHolder',
|
||||
minWidth: 120,
|
||||
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '数据来源',
|
||||
prop: 'dataSource',
|
||||
value: '手工录入',
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '数量',
|
||||
prop: 'quantity',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 110,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '单价',
|
||||
prop: 'unitPrice',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 120,
|
||||
append: '元/升',
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '交易金额',
|
||||
prop: 'transactionAmount',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 130,
|
||||
rules: [
|
||||
{ required: true, message: '请输入交易金额', trigger: 'blur' },
|
||||
{ validator: validateNonNegative, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '余额',
|
||||
prop: 'balance',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 110,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '站点',
|
||||
prop: 'station',
|
||||
minWidth: 150,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '交易开始时间',
|
||||
prop: 'transactionTimeStart',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '交易结束时间',
|
||||
prop: 'transactionTimeEnd',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '消费金额开始值',
|
||||
prop: 'transactionAmountStart',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '消费金额结束值',
|
||||
prop: 'transactionAmountEnd',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/oil-electric-record/import-oil-electric-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
220
src/option/vehicle/other-expense-record.js
Normal file
220
src/option/vehicle/other-expense-record.js
Normal file
@@ -0,0 +1,220 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
const isEmpty = value => value === undefined || value === null || value === '';
|
||||
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (isEmpty(value)) {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const vehicleTypeDic = [
|
||||
{ label: '车辆', value: '车辆' },
|
||||
{ label: '船舶', value: '船舶' },
|
||||
];
|
||||
|
||||
export const expenseTypeDic = [
|
||||
{ label: '过路费', value: '过路费' },
|
||||
{ label: '停车费', value: '停车费' },
|
||||
{ label: '维修费', value: '维修费' },
|
||||
{ label: '保险费', value: '保险费' },
|
||||
{ label: '年检费', value: '年检费' },
|
||||
{ label: '装卸费', value: '装卸费' },
|
||||
{ label: '其他', value: '其他' },
|
||||
];
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
menuFixed: 'right',
|
||||
column: [
|
||||
{
|
||||
label: '费用日期',
|
||||
prop: 'expenseDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 140,
|
||||
rules: [{ required: true, message: '请选择费用日期', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '其他费用类型',
|
||||
prop: 'expenseType',
|
||||
type: 'select',
|
||||
dicData: expenseTypeDic,
|
||||
search: true,
|
||||
minWidth: 150,
|
||||
rules: [{ required: true, message: '请选择其他费用类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '车船类型',
|
||||
prop: 'vehicleType',
|
||||
type: 'radio',
|
||||
dicData: vehicleTypeDic,
|
||||
value: '车辆',
|
||||
search: true,
|
||||
minWidth: 110,
|
||||
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '车牌号/船号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 150,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '数据来源',
|
||||
prop: 'dataSource',
|
||||
value: '手工录入',
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '金额',
|
||||
prop: 'amount',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
append: '元',
|
||||
minWidth: 120,
|
||||
rules: [
|
||||
{ required: true, message: '请输入金额', trigger: 'blur' },
|
||||
{ validator: validateNonNegative, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '费用日期',
|
||||
prop: 'expenseDateRange',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/other-expense-record/import-other-expense-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
223
src/option/vehicle/tire-replacement-record.js
Normal file
223
src/option/vehicle/tire-replacement-record.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const validatePositiveInteger = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (!Number.isInteger(Number(value)) || Number(value) <= 0) {
|
||||
callback(new Error('换胎数量必须为正整数'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
column: [
|
||||
{
|
||||
label: '车牌号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 130,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '处理人',
|
||||
prop: 'handler',
|
||||
minWidth: 120,
|
||||
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '换胎时间',
|
||||
prop: 'replacementTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 130,
|
||||
rules: [{ required: true, message: '请选择换胎时间', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '轮胎品牌',
|
||||
prop: 'tireBrand',
|
||||
minWidth: 140,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '换胎数量',
|
||||
prop: 'tireQuantity',
|
||||
type: 'number',
|
||||
precision: 0,
|
||||
minWidth: 110,
|
||||
rules: [{ validator: validatePositiveInteger, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '换胎费用',
|
||||
prop: 'replacementCost',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 120,
|
||||
rules: [
|
||||
{ required: true, message: '请输入换胎费用', trigger: 'blur' },
|
||||
{ validator: validateNonNegative, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '换胎说明',
|
||||
prop: 'replacementDescription',
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
rules: [{ max: 500, message: '最多 500 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/tire-replacement-record/import-tire-replacement-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
199
src/option/vehicle/transport-change-record.js
Normal file
199
src/option/vehicle/transport-change-record.js
Normal file
@@ -0,0 +1,199 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
export const vehicleTypeDic = [
|
||||
{ label: '车辆', value: '车辆' },
|
||||
{ label: '船舶', value: '船舶' },
|
||||
];
|
||||
|
||||
export const changeItemDic = [
|
||||
{ label: '所有人变更', value: '所有人变更' },
|
||||
{ label: '挂靠变更', value: '挂靠变更' },
|
||||
{ label: '经营范围变更', value: '经营范围变更' },
|
||||
{ label: '车牌号变更', value: '车牌号变更' },
|
||||
{ label: '其他', value: '其他' },
|
||||
];
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
menuFixed: 'right',
|
||||
column: [
|
||||
{
|
||||
label: '车船类型',
|
||||
prop: 'vehicleType',
|
||||
type: 'radio',
|
||||
minWidth: 110,
|
||||
dicData: vehicleTypeDic,
|
||||
value: '车辆',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '车牌号/船号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 140,
|
||||
rules: [{ max: 30, message: '最多 30 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '变更事项',
|
||||
prop: 'changeItem',
|
||||
type: 'select',
|
||||
dicData: changeItemDic,
|
||||
minWidth: 180,
|
||||
rules: [{ required: true, message: '请选择变更事项', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '变更内容',
|
||||
prop: 'changeContent',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
search: true,
|
||||
minWidth: 260,
|
||||
overHidden: true,
|
||||
rules: [
|
||||
{ required: true, message: '请输入变更内容', trigger: 'blur' },
|
||||
{ max: 200, message: '最多 200 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/transport-change-record/import-transport-change-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
297
src/option/vehicle/violation-record.js
Normal file
297
src/option/vehicle/violation-record.js
Normal file
@@ -0,0 +1,297 @@
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const validateDeductPoints = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (!Number.isInteger(Number(value)) || Number(value) < 0 || Number(value) > 15) {
|
||||
callback(new Error('被扣分数范围为 0-15 分'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const vehicleTypeDic = [
|
||||
{ label: '车辆', value: '车辆' },
|
||||
{ label: '船舶', value: '船舶' },
|
||||
];
|
||||
|
||||
export const violationTypeDic = [
|
||||
{ label: '闯红灯', value: '闯红灯' },
|
||||
{ label: '超速', value: '超速' },
|
||||
{ label: '违停', value: '违停' },
|
||||
{ label: '压线', value: '压线' },
|
||||
{ label: '其他', value: '其他' },
|
||||
];
|
||||
|
||||
export const processStatusDic = [
|
||||
{ label: '未处理', value: '未处理' },
|
||||
{ label: '已处理', value: '已处理' },
|
||||
];
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 1100,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 180,
|
||||
column: [
|
||||
{
|
||||
label: '车船类型',
|
||||
prop: 'vehicleType',
|
||||
type: 'radio',
|
||||
minWidth: 110,
|
||||
dicData: vehicleTypeDic,
|
||||
value: '车辆',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '车牌号/船号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 140,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '驾驶人',
|
||||
prop: 'driverName',
|
||||
formslot: true,
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [
|
||||
{ required: true, message: '请输入驾驶人/船长', trigger: 'blur' },
|
||||
{ max: 20, message: '最多 20 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '类型',
|
||||
prop: 'violationType',
|
||||
type: 'select',
|
||||
dicData: violationTypeDic,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '事项',
|
||||
prop: 'violationItem',
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
display: false,
|
||||
rules: [
|
||||
{ required: true, message: '请输入事项', trigger: 'blur' },
|
||||
{ max: 100, message: '最多 100 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '日期',
|
||||
prop: 'violationTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 170,
|
||||
rules: [{ required: true, message: '请选择时间', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '地点',
|
||||
prop: 'location',
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
rules: [
|
||||
{ required: true, message: '请输入地点', trigger: 'blur' },
|
||||
{ max: 100, message: '最多 100 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '罚款',
|
||||
prop: 'fineAmount',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 100,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '被扣分数',
|
||||
prop: 'deductPoints',
|
||||
type: 'number',
|
||||
precision: 0,
|
||||
hide: true,
|
||||
rules: [{ validator: validateDeductPoints, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '被罚单位',
|
||||
prop: 'penaltyUnit',
|
||||
minWidth: 150,
|
||||
hide: true,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'processStatus',
|
||||
type: 'radio',
|
||||
dicData: processStatusDic,
|
||||
value: '已处理',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '请选择状态', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '过程描述',
|
||||
prop: 'processDescription',
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [
|
||||
{ required: true, message: '请输入过程描述', trigger: 'blur' },
|
||||
{ max: 500, message: '最多 500 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '处理结果',
|
||||
prop: 'processResult',
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [{ max: 500, message: '最多 500 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/violation-record/import-violation-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -7,7 +7,7 @@
|
||||
</transition>
|
||||
<transition-group name="fade">
|
||||
<template v-if="getScreen(!isCollapse)">
|
||||
<span class="logo-title" key="1">{{ website.indexTitle }} </span>
|
||||
<span class="logo-title" key="1">{{ website.indexTitle }}</span>
|
||||
</template>
|
||||
</transition-group>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@ export default {
|
||||
</script>
|
||||
<style scoped>
|
||||
.logo-title {
|
||||
font-size: 20px;
|
||||
font-size: 16px;
|
||||
background-image: linear-gradient(120deg, #54b6d0 16%, #3f8bdb, #2c77f1);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
74
src/page/login/callback.vue
Normal file
74
src/page/login/callback.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="callback-container">
|
||||
<div class="loading-174"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import website from '@/config/website';
|
||||
|
||||
export default {
|
||||
name: 'iamCallback',
|
||||
computed: {
|
||||
...mapGetters(['tagWel']),
|
||||
},
|
||||
created() {
|
||||
this.handleCallback();
|
||||
},
|
||||
methods: {
|
||||
handleCallback() {
|
||||
const { code, state } = this.$route.query;
|
||||
// console.log(this.$route.query);
|
||||
if (!code) {
|
||||
this.$router.replace('/login');
|
||||
return;
|
||||
}
|
||||
this.$store
|
||||
.dispatch('LoginBySso', {
|
||||
tenantId: state || website.tenantId,
|
||||
code,
|
||||
state: state || website.tenantId,
|
||||
})
|
||||
.then(() => this.$store.dispatch('FlowRoutes'))
|
||||
.then(() => {
|
||||
this.$router.replace(this.tagWel);
|
||||
})
|
||||
.catch(() => {
|
||||
this.$router.replace('/login');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.callback-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.loading-174 {
|
||||
width: 10px;
|
||||
aspect-ratio: 1;
|
||||
background: #0694cd;
|
||||
border-radius: 50%;
|
||||
animation: loading-174 1s infinite linear alternate;
|
||||
}
|
||||
|
||||
@keyframes loading-174 {
|
||||
0% {
|
||||
box-shadow: 15px 0 #0694cd, -25px 0 #0694cd;
|
||||
}
|
||||
50% {
|
||||
box-shadow: 15px 0 #0694cd, -15px 0 #0694cd;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 25px 0 #0694cd, -15px 0 #0694cd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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})`;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -4,36 +4,33 @@
|
||||
{{ time }}
|
||||
</div>
|
||||
<div class="login-weaper">
|
||||
<div class="login-left animate__animated animate__fadeInLeft">
|
||||
<img class="img" src="/img/logo.png" alt="" />
|
||||
<p class="title">{{ $t('login.info') }}</p>
|
||||
</div>
|
||||
<div class="login-border animate__animated animate__fadeInRight">
|
||||
<div class="login-main">
|
||||
<p class="login-title">
|
||||
{{ $t('login.title') }}{{ website.title }}
|
||||
<top-lang></top-lang>
|
||||
</p>
|
||||
<!-- 原登录方式先注释保留备用
|
||||
<userLogin v-if="activeName === 'user'"></userLogin>
|
||||
<codeLogin v-else-if="activeName === 'code'"></codeLogin>
|
||||
<thirdLogin v-else-if="activeName === 'third'"></thirdLogin>
|
||||
<registerLogin v-else-if="activeName === 'register'"></registerLogin>
|
||||
<div class="login-menu">
|
||||
<el-link href="#" @click.stop="activeName = 'user'">{{
|
||||
$t('login.userLogin')
|
||||
}}</el-link>
|
||||
<el-link href="#" @click.stop="activeName = 'code'">{{
|
||||
$t('login.phoneLogin')
|
||||
}}</el-link>
|
||||
<el-link href="#" @click.stop="activeName = 'third'">{{
|
||||
$t('login.thirdLogin')
|
||||
}}</el-link>
|
||||
<el-link
|
||||
:href="
|
||||
website.oauth2.ssoBaseUrl + website.oauth2.ssoAuthUrl + website.oauth2.redirectUri
|
||||
"
|
||||
>{{ $t('login.ssoLogin') }}</el-link
|
||||
>
|
||||
<el-link href="#" @click.stop="activeName = 'user'">{{ $t('login.userLogin') }}</el-link>
|
||||
<el-link href="#" @click.stop="activeName = 'code'">{{ $t('login.phoneLogin') }}</el-link>
|
||||
<el-link href="#" @click.stop="activeName = 'third'">{{ $t('login.thirdLogin') }}</el-link>
|
||||
<el-link :href="iamAuthorizeUrl">{{ $t('login.ssoLogin') }}</el-link>
|
||||
</div>
|
||||
-->
|
||||
<userLogin v-if="activeName === 'user'"></userLogin>
|
||||
<registerLogin v-else-if="activeName === 'register'"></registerLogin>
|
||||
<div v-else class="iam-login">
|
||||
<el-button type="primary" class="login-submit" @click.prevent="handleIamLogin">
|
||||
IAM统一身份认证
|
||||
</el-button>
|
||||
<el-button class="account-submit" @click.prevent="activeName = 'user'">
|
||||
账号密码登录
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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(() => {});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.account-submit {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
font-size: 18px;
|
||||
letter-spacing: 2px;
|
||||
font-weight: 300;
|
||||
cursor: pointer;
|
||||
margin-top: 18px;
|
||||
margin-left: 0;
|
||||
transition: 0.25s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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})`;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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: '锁屏页',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
4
src/utils/audit.js
Normal file
4
src/utils/audit.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export const formatUpdateUserName = row => {
|
||||
if (!row) return '';
|
||||
return row.updateUserName || row.updateUser || '';
|
||||
};
|
||||
@@ -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 加密方法
|
||||
|
||||
665
src/views/base/airport-master.vue
Normal file
665
src/views/base/airport-master.vue
Normal file
@@ -0,0 +1,665 @@
|
||||
<template>
|
||||
<basic-container class="airport-master-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('airport_master_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('airport_master_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('airport_master_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('airport_master_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row, index }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="hasPermission('airport_master_edit')"
|
||||
@click="$refs.crud.rowEdit(row, index)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="hasPermission('airport_master_status')"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog title="空港机场主数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form
|
||||
:option="excelOption"
|
||||
v-model="excelForm"
|
||||
:upload-before="uploadBefore"
|
||||
:upload-after="uploadAfter"
|
||||
>
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/airport-master';
|
||||
import { getLazyTree } from '@/api/base/region';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const validateIataCode = (rule, value, callback) => {
|
||||
if (!/^[A-Z]{3}$/.test(String(value || '').toUpperCase())) {
|
||||
callback(new Error('IATA编码为3位大写字母'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateIcaoCode = (rule, value, callback) => {
|
||||
if (!/^[A-Z]{4}$/.test(String(value || '').toUpperCase())) {
|
||||
callback(new Error('ICAO代码为4位大写字母'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateLongitude = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < -180 || Number(value) > 180) {
|
||||
callback(new Error('经度范围为 -180 到 180'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateLatitude = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < -90 || Number(value) > 90) {
|
||||
callback(new Error('纬度范围为 -90 到 90'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
data: [],
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
provinceOptions: [],
|
||||
cityOptions: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
indexLabel: '序号',
|
||||
indexWidth: 70,
|
||||
viewBtn: false,
|
||||
delBtn: false,
|
||||
editBtn: false,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 160,
|
||||
column: [
|
||||
{
|
||||
label: '编码',
|
||||
prop: 'code',
|
||||
search: true,
|
||||
addDisabled: true,
|
||||
editDisabled: true,
|
||||
placeholder: '系统自动生成',
|
||||
searchPlaceholder: '请输入编码',
|
||||
},
|
||||
{
|
||||
label: 'IATA编码',
|
||||
prop: 'iataCode',
|
||||
minWidth: 120,
|
||||
maxlength: 3,
|
||||
rules: [
|
||||
{ required: true, message: '请输入IATA编码', trigger: 'blur' },
|
||||
{ validator: validateIataCode, trigger: 'blur' },
|
||||
],
|
||||
change: ({ value }) => this.handleIataChange(value),
|
||||
blur: ({ value }) => this.handleIataChange(value),
|
||||
},
|
||||
{
|
||||
label: 'ICAO代码',
|
||||
prop: 'icaoCode',
|
||||
minWidth: 100,
|
||||
maxlength: 4,
|
||||
rules: [
|
||||
{ required: true, message: '请输入ICAO代码', trigger: 'blur' },
|
||||
{ validator: validateIcaoCode, trigger: 'blur' },
|
||||
],
|
||||
change: ({ value }) => this.handleIcaoChange(value),
|
||||
blur: ({ value }) => this.handleIcaoChange(value),
|
||||
},
|
||||
{
|
||||
label: '机场标准名称',
|
||||
prop: 'name',
|
||||
minWidth: 180,
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请输入机场标准名称', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '机场简称',
|
||||
prop: 'shortName',
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '所属省份',
|
||||
prop: 'provinceCode',
|
||||
type: 'select',
|
||||
minWidth: 120,
|
||||
hide: true,
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
dicData: [],
|
||||
filterable: true,
|
||||
rules: [{ required: true, message: '请选择省份', trigger: 'change' }],
|
||||
change: ({ value }) => this.handleProvinceChange(value),
|
||||
},
|
||||
{
|
||||
label: '所属省份',
|
||||
prop: 'provinceName',
|
||||
minWidth: 140,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '所属城市',
|
||||
prop: 'cityCode',
|
||||
type: 'select',
|
||||
minWidth: 120,
|
||||
hide: true,
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
dicData: [],
|
||||
filterable: true,
|
||||
rules: [{ required: true, message: '请选择城市', trigger: 'change' }],
|
||||
change: ({ value }) => this.handleCityChange(value),
|
||||
},
|
||||
{
|
||||
label: '所属城市',
|
||||
prop: 'cityName',
|
||||
minWidth: 140,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '经度',
|
||||
prop: 'longitude',
|
||||
type: 'number',
|
||||
precision: 6,
|
||||
rules: [{ validator: validateLongitude, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '纬度',
|
||||
prop: 'latitude',
|
||||
type: 'number',
|
||||
precision: 6,
|
||||
rules: [{ validator: validateLatitude, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '数据来源',
|
||||
prop: 'dataSource',
|
||||
type: 'select',
|
||||
search: true,
|
||||
dicData: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '初始导入', value: '初始导入' },
|
||||
{ label: '批量导入', value: '批量导入' },
|
||||
{ label: '手动录入', value: '手动录入' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '启停状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
search: true,
|
||||
slot: true,
|
||||
dataType: 'number',
|
||||
dicData: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '停用', value: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
hide: true,
|
||||
maxlength: 200,
|
||||
showWordLimit: true,
|
||||
rules: [{ max: 200, message: '备注不能超过200字', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
],
|
||||
},
|
||||
excelOption: {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
accept: '.xls,.xlsx',
|
||||
action: '/blade-system/airport-master/import-airport-master',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('airport_master_add'),
|
||||
};
|
||||
},
|
||||
isAdmin() {
|
||||
const authority = this.userInfo && this.userInfo.authority;
|
||||
return Array.isArray(authority) ? authority.includes('admin') : String(authority || '').includes('admin');
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadProvinceOptions();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||||
},
|
||||
findColumn(prop) {
|
||||
return this.option.column.find(item => item.prop === prop);
|
||||
},
|
||||
loadProvinceOptions() {
|
||||
getLazyTree('00').then(res => {
|
||||
this.provinceOptions = res.data.data;
|
||||
this.findColumn('provinceCode').dicData = this.provinceOptions;
|
||||
});
|
||||
},
|
||||
loadCityOptions(provinceCode) {
|
||||
if (!provinceCode) {
|
||||
this.cityOptions = [];
|
||||
this.findColumn('cityCode').dicData = [];
|
||||
return Promise.resolve();
|
||||
}
|
||||
return getLazyTree(provinceCode).then(res => {
|
||||
this.cityOptions = res.data.data;
|
||||
this.findColumn('cityCode').dicData = this.cityOptions;
|
||||
});
|
||||
},
|
||||
handleIataChange(value) {
|
||||
this.form.iataCode = String(value || '')
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z]/g, '')
|
||||
.slice(0, 3);
|
||||
this.form.code = this.form.iataCode ? `JC-${this.form.iataCode}` : '';
|
||||
},
|
||||
handleIcaoChange(value) {
|
||||
this.form.icaoCode = String(value || '')
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z]/g, '')
|
||||
.slice(0, 4);
|
||||
},
|
||||
handleProvinceChange(value) {
|
||||
const province = this.provinceOptions.find(item => item.id === value);
|
||||
this.form.provinceName = province ? province.title : '';
|
||||
this.form.cityCode = undefined;
|
||||
this.form.cityName = '';
|
||||
this.loadCityOptions(value);
|
||||
},
|
||||
handleCityChange(value) {
|
||||
const city = this.cityOptions.find(item => item.id === value);
|
||||
this.form.cityName = city ? city.title : '';
|
||||
},
|
||||
normalizeRow(row) {
|
||||
row.code = row.iataCode ? `JC-${row.iataCode}` : row.code;
|
||||
if (!row.dataSource) row.dataSource = '手动录入';
|
||||
if (!row.status) row.status = 1;
|
||||
return row;
|
||||
},
|
||||
validateUnique(row) {
|
||||
const rowId = String(row.id || '');
|
||||
const hasDuplicate = (params, prop) => {
|
||||
return getList(1, 10, params).then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
return records.some(item => item[prop] === row[prop] && String(item.id || '') !== rowId);
|
||||
});
|
||||
};
|
||||
return Promise.all([
|
||||
hasDuplicate({ iataCode: row.iataCode }, 'iataCode'),
|
||||
hasDuplicate({ icaoCode: row.icaoCode }, 'icaoCode'),
|
||||
]).then(([iataExists, icaoExists]) => {
|
||||
if (iataExists) {
|
||||
return Promise.reject(new Error('该IATA编码已存在'));
|
||||
}
|
||||
if (icaoExists) {
|
||||
return Promise.reject(new Error('该ICAO代码已存在'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
},
|
||||
handleSubmitError(error, loading) {
|
||||
const uniqueMessages = ['该IATA编码已存在', '该ICAO代码已存在'];
|
||||
if (uniqueMessages.includes(error.message)) {
|
||||
this.$message.warning(error.message);
|
||||
}
|
||||
window.console.log(error);
|
||||
loading();
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const submitRow = this.normalizeRow(row);
|
||||
this.validateUnique(submitRow)
|
||||
.then(() => submit(submitRow))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
})
|
||||
.catch(error => this.handleSubmitError(error, loading));
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const submitRow = this.normalizeRow(row);
|
||||
this.validateUnique(submitRow)
|
||||
.then(() => submit(submitRow))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
})
|
||||
.catch(error => this.handleSubmitError(error, loading));
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const nextStatus = row.status === 1 ? 2 : 1;
|
||||
const actionName = nextStatus === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否确认${actionName}该条数据?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => changeStatus(row.id, nextStatus))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
this.loadCityOptions(this.form.provinceCode).then(() => done());
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.form.code = '';
|
||||
this.form.dataSource = '手动录入';
|
||||
this.form.status = 1;
|
||||
this.loadCityOptions(this.form.provinceCode).then(() => done());
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadBefore(file, done) {
|
||||
const fileName = file && file.name ? file.name.toLowerCase() : '';
|
||||
if (!/\.(xls|xlsx)$/.test(fileName)) {
|
||||
this.$message.error('请上传 .xls,.xlsx 标准格式文件');
|
||||
return false;
|
||||
}
|
||||
if (typeof done === 'function') {
|
||||
done();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出空港机场主数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-system/airport-master/export-airport-master', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(
|
||||
res.data,
|
||||
`空港机场主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-system/airport-master/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '空港机场主数据模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.airport-master-page {
|
||||
:deep(.avue-crud__header),
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
815
src/views/base/common-address.vue
Normal file
815
src/views/base/common-address.vue
Normal file
@@ -0,0 +1,815 @@
|
||||
<template>
|
||||
<basic-container class="common-address-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
plain
|
||||
v-if="hasPermission('common_address_add')"
|
||||
@click="$refs.crud.rowAdd()"
|
||||
>新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('common_address_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('common_address_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
<el-switch
|
||||
v-if="canViewAllDept"
|
||||
v-model="allDept"
|
||||
class="common-address-page__scope"
|
||||
active-text="全部组织"
|
||||
inactive-text="当前组织"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="handleScopeChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #siteCodeDisplay="{ row }">
|
||||
<span>{{ row.addressType === '常规地址' ? '/' : row.siteCodeDisplay || row.siteCode }}</span>
|
||||
</template>
|
||||
|
||||
<template #menu="{ row, index }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-view"
|
||||
v-if="hasPermission('common_address_view')"
|
||||
@click="$refs.crud.rowView(row, index)"
|
||||
>
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="hasPermission('common_address_edit') && !row.readonly"
|
||||
@click="$refs.crud.rowEdit(row, index)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-delete"
|
||||
v-if="hasPermission('common_address_delete') && !row.readonly"
|
||||
@click="rowDel(row)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #sourceId-form>
|
||||
<el-select
|
||||
v-model="form.sourceId"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
:remote-method="loadSourceOptions"
|
||||
:loading="sourceLoading"
|
||||
:disabled="dialogReadonly || form.addressType === '常规地址'"
|
||||
placeholder="请选择来源主数据"
|
||||
style="width: 100%"
|
||||
@change="handleSourceChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in sourceOptions"
|
||||
:key="item.sourceId"
|
||||
:label="`${item.addressName}(${item.siteCode})`"
|
||||
:value="item.sourceId"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<template #detailAddress-form>
|
||||
<div class="common-address-page__address">
|
||||
<el-input
|
||||
v-model="form.detailAddress"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="255"
|
||||
show-word-limit
|
||||
:disabled="dialogReadonly || form.addressType !== '常规地址'"
|
||||
/>
|
||||
<el-button
|
||||
icon="el-icon-location"
|
||||
plain
|
||||
:disabled="dialogReadonly || form.addressType !== '常规地址'"
|
||||
@click="handleMapPick"
|
||||
>
|
||||
地图选点
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog
|
||||
title="地图选点"
|
||||
append-to-body
|
||||
v-model="mapBox"
|
||||
width="920px"
|
||||
class="common-address-page__map-dialog"
|
||||
@opened="initTiandituMap"
|
||||
>
|
||||
<div class="common-address-page__map-toolbar">
|
||||
<el-input
|
||||
v-model="mapKeyword"
|
||||
clearable
|
||||
placeholder="输入地址关键词"
|
||||
@keyup.enter="searchMapKeyword"
|
||||
/>
|
||||
<el-button type="primary" icon="el-icon-search" :loading="mapLoading" @click="searchMapKeyword">
|
||||
搜索
|
||||
</el-button>
|
||||
</div>
|
||||
<div ref="tiandituMap" class="common-address-page__map"></div>
|
||||
<div class="common-address-page__map-info">
|
||||
<span>{{ mapStatus }}</span>
|
||||
<span v-if="mapSelected.longitude">
|
||||
经度:{{ mapSelected.longitude }},纬度:{{ mapSelected.latitude }}
|
||||
</span>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="mapBox = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!mapSelected.longitude" @click="confirmMapPick">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getDetail, getList, getSourceOptions, remove, submit } from '@/api/base/common-address';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { createOption } from '@/option/base/common-address';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
const TIANDITU_KEY = '71f234f251d4cb1feeb3c293a0f21709';
|
||||
let tiandituLoader;
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const validateLongitude = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < -180 || Number(value) > 180) {
|
||||
callback(new Error('经度范围为 -180 到 180'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateLatitude = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < -90 || Number(value) > 90) {
|
||||
callback(new Error('纬度范围为 -90 到 90'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
data: [],
|
||||
allDept: 0,
|
||||
dialogReadonly: false,
|
||||
sourceLoading: false,
|
||||
sourceOptions: [],
|
||||
deptOptions: [],
|
||||
mapBox: false,
|
||||
mapLoading: false,
|
||||
mapKeyword: '',
|
||||
mapStatus: '可搜索地址或点击地图选点',
|
||||
mapSelected: {},
|
||||
tiandituMap: null,
|
||||
tiandituMarker: null,
|
||||
tiandituGeocoder: null,
|
||||
tiandituClickHandler: null,
|
||||
option: createOption({ validateLongitude, validateLatitude }),
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('common_address_add'),
|
||||
};
|
||||
},
|
||||
isAdmin() {
|
||||
const authority = this.userInfo && this.userInfo.authority;
|
||||
return Array.isArray(authority) ? authority.includes('admin') : String(authority || '').includes('admin');
|
||||
},
|
||||
canViewAllDept() {
|
||||
return this.isAdmin;
|
||||
},
|
||||
ids() {
|
||||
return this.selectionList.map(item => item.id).join(',');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
this.deptOptions = this.flattenDept(res.data.data || []);
|
||||
const deptColumn = this.findColumn(this.option.column, 'deptId');
|
||||
deptColumn.dicData = this.deptOptions;
|
||||
});
|
||||
},
|
||||
flattenDept(tree, level = 0) {
|
||||
const result = [];
|
||||
tree.forEach(item => {
|
||||
result.push({
|
||||
label: `${' '.repeat(level)}${item.title || item.deptName || item.name}`,
|
||||
value: item.id,
|
||||
rawLabel: item.title || item.deptName || item.name,
|
||||
});
|
||||
if (item.children && item.children.length) {
|
||||
result.push(...this.flattenDept(item.children, level + 1));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
handleScopeChange() {
|
||||
if (!this.canViewAllDept) {
|
||||
this.allDept = 0;
|
||||
}
|
||||
if (!this.allDept) {
|
||||
this.query.deptId = undefined;
|
||||
}
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
loadSourceOptions(keyword = '') {
|
||||
if (!this.form.addressType || this.form.addressType === '常规地址') {
|
||||
this.sourceOptions = [];
|
||||
return;
|
||||
}
|
||||
this.sourceLoading = true;
|
||||
getSourceOptions(this.form.addressType, keyword)
|
||||
.then(res => {
|
||||
this.sourceOptions = res.data.data || [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.sourceLoading = false;
|
||||
});
|
||||
},
|
||||
handleSourceChange(value) {
|
||||
const source = this.sourceOptions.find(item => item.sourceId === value);
|
||||
if (!source) return;
|
||||
this.form.addressName = this.form.addressName || source.addressName;
|
||||
this.form.siteCode = source.siteCode;
|
||||
this.form.siteCodeDisplay = source.siteCode;
|
||||
this.form.detailAddress = source.detailAddress;
|
||||
this.form.regionName = source.regionName;
|
||||
this.form.longitude = source.longitude;
|
||||
this.form.latitude = source.latitude;
|
||||
},
|
||||
handleAddressTypeChange(value, reset = true) {
|
||||
const sourceColumn = this.findColumn(this.option.column, 'sourceId');
|
||||
sourceColumn.rules = value === '常规地址' ? [] : [{ required: true, message: '请选择来源主数据', trigger: 'change' }];
|
||||
const sourceDisabled = value === '常规地址';
|
||||
this.findColumn(this.option.column, 'longitude').disabled = !sourceDisabled;
|
||||
this.findColumn(this.option.column, 'latitude').disabled = !sourceDisabled;
|
||||
if (value === '常规地址') {
|
||||
if (reset) {
|
||||
this.form.sourceId = undefined;
|
||||
this.form.siteCode = '/';
|
||||
this.form.siteCodeDisplay = '/';
|
||||
this.sourceOptions = [];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (reset) {
|
||||
this.form.sourceId = undefined;
|
||||
this.form.siteCode = '';
|
||||
this.form.siteCodeDisplay = '';
|
||||
}
|
||||
this.loadSourceOptions();
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const submitRow = {
|
||||
...row,
|
||||
addressName: String(row.addressName || '').trim(),
|
||||
addressType: String(row.addressType || '').trim(),
|
||||
detailAddress: String(row.detailAddress || '').trim(),
|
||||
regionName: String(row.regionName || '').trim(),
|
||||
contactName: String(row.contactName || '').trim(),
|
||||
contactPhone: String(row.contactPhone || '').trim(),
|
||||
remark: String(row.remark || '').trim(),
|
||||
};
|
||||
if (submitRow.addressType === '常规地址') {
|
||||
submitRow.siteCode = '/';
|
||||
submitRow.sourceId = undefined;
|
||||
}
|
||||
return submitRow;
|
||||
},
|
||||
validateRequiredFields(row) {
|
||||
if (!row.addressType) {
|
||||
this.$message.warning('请选择类型');
|
||||
return false;
|
||||
}
|
||||
if (!row.addressName) {
|
||||
this.$message.warning('请输入地址名称');
|
||||
return false;
|
||||
}
|
||||
if (!row.detailAddress) {
|
||||
this.$message.warning('请输入详细地址');
|
||||
return false;
|
||||
}
|
||||
if (row.addressType !== '常规地址' && !row.sourceId) {
|
||||
this.$message.warning('请选择来源主数据');
|
||||
return false;
|
||||
}
|
||||
if (row.remark && row.remark.length > 200) {
|
||||
this.$message.warning('备注不能超过200个字');
|
||||
return false;
|
||||
}
|
||||
if (row.longitude !== undefined && row.longitude !== null && row.longitude !== '') {
|
||||
if (Number(row.longitude) < -180 || Number(row.longitude) > 180) {
|
||||
this.$message.warning('经度范围为 -180 到 180');
|
||||
return false;
|
||||
}
|
||||
row.longitude = Number(row.longitude).toFixed(6);
|
||||
}
|
||||
if (row.latitude !== undefined && row.latitude !== null && row.latitude !== '') {
|
||||
if (Number(row.latitude) < -90 || Number(row.latitude) > 90) {
|
||||
this.$message.warning('纬度范围为 -90 到 90');
|
||||
return false;
|
||||
}
|
||||
row.latitude = Number(row.latitude).toFixed(6);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
stopSubmitLoading(loading) {
|
||||
if (typeof loading === 'function') {
|
||||
loading();
|
||||
}
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const submitRow = this.normalizeRow(row);
|
||||
if (!this.validateRequiredFields(submitRow)) {
|
||||
this.stopSubmitLoading(loading);
|
||||
return;
|
||||
}
|
||||
submit(submitRow).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const submitRow = this.normalizeRow(row);
|
||||
if (!this.validateRequiredFields(submitRow)) {
|
||||
this.stopSubmitLoading(loading);
|
||||
return;
|
||||
}
|
||||
submit(submitRow).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(res => {
|
||||
this.afterRemove(res.data.data);
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
const editableSelection = this.selectionList.filter(item => !item.readonly);
|
||||
if (!editableSelection.length) {
|
||||
this.$message.warning('所选数据均为只读组织数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(editableSelection.map(item => item.id).join(',')))
|
||||
.then(res => {
|
||||
this.afterRemove(res.data.data);
|
||||
});
|
||||
},
|
||||
afterRemove(result = {}) {
|
||||
this.onLoad(this.page);
|
||||
const successCount = result.successCount || 0;
|
||||
const skippedCount = result.skippedCount || 0;
|
||||
if (skippedCount) {
|
||||
this.$message.warning(`成功删除${successCount}条,${skippedCount}条因业务引用已跳过`);
|
||||
} else {
|
||||
this.$message({ type: 'success', message: '删除成功!' });
|
||||
}
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
this.dialogReadonly = type === 'view';
|
||||
const addressTypeColumn = this.findColumn(this.option.column, 'addressType');
|
||||
addressTypeColumn.change = ({ value }) => this.handleAddressTypeChange(value);
|
||||
if (type === 'add') {
|
||||
this.form.addressType = this.form.addressType || '常规地址';
|
||||
this.form.siteCode = '/';
|
||||
this.form.siteCodeDisplay = '/';
|
||||
this.handleAddressTypeChange(this.form.addressType);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
this.handleAddressTypeChange(this.form.addressType, false);
|
||||
if (this.form.addressType !== '常规地址' && this.form.sourceId) {
|
||||
this.sourceOptions = [
|
||||
{
|
||||
sourceId: this.form.sourceId,
|
||||
addressName: this.form.addressName,
|
||||
siteCode: this.form.siteCode,
|
||||
detailAddress: this.form.detailAddress,
|
||||
regionName: this.form.regionName,
|
||||
longitude: this.form.longitude,
|
||||
latitude: this.form.latitude,
|
||||
},
|
||||
];
|
||||
}
|
||||
done();
|
||||
});
|
||||
return;
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query, allDept: this.allDept })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出常用地址?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/common-address/export-common-address', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `常用地址${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
allDept: this.allDept,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleMapPick() {
|
||||
if (this.dialogReadonly || this.form.addressType !== '常规地址') {
|
||||
return;
|
||||
}
|
||||
this.mapKeyword = this.form.detailAddress || this.form.regionName || '';
|
||||
this.mapSelected = this.buildMapSelection({
|
||||
lng: this.form.longitude,
|
||||
lat: this.form.latitude,
|
||||
address: this.form.detailAddress,
|
||||
regionName: this.form.regionName,
|
||||
});
|
||||
this.mapStatus = this.mapSelected.longitude ? '已加载当前经纬度,可重新选点' : '可搜索地址或点击地图选点';
|
||||
this.mapBox = true;
|
||||
},
|
||||
loadTianditu() {
|
||||
if (window.T && window.T.Map) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (!tiandituLoader) {
|
||||
tiandituLoader = new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://api.tianditu.gov.cn/api?v=4.0&tk=${TIANDITU_KEY}`;
|
||||
script.async = true;
|
||||
script.onload = resolve;
|
||||
script.onerror = () => reject(new Error('天地图组件加载失败'));
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
}
|
||||
return tiandituLoader;
|
||||
},
|
||||
initTiandituMap() {
|
||||
this.loadTianditu()
|
||||
.then(() => {
|
||||
this.$nextTick(() => {
|
||||
if (!this.tiandituMap) {
|
||||
const center = this.toLngLat(
|
||||
this.mapSelected.longitude || 116.40769,
|
||||
this.mapSelected.latitude || 39.89945
|
||||
);
|
||||
this.tiandituMap = new window.T.Map(this.$refs.tiandituMap);
|
||||
this.tiandituMap.centerAndZoom(center, this.mapSelected.longitude ? 14 : 11);
|
||||
this.tiandituMap.addControl(new window.T.Control.Zoom());
|
||||
this.tiandituClickHandler = event => this.pickMapPoint(event.lnglat);
|
||||
this.tiandituMap.addEventListener('click', this.tiandituClickHandler);
|
||||
this.tiandituGeocoder = new window.T.Geocoder();
|
||||
} else {
|
||||
if (typeof this.tiandituMap.checkResize === 'function') {
|
||||
this.tiandituMap.checkResize();
|
||||
}
|
||||
}
|
||||
if (this.mapSelected.longitude) {
|
||||
this.renderMapMarker(this.toLngLat(this.mapSelected.longitude, this.mapSelected.latitude));
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('天地图组件加载失败,请稍后重试或重新打开弹窗');
|
||||
this.mapBox = false;
|
||||
});
|
||||
},
|
||||
searchMapKeyword() {
|
||||
const keyword = String(this.mapKeyword || '').trim();
|
||||
if (!keyword) {
|
||||
this.$message.warning('请输入地址关键词');
|
||||
return;
|
||||
}
|
||||
this.loadTianditu()
|
||||
.then(() => {
|
||||
this.mapLoading = true;
|
||||
if (!this.tiandituGeocoder) {
|
||||
this.tiandituGeocoder = new window.T.Geocoder();
|
||||
}
|
||||
this.tiandituGeocoder.getPoint(keyword, result => {
|
||||
this.mapLoading = false;
|
||||
const point = this.resolveMapPoint(result);
|
||||
if (!point) {
|
||||
this.mapStatus = '未找到匹配地址';
|
||||
this.$message.warning('地图搜索无匹配地址');
|
||||
return;
|
||||
}
|
||||
this.pickMapPoint(point, keyword);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('天地图组件加载失败,请稍后重试或重新打开弹窗');
|
||||
});
|
||||
},
|
||||
pickMapPoint(lnglat, keyword) {
|
||||
const longitude = this.getPointLng(lnglat);
|
||||
const latitude = this.getPointLat(lnglat);
|
||||
if (longitude === undefined || latitude === undefined) {
|
||||
this.$message.warning('选点坐标无效');
|
||||
return;
|
||||
}
|
||||
const point = this.toLngLat(longitude, latitude);
|
||||
this.renderMapMarker(point);
|
||||
this.mapSelected = this.buildMapSelection({ lng: longitude, lat: latitude, address: keyword });
|
||||
this.mapStatus = '正在反查地址...';
|
||||
if (!this.tiandituGeocoder) {
|
||||
this.tiandituGeocoder = new window.T.Geocoder();
|
||||
}
|
||||
this.tiandituGeocoder.getLocation(point, result => {
|
||||
const address = this.resolveMapAddress(result);
|
||||
this.mapSelected = {
|
||||
...this.mapSelected,
|
||||
detailAddress: address.detailAddress || this.mapSelected.detailAddress,
|
||||
regionName: address.regionName || this.mapSelected.regionName,
|
||||
regionCode: address.regionCode || this.mapSelected.regionCode,
|
||||
};
|
||||
this.mapKeyword = this.mapSelected.detailAddress || this.mapKeyword;
|
||||
this.mapStatus = this.mapSelected.detailAddress || '已选点,可确认回填';
|
||||
});
|
||||
},
|
||||
renderMapMarker(point) {
|
||||
if (!this.tiandituMap || !window.T) return;
|
||||
if (this.tiandituMarker) {
|
||||
this.tiandituMap.removeOverLay(this.tiandituMarker);
|
||||
}
|
||||
this.tiandituMarker = new window.T.Marker(point);
|
||||
this.tiandituMap.addOverLay(this.tiandituMarker);
|
||||
this.tiandituMap.panTo(point);
|
||||
},
|
||||
confirmMapPick() {
|
||||
if (!this.mapSelected.longitude) {
|
||||
this.$message.warning('请先搜索或点击地图完成选点');
|
||||
return;
|
||||
}
|
||||
this.form.detailAddress = this.mapSelected.detailAddress || this.form.detailAddress;
|
||||
this.form.regionName = this.mapSelected.regionName || this.form.regionName;
|
||||
this.form.regionCode = this.mapSelected.regionCode || this.form.regionCode;
|
||||
this.form.longitude = this.mapSelected.longitude;
|
||||
this.form.latitude = this.mapSelected.latitude;
|
||||
this.mapBox = false;
|
||||
},
|
||||
buildMapSelection({ lng, lat, address, regionName, regionCode }) {
|
||||
const longitude = lng === undefined || lng === null || lng === '' ? '' : Number(lng).toFixed(6);
|
||||
const latitude = lat === undefined || lat === null || lat === '' ? '' : Number(lat).toFixed(6);
|
||||
return {
|
||||
longitude,
|
||||
latitude,
|
||||
detailAddress: address || '',
|
||||
regionName: regionName || '',
|
||||
regionCode: regionCode || '',
|
||||
};
|
||||
},
|
||||
toLngLat(lng, lat) {
|
||||
return new window.T.LngLat(Number(lng), Number(lat));
|
||||
},
|
||||
getPointLng(point) {
|
||||
if (!point) return undefined;
|
||||
if (typeof point.getLng === 'function') return point.getLng();
|
||||
return point.lng ?? point.lon;
|
||||
},
|
||||
getPointLat(point) {
|
||||
if (!point) return undefined;
|
||||
if (typeof point.getLat === 'function') return point.getLat();
|
||||
return point.lat;
|
||||
},
|
||||
resolveMapPoint(result) {
|
||||
if (!result) return null;
|
||||
if (typeof result.getLocationPoint === 'function') return result.getLocationPoint();
|
||||
if (Array.isArray(result)) return result[0] || null;
|
||||
if (result.location) return result.location;
|
||||
if (result.lnglat) return result.lnglat;
|
||||
if (result.lonlat) return result.lonlat;
|
||||
if (result.getLng || result.lng || result.lon) return result;
|
||||
return null;
|
||||
},
|
||||
resolveMapAddress(result = {}) {
|
||||
const component =
|
||||
(typeof result.getAddressComponent === 'function' && result.getAddressComponent()) ||
|
||||
result.addressComponent ||
|
||||
{};
|
||||
const detailAddress =
|
||||
(typeof result.getAddress === 'function' && result.getAddress()) ||
|
||||
result.formatted_address ||
|
||||
result.address ||
|
||||
result.formattedAddress ||
|
||||
'';
|
||||
const regionName = [component.province, component.city, component.county || component.district]
|
||||
.filter(Boolean)
|
||||
.join('');
|
||||
return {
|
||||
detailAddress,
|
||||
regionName,
|
||||
regionCode: component.countyCode || component.adcode || component.cityCode || '',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.common-address-page {
|
||||
&__scope {
|
||||
margin-left: 12px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&__address {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
|
||||
.el-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__map-toolbar {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.el-input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__map {
|
||||
width: 100%;
|
||||
height: 460px;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
&__map-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 22px;
|
||||
margin-top: 10px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
580
src/views/base/currency.vue
Normal file
580
src/views/base/currency.vue
Normal file
@@ -0,0 +1,580 @@
|
||||
<template>
|
||||
<basic-container class="currency-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="permission.currency_import"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="permission.currency_template"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="permission.currency_export"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
</template>
|
||||
<template #codeForm>
|
||||
<el-autocomplete
|
||||
v-model="form.code"
|
||||
:fetch-suggestions="fetchCurrencyOptions"
|
||||
clearable
|
||||
placeholder="请输入币种编码查询选择"
|
||||
value-key="code"
|
||||
class="currency-page__input"
|
||||
@select="handleCurrencySelect"
|
||||
@change="handleCodeChange"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<span>{{ item.code }}</span>
|
||||
<span class="currency-page__option-name">{{ item.name }}</span>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row, index }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="permission.currency_edit"
|
||||
@click="$refs.crud.rowEdit(row, index)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="permission.currency_status"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog title="币种汇率导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { changeStatus, getDetail, getList, submit } from '@/api/base/currency';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
const statusDic = [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '停用', value: 2 },
|
||||
];
|
||||
|
||||
const sourceDic = [
|
||||
{ label: '手工录入', value: '手工录入' },
|
||||
{ label: '批量导入', value: '批量导入' },
|
||||
];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const validateCurrencyCode = (rule, value, callback) => {
|
||||
if (!/^[A-Z]{3}$/.test(value || '')) {
|
||||
callback(new Error('币种编码为3位大写字母'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateExchangeRate = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback(new Error('请输入汇率'));
|
||||
} else if (Number(value) <= 0) {
|
||||
callback(new Error('汇率必须大于0'));
|
||||
} else if (!/^\d+(\.\d{1,6})?$/.test(String(value))) {
|
||||
callback(new Error('汇率最多保留6位小数'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
data: [],
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 860,
|
||||
labelPosition: 'top',
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
indexLabel: '序号',
|
||||
viewBtn: false,
|
||||
delBtn: false,
|
||||
editBtn: false,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 170,
|
||||
menuFixed: 'right',
|
||||
column: [
|
||||
{
|
||||
label: '币种编码',
|
||||
prop: 'code',
|
||||
search: true,
|
||||
formslot: true,
|
||||
minWidth: 120,
|
||||
maxlength: 3,
|
||||
rules: [
|
||||
{ required: true, message: '请选择币种编码', trigger: 'blur' },
|
||||
{ validator: validateCurrencyCode, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '币种名称',
|
||||
prop: 'name',
|
||||
minWidth: 150,
|
||||
search: true,
|
||||
disabled: true,
|
||||
rules: [{ required: true, message: '请选择币种编码带出币种名称', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '汇率',
|
||||
prop: 'exchangeRate',
|
||||
type: 'number',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
precision: 6,
|
||||
rules: [{ validator: validateExchangeRate, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '生效日期',
|
||||
prop: 'effectiveDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 130,
|
||||
rules: [{ required: true, message: '请选择生效日期', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '生效日期',
|
||||
prop: 'effectiveDateRange',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '失效日期',
|
||||
prop: 'expiryDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 130,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '失效日期',
|
||||
prop: 'expiryDateRange',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
search: true,
|
||||
slot: true,
|
||||
dataType: 'number',
|
||||
dicData: statusDic,
|
||||
value: 1,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
label: '来源',
|
||||
prop: 'dataSource',
|
||||
type: 'select',
|
||||
search: true,
|
||||
dicData: sourceDic,
|
||||
value: '手工录入',
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
hide: true,
|
||||
maxlength: 200,
|
||||
showWordLimit: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTimeRange',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
excelOption: {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-system/currency/import-currency',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.validData(this.permission.currency_add, false),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
return this.selectionList.map(ele => ele.id).join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleCodeChange(value) {
|
||||
this.form.code = String(value || '').toUpperCase().replace(/[^A-Z]/g, '').slice(0, 3);
|
||||
const matched = this.data.find(item => item.code === this.form.code);
|
||||
if (matched) {
|
||||
this.form.name = matched.name;
|
||||
}
|
||||
},
|
||||
fetchCurrencyOptions(queryString, callback) {
|
||||
getList(1, 20, { code: String(queryString || '').toUpperCase() })
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
const exists = new Map();
|
||||
records.forEach(item => {
|
||||
if (!exists.has(item.code)) {
|
||||
exists.set(item.code, {
|
||||
code: item.code,
|
||||
value: item.code,
|
||||
name: item.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
callback(Array.from(exists.values()));
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
handleCurrencySelect(item) {
|
||||
this.form.code = item.code;
|
||||
this.form.name = item.name;
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
values.code = String(values.code || '').toUpperCase();
|
||||
values.dataSource = values.dataSource || '手工录入';
|
||||
values.status = values.status || 1;
|
||||
return values;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (row.remark && row.remark.length > 200) {
|
||||
this.$message.warning('备注不能超过 200 字');
|
||||
return false;
|
||||
}
|
||||
if (Number(row.exchangeRate) <= 0) {
|
||||
this.$message.warning('汇率必须大于0');
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+(\.\d{1,6})?$/.test(String(row.exchangeRate))) {
|
||||
this.$message.warning('汇率最多保留6位小数');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
submit(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
submit(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
handleStatus(row) {
|
||||
const nextStatus = row.status === 1 ? 2 : 1;
|
||||
const actionName = nextStatus === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否确认${actionName}该条汇率?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => changeStatus(row.id, nextStatus))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
done();
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.form = {
|
||||
status: 1,
|
||||
dataSource: '手工录入',
|
||||
};
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
const values = { ...params, ...this.query };
|
||||
const rangeMap = {
|
||||
effectiveDateRange: ['effectiveDateStart', 'effectiveDateEnd'],
|
||||
expiryDateRange: ['expiryDateStart', 'expiryDateEnd'],
|
||||
updateTimeRange: ['updateTimeStart', 'updateTimeEnd'],
|
||||
};
|
||||
Object.keys(rangeMap).forEach(key => {
|
||||
const range = values[key];
|
||||
if (range && range.length === 2) {
|
||||
values[rangeMap[key][0]] = range[0];
|
||||
values[rangeMap[key][1]] = range[1];
|
||||
}
|
||||
values[key] = null;
|
||||
});
|
||||
return values;
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, this.buildQuery(params))
|
||||
.then(res => {
|
||||
const result = res.data.data;
|
||||
this.page.total = result.total;
|
||||
this.data = result.records;
|
||||
if (result.records.length === 0 && result.total > 0 && page.currentPage > 1) {
|
||||
this.page.currentPage = page.currentPage - 1;
|
||||
this.onLoad(this.page, this.query);
|
||||
}
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出币种汇率数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-system/currency/export-currency', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `币种汇率${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.buildQuery(),
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(`/blade-system/currency/export-template?${this.website.tokenHeader}=${getToken()}`).then(res => {
|
||||
downloadXls(res.data, '币种汇率模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.currency-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__option-name {
|
||||
float: right;
|
||||
color: #8492a6;
|
||||
font-size: 13px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
290
src/views/base/customer-type.vue
Normal file
290
src/views/base/customer-type.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="permission.customer_type_delete"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row, index }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="permission.customer_type_edit"
|
||||
@click="$refs.crud.rowEdit(row, index)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="permission.customer_type_status"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/customer-type';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
data: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 680,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
indexLabel: '序号',
|
||||
viewBtn: false,
|
||||
delBtn: false,
|
||||
editBtn: false,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 160,
|
||||
column: [
|
||||
{
|
||||
label: '中文名称',
|
||||
prop: 'name',
|
||||
minWidth: 140,
|
||||
search: true,
|
||||
maxlength: 50,
|
||||
rules: [{ required: true, message: '请输入中文名称', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '英文名称',
|
||||
prop: 'englishName',
|
||||
minWidth: 160,
|
||||
search: true,
|
||||
maxlength: 100,
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
search: true,
|
||||
slot: true,
|
||||
dataType: 'number',
|
||||
dicData: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '停用', value: 2 },
|
||||
],
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.validData(this.permission.customer_type_add, false),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
normalizeRow(row) {
|
||||
row.name = String(row.name || '').trim();
|
||||
row.englishName = String(row.englishName || '').trim();
|
||||
if (!row.status) row.status = 1;
|
||||
return row;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
submit(this.normalizeRow(row)).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
submit(this.normalizeRow(row)).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const nextStatus = row.status === 1 ? 2 : 1;
|
||||
const actionName = nextStatus === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否确认${actionName}该条数据?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => changeStatus(row.id, nextStatus))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
done();
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.form.status = 1;
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.loading = false;
|
||||
this.selectionClear();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
290
src/views/base/fee-item.vue
Normal file
290
src/views/base/fee-item.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="permission.fee_item_delete"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row, index }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="permission.fee_item_edit"
|
||||
@click="$refs.crud.rowEdit(row, index)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="permission.fee_item_status"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/fee-item';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
data: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 680,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
indexLabel: '序号',
|
||||
viewBtn: false,
|
||||
delBtn: false,
|
||||
editBtn: false,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 160,
|
||||
column: [
|
||||
{
|
||||
label: '中文名称',
|
||||
prop: 'name',
|
||||
minWidth: 140,
|
||||
search: true,
|
||||
maxlength: 50,
|
||||
rules: [{ required: true, message: '请输入中文名称', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '英文名称',
|
||||
prop: 'englishName',
|
||||
minWidth: 160,
|
||||
search: true,
|
||||
maxlength: 100,
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
search: true,
|
||||
slot: true,
|
||||
dataType: 'number',
|
||||
dicData: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '停用', value: 2 },
|
||||
],
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.validData(this.permission.fee_item_add, false),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
normalizeRow(row) {
|
||||
row.name = String(row.name || '').trim();
|
||||
row.englishName = String(row.englishName || '').trim();
|
||||
if (!row.status) row.status = 1;
|
||||
return row;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
submit(this.normalizeRow(row)).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
submit(this.normalizeRow(row)).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const nextStatus = row.status === 1 ? 2 : 1;
|
||||
const actionName = nextStatus === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否确认${actionName}该条数据?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => changeStatus(row.id, nextStatus))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
done();
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.form.status = 1;
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.loading = false;
|
||||
this.selectionClear();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
804
src/views/base/port-terminal.vue
Normal file
804
src/views/base/port-terminal.vue
Normal file
@@ -0,0 +1,804 @@
|
||||
<template>
|
||||
<basic-container class="port-terminal-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('port_terminal_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('port_terminal_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('port_terminal_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('port_terminal_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #parentName="{ row }">
|
||||
<span>{{ row.category === '港口' ? '-' : row.parentName }}</span>
|
||||
</template>
|
||||
<template #parentCode="{ row }">
|
||||
<span>{{ row.category === '港口' ? '-' : row.parentCode }}</span>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row, index }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="hasPermission('port_terminal_edit')"
|
||||
@click="$refs.crud.rowEdit(row, index)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="hasPermission('port_terminal_status')"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #regionCode-form>
|
||||
<el-cascader
|
||||
v-model="form.regionCode"
|
||||
:props="regionProps"
|
||||
clearable
|
||||
filterable
|
||||
:disabled="isParentRegionLocked"
|
||||
style="width: 100%"
|
||||
@change="handleRegionChange"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog title="港口码头主数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form
|
||||
:option="excelOption"
|
||||
v-model="excelForm"
|
||||
:upload-before="uploadBefore"
|
||||
:upload-after="uploadAfter"
|
||||
>
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getList,
|
||||
getDetail,
|
||||
submit,
|
||||
remove,
|
||||
changeStatus,
|
||||
getPortSelect,
|
||||
} from '@/api/base/port-terminal';
|
||||
import { getLazyTree } from '@/api/base/region';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const validateCode = (rule, value, callback) => {
|
||||
const code = String(value || '').toUpperCase();
|
||||
if (this.form.category === '港口') {
|
||||
if (!/^[A-Z]{5}$/.test(code)) {
|
||||
callback(new Error('港口编码为5位大写字母,如:CNSHG'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.form.category === '码头') {
|
||||
if (!/^[A-Z]{5}-[A-Z0-9]+$/.test(code)) {
|
||||
callback(new Error('码头编码格式为“港口编码-码头标识”,如:CNSHG-CT1'));
|
||||
} else if (this.form.parentCode && !code.startsWith(`${this.form.parentCode}-`)) {
|
||||
callback(new Error(`码头编码须以所选上级港口编码 ${this.form.parentCode}- 开头`));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
const validateLongitude = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < -180 || Number(value) > 180) {
|
||||
callback(new Error('经度范围为 -180 到 180'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateLatitude = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < -90 || Number(value) > 90) {
|
||||
callback(new Error('纬度范围为 -90 到 90'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
data: [],
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
portOptions: [],
|
||||
regionNodeMap: {},
|
||||
regionNameMap: {},
|
||||
regionProps: {
|
||||
lazy: true,
|
||||
lazyLoad: this.loadRegionNode,
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
checkStrictly: false,
|
||||
emitPath: true,
|
||||
},
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
indexLabel: '序号',
|
||||
indexWidth: 70,
|
||||
viewBtn: false,
|
||||
delBtn: false,
|
||||
editBtn: false,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 160,
|
||||
column: [
|
||||
{
|
||||
label: '编码',
|
||||
prop: 'code',
|
||||
search: true,
|
||||
width: 140,
|
||||
placeholder: '港口如 CNSHG,码头如 CNSHG-CT1',
|
||||
rules: [
|
||||
{ required: true, message: '请输入编码', trigger: 'blur' },
|
||||
{ validator: validateCode, trigger: 'blur' },
|
||||
],
|
||||
change: ({ value }) => this.handleCodeChange(value),
|
||||
blur: ({ value }) => this.handleCodeChange(value),
|
||||
},
|
||||
{
|
||||
label: '港口/码头名称',
|
||||
prop: 'name',
|
||||
minWidth: 140,
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请输入港口/码头名称', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '类型',
|
||||
prop: 'category',
|
||||
type: 'select',
|
||||
search: true,
|
||||
dicData: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '港口', value: '港口' },
|
||||
{ label: '码头', value: '码头' },
|
||||
],
|
||||
rules: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||
change: ({ value }) => this.handleCategoryChange(value),
|
||||
},
|
||||
{
|
||||
label: '上级港口',
|
||||
prop: 'parentId',
|
||||
type: 'select',
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
dicData: [],
|
||||
hide: true,
|
||||
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
|
||||
change: ({ value }) => this.handleParentChange(value),
|
||||
},
|
||||
{
|
||||
label: '上级港口',
|
||||
prop: 'parentName',
|
||||
minWidth: 120,
|
||||
slot: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '上级港口编码',
|
||||
prop: 'parentCode',
|
||||
minWidth: 120,
|
||||
slot: true,
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
label: '城市',
|
||||
prop: 'regionCode',
|
||||
formslot: true,
|
||||
hide: true,
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '国家',
|
||||
prop: 'country',
|
||||
search: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
label: '城市',
|
||||
prop: 'city',
|
||||
search: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
label: '经度',
|
||||
prop: 'longitude',
|
||||
type: 'number',
|
||||
precision: 6,
|
||||
minWidth: 130,
|
||||
rules: [{ validator: validateLongitude, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '纬度',
|
||||
prop: 'latitude',
|
||||
type: 'number',
|
||||
precision: 6,
|
||||
minWidth: 130,
|
||||
rules: [{ validator: validateLatitude, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '数据来源',
|
||||
prop: 'dataSource',
|
||||
type: 'select',
|
||||
search: true,
|
||||
dicData: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '初始化导入', value: '初始化导入' },
|
||||
{ label: '批量导入', value: '批量导入' },
|
||||
{ label: '手工导入', value: '手工导入' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '启停状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
search: true,
|
||||
slot: true,
|
||||
dataType: 'number',
|
||||
dicData: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '停用', value: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
hide: true,
|
||||
maxlength: 200,
|
||||
showWordLimit: true,
|
||||
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
],
|
||||
},
|
||||
excelOption: {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
accept: '.xls,.xlsx',
|
||||
action: '/blade-system/port-terminal/import-port-terminal',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('port_terminal_add'),
|
||||
};
|
||||
},
|
||||
isAdmin() {
|
||||
const authority = this.userInfo && this.userInfo.authority;
|
||||
return Array.isArray(authority) ? authority.includes('admin') : String(authority || '').includes('admin');
|
||||
},
|
||||
isParentRegionLocked() {
|
||||
return this.form.category === '码头' && !!this.form.parentId;
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadPortOptions();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||||
},
|
||||
handleCodeChange(value) {
|
||||
const code = String(value || '').toUpperCase();
|
||||
if (this.form.category === '港口') {
|
||||
this.form.code = code.replace(/[^A-Z]/g, '').slice(0, 5);
|
||||
return;
|
||||
}
|
||||
if (this.form.category === '码头') {
|
||||
this.form.code = code.replace(/[^A-Z0-9-]/g, '');
|
||||
}
|
||||
},
|
||||
loadRegionNode(node, resolve) {
|
||||
const parentCode = node.level === 0 ? '00' : node.value;
|
||||
getLazyTree(parentCode).then(res => {
|
||||
const regionList = res.data.data || [];
|
||||
const nodes = regionList.map(region => {
|
||||
this.regionNodeMap[region.id] = region;
|
||||
this.regionNameMap[region.title] = region.id;
|
||||
return {
|
||||
id: region.id,
|
||||
title: region.title,
|
||||
leaf: node.level >= 1,
|
||||
};
|
||||
});
|
||||
resolve(nodes);
|
||||
});
|
||||
},
|
||||
loadPortOptions() {
|
||||
getPortSelect().then(res => {
|
||||
this.portOptions = res.data.data;
|
||||
this.findColumn(this.option.column, 'parentId').dicData = this.portOptions;
|
||||
});
|
||||
},
|
||||
handleCategoryChange(value) {
|
||||
const parentColumn = this.findColumn(this.option.column, 'parentId');
|
||||
parentColumn.rules[0].required = value === '码头';
|
||||
if (value === '港口') {
|
||||
this.form.parentId = undefined;
|
||||
this.form.parentCode = '';
|
||||
this.form.parentName = '';
|
||||
}
|
||||
this.handleCodeChange(this.form.code);
|
||||
},
|
||||
handleParentChange(value) {
|
||||
const parent = this.portOptions.find(item => item.id === value);
|
||||
if (!parent) return;
|
||||
const terminalCode = String(this.form.code || '')
|
||||
.split('-')
|
||||
.slice(1)
|
||||
.join('-');
|
||||
this.form.parentCode = parent.code;
|
||||
this.form.parentName = parent.name;
|
||||
if (this.form.category === '码头' && terminalCode) {
|
||||
this.form.code = `${parent.code}-${terminalCode}`;
|
||||
}
|
||||
this.form.country = parent.country;
|
||||
this.form.city = parent.city;
|
||||
this.resolveRegionCode(parent.country, parent.city).then(regionCode => {
|
||||
this.form.regionCode = regionCode;
|
||||
});
|
||||
},
|
||||
handleRegionChange(value) {
|
||||
const regionCode = Array.isArray(value) ? value : [];
|
||||
const provinceCode = regionCode[0];
|
||||
const cityCode = regionCode[1];
|
||||
const province = this.regionNodeMap[provinceCode];
|
||||
const city = this.regionNodeMap[cityCode];
|
||||
this.form.country = province ? province.title : '';
|
||||
this.form.city = city ? city.title : '';
|
||||
},
|
||||
resolveRegionCode(country, city) {
|
||||
if (!country || !city) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
const provinceCode = this.regionNameMap[country];
|
||||
if (provinceCode) {
|
||||
const cityCode = this.regionNameMap[city];
|
||||
if (cityCode) {
|
||||
return Promise.resolve([provinceCode, cityCode]);
|
||||
}
|
||||
return this.loadRegionList(provinceCode).then(cityList => {
|
||||
const cityNode = cityList.find(item => item.title === city);
|
||||
return cityNode ? [provinceCode, cityNode.id] : [provinceCode];
|
||||
});
|
||||
}
|
||||
return this.loadRegionList('00').then(provinceList => {
|
||||
const province = provinceList.find(item => item.title === country);
|
||||
if (!province) {
|
||||
return [];
|
||||
}
|
||||
return this.loadRegionList(province.id).then(cityList => {
|
||||
const cityNode = cityList.find(item => item.title === city);
|
||||
return cityNode ? [province.id, cityNode.id] : [province.id];
|
||||
});
|
||||
});
|
||||
},
|
||||
loadRegionList(parentCode) {
|
||||
return getLazyTree(parentCode).then(res => {
|
||||
const regionList = res.data.data || [];
|
||||
regionList.forEach(region => {
|
||||
this.regionNodeMap[region.id] = region;
|
||||
this.regionNameMap[region.title] = region.id;
|
||||
});
|
||||
return regionList;
|
||||
});
|
||||
},
|
||||
normalizeRow(row) {
|
||||
row.code = String(row.code || '').toUpperCase();
|
||||
if (row.category === '港口') {
|
||||
row.parentId = undefined;
|
||||
row.parentCode = undefined;
|
||||
row.parentName = undefined;
|
||||
}
|
||||
if (!row.dataSource) row.dataSource = '手工导入';
|
||||
if (!row.status) row.status = 1;
|
||||
return row;
|
||||
},
|
||||
validateRequiredFields(row) {
|
||||
const isBlank = value => value === undefined || value === null || String(value).trim() === '';
|
||||
if (isBlank(row.code)) {
|
||||
this.$message.warning('请输入编码');
|
||||
return false;
|
||||
}
|
||||
if (isBlank(row.name)) {
|
||||
this.$message.warning('请输入港口/码头名称');
|
||||
return false;
|
||||
}
|
||||
if (isBlank(row.category)) {
|
||||
this.$message.warning('请选择类型');
|
||||
return false;
|
||||
}
|
||||
if (row.category === '码头' && isBlank(row.parentId)) {
|
||||
this.$message.warning('请选择上级港口');
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(row.regionCode) || row.regionCode.length < 2) {
|
||||
this.$message.warning('请选择所属城市');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
stopSubmitLoading(loading) {
|
||||
if (typeof loading === 'function') {
|
||||
loading();
|
||||
}
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const submitRow = this.normalizeRow(row);
|
||||
if (!this.validateRequiredFields(submitRow)) {
|
||||
this.stopSubmitLoading(loading);
|
||||
return;
|
||||
}
|
||||
submit(submitRow).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.loadPortOptions();
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const submitRow = this.normalizeRow(row);
|
||||
if (!this.validateRequiredFields(submitRow)) {
|
||||
this.stopSubmitLoading(loading);
|
||||
return;
|
||||
}
|
||||
submit(submitRow).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.loadPortOptions();
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.loadPortOptions();
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.loadPortOptions();
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const nextStatus = row.status === 1 ? 2 : 1;
|
||||
const actionName = nextStatus === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否确认${actionName}该条数据?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => changeStatus(row.id, nextStatus))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.loadPortOptions();
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form.category = this.form.category || '港口';
|
||||
this.form.dataSource = this.form.dataSource || '手工导入';
|
||||
this.form.status = this.form.status || 1;
|
||||
}
|
||||
this.handleCategoryChange(this.form.category || '港口');
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
this.resolveRegionCode(this.form.country, this.form.city).then(regionCode => {
|
||||
this.form.regionCode = regionCode;
|
||||
this.handleCategoryChange(this.form.category);
|
||||
done();
|
||||
});
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.resolveRegionCode(this.form.country, this.form.city).then(regionCode => {
|
||||
this.form.regionCode = regionCode;
|
||||
done();
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadBefore(file, done) {
|
||||
const fileName = file && file.name ? file.name.toLowerCase() : '';
|
||||
if (!/\.(xls|xlsx)$/.test(fileName)) {
|
||||
this.$message.error('请上传 .xls,.xlsx 标准格式文件');
|
||||
return false;
|
||||
}
|
||||
if (typeof done === 'function') {
|
||||
done();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
this.loadPortOptions();
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出港口码头主数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-system/port-terminal/export-port-terminal', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(
|
||||
res.data,
|
||||
`港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-system/port-terminal/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '港口码头主数据模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.port-terminal-page {
|
||||
:deep(.avue-crud__header),
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
663
src/views/base/railway-station.vue
Normal file
663
src/views/base/railway-station.vue
Normal file
@@ -0,0 +1,663 @@
|
||||
<template>
|
||||
<basic-container class="railway-station-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('railway_station_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('railway_station_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('railway_station_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('railway_station_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row, index }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="hasPermission('railway_station_edit')"
|
||||
@click="$refs.crud.rowEdit(row, index)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="hasPermission('railway_station_status')"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog title="铁路车站主数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form
|
||||
:option="excelOption"
|
||||
v-model="excelForm"
|
||||
:upload-before="uploadBefore"
|
||||
:upload-after="uploadAfter"
|
||||
>
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/railway-station';
|
||||
import { getLazyTree } from '@/api/base/region';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const validateTmisCode = (rule, value, callback) => {
|
||||
if (!/^\d{5}$/.test(value || '')) {
|
||||
callback(new Error('TMIS国标编码为5位数字'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateTelegraphCode = (rule, value, callback) => {
|
||||
if (!/^[A-Z]{3}$/.test(String(value || '').toUpperCase())) {
|
||||
callback(new Error('电报码为3位大写字母'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateLongitude = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < -180 || Number(value) > 180) {
|
||||
callback(new Error('经度范围为 -180 到 180'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateLatitude = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < -90 || Number(value) > 90) {
|
||||
callback(new Error('纬度范围为 -90 到 90'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
data: [],
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
provinceOptions: [],
|
||||
cityOptions: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
indexLabel: '序号',
|
||||
indexWidth: 70,
|
||||
viewBtn: false,
|
||||
delBtn: false,
|
||||
editBtn: false,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 160,
|
||||
column: [
|
||||
{
|
||||
label: '编码',
|
||||
prop: 'code',
|
||||
search: true,
|
||||
addDisabled: true,
|
||||
editDisabled: true,
|
||||
placeholder: '系统自动生成',
|
||||
searchPlaceholder: '请输入编码',
|
||||
},
|
||||
{
|
||||
label: 'TMIS国标编码',
|
||||
prop: 'tmisCode',
|
||||
minWidth: 120,
|
||||
maxlength: 5,
|
||||
rules: [
|
||||
{ required: true, message: '请输入TMIS国标编码', trigger: 'blur' },
|
||||
{ validator: validateTmisCode, trigger: 'blur' },
|
||||
],
|
||||
change: ({ value }) => this.handleTmisChange(value),
|
||||
},
|
||||
{
|
||||
label: '电报码',
|
||||
prop: 'telegraphCode',
|
||||
minWidth: 100,
|
||||
maxlength: 3,
|
||||
rules: [
|
||||
{ required: true, message: '请输入电报码', trigger: 'blur' },
|
||||
{ validator: validateTelegraphCode, trigger: 'blur' },
|
||||
],
|
||||
change: ({ value }) => this.handleTelegraphChange(value),
|
||||
blur: ({ value }) => this.handleTelegraphChange(value),
|
||||
},
|
||||
{
|
||||
label: '车站名称',
|
||||
prop: 'name',
|
||||
minWidth: 120,
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请输入车站名称', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '所属铁路线路',
|
||||
prop: 'railwayLine',
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '所属省份',
|
||||
prop: 'provinceCode',
|
||||
type: 'select',
|
||||
hide: true,
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
dicData: [],
|
||||
filterable: true,
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '请选择所属省份', trigger: 'change' }],
|
||||
change: ({ value }) => this.handleProvinceChange(value),
|
||||
},
|
||||
{
|
||||
label: '所属城市',
|
||||
prop: 'cityCode',
|
||||
type: 'select',
|
||||
hide: true,
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
dicData: [],
|
||||
filterable: true,
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }],
|
||||
change: ({ value }) => this.handleCityChange(value),
|
||||
},
|
||||
{
|
||||
label: '所属省份',
|
||||
prop: 'provinceName',
|
||||
minWidth: 140,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '所属城市',
|
||||
prop: 'cityName',
|
||||
minWidth: 140,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '经度',
|
||||
prop: 'longitude',
|
||||
type: 'number',
|
||||
precision: 6,
|
||||
rules: [{ validator: validateLongitude, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '纬度',
|
||||
prop: 'latitude',
|
||||
type: 'number',
|
||||
precision: 6,
|
||||
rules: [{ validator: validateLatitude, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '数据来源',
|
||||
prop: 'dataSource',
|
||||
type: 'select',
|
||||
search: true,
|
||||
dicData: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '初始化导入', value: '初始化导入' },
|
||||
{ label: '批量', value: '批量' },
|
||||
{ label: '手动', value: '手动' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '启停状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
search: true,
|
||||
slot: true,
|
||||
dataType: 'number',
|
||||
dicData: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '停用', value: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
hide: true,
|
||||
maxlength: 200,
|
||||
showWordLimit: true,
|
||||
rules: [{ max: 200, message: '备注不能超过200字', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 160,
|
||||
},
|
||||
],
|
||||
},
|
||||
excelOption: {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
accept: '.xls,.xlsx',
|
||||
action: '/blade-system/railway-station/import-railway-station',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('railway_station_add'),
|
||||
};
|
||||
},
|
||||
isAdmin() {
|
||||
const authority = this.userInfo && this.userInfo.authority;
|
||||
return Array.isArray(authority) ? authority.includes('admin') : String(authority || '').includes('admin');
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadProvinceOptions();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||||
},
|
||||
findColumn(prop) {
|
||||
return this.option.column.find(item => item.prop === prop);
|
||||
},
|
||||
loadProvinceOptions() {
|
||||
getLazyTree('00').then(res => {
|
||||
this.provinceOptions = res.data.data;
|
||||
this.findColumn('provinceCode').dicData = this.provinceOptions;
|
||||
});
|
||||
},
|
||||
loadCityOptions(provinceCode) {
|
||||
if (!provinceCode) {
|
||||
this.cityOptions = [];
|
||||
this.findColumn('cityCode').dicData = [];
|
||||
return Promise.resolve();
|
||||
}
|
||||
return getLazyTree(provinceCode).then(res => {
|
||||
this.cityOptions = res.data.data;
|
||||
this.findColumn('cityCode').dicData = this.cityOptions;
|
||||
});
|
||||
},
|
||||
handleTmisChange(value) {
|
||||
this.form.tmisCode = String(value || '')
|
||||
.replace(/\D/g, '')
|
||||
.slice(0, 5);
|
||||
this.form.code = this.form.tmisCode ? `TL${this.form.tmisCode}` : '';
|
||||
},
|
||||
handleTelegraphChange(value) {
|
||||
this.form.telegraphCode = String(value || '')
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z]/g, '')
|
||||
.slice(0, 3);
|
||||
},
|
||||
handleProvinceChange(value) {
|
||||
const province = this.provinceOptions.find(item => item.id === value);
|
||||
this.form.provinceName = province ? province.title : '';
|
||||
this.form.cityCode = undefined;
|
||||
this.form.cityName = '';
|
||||
this.loadCityOptions(value);
|
||||
},
|
||||
handleCityChange(value) {
|
||||
const city = this.cityOptions.find(item => item.id === value);
|
||||
this.form.cityName = city ? city.title : '';
|
||||
},
|
||||
normalizeRow(row) {
|
||||
row.code = row.tmisCode ? `TL${row.tmisCode}` : row.code;
|
||||
if (!row.dataSource) row.dataSource = '手动';
|
||||
if (!row.status) row.status = 1;
|
||||
return row;
|
||||
},
|
||||
validateUnique(row) {
|
||||
const rowId = String(row.id || '');
|
||||
const hasDuplicate = (params, prop) => {
|
||||
return getList(1, 10, params).then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
return records.some(item => item[prop] === row[prop] && String(item.id || '') !== rowId);
|
||||
});
|
||||
};
|
||||
return Promise.all([
|
||||
hasDuplicate({ tmisCode: row.tmisCode }, 'tmisCode'),
|
||||
hasDuplicate({ telegraphCode: row.telegraphCode }, 'telegraphCode'),
|
||||
]).then(([tmisExists, telegraphExists]) => {
|
||||
if (tmisExists) {
|
||||
return Promise.reject(new Error('该TMIS编码已存在'));
|
||||
}
|
||||
if (telegraphExists) {
|
||||
return Promise.reject(new Error('该电报码已存在'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
},
|
||||
handleSubmitError(error, loading) {
|
||||
const uniqueMessages = ['该TMIS编码已存在', '该电报码已存在'];
|
||||
if (uniqueMessages.includes(error.message)) {
|
||||
this.$message.warning(error.message);
|
||||
}
|
||||
window.console.log(error);
|
||||
loading();
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const submitRow = this.normalizeRow(row);
|
||||
this.validateUnique(submitRow)
|
||||
.then(() => submit(submitRow))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
})
|
||||
.catch(error => this.handleSubmitError(error, loading));
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const submitRow = this.normalizeRow(row);
|
||||
this.validateUnique(submitRow)
|
||||
.then(() => submit(submitRow))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
})
|
||||
.catch(error => this.handleSubmitError(error, loading));
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const nextStatus = row.status === 1 ? 2 : 1;
|
||||
const actionName = nextStatus === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否确认${actionName}该条数据?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => changeStatus(row.id, nextStatus))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
this.loadCityOptions(this.form.provinceCode).then(() => done());
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.form.code = '';
|
||||
this.form.dataSource = '手动';
|
||||
this.form.status = 1;
|
||||
this.loadCityOptions(this.form.provinceCode).then(() => done());
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadBefore(file, done) {
|
||||
const fileName = file && file.name ? file.name.toLowerCase() : '';
|
||||
if (!/\.(xls|xlsx)$/.test(fileName)) {
|
||||
this.$message.error('请上传 .xls,.xlsx 标准格式文件');
|
||||
return false;
|
||||
}
|
||||
if (typeof done === 'function') {
|
||||
done();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出铁路车站主数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-system/railway-station/export-railway-station', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(
|
||||
res.data,
|
||||
`铁路车站主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-system/railway-station/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '铁路车站主数据模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.railway-station-page {
|
||||
:deep(.avue-crud__header),
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -40,6 +40,14 @@
|
||||
@click="handleExport"
|
||||
>导出
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="permission.region_sync"
|
||||
type="primary"
|
||||
icon="el-icon-refresh"
|
||||
:loading="syncLoading"
|
||||
@click="handleSync"
|
||||
>同步
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="permission.region_debug"
|
||||
type="primary"
|
||||
@@ -75,7 +83,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getLazyTree, getDetail, submit, remove } from '@/api/base/region';
|
||||
import { getLazyTree, getDetail, submit, remove, sync } from '@/api/base/region';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { validatenull } from '@/utils/validate';
|
||||
@@ -91,6 +99,7 @@ export default {
|
||||
treeCode: '',
|
||||
treeParentCode: '',
|
||||
treeData: [],
|
||||
syncLoading: false,
|
||||
treeOption: {
|
||||
nodeKey: 'id',
|
||||
lazy: true,
|
||||
@@ -431,6 +440,28 @@ export default {
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
handleSync() {
|
||||
this.$confirm('确定同步最新行政区划数据?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
this.syncLoading = true;
|
||||
return sync();
|
||||
})
|
||||
.then(() => {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '同步成功!',
|
||||
});
|
||||
this.initTree();
|
||||
this.regionForm = {};
|
||||
})
|
||||
.finally(() => {
|
||||
this.syncLoading = false;
|
||||
});
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出行政区划数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
|
||||
@@ -216,10 +216,34 @@ export default {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '是否显示',
|
||||
prop: 'isDisplay',
|
||||
type: 'radio',
|
||||
dicData: [
|
||||
{
|
||||
label: '显示',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '不显示',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
value: 1,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择是否显示',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '菜单排序',
|
||||
prop: 'sort',
|
||||
type: 'number',
|
||||
value: 1,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
@@ -248,9 +272,15 @@ export default {
|
||||
if (item.prop === 'path') {
|
||||
item.rules[0].required = category === 1;
|
||||
}
|
||||
if (item.prop === 'source') {
|
||||
item.rules[0].required = category === 1;
|
||||
}
|
||||
if (item.prop === 'isOpen') {
|
||||
item.disabled = category === 2;
|
||||
}
|
||||
if (item.prop === 'isDisplay') {
|
||||
item.disabled = category === 2;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -389,6 +419,10 @@ export default {
|
||||
if (['add', 'edit'].includes(type)) {
|
||||
this.initData();
|
||||
}
|
||||
if (type === 'add') {
|
||||
this.form.sort = 1;
|
||||
this.form.isDisplay = 1;
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getMenu(this.form.id).then(res => {
|
||||
this.form = Object.assign(res.data.data, {
|
||||
|
||||
@@ -172,6 +172,10 @@
|
||||
<el-icon><refresh /></el-icon>
|
||||
密码重置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="setPassword" v-if="permission.user_reset">
|
||||
<el-icon><key /></el-icon>
|
||||
修改密码
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" v-if="this.permission.user_delete">
|
||||
<el-icon><delete /></el-icon>
|
||||
删除用户
|
||||
@@ -265,6 +269,32 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 管理员修改密码 -->
|
||||
<el-dialog title="修改密码" append-to-body v-model="passwordBox" width="460px">
|
||||
<el-form
|
||||
ref="passwordFormRef"
|
||||
:model="passwordForm"
|
||||
:rules="passwordRules"
|
||||
label-width="90px"
|
||||
>
|
||||
<el-form-item label="登录账号">
|
||||
<el-input v-model="passwordForm.account" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="password">
|
||||
<el-input v-model="passwordForm.password" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="password2">
|
||||
<el-input v-model="passwordForm.password2" type="password" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="passwordBox = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitPassword">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 认证日志组件 -->
|
||||
<auth-log ref="authLog" />
|
||||
<!-- 认证锁定配置组件 -->
|
||||
@@ -285,6 +315,7 @@ import {
|
||||
add,
|
||||
grant,
|
||||
resetPassword,
|
||||
setPassword,
|
||||
auditPass,
|
||||
auditRefuse,
|
||||
setLeader,
|
||||
@@ -295,6 +326,7 @@ import { getDeptTree } from '@/api/system/dept';
|
||||
import { getRoleTree } from '@/api/system/role';
|
||||
import { getPostList } from '@/api/system/post';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { h } from 'vue';
|
||||
import website from '@/config/website';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
@@ -318,6 +350,7 @@ export default {
|
||||
grantUserId: '',
|
||||
excelBox: false,
|
||||
platformBox: false,
|
||||
passwordBox: false,
|
||||
initFlag: true,
|
||||
auditMode: false,
|
||||
selectionList: [],
|
||||
@@ -341,6 +374,17 @@ export default {
|
||||
platformFormOption: platformFormOption,
|
||||
data: [],
|
||||
platformForm: {},
|
||||
passwordForm: {},
|
||||
passwordRules: {
|
||||
password: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, max: 32, message: '密码长度在6到32个字符', trigger: 'blur' },
|
||||
],
|
||||
password2: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{ validator: this.validatePassword2, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
excelForm: {},
|
||||
excelOption: excelOption,
|
||||
};
|
||||
@@ -409,12 +453,14 @@ export default {
|
||||
this.handlePlatformForRow(row);
|
||||
} else if (command === 'resetPassword') {
|
||||
this.handleResetForRow(row);
|
||||
} else if (command === 'setPassword') {
|
||||
this.handlePasswordForRow(row);
|
||||
} else if (command === 'delete') {
|
||||
this.$refs.crud.rowDel(row, index);
|
||||
}
|
||||
},
|
||||
handleResetForRow(row) {
|
||||
this.$confirm(`确定将账号【${row.account}】的密码重置为初始密码?`, '提示', {
|
||||
this.$confirm(`确定将账号【${row.account}】的密码重置为随机密码?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
@@ -422,11 +468,8 @@ export default {
|
||||
.then(() => {
|
||||
return resetPassword(row.id);
|
||||
})
|
||||
.then(() => {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '密码重置成功!',
|
||||
});
|
||||
.then(res => {
|
||||
this.showResetPassword(res.data.data);
|
||||
});
|
||||
},
|
||||
handleDataManageCommand(command) {
|
||||
@@ -642,7 +685,7 @@ export default {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择账号密码重置为初始密码?', {
|
||||
this.$confirm('确定将选择账号密码重置为随机密码?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
@@ -650,14 +693,20 @@ export default {
|
||||
.then(() => {
|
||||
return resetPassword(this.ids);
|
||||
})
|
||||
.then(() => {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
});
|
||||
.then(res => {
|
||||
this.showResetPassword(res.data.data);
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
showResetPassword(passwordMap) {
|
||||
const passwordList = Object.keys(passwordMap || {}).map(account =>
|
||||
h('div', null, `${account}:${passwordMap[account]}`)
|
||||
);
|
||||
this.$alert(h('div', null, passwordList), '密码重置成功,请立即保存', {
|
||||
confirmButtonText: '我已保存',
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
handleGrant() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
@@ -759,6 +808,40 @@ export default {
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handlePasswordForRow(row) {
|
||||
this.passwordForm = {
|
||||
userId: row.id,
|
||||
account: row.account,
|
||||
password: '',
|
||||
password2: '',
|
||||
};
|
||||
this.passwordBox = true;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.passwordFormRef && this.$refs.passwordFormRef.clearValidate();
|
||||
});
|
||||
},
|
||||
validatePassword2(rule, value, callback) {
|
||||
if (value !== this.passwordForm.password) {
|
||||
callback(new Error('两次输入密码不一致!'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
submitPassword() {
|
||||
this.$refs.passwordFormRef.validate(valid => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
setPassword(
|
||||
this.passwordForm.userId,
|
||||
this.passwordForm.password,
|
||||
this.passwordForm.password2
|
||||
).then(() => {
|
||||
this.passwordBox = false;
|
||||
this.$message({ type: 'success', message: '密码修改成功!' });
|
||||
});
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
|
||||
875
src/views/transportCapacity/driver.vue
Normal file
875
src/views/transportCapacity/driver.vue
Normal file
@@ -0,0 +1,875 @@
|
||||
<template>
|
||||
<basic-container class="driver-page">
|
||||
<div class="driver-toolbar">
|
||||
<div class="driver-toolbar__actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
plain
|
||||
v-if="hasPermission('driver_add')"
|
||||
@click="openDriver()"
|
||||
>新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('driver_export')"
|
||||
@click="handleExport"
|
||||
>导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('driver_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
<el-radio-group v-model="query.expireStatus" class="driver-stat" @change="handleExpireChange">
|
||||
<el-radio-button label="">全部({{ expiryStat.total }})</el-radio-button>
|
||||
<el-radio-button label="within30">30天内到期({{ expiryStat.within30 }})</el-radio-button>
|
||||
<el-radio-button label="expired">已到期({{ expiryStat.expired }})</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #driverName="{ row }">
|
||||
<el-button type="primary" link @click="openDriver(row, true)">
|
||||
{{ row.driverName }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #drivingLicenseEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'drivingLicenseEndDate', 'drivingLicenseLongTerm')">
|
||||
{{ formatEndDate(row.drivingLicenseEndDate, row.drivingLicenseLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #qualificationEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'qualificationEndDate', 'qualificationLongTerm')">
|
||||
{{ formatEndDate(row.qualificationEndDate, row.qualificationLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-view"
|
||||
v-if="hasPermission('driver_view')"
|
||||
@click="openDriver(row, true)"
|
||||
>
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="hasPermission('driver_edit')"
|
||||
@click="openDriver(row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="hasPermission('driver_status')"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
append-to-body
|
||||
v-model="driverBox"
|
||||
width="92%"
|
||||
top="4vh"
|
||||
class="driver-dialog"
|
||||
@closed="resetDriver"
|
||||
>
|
||||
<el-form
|
||||
ref="driverForm"
|
||||
:model="driverForm"
|
||||
:rules="formRules"
|
||||
label-position="top"
|
||||
:disabled="readonly"
|
||||
class="driver-form"
|
||||
>
|
||||
<div class="section-title">基础身份信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="司机姓名" prop="driverName">
|
||||
<el-input v-model="driverForm.driverName" maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="身份证号" prop="idCardNo">
|
||||
<el-input
|
||||
v-model="driverForm.idCardNo"
|
||||
maxlength="18"
|
||||
show-word-limit
|
||||
@input="driverForm.idCardNo = String(driverForm.idCardNo || '').toUpperCase()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="出生年月" prop="birthday">
|
||||
<el-date-picker v-model="driverForm.birthday" type="date" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-select v-model="driverForm.gender" clearable>
|
||||
<el-option label="男" value="男" />
|
||||
<el-option label="女" value="女" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="民族" prop="nation">
|
||||
<el-input v-model="driverForm.nation" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="学历" prop="education">
|
||||
<el-select v-model="driverForm.education" clearable filterable>
|
||||
<el-option v-for="item in educationOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="住址" prop="addressRegion">
|
||||
<el-input v-model="driverForm.addressRegion" maxlength="100" placeholder="省 / 市 / 区" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="详细地址" prop="address">
|
||||
<el-input v-model="driverForm.address" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="driverForm.status">
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="停用" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="岗位" prop="postList">
|
||||
<el-checkbox-group v-model="driverForm.postList">
|
||||
<el-checkbox label="司机" />
|
||||
<el-checkbox label="押运员" />
|
||||
<el-checkbox label="装卸管理员" />
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<image-upload-field
|
||||
label="身份证正面照"
|
||||
prop="idCardFront"
|
||||
:value="driverForm.idCardFront"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
@success="url => setImage('idCardFront', url)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<image-upload-field
|
||||
label="身份证反面照"
|
||||
prop="idCardBack"
|
||||
:value="driverForm.idCardBack"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
@success="url => setImage('idCardBack', url)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<image-upload-field
|
||||
label="大头照"
|
||||
prop="headPhoto"
|
||||
:value="driverForm.headPhoto"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
@success="url => setImage('headPhoto', url)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">驾驶证信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="准驾车型" prop="drivingType">
|
||||
<el-select v-model="driverForm.drivingType" clearable filterable allow-create default-first-option>
|
||||
<el-option v-for="item in drivingTypeOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="档案编号" prop="drivingLicenseNo">
|
||||
<el-input v-model="driverForm.drivingLicenseNo" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="有效期" prop="drivingLicenseEndDate">
|
||||
<div class="date-range">
|
||||
<el-date-picker
|
||||
v-model="driverForm.drivingLicenseStartDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="起"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-model="driverForm.drivingLicenseEndDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="止"
|
||||
:disabled="driverForm.drivingLicenseLongTerm === 1"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="长期有效" prop="drivingLicenseLongTerm">
|
||||
<el-checkbox v-model="driverForm.drivingLicenseLongTerm" :true-label="1" :false-label="0">
|
||||
是
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field
|
||||
label="驾驶证主页"
|
||||
prop="drivingLicenseFront"
|
||||
:value="driverForm.drivingLicenseFront"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
large
|
||||
@success="url => setImage('drivingLicenseFront', url)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field
|
||||
label="驾驶证副页"
|
||||
prop="drivingLicenseBack"
|
||||
:value="driverForm.drivingLicenseBack"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
large
|
||||
@success="url => setImage('drivingLicenseBack', url)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">从业资格证信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="从业资格证类型" prop="qualificationType">
|
||||
<el-select v-model="driverForm.qualificationType" clearable filterable allow-create default-first-option>
|
||||
<el-option label="道路运输从业资格证" value="道路运输从业资格证" />
|
||||
<el-option label="危险货物运输从业资格证" value="危险货物运输从业资格证" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="资格证号" prop="qualificationNo">
|
||||
<el-input v-model="driverForm.qualificationNo" maxlength="50" placeholder="默认身份证号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="有效期" prop="qualificationEndDate">
|
||||
<el-date-picker
|
||||
v-model="driverForm.qualificationEndDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled="driverForm.qualificationLongTerm === 1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="长期有效" prop="qualificationLongTerm">
|
||||
<el-checkbox v-model="driverForm.qualificationLongTerm" :true-label="1" :false-label="0">
|
||||
是
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field
|
||||
label="封面页"
|
||||
prop="qualificationFront"
|
||||
:value="driverForm.qualificationFront"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
large
|
||||
@success="url => setImage('qualificationFront', url)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field
|
||||
label="内容页"
|
||||
prop="qualificationBack"
|
||||
:value="driverForm.qualificationBack"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
large
|
||||
@success="url => setImage('qualificationBack', url)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">司机联系信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="司机类型" prop="driverType">
|
||||
<el-select v-model="driverForm.driverType">
|
||||
<el-option label="自有" value="自有" />
|
||||
<el-option label="外协" value="外协" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="driverForm.mobile" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="紧急联系人姓名" prop="emergencyContactName">
|
||||
<el-input v-model="driverForm.emergencyContactName" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="紧急联系人手机号" prop="emergencyContactMobile">
|
||||
<el-input v-model="driverForm.emergencyContactMobile" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="与联系人关系" prop="contactRelation">
|
||||
<el-select v-model="driverForm.contactRelation" clearable filterable allow-create default-first-option>
|
||||
<el-option v-for="item in relationOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="所属组织" prop="organizationName">
|
||||
<el-input v-model="driverForm.organizationName" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="driverForm.remark" type="textarea" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="driverBox = false">取消</el-button>
|
||||
<el-button type="primary" v-if="!readonly" :loading="submitLoading" @click="handleSubmit">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import NProgress from 'nprogress';
|
||||
import { option } from '@/option/transportCapacity/driver';
|
||||
import { getList, getDetail, submit, remove, changeStatus, getExpiryStat } from '@/api/transportCapacity/driver';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: '',
|
||||
driverName: '',
|
||||
idCardNo: '',
|
||||
birthday: '',
|
||||
gender: '',
|
||||
nation: '',
|
||||
education: '',
|
||||
addressRegion: '',
|
||||
address: '',
|
||||
postList: ['司机'],
|
||||
posts: '',
|
||||
idCardFront: '',
|
||||
idCardBack: '',
|
||||
headPhoto: '',
|
||||
drivingType: '',
|
||||
drivingLicenseNo: '',
|
||||
drivingLicenseStartDate: '',
|
||||
drivingLicenseEndDate: '',
|
||||
drivingLicenseLongTerm: 0,
|
||||
drivingLicenseFront: '',
|
||||
drivingLicenseBack: '',
|
||||
qualificationType: '',
|
||||
qualificationNo: '',
|
||||
qualificationEndDate: '',
|
||||
qualificationLongTerm: 0,
|
||||
qualificationFront: '',
|
||||
qualificationBack: '',
|
||||
driverType: '自有',
|
||||
mobile: '',
|
||||
contactRelation: '',
|
||||
organizationName: '',
|
||||
emergencyContactName: '',
|
||||
emergencyContactMobile: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
});
|
||||
|
||||
const imageUploadField = {
|
||||
name: 'ImageUploadField',
|
||||
props: {
|
||||
label: String,
|
||||
prop: String,
|
||||
value: String,
|
||||
readonly: Boolean,
|
||||
headers: Object,
|
||||
large: Boolean,
|
||||
},
|
||||
emits: ['success'],
|
||||
methods: {
|
||||
handleSuccess(res) {
|
||||
if (res.code === 200 && res.data) {
|
||||
this.$emit('success', res.data.link || res.data.url || res.data.domain || '');
|
||||
} else {
|
||||
this.$message.error(res.msg || '上传失败');
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const validType = ['image/jpeg', 'image/png', 'image/jpg', 'image/bmp'].includes(file.type);
|
||||
const validSize = file.size / 1024 / 1024 < 5;
|
||||
if (!validType) {
|
||||
this.$message.error('仅支持 JPG、PNG、BMP 图片');
|
||||
}
|
||||
if (!validSize) {
|
||||
this.$message.error('图片大小不能超过 5MB');
|
||||
}
|
||||
return validType && validSize;
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<el-form-item :label="label" :prop="prop">
|
||||
<el-upload
|
||||
class="driver-uploader"
|
||||
:class="{ 'driver-uploader--large': large }"
|
||||
action="/api/blade-resource/oss/endpoint/put-file"
|
||||
name="file"
|
||||
:headers="headers"
|
||||
:show-file-list="false"
|
||||
:disabled="readonly"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="handleSuccess"
|
||||
>
|
||||
<img v-if="value" :src="value" class="driver-uploader__image" />
|
||||
<div v-else class="driver-uploader__empty">上传证件图片</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
`,
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ImageUploadField: imageUploadField,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
option,
|
||||
form: {},
|
||||
query: {
|
||||
expireStatus: '',
|
||||
},
|
||||
searchForm: {},
|
||||
loading: true,
|
||||
submitLoading: false,
|
||||
data: [],
|
||||
ids: '',
|
||||
selectionList: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
expiryStat: {
|
||||
total: 0,
|
||||
within30: 0,
|
||||
expired: 0,
|
||||
},
|
||||
driverBox: false,
|
||||
readonly: false,
|
||||
driverForm: emptyForm(),
|
||||
uploadHeaders: {
|
||||
'Blade-Auth': `bearer ${getToken()}`,
|
||||
'Blade-Requested-With': 'BladeHttpRequest',
|
||||
},
|
||||
educationOptions: ['初中', '高中', '中专', '大专', '本科', '硕士及以上'],
|
||||
drivingTypeOptions: ['A1', 'A2', 'A3', 'B1', 'B2', 'C1', 'C2'],
|
||||
relationOptions: ['父母', '配偶', '子女', '兄弟姐妹', '朋友', '同事'],
|
||||
formRules: {
|
||||
driverName: [{ required: true, message: '请输入司机姓名', trigger: 'blur' }],
|
||||
idCardNo: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
|
||||
drivingType: [{ required: true, message: '请选择准驾车型', trigger: 'change' }],
|
||||
drivingLicenseEndDate: [{ validator: this.validateDrivingLicenseEndDate, trigger: 'change' }],
|
||||
driverType: [{ required: true, message: '请选择司机类型', trigger: 'change' }],
|
||||
mobile: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||||
emergencyContactName: [{ required: true, message: '请输入紧急联系人姓名', trigger: 'blur' }],
|
||||
emergencyContactMobile: [{ required: true, message: '请输入紧急联系人手机号', trigger: 'blur' }],
|
||||
organizationName: [{ required: true, message: '请输入所属组织', trigger: 'blur' }],
|
||||
idCardFront: [{ required: true, message: '请上传身份证正面照', trigger: 'change' }],
|
||||
idCardBack: [{ required: true, message: '请上传身份证反面照', trigger: 'change' }],
|
||||
drivingLicenseFront: [{ required: true, message: '请上传驾驶证主页', trigger: 'change' }],
|
||||
drivingLicenseBack: [{ required: true, message: '请上传驾驶证副页', trigger: 'change' }],
|
||||
remark: [{ max: 200, message: '备注最多200字', trigger: 'blur' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('driver_add'),
|
||||
viewBtn: this.hasPermission('driver_view'),
|
||||
delBtn: this.hasPermission('driver_delete'),
|
||||
editBtn: this.hasPermission('driver_edit'),
|
||||
};
|
||||
},
|
||||
dialogTitle() {
|
||||
if (this.readonly) {
|
||||
return '查看司机';
|
||||
}
|
||||
return this.driverForm.id ? '修改司机' : '新增司机';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.permission?.[code] === true;
|
||||
},
|
||||
validateDrivingLicenseEndDate(rule, value, callback) {
|
||||
if (this.driverForm.drivingLicenseLongTerm !== 1 && !value) {
|
||||
callback(new Error('请选择驾驶证有效期止'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
openDriver(row, readonly = false) {
|
||||
this.readonly = readonly;
|
||||
if (!row?.id) {
|
||||
this.driverForm = emptyForm();
|
||||
this.driverBox = true;
|
||||
return;
|
||||
}
|
||||
getDetail(row.id).then(res => {
|
||||
const detail = res.data.data || {};
|
||||
this.driverForm = {
|
||||
...emptyForm(),
|
||||
...detail,
|
||||
postList: detail.posts ? detail.posts.split(',').filter(Boolean) : [],
|
||||
};
|
||||
this.driverBox = true;
|
||||
});
|
||||
},
|
||||
resetDriver() {
|
||||
this.driverForm = emptyForm();
|
||||
this.readonly = false;
|
||||
this.submitLoading = false;
|
||||
this.$refs.driverForm?.clearValidate();
|
||||
},
|
||||
setImage(prop, url) {
|
||||
this.driverForm[prop] = url;
|
||||
this.$refs.driverForm?.validateField(prop);
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.driverForm.validate(valid => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
const row = {
|
||||
...this.driverForm,
|
||||
qualificationNo: this.driverForm.qualificationNo || this.driverForm.idCardNo,
|
||||
posts: this.driverForm.postList.join(','),
|
||||
};
|
||||
this.submitLoading = true;
|
||||
submit(row)
|
||||
.then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.driverBox = false;
|
||||
this.onLoad(this.page);
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const status = row.status === 1 ? 2 : 1;
|
||||
const text = status === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否${text}该司机?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
changeStatus(row.id, status).then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.onLoad(this.page);
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('是否删除选中的司机数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
remove(this.ids).then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.onLoad(this.page);
|
||||
});
|
||||
});
|
||||
},
|
||||
handleExpireChange() {
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
refreshStat() {
|
||||
getExpiryStat(this.buildQuery(false)).then(res => {
|
||||
this.expiryStat = {
|
||||
total: res.data.data?.total || 0,
|
||||
within30: res.data.data?.within30 || 0,
|
||||
expired: res.data.data?.expired || 0,
|
||||
};
|
||||
});
|
||||
},
|
||||
searchReset() {
|
||||
this.searchForm = {};
|
||||
this.query = {
|
||||
expireStatus: this.query.expireStatus,
|
||||
};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.searchForm = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
this.ids = list.map(item => item.id).join(',');
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.ids = '';
|
||||
this.$refs.crud?.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.searchForm);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, this.buildQuery(true, params))
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
this.refreshStat();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
buildQuery(includeExpire = true, params = {}) {
|
||||
const query = {
|
||||
...params,
|
||||
...this.searchForm,
|
||||
};
|
||||
if (includeExpire && this.query.expireStatus) {
|
||||
query.expireStatus = this.query.expireStatus;
|
||||
}
|
||||
return query;
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出司机数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/driver/export-driver', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `司机管理${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.buildQuery(true),
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
formatEndDate(date, longTerm) {
|
||||
if (longTerm === 1) {
|
||||
return '长期有效';
|
||||
}
|
||||
return date || '-';
|
||||
},
|
||||
getDateClass(row, dateProp, longTermProp) {
|
||||
if (row[longTermProp] === 1 || !row[dateProp]) {
|
||||
return '';
|
||||
}
|
||||
const today = this.$dayjs();
|
||||
const endDate = this.$dayjs(row[dateProp]);
|
||||
if (endDate.isBefore(today, 'day')) {
|
||||
return 'date-danger';
|
||||
}
|
||||
if (endDate.diff(today, 'day') <= 30) {
|
||||
return 'date-warning';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.driver-page {
|
||||
.driver-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.driver-toolbar__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.driver-stat {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.driver-form {
|
||||
.section-title {
|
||||
margin: 18px 0 14px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid #e74b5f;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.section-title:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:deep(.el-date-editor.el-input),
|
||||
:deep(.el-date-editor.el-input__wrapper),
|
||||
:deep(.el-select),
|
||||
:deep(.el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date-range {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.driver-uploader .el-upload) {
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
border: 1px dashed #c0c4cc;
|
||||
background: #fafafa;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.driver-uploader--large .el-upload) {
|
||||
height: 260px;
|
||||
}
|
||||
|
||||
:deep(.driver-uploader__image) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:deep(.driver-uploader__empty) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.date-warning {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.date-danger {
|
||||
color: #d40000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.driver-page {
|
||||
.driver-toolbar {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
603
src/views/transportCapacity/ship.vue
Normal file
603
src/views/transportCapacity/ship.vue
Normal file
@@ -0,0 +1,603 @@
|
||||
<template>
|
||||
<basic-container class="transport-ship-page">
|
||||
<div class="ship-toolbar">
|
||||
<div class="ship-toolbar__actions">
|
||||
<el-button type="primary" icon="el-icon-plus" plain v-if="hasPermission('transport_ship_add')" @click="openShip()">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button type="primary" icon="el-icon-download" plain v-if="hasPermission('transport_ship_export')" @click="handleExport">
|
||||
导出
|
||||
</el-button>
|
||||
<el-button type="danger" icon="el-icon-delete" plain v-if="hasPermission('transport_ship_delete')" @click="handleDelete">
|
||||
批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
<el-radio-group v-model="query.expireStatus" class="ship-stat" @change="handleExpireChange">
|
||||
<el-radio-button label="">全部({{ expiryStat.total }})</el-radio-button>
|
||||
<el-radio-button label="within30">30天内到期({{ expiryStat.within30 }})</el-radio-button>
|
||||
<el-radio-button label="expired">已到期({{ expiryStat.expired }})</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #shipName="{ row }">
|
||||
<el-button type="primary" link @click="openShip(row, true)">
|
||||
{{ row.shipName }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #nationalityCertEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'nationalityCertEndDate', 'nationalityCertLongTerm')">
|
||||
{{ formatEndDate(row.nationalityCertEndDate, row.nationalityCertLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #safeManningCertEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'safeManningCertEndDate', 'safeManningCertLongTerm')">
|
||||
{{ formatEndDate(row.safeManningCertEndDate, row.safeManningCertLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #businessTransportCertEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'businessTransportCertEndDate', 'businessTransportCertLongTerm')">
|
||||
{{ formatEndDate(row.businessTransportCertEndDate, row.businessTransportCertLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #leaseEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'leaseEndDate', 'leaseLongTerm')">
|
||||
{{ formatEndDate(row.leaseEndDate, row.leaseLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button type="primary" text icon="el-icon-edit" v-if="hasPermission('transport_ship_edit')" @click="openShip(row)">
|
||||
修改
|
||||
</el-button>
|
||||
<el-button type="primary" text icon="el-icon-view" v-if="hasPermission('transport_ship_view')" @click="openShip(row, true)">
|
||||
综合台账
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="hasPermission('transport_ship_status')"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog :title="dialogTitle" append-to-body v-model="shipBox" width="92%" top="4vh" @closed="resetShip">
|
||||
<el-form ref="shipForm" :model="shipForm" :rules="formRules" label-position="top" :disabled="readonly" class="ship-form">
|
||||
<div class="section-title">基础船舶信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="船舶名" prop="shipName">
|
||||
<el-input v-model="shipForm.shipName" maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="船舶识别号" prop="shipIdentifierNo">
|
||||
<el-input v-model="shipForm.shipIdentifierNo" maxlength="50" show-word-limit @input="shipForm.shipIdentifierNo = String(shipForm.shipIdentifierNo || '').toUpperCase()" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="所属组织" prop="organizationName">
|
||||
<el-input v-model="shipForm.organizationName" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="船检登记号" prop="shipInspectionNo">
|
||||
<el-input v-model="shipForm.shipInspectionNo" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="船舶类型" prop="shipType">
|
||||
<el-select v-model="shipForm.shipType" filterable allow-create default-first-option>
|
||||
<el-option v-for="item in shipTypeOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="shipForm.status">
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="停用" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="shipForm.remark" type="textarea" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">国籍证信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="国籍证有效期至" prop="nationalityCertEndDate">
|
||||
<el-date-picker v-model="shipForm.nationalityCertEndDate" type="date" value-format="YYYY-MM-DD" :disabled="shipForm.nationalityCertLongTerm === 1" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="长期有效" prop="nationalityCertLongTerm">
|
||||
<el-checkbox v-model="shipForm.nationalityCertLongTerm" :true-label="1" :false-label="0">是</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field label="国籍证图片" prop="nationalityCertImage" :value="shipForm.nationalityCertImage" :readonly="readonly" :headers="uploadHeaders" large @success="url => setImage('nationalityCertImage', url)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">安全配员证信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最低安全配员证书有效期至" prop="safeManningCertEndDate">
|
||||
<el-date-picker v-model="shipForm.safeManningCertEndDate" type="date" value-format="YYYY-MM-DD" :disabled="shipForm.safeManningCertLongTerm === 1" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="长期有效" prop="safeManningCertLongTerm">
|
||||
<el-checkbox v-model="shipForm.safeManningCertLongTerm" :true-label="1" :false-label="0">是</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field label="最低安全配员证书图片" prop="safeManningCertImage" :value="shipForm.safeManningCertImage" :readonly="readonly" :headers="uploadHeaders" large @success="url => setImage('safeManningCertImage', url)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">营业运输证信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="营业运输证有效期至" prop="businessTransportCertEndDate">
|
||||
<el-date-picker v-model="shipForm.businessTransportCertEndDate" type="date" value-format="YYYY-MM-DD" :disabled="shipForm.businessTransportCertLongTerm === 1" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="长期有效" prop="businessTransportCertLongTerm">
|
||||
<el-checkbox v-model="shipForm.businessTransportCertLongTerm" :true-label="1" :false-label="0">是</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field label="营业运输证图片" prop="businessTransportCertImage" :value="shipForm.businessTransportCertImage" :readonly="readonly" :headers="uploadHeaders" large @success="url => setImage('businessTransportCertImage', url)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">承租信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="承租有效期至" prop="leaseEndDate">
|
||||
<el-date-picker v-model="shipForm.leaseEndDate" type="date" value-format="YYYY-MM-DD" :disabled="shipForm.leaseLongTerm === 1" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="长期有效" prop="leaseLongTerm">
|
||||
<el-checkbox v-model="shipForm.leaseLongTerm" :true-label="1" :false-label="0">是</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field label="承租合同图片" prop="leaseContractImage" :value="shipForm.leaseContractImage" :readonly="readonly" :headers="uploadHeaders" large @success="url => setImage('leaseContractImage', url)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="shipBox = false">取消</el-button>
|
||||
<el-button type="primary" v-if="!readonly" :loading="submitLoading" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import NProgress from 'nprogress';
|
||||
import { option } from '@/option/transportCapacity/transport-ship';
|
||||
import { getList, getDetail, submit, remove, changeStatus, getExpiryStat } from '@/api/transportCapacity/transport-ship';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: '',
|
||||
shipName: '',
|
||||
shipIdentifierNo: '',
|
||||
organizationName: '',
|
||||
shipInspectionNo: '',
|
||||
shipType: '',
|
||||
nationalityCertEndDate: '',
|
||||
nationalityCertLongTerm: 0,
|
||||
nationalityCertImage: '',
|
||||
safeManningCertEndDate: '',
|
||||
safeManningCertLongTerm: 0,
|
||||
safeManningCertImage: '',
|
||||
businessTransportCertEndDate: '',
|
||||
businessTransportCertLongTerm: 0,
|
||||
businessTransportCertImage: '',
|
||||
leaseEndDate: '',
|
||||
leaseLongTerm: 0,
|
||||
leaseContractImage: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
});
|
||||
|
||||
const imageUploadField = {
|
||||
name: 'ImageUploadField',
|
||||
props: {
|
||||
label: String,
|
||||
prop: String,
|
||||
value: String,
|
||||
readonly: Boolean,
|
||||
headers: Object,
|
||||
large: Boolean,
|
||||
},
|
||||
emits: ['success'],
|
||||
methods: {
|
||||
handleSuccess(res) {
|
||||
if (res.code === 200 && res.data) {
|
||||
this.$emit('success', res.data.link || res.data.url || res.data.domain || '');
|
||||
} else {
|
||||
this.$message.error(res.msg || '上传失败');
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const validType = ['image/jpeg', 'image/png', 'image/jpg', 'image/bmp'].includes(file.type);
|
||||
const validSize = file.size / 1024 / 1024 < 5;
|
||||
if (!validType) this.$message.error('仅支持 JPG、PNG、BMP 图片');
|
||||
if (!validSize) this.$message.error('图片大小不能超过 5MB');
|
||||
return validType && validSize;
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<el-form-item :label="label" :prop="prop">
|
||||
<el-upload
|
||||
class="ship-uploader"
|
||||
:class="{ 'ship-uploader--large': large }"
|
||||
action="/api/blade-resource/oss/endpoint/put-file"
|
||||
name="file"
|
||||
:headers="headers"
|
||||
:show-file-list="false"
|
||||
:disabled="readonly"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="handleSuccess"
|
||||
>
|
||||
<img v-if="value" :src="value" class="ship-uploader__image" />
|
||||
<div v-else class="ship-uploader__empty">上传证件图片</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
`,
|
||||
};
|
||||
|
||||
export default {
|
||||
components: { ImageUploadField: imageUploadField },
|
||||
data() {
|
||||
return {
|
||||
option,
|
||||
form: {},
|
||||
query: { expireStatus: '' },
|
||||
searchForm: {},
|
||||
loading: true,
|
||||
submitLoading: false,
|
||||
data: [],
|
||||
ids: '',
|
||||
selectionList: [],
|
||||
page: { pageSize: 10, currentPage: 1, total: 0 },
|
||||
expiryStat: { total: 0, within30: 0, expired: 0 },
|
||||
shipBox: false,
|
||||
readonly: false,
|
||||
shipForm: emptyForm(),
|
||||
uploadHeaders: {
|
||||
'Blade-Auth': `bearer ${getToken()}`,
|
||||
'Blade-Requested-With': 'BladeHttpRequest',
|
||||
},
|
||||
shipTypeOptions: ['干货船', '集装箱船', '散货船', '油船', '拖轮', '驳船'],
|
||||
formRules: {
|
||||
shipName: [{ required: true, message: '请输入船舶名', trigger: 'blur' }],
|
||||
shipIdentifierNo: [{ required: true, message: '请输入船舶识别号', trigger: 'blur' }],
|
||||
organizationName: [{ required: true, message: '请输入所属组织', trigger: 'blur' }],
|
||||
shipType: [{ required: true, message: '请选择船舶类型', trigger: 'change' }],
|
||||
nationalityCertEndDate: [{ validator: this.validateNationalityCertEndDate, trigger: 'change' }],
|
||||
safeManningCertEndDate: [{ validator: this.validateSafeManningCertEndDate, trigger: 'change' }],
|
||||
businessTransportCertEndDate: [{ validator: this.validateBusinessTransportCertEndDate, trigger: 'change' }],
|
||||
remark: [{ max: 200, message: '备注最多200字', trigger: 'blur' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('transport_ship_add'),
|
||||
viewBtn: this.hasPermission('transport_ship_view'),
|
||||
delBtn: this.hasPermission('transport_ship_delete'),
|
||||
editBtn: this.hasPermission('transport_ship_edit'),
|
||||
};
|
||||
},
|
||||
dialogTitle() {
|
||||
if (this.readonly) return '船舶综合台账';
|
||||
return this.shipForm.id ? '修改船舶' : '新增船舶';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.permission?.[code] === true;
|
||||
},
|
||||
validateNationalityCertEndDate(rule, value, callback) {
|
||||
if (this.shipForm.nationalityCertLongTerm !== 1 && !value) return callback(new Error('请选择国籍证有效期至'));
|
||||
callback();
|
||||
},
|
||||
validateSafeManningCertEndDate(rule, value, callback) {
|
||||
if (this.shipForm.safeManningCertLongTerm !== 1 && !value) return callback(new Error('请选择最低安全配员证书有效期至'));
|
||||
callback();
|
||||
},
|
||||
validateBusinessTransportCertEndDate(rule, value, callback) {
|
||||
if (this.shipForm.businessTransportCertLongTerm !== 1 && !value) return callback(new Error('请选择营业运输证有效期至'));
|
||||
callback();
|
||||
},
|
||||
openShip(row, readonly = false) {
|
||||
this.readonly = readonly;
|
||||
if (!row?.id) {
|
||||
this.shipForm = emptyForm();
|
||||
this.shipBox = true;
|
||||
return;
|
||||
}
|
||||
getDetail(row.id).then(res => {
|
||||
this.shipForm = { ...emptyForm(), ...(res.data.data || {}) };
|
||||
this.shipBox = true;
|
||||
});
|
||||
},
|
||||
resetShip() {
|
||||
this.shipForm = emptyForm();
|
||||
this.readonly = false;
|
||||
this.submitLoading = false;
|
||||
this.$refs.shipForm?.clearValidate();
|
||||
},
|
||||
setImage(prop, url) {
|
||||
this.shipForm[prop] = url;
|
||||
this.$refs.shipForm?.validateField(prop);
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.shipForm.validate(valid => {
|
||||
if (!valid) return;
|
||||
this.submitLoading = true;
|
||||
submit(this.shipForm)
|
||||
.then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.shipBox = false;
|
||||
this.onLoad(this.page);
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const status = row.status === 1 ? 2 : 1;
|
||||
const text = status === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否${text}该船舶?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
changeStatus(row.id, status).then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.onLoad(this.page);
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('是否删除选中的船舶数据?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
remove(this.ids).then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.onLoad(this.page);
|
||||
});
|
||||
});
|
||||
},
|
||||
handleExpireChange() {
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
refreshStat() {
|
||||
getExpiryStat(this.buildQuery(false)).then(res => {
|
||||
this.expiryStat = {
|
||||
total: res.data.data?.total || 0,
|
||||
within30: res.data.data?.within30 || 0,
|
||||
expired: res.data.data?.expired || 0,
|
||||
};
|
||||
});
|
||||
},
|
||||
searchReset() {
|
||||
this.searchForm = {};
|
||||
this.query = { expireStatus: this.query.expireStatus };
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.searchForm = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
this.ids = list.map(item => item.id).join(',');
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.ids = '';
|
||||
this.$refs.crud?.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.searchForm);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, this.buildQuery(true, params))
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
this.refreshStat();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
buildQuery(includeExpire = true, params = {}) {
|
||||
const query = { ...params, ...this.searchForm };
|
||||
if (includeExpire && this.query.expireStatus) query.expireStatus = this.query.expireStatus;
|
||||
return query;
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出船舶数据?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/transport-ship/export-transport-ship', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `船舶管理${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return { ...this.buildQuery(true), ids: this.ids, [this.website.tokenHeader]: getToken() };
|
||||
},
|
||||
formatEndDate(date, longTerm) {
|
||||
if (longTerm === 1) return '长期有效';
|
||||
return date || '-';
|
||||
},
|
||||
getDateClass(row, dateProp, longTermProp) {
|
||||
if (row[longTermProp] === 1 || !row[dateProp]) return '';
|
||||
const today = this.$dayjs();
|
||||
const endDate = this.$dayjs(row[dateProp]);
|
||||
if (endDate.isBefore(today, 'day')) return 'date-danger';
|
||||
if (endDate.diff(today, 'day') <= 30) return 'date-warning';
|
||||
return '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.transport-ship-page {
|
||||
.ship-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.ship-toolbar__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ship-stat {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.ship-form {
|
||||
.section-title {
|
||||
margin: 18px 0 14px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid #e74b5f;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.section-title:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:deep(.el-date-editor.el-input),
|
||||
:deep(.el-date-editor.el-input__wrapper),
|
||||
:deep(.el-select),
|
||||
:deep(.el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ship-uploader .el-upload) {
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
border: 1px dashed #c0c4cc;
|
||||
background: #fafafa;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.ship-uploader--large .el-upload) {
|
||||
height: 260px;
|
||||
}
|
||||
|
||||
:deep(.ship-uploader__image) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:deep(.ship-uploader__empty) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.date-warning {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.date-danger {
|
||||
color: #d40000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.transport-ship-page {
|
||||
.ship-toolbar {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
852
src/views/transportCapacity/vehicle.vue
Normal file
852
src/views/transportCapacity/vehicle.vue
Normal file
@@ -0,0 +1,852 @@
|
||||
<template>
|
||||
<basic-container class="transport-vehicle-page">
|
||||
<div class="vehicle-toolbar">
|
||||
<div class="vehicle-toolbar__actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
plain
|
||||
v-if="hasPermission('transport_vehicle_add')"
|
||||
@click="openVehicle()"
|
||||
>新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('transport_vehicle_export')"
|
||||
@click="handleExport"
|
||||
>导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('transport_vehicle_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
<el-radio-group v-model="query.expireStatus" class="vehicle-stat" @change="handleExpireChange">
|
||||
<el-radio-button label="">全部({{ expiryStat.total }})</el-radio-button>
|
||||
<el-radio-button label="within30">30天内到期({{ expiryStat.within30 }})</el-radio-button>
|
||||
<el-radio-button label="expired">已到期({{ expiryStat.expired }})</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #plateNo="{ row }">
|
||||
<el-button type="primary" link @click="openVehicle(row, true)">
|
||||
{{ row.plateNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #drivingLicenseEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'drivingLicenseEndDate', 'drivingLicenseLongTerm')">
|
||||
{{ formatEndDate(row.drivingLicenseEndDate, row.drivingLicenseLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #roadTransportCertEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'roadTransportCertEndDate', 'roadTransportCertLongTerm')">
|
||||
{{ formatEndDate(row.roadTransportCertEndDate, row.roadTransportCertLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #annualReviewEndDate="{ row }">
|
||||
<span :class="getDateClass(row, 'annualReviewEndDate', 'annualReviewLongTerm')">
|
||||
{{ formatEndDate(row.annualReviewEndDate, row.annualReviewLongTerm) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-view"
|
||||
v-if="hasPermission('transport_vehicle_view')"
|
||||
@click="openVehicle(row, true)"
|
||||
>
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="hasPermission('transport_vehicle_edit')"
|
||||
@click="openVehicle(row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="hasPermission('transport_vehicle_status')"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
append-to-body
|
||||
v-model="vehicleBox"
|
||||
width="92%"
|
||||
top="4vh"
|
||||
class="vehicle-dialog"
|
||||
@closed="resetVehicle"
|
||||
>
|
||||
<el-form
|
||||
ref="vehicleForm"
|
||||
:model="vehicleForm"
|
||||
:rules="formRules"
|
||||
label-position="top"
|
||||
:disabled="readonly"
|
||||
class="vehicle-form"
|
||||
>
|
||||
<div class="section-title">基础车辆信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="所属组织" prop="organizationName">
|
||||
<el-input v-model="vehicleForm.organizationName" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="车牌号" prop="plateNo">
|
||||
<el-input
|
||||
v-model="vehicleForm.plateNo"
|
||||
maxlength="16"
|
||||
show-word-limit
|
||||
@input="vehicleForm.plateNo = String(vehicleForm.plateNo || '').toUpperCase()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="车辆类型" prop="vehicleType">
|
||||
<el-select v-model="vehicleForm.vehicleType" filterable allow-create default-first-option>
|
||||
<el-option v-for="item in vehicleTypeOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="业务关系" prop="businessRelation">
|
||||
<el-select v-model="vehicleForm.businessRelation">
|
||||
<el-option v-for="item in businessRelationOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="能源类型" prop="energyType">
|
||||
<el-select v-model="vehicleForm.energyType" filterable allow-create default-first-option>
|
||||
<el-option v-for="item in energyTypeOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="核定载质量(KG)" prop="approvedLoadKg">
|
||||
<el-input-number v-model="vehicleForm.approvedLoadKg" :min="0" :precision="0" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="准牵引总质量(KG)" prop="tractionMassKg">
|
||||
<el-input-number v-model="vehicleForm.tractionMassKg" :min="0" :precision="0" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="vehicleForm.status">
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="停用" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="外廓长度(mm)" prop="outerLength">
|
||||
<el-input-number v-model="vehicleForm.outerLength" :min="0" :precision="0" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="外廓宽度(mm)" prop="outerWidth">
|
||||
<el-input-number v-model="vehicleForm.outerWidth" :min="0" :precision="0" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="外廓高度(mm)" prop="outerHeight">
|
||||
<el-input-number v-model="vehicleForm.outerHeight" :min="0" :precision="0" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="海关备案号" prop="customsRecordNo">
|
||||
<el-input v-model="vehicleForm.customsRecordNo" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="强制报废日期" prop="compulsoryScrapDate">
|
||||
<el-date-picker
|
||||
v-model="vehicleForm.compulsoryScrapDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled="vehicleForm.compulsoryScrapLongTerm === 1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="长期有效" prop="compulsoryScrapLongTerm">
|
||||
<el-checkbox v-model="vehicleForm.compulsoryScrapLongTerm" :true-label="1" :false-label="0">
|
||||
是
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">行驶证信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="行驶证档案编号" prop="drivingLicenseNo">
|
||||
<el-input v-model="vehicleForm.drivingLicenseNo" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="行驶证有效期" prop="drivingLicenseEndDate">
|
||||
<div class="date-range">
|
||||
<el-date-picker v-model="vehicleForm.drivingLicenseStartDate" type="date" value-format="YYYY-MM-DD" placeholder="起" />
|
||||
<el-date-picker
|
||||
v-model="vehicleForm.drivingLicenseEndDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="止"
|
||||
:disabled="vehicleForm.drivingLicenseLongTerm === 1"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="长期有效" prop="drivingLicenseLongTerm">
|
||||
<el-checkbox v-model="vehicleForm.drivingLicenseLongTerm" :true-label="1" :false-label="0">
|
||||
是
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field
|
||||
label="行驶证图片"
|
||||
prop="drivingLicenseImage"
|
||||
:value="vehicleForm.drivingLicenseImage"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
large
|
||||
@success="url => setImage('drivingLicenseImage', url)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">道路运输证信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="道路运输证号" prop="roadTransportCertNo">
|
||||
<el-input v-model="vehicleForm.roadTransportCertNo" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="道路运输证有效期" prop="roadTransportCertEndDate">
|
||||
<div class="date-range">
|
||||
<el-date-picker v-model="vehicleForm.roadTransportCertStartDate" type="date" value-format="YYYY-MM-DD" placeholder="起" />
|
||||
<el-date-picker
|
||||
v-model="vehicleForm.roadTransportCertEndDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="止"
|
||||
:disabled="vehicleForm.roadTransportCertLongTerm === 1"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="长期有效" prop="roadTransportCertLongTerm">
|
||||
<el-checkbox v-model="vehicleForm.roadTransportCertLongTerm" :true-label="1" :false-label="0">
|
||||
是
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="道路运输年审有效期" prop="annualReviewEndDate">
|
||||
<el-date-picker
|
||||
v-model="vehicleForm.annualReviewEndDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled="vehicleForm.annualReviewLongTerm === 1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item label="年审长期有效" prop="annualReviewLongTerm">
|
||||
<el-checkbox v-model="vehicleForm.annualReviewLongTerm" :true-label="1" :false-label="0">
|
||||
是
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field
|
||||
label="道路运输证图片"
|
||||
prop="roadTransportCertImage"
|
||||
:value="vehicleForm.roadTransportCertImage"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
large
|
||||
@success="url => setImage('roadTransportCertImage', url)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="section-title">机动车登记本信息</div>
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="机动车登记编号" prop="registrationNo">
|
||||
<el-input v-model="vehicleForm.registrationNo" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="机动车登记日期" prop="registrationDate">
|
||||
<el-date-picker v-model="vehicleForm.registrationDate" type="date" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="vehicleForm.remark" type="textarea" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<image-upload-field
|
||||
label="机动车登记本图片"
|
||||
prop="registrationImage"
|
||||
:value="vehicleForm.registrationImage"
|
||||
:readonly="readonly"
|
||||
:headers="uploadHeaders"
|
||||
large
|
||||
@success="url => setImage('registrationImage', url)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="vehicleBox = false">取消</el-button>
|
||||
<el-button type="primary" v-if="!readonly" :loading="submitLoading" @click="handleSubmit">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import NProgress from 'nprogress';
|
||||
import { option } from '@/option/transportCapacity/transport-vehicle';
|
||||
import {
|
||||
getList,
|
||||
getDetail,
|
||||
submit,
|
||||
remove,
|
||||
changeStatus,
|
||||
getExpiryStat,
|
||||
} from '@/api/transportCapacity/transport-vehicle';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: '',
|
||||
organizationName: '',
|
||||
plateNo: '',
|
||||
vehicleType: '',
|
||||
outerLength: undefined,
|
||||
outerWidth: undefined,
|
||||
outerHeight: undefined,
|
||||
approvedLoadKg: undefined,
|
||||
tractionMassKg: undefined,
|
||||
businessRelation: '自有',
|
||||
energyType: '',
|
||||
compulsoryScrapDate: '',
|
||||
compulsoryScrapLongTerm: 0,
|
||||
customsRecordNo: '',
|
||||
drivingLicenseNo: '',
|
||||
drivingLicenseStartDate: '',
|
||||
drivingLicenseEndDate: '',
|
||||
drivingLicenseLongTerm: 0,
|
||||
drivingLicenseImage: '',
|
||||
roadTransportCertNo: '',
|
||||
roadTransportCertStartDate: '',
|
||||
roadTransportCertEndDate: '',
|
||||
roadTransportCertLongTerm: 0,
|
||||
roadTransportCertImage: '',
|
||||
annualReviewEndDate: '',
|
||||
annualReviewLongTerm: 0,
|
||||
registrationNo: '',
|
||||
registrationDate: '',
|
||||
registrationImage: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
});
|
||||
|
||||
const imageUploadField = {
|
||||
name: 'ImageUploadField',
|
||||
props: {
|
||||
label: String,
|
||||
prop: String,
|
||||
value: String,
|
||||
readonly: Boolean,
|
||||
headers: Object,
|
||||
large: Boolean,
|
||||
},
|
||||
emits: ['success'],
|
||||
methods: {
|
||||
handleSuccess(res) {
|
||||
if (res.code === 200 && res.data) {
|
||||
this.$emit('success', res.data.link || res.data.url || res.data.domain || '');
|
||||
} else {
|
||||
this.$message.error(res.msg || '上传失败');
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const validType = ['image/jpeg', 'image/png', 'image/jpg', 'image/bmp'].includes(file.type);
|
||||
const validSize = file.size / 1024 / 1024 < 5;
|
||||
if (!validType) {
|
||||
this.$message.error('仅支持 JPG、PNG、BMP 图片');
|
||||
}
|
||||
if (!validSize) {
|
||||
this.$message.error('图片大小不能超过 5MB');
|
||||
}
|
||||
return validType && validSize;
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<el-form-item :label="label" :prop="prop">
|
||||
<el-upload
|
||||
class="vehicle-uploader"
|
||||
:class="{ 'vehicle-uploader--large': large }"
|
||||
action="/api/blade-resource/oss/endpoint/put-file"
|
||||
name="file"
|
||||
:headers="headers"
|
||||
:show-file-list="false"
|
||||
:disabled="readonly"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="handleSuccess"
|
||||
>
|
||||
<img v-if="value" :src="value" class="vehicle-uploader__image" />
|
||||
<div v-else class="vehicle-uploader__empty">上传证件图片</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
`,
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ImageUploadField: imageUploadField,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
option,
|
||||
form: {},
|
||||
query: {
|
||||
expireStatus: '',
|
||||
},
|
||||
searchForm: {},
|
||||
loading: true,
|
||||
submitLoading: false,
|
||||
data: [],
|
||||
ids: '',
|
||||
selectionList: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
expiryStat: {
|
||||
total: 0,
|
||||
within30: 0,
|
||||
expired: 0,
|
||||
},
|
||||
vehicleBox: false,
|
||||
readonly: false,
|
||||
vehicleForm: emptyForm(),
|
||||
uploadHeaders: {
|
||||
'Blade-Auth': `bearer ${getToken()}`,
|
||||
'Blade-Requested-With': 'BladeHttpRequest',
|
||||
},
|
||||
vehicleTypeOptions: ['厢式', '高栏', '平板', '仓栅', '牵引车', '冷藏车'],
|
||||
businessRelationOptions: ['自有', '业务合作', '管理挂靠'],
|
||||
energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'],
|
||||
formRules: {
|
||||
organizationName: [{ required: true, message: '请输入所属组织', trigger: 'blur' }],
|
||||
plateNo: [
|
||||
{ required: true, message: '请输入车牌号', trigger: 'blur' },
|
||||
{ pattern: /^[\u4e00-\u9fa5][A-Z][A-Z0-9挂学警港澳]{5,6}$/, message: '请输入标准车牌号', trigger: 'blur' },
|
||||
],
|
||||
vehicleType: [{ required: true, message: '请选择车辆类型', trigger: 'change' }],
|
||||
approvedLoadKg: [{ required: true, message: '请输入核定载质量', trigger: 'blur' }],
|
||||
businessRelation: [{ required: true, message: '请选择业务关系', trigger: 'change' }],
|
||||
energyType: [{ required: true, message: '请选择能源类型', trigger: 'change' }],
|
||||
drivingLicenseNo: [{ required: true, message: '请输入行驶证档案编号', trigger: 'blur' }],
|
||||
drivingLicenseEndDate: [{ validator: this.validateDrivingLicenseEndDate, trigger: 'change' }],
|
||||
roadTransportCertNo: [{ required: true, message: '请输入道路运输证号', trigger: 'blur' }],
|
||||
roadTransportCertEndDate: [{ validator: this.validateRoadTransportCertEndDate, trigger: 'change' }],
|
||||
annualReviewEndDate: [{ validator: this.validateAnnualReviewEndDate, trigger: 'change' }],
|
||||
registrationNo: [{ required: true, message: '请输入机动车登记编号', trigger: 'blur' }],
|
||||
remark: [{ max: 200, message: '备注最多200字', trigger: 'blur' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('transport_vehicle_add'),
|
||||
viewBtn: this.hasPermission('transport_vehicle_view'),
|
||||
delBtn: this.hasPermission('transport_vehicle_delete'),
|
||||
editBtn: this.hasPermission('transport_vehicle_edit'),
|
||||
};
|
||||
},
|
||||
dialogTitle() {
|
||||
if (this.readonly) {
|
||||
return '查看车辆';
|
||||
}
|
||||
return this.vehicleForm.id ? '修改车辆' : '新增车辆';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.permission?.[code] === true;
|
||||
},
|
||||
validateDrivingLicenseEndDate(rule, value, callback) {
|
||||
if (this.vehicleForm.drivingLicenseLongTerm !== 1 && !value) {
|
||||
callback(new Error('请选择行驶证有效期止'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateRoadTransportCertEndDate(rule, value, callback) {
|
||||
if (this.vehicleForm.roadTransportCertLongTerm !== 1 && !value) {
|
||||
callback(new Error('请选择道路运输证有效期止'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateAnnualReviewEndDate(rule, value, callback) {
|
||||
if (this.vehicleForm.annualReviewLongTerm !== 1 && !value) {
|
||||
callback(new Error('请选择道路运输年审有效期'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
openVehicle(row, readonly = false) {
|
||||
this.readonly = readonly;
|
||||
if (!row?.id) {
|
||||
this.vehicleForm = emptyForm();
|
||||
this.vehicleBox = true;
|
||||
return;
|
||||
}
|
||||
getDetail(row.id).then(res => {
|
||||
this.vehicleForm = {
|
||||
...emptyForm(),
|
||||
...(res.data.data || {}),
|
||||
};
|
||||
this.vehicleBox = true;
|
||||
});
|
||||
},
|
||||
resetVehicle() {
|
||||
this.vehicleForm = emptyForm();
|
||||
this.readonly = false;
|
||||
this.submitLoading = false;
|
||||
this.$refs.vehicleForm?.clearValidate();
|
||||
},
|
||||
setImage(prop, url) {
|
||||
this.vehicleForm[prop] = url;
|
||||
this.$refs.vehicleForm?.validateField(prop);
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.vehicleForm.validate(valid => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
this.submitLoading = true;
|
||||
submit(this.vehicleForm)
|
||||
.then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.vehicleBox = false;
|
||||
this.onLoad(this.page);
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const status = row.status === 1 ? 2 : 1;
|
||||
const text = status === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否${text}该车辆?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
changeStatus(row.id, status).then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.onLoad(this.page);
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('是否删除选中的车辆数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
remove(this.ids).then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.onLoad(this.page);
|
||||
});
|
||||
});
|
||||
},
|
||||
handleExpireChange() {
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
refreshStat() {
|
||||
getExpiryStat(this.buildQuery(false)).then(res => {
|
||||
this.expiryStat = {
|
||||
total: res.data.data?.total || 0,
|
||||
within30: res.data.data?.within30 || 0,
|
||||
expired: res.data.data?.expired || 0,
|
||||
};
|
||||
});
|
||||
},
|
||||
searchReset() {
|
||||
this.searchForm = {};
|
||||
this.query = {
|
||||
expireStatus: this.query.expireStatus,
|
||||
};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.searchForm = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
this.ids = list.map(item => item.id).join(',');
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.ids = '';
|
||||
this.$refs.crud?.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.searchForm);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, this.buildQuery(true, params))
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
this.refreshStat();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
buildQuery(includeExpire = true, params = {}) {
|
||||
const query = {
|
||||
...params,
|
||||
...this.searchForm,
|
||||
};
|
||||
if (includeExpire && this.query.expireStatus) {
|
||||
query.expireStatus = this.query.expireStatus;
|
||||
}
|
||||
return query;
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出车辆数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/transport-vehicle/export-transport-vehicle', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `车辆管理${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.buildQuery(true),
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
formatEndDate(date, longTerm) {
|
||||
if (longTerm === 1) {
|
||||
return '长期有效';
|
||||
}
|
||||
return date || '-';
|
||||
},
|
||||
getDateClass(row, dateProp, longTermProp) {
|
||||
if (row[longTermProp] === 1 || !row[dateProp]) {
|
||||
return '';
|
||||
}
|
||||
const today = this.$dayjs();
|
||||
const endDate = this.$dayjs(row[dateProp]);
|
||||
if (endDate.isBefore(today, 'day')) {
|
||||
return 'date-danger';
|
||||
}
|
||||
if (endDate.diff(today, 'day') <= 30) {
|
||||
return 'date-warning';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.transport-vehicle-page {
|
||||
.vehicle-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.vehicle-toolbar__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vehicle-stat {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.vehicle-form {
|
||||
.section-title {
|
||||
margin: 18px 0 14px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid #e74b5f;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.section-title:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:deep(.el-date-editor.el-input),
|
||||
:deep(.el-date-editor.el-input__wrapper),
|
||||
:deep(.el-select),
|
||||
:deep(.el-input),
|
||||
:deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date-range {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.vehicle-uploader .el-upload) {
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
border: 1px dashed #c0c4cc;
|
||||
background: #fafafa;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.vehicle-uploader--large .el-upload) {
|
||||
height: 260px;
|
||||
}
|
||||
|
||||
:deep(.vehicle-uploader__image) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:deep(.vehicle-uploader__empty) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.date-warning {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.date-danger {
|
||||
color: #d40000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.transport-vehicle-page {
|
||||
.vehicle-toolbar {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
426
src/views/vehicle/accident-record.vue
Normal file
426
src/views/vehicle/accident-record.vue
Normal file
@@ -0,0 +1,426 @@
|
||||
<template>
|
||||
<basic-container class="accident-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('accident_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('accident_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('accident_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('accident_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
clearable
|
||||
placeholder="输入车牌号/船号查询选择"
|
||||
value-key="value"
|
||||
class="accident-record-page__input"
|
||||
/>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="事故记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/accident-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/accident-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('accident_record_add'),
|
||||
viewBtn: this.hasPermission('accident_record_view'),
|
||||
delBtn: this.hasPermission('accident_record_delete'),
|
||||
editBtn: this.hasPermission('accident_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'form.vehicleType': {
|
||||
handler(value) {
|
||||
this.updateVehicleTypeDisplays(value || '车辆');
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
updateVehicleTypeDisplays(vehicleType) {
|
||||
const vehicleTypeColumn = this.findColumn(this.option.column, 'vehicleType');
|
||||
vehicleTypeColumn.disabled = Boolean(this.form.id);
|
||||
if (!this.form.id && this.form.vehicleNo) {
|
||||
this.form.vehicleNo = '';
|
||||
}
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
const vehicleType = this.form.vehicleType || '车辆';
|
||||
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
|
||||
const params =
|
||||
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
|
||||
request(1, 20, params)
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(
|
||||
records.map(item => ({
|
||||
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return (
|
||||
value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个'
|
||||
);
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
values.vehicleType = values.vehicleType || '车辆';
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (Number(row.directEconomicLoss) < 0) {
|
||||
this.$message.warning('直接经济损失不能小于 0');
|
||||
return false;
|
||||
}
|
||||
if (Number(row.insuranceClaimAmount) < 0) {
|
||||
this.$message.warning('保险理赔金额不能小于 0');
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
row.directEconomicLoss !== undefined &&
|
||||
row.directEconomicLoss !== null &&
|
||||
row.directEconomicLoss !== '' &&
|
||||
row.insuranceClaimAmount !== undefined &&
|
||||
row.insuranceClaimAmount !== null &&
|
||||
row.insuranceClaimAmount !== '' &&
|
||||
Number(row.insuranceClaimAmount) > Number(row.directEconomicLoss)
|
||||
) {
|
||||
this.$message.warning('保险理赔金额不能超过直接经济损失金额');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form = {
|
||||
vehicleType: '车辆',
|
||||
};
|
||||
this.updateVehicleTypeDisplays('车辆');
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
this.updateVehicleTypeDisplays(detail.vehicleType);
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出事故记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/accident-record/export-accident-record', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `事故记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-transport/accident-record/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '事故记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.accident-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
441
src/views/vehicle/annual-inspection-record.vue
Normal file
441
src/views/vehicle/annual-inspection-record.vue
Normal file
@@ -0,0 +1,441 @@
|
||||
<template>
|
||||
<basic-container class="annual-inspection-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('annual_inspection_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('annual_inspection_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('annual_inspection_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('annual_inspection_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
clearable
|
||||
placeholder="输入车牌号/船号查询选择"
|
||||
value-key="value"
|
||||
class="annual-inspection-record-page__input"
|
||||
/>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="年检记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/annual-inspection-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/annual-inspection-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('annual_inspection_record_add'),
|
||||
viewBtn: this.hasPermission('annual_inspection_record_view'),
|
||||
delBtn: this.hasPermission('annual_inspection_record_delete'),
|
||||
editBtn: this.hasPermission('annual_inspection_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'form.vehicleType': {
|
||||
handler(value) {
|
||||
this.updateVehicleTypeDisplays(value || '车辆');
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
updateVehicleTypeDisplays(vehicleType) {
|
||||
const vehicleTypeColumn = this.findColumn(this.option.column, 'vehicleType');
|
||||
const vehicleLevelColumn = this.findColumn(this.option.column, 'vehicleTechnicalLevel');
|
||||
const shipTypeColumn = this.findColumn(this.option.column, 'shipInspectionType');
|
||||
const passengerColumn = this.findColumn(this.option.column, 'passengerTypeLevel');
|
||||
vehicleTypeColumn.disabled = Boolean(this.form.id);
|
||||
vehicleLevelColumn.display = vehicleType !== '船舶';
|
||||
shipTypeColumn.display = vehicleType === '船舶';
|
||||
passengerColumn.display = vehicleType !== '船舶';
|
||||
if (vehicleType === '船舶') {
|
||||
this.form.vehicleTechnicalLevel = undefined;
|
||||
this.form.passengerTypeLevel = undefined;
|
||||
} else {
|
||||
this.form.shipInspectionType = undefined;
|
||||
}
|
||||
if (!this.form.id && this.form.vehicleNo) {
|
||||
this.form.vehicleNo = '';
|
||||
}
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
const vehicleType = this.form.vehicleType || '车辆';
|
||||
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
|
||||
const params =
|
||||
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
|
||||
request(1, 20, params)
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(
|
||||
records.map(item => ({
|
||||
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return (
|
||||
value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个'
|
||||
);
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
values.vehicleType = values.vehicleType || '车辆';
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
if (values.vehicleType === '船舶') {
|
||||
values.vehicleTechnicalLevel = undefined;
|
||||
values.passengerTypeLevel = undefined;
|
||||
} else {
|
||||
values.shipInspectionType = undefined;
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (Number(row.fee) < 0) {
|
||||
this.$message.warning('费用不能小于 0');
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
row.inspectionAssessmentDate &&
|
||||
row.validUntilDate &&
|
||||
new Date(row.validUntilDate.replace(/-/g, '/')).getTime() <=
|
||||
new Date(row.inspectionAssessmentDate.replace(/-/g, '/')).getTime()
|
||||
) {
|
||||
this.$message.warning('有效期截止日应大于检测评定日期');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form = {
|
||||
vehicleType: '车辆',
|
||||
inspectionAssessmentDate: this.$dayjs().format('YYYY-MM-DD'),
|
||||
};
|
||||
this.updateVehicleTypeDisplays('车辆');
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
this.updateVehicleTypeDisplays(detail.vehicleType);
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出年检记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob(
|
||||
'/blade-transport/annual-inspection-record/export-annual-inspection-record',
|
||||
this.buildExportParams()
|
||||
)
|
||||
.then(res => {
|
||||
downloadXls(res.data, `年检记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-transport/annual-inspection-record/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '年检记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.annual-inspection-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
862
src/views/vehicle/credit-score-quantification.vue
Normal file
862
src/views/vehicle/credit-score-quantification.vue
Normal file
@@ -0,0 +1,862 @@
|
||||
<template>
|
||||
<basic-container>
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
plain
|
||||
v-if="hasPermission('credit_score_quantification_add')"
|
||||
@click="openConfig()"
|
||||
>新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('credit_score_quantification_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('credit_score_quantification_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('credit_score_quantification_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('credit_score_quantification_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #name="{ row }">
|
||||
<el-button type="primary" link @click="openConfig(row, true)">
|
||||
{{ row.name }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="statusMap[row.status].type">
|
||||
{{ statusMap[row.status].label }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-edit"
|
||||
v-if="hasPermission('credit_score_quantification_edit')"
|
||||
@click="openConfig(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
icon="el-icon-position"
|
||||
v-if="hasPermission('credit_score_quantification_publish') && row.status !== 1"
|
||||
@click="handlePublish(row)"
|
||||
>
|
||||
发布
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
:icon="row.status === 1 ? 'el-icon-close' : 'el-icon-check'"
|
||||
v-if="hasPermission('credit_score_quantification_status') && row.status !== 3"
|
||||
@click="handleStatus(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
text
|
||||
icon="el-icon-delete"
|
||||
v-if="hasPermission('credit_score_quantification_delete') && row.status === 3"
|
||||
@click="rowDel(row)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog title="评分量化表数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
:title="readonly ? '查看评分量化表' : configForm.id ? '编辑评分量化表' : '新增评分量化表'"
|
||||
append-to-body
|
||||
v-model="configBox"
|
||||
width="88%"
|
||||
class="score-dialog"
|
||||
@closed="resetConfig"
|
||||
>
|
||||
<div class="score-config">
|
||||
<div class="section-title">评分量化表</div>
|
||||
<el-form :model="configForm" label-width="110px" class="base-form" :disabled="readonly">
|
||||
<el-form-item label="评定表名称" required>
|
||||
<el-input v-model="configForm.name" maxlength="30" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="configForm.remark" type="textarea" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="section-head">
|
||||
<div class="section-title">评分表</div>
|
||||
</div>
|
||||
<el-table :data="configForm.categories" border class="config-table">
|
||||
<el-table-column label="序号" type="index" width="70" align="center" />
|
||||
<el-table-column label="评分类目" prop="categoryName" min-width="180" />
|
||||
<el-table-column label="评分项目数" width="140" align="center">
|
||||
<template #default="{ row }">{{ row.items.length }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" text @click="openCategory(row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="section-head standard-head">
|
||||
<div class="section-title">评估标准</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
v-if="!readonly"
|
||||
@click="openStandard()"
|
||||
>
|
||||
添加
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="configForm.standards" border class="config-table">
|
||||
<el-table-column label="序号" type="index" width="70" align="center" />
|
||||
<el-table-column label="信用等级" prop="creditLevel" width="120" align="center" />
|
||||
<el-table-column label="得分率" width="160" align="center">
|
||||
<template #default="{ row }">{{ row.scoreRateLower }}% - {{ row.scoreRateUpper }}%</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最大资金使用额度(万元)" width="210" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.creditLimitLower }} - {{ row.creditLimitUpper }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标准说明" prop="standardDescription" min-width="320" />
|
||||
<el-table-column label="操作" width="160" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<el-button type="primary" text @click="openStandard(row, $index)">编辑</el-button>
|
||||
<el-button type="danger" text v-if="!readonly" @click="removeStandard($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="configBox = false">{{ readonly ? '关闭' : '取消' }}</el-button>
|
||||
<el-button type="primary" v-if="!readonly" @click="saveConfig">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="评分类目详情"
|
||||
append-to-body
|
||||
v-model="categoryBox"
|
||||
width="82%"
|
||||
class="score-dialog"
|
||||
>
|
||||
<div class="category-detail">
|
||||
<div class="category-line">评分类目:{{ currentCategory.categoryName }}</div>
|
||||
<div class="section-head">
|
||||
<span></span>
|
||||
<el-button type="primary" icon="el-icon-plus" v-if="!readonly" @click="openItem()">
|
||||
添加
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="currentCategory.items" border class="config-table">
|
||||
<el-table-column label="序号" type="index" width="70" align="center" />
|
||||
<el-table-column label="评分项目" prop="itemName" width="180" />
|
||||
<el-table-column label="得分说明" prop="scoreDescription" min-width="260" />
|
||||
<el-table-column label="选项数" width="100" align="center">
|
||||
<template #default="{ row }">{{ row.options.length }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<el-button type="primary" text @click="openItem(row, $index)">编辑</el-button>
|
||||
<el-button type="danger" text v-if="!readonly" @click="removeItem($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="categoryBox = false">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="评分项目详情" append-to-body v-model="itemBox" width="76%">
|
||||
<el-form :model="itemForm" label-width="100px" :disabled="readonly">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="评分项目" required>
|
||||
<el-input v-model="itemForm.itemName" maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="得分说明">
|
||||
<el-input v-model="itemForm.scoreDescription" maxlength="300" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="选项描述">
|
||||
<el-input v-model="itemForm.optionDescription" maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="选项" required>
|
||||
<div class="option-editor">
|
||||
<div class="option-row" v-for="(option, index) in itemForm.options" :key="index">
|
||||
<el-input v-model="option.optionName" maxlength="100" placeholder="选项描述" />
|
||||
<el-input-number v-model="option.score" :min="0" :precision="2" placeholder="分值" />
|
||||
<span class="score-unit">分</span>
|
||||
<el-button type="danger" text v-if="!readonly && itemForm.options.length > 1" @click="removeOption(index)">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button type="primary" text v-if="!readonly" @click="addOption">+添加选项</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="itemBox = false">取消</el-button>
|
||||
<el-button type="primary" v-if="!readonly" @click="saveItem">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="评估标准设置" append-to-body v-model="standardBox" width="76%">
|
||||
<el-form :model="standardForm" label-width="170px" :disabled="readonly">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="信用等级" required>
|
||||
<el-input v-model="standardForm.creditLevel" maxlength="30" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="得分率(%)" required>
|
||||
<div class="range-input">
|
||||
<el-input-number v-model="standardForm.scoreRateLower" :min="0" :max="100" :precision="0" />
|
||||
<span>至</span>
|
||||
<el-input-number v-model="standardForm.scoreRateUpper" :min="0" :max="100" :precision="0" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="得分率每增加" required>
|
||||
<el-input-number v-model="standardForm.scoreRateIncrease" :min="0" :precision="0" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最大资金使用额度(万元)" required>
|
||||
<div class="range-input">
|
||||
<el-input-number v-model="standardForm.creditLimitLower" :min="0" :precision="2" />
|
||||
<span>至</span>
|
||||
<el-input-number v-model="standardForm.creditLimitUpper" :min="0" :precision="2" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="额度增加" required>
|
||||
<el-input-number v-model="standardForm.creditLimitIncrease" :min="0" :precision="2" />
|
||||
<span class="score-unit">万元</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="标准说明">
|
||||
<el-input v-model="standardForm.standardDescription" type="textarea" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="standardBox = false">取消</el-button>
|
||||
<el-button type="primary" v-if="!readonly" @click="saveStandard">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getList,
|
||||
getDetail,
|
||||
submit,
|
||||
remove,
|
||||
publish,
|
||||
changeStatus,
|
||||
} from '@/api/vehicle/credit-score-quantification';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
data: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
configBox: false,
|
||||
categoryBox: false,
|
||||
itemBox: false,
|
||||
standardBox: false,
|
||||
excelBox: false,
|
||||
readonly: false,
|
||||
excelForm: {},
|
||||
configForm: this.emptyConfig(),
|
||||
currentCategory: { items: [] },
|
||||
currentItemIndex: -1,
|
||||
currentStandardIndex: -1,
|
||||
itemForm: this.emptyItem(),
|
||||
standardForm: this.emptyStandard(),
|
||||
statusMap: {
|
||||
1: { label: '正常', type: 'success' },
|
||||
2: { label: '停用', type: 'info' },
|
||||
3: { label: '草稿', type: 'warning' },
|
||||
},
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 760,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
indexLabel: '序号',
|
||||
addBtn: false,
|
||||
viewBtn: false,
|
||||
delBtn: false,
|
||||
editBtn: false,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 300,
|
||||
column: [
|
||||
{
|
||||
label: '评定表名称',
|
||||
prop: 'name',
|
||||
slot: true,
|
||||
minWidth: 180,
|
||||
search: true,
|
||||
maxlength: 30,
|
||||
rules: [{ required: true, message: '请输入评定表名称', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
minWidth: 260,
|
||||
overHidden: true,
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
search: true,
|
||||
slot: true,
|
||||
dataType: 'number',
|
||||
dicData: [
|
||||
{ label: '正常', value: 1 },
|
||||
{ label: '停用', value: 2 },
|
||||
{ label: '草稿', value: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
excelOption: {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/credit-score-quantification/import-credit-score-quantification',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: false,
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
emptyConfig() {
|
||||
return {
|
||||
name: '',
|
||||
remark: '',
|
||||
standardDescription: '',
|
||||
status: 3,
|
||||
categories: this.defaultCategories(),
|
||||
standards: [],
|
||||
};
|
||||
},
|
||||
defaultCategories() {
|
||||
return [
|
||||
{ categoryCode: 'basic', categoryName: '基础得分项', sort: 1, items: [] },
|
||||
{ categoryCode: 'plus', categoryName: '加分项目', sort: 2, items: [] },
|
||||
{ categoryCode: 'minus', categoryName: '减分项目', sort: 3, items: [] },
|
||||
];
|
||||
},
|
||||
emptyItem() {
|
||||
return {
|
||||
itemName: '',
|
||||
scoreDescription: '',
|
||||
optionDescription: '',
|
||||
options: [{ optionName: '', score: 0 }],
|
||||
};
|
||||
},
|
||||
emptyStandard() {
|
||||
return {
|
||||
creditLevel: '',
|
||||
scoreRateLower: undefined,
|
||||
scoreRateUpper: undefined,
|
||||
scoreRateIncrease: undefined,
|
||||
creditLimitLower: undefined,
|
||||
creditLimitUpper: undefined,
|
||||
creditLimitIncrease: undefined,
|
||||
standardDescription: '',
|
||||
};
|
||||
},
|
||||
normalizeDetail(detail) {
|
||||
const categories = this.defaultCategories();
|
||||
const map = {};
|
||||
(detail.categories || []).forEach(category => {
|
||||
map[category.categoryCode] = {
|
||||
...category,
|
||||
items: (category.items || []).map(item => ({
|
||||
...item,
|
||||
options: item.options && item.options.length ? item.options : [{ optionName: '', score: 0 }],
|
||||
})),
|
||||
};
|
||||
});
|
||||
return {
|
||||
...detail,
|
||||
categories: categories.map(category => map[category.categoryCode] || category),
|
||||
standards: detail.standards || [],
|
||||
};
|
||||
},
|
||||
openConfig(row, readonly = false) {
|
||||
this.readonly = readonly;
|
||||
if (row && row.id) {
|
||||
getDetail(row.id).then(res => {
|
||||
this.configForm = this.normalizeDetail(res.data.data);
|
||||
this.configBox = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.configForm = this.emptyConfig();
|
||||
if (row && row.name) {
|
||||
this.configForm.name = row.name;
|
||||
}
|
||||
this.configBox = true;
|
||||
},
|
||||
resetConfig() {
|
||||
this.readonly = false;
|
||||
this.configForm = this.emptyConfig();
|
||||
},
|
||||
openCategory(category) {
|
||||
this.currentCategory = category;
|
||||
this.categoryBox = true;
|
||||
},
|
||||
openItem(row, index = -1) {
|
||||
this.currentItemIndex = index;
|
||||
this.itemForm = row ? JSON.parse(JSON.stringify(row)) : this.emptyItem();
|
||||
this.itemBox = true;
|
||||
},
|
||||
saveItem() {
|
||||
if (!this.itemForm.itemName || !this.itemForm.itemName.trim()) {
|
||||
this.$message.warning('请输入评分项目');
|
||||
return;
|
||||
}
|
||||
if (!this.itemForm.options.length) {
|
||||
this.$message.warning('至少添加1条档位选项');
|
||||
return;
|
||||
}
|
||||
const item = JSON.parse(JSON.stringify(this.itemForm));
|
||||
if (this.currentItemIndex >= 0) {
|
||||
this.currentCategory.items.splice(this.currentItemIndex, 1, item);
|
||||
} else {
|
||||
this.currentCategory.items.push(item);
|
||||
}
|
||||
this.itemBox = false;
|
||||
},
|
||||
removeItem(index) {
|
||||
this.currentCategory.items.splice(index, 1);
|
||||
},
|
||||
addOption() {
|
||||
this.itemForm.options.push({ optionName: '', score: 0 });
|
||||
},
|
||||
removeOption(index) {
|
||||
this.itemForm.options.splice(index, 1);
|
||||
},
|
||||
openStandard(row, index = -1) {
|
||||
this.currentStandardIndex = index;
|
||||
this.standardForm = row ? JSON.parse(JSON.stringify(row)) : this.emptyStandard();
|
||||
this.standardBox = true;
|
||||
},
|
||||
saveStandard() {
|
||||
if (!this.standardForm.creditLevel || !this.standardForm.creditLevel.trim()) {
|
||||
this.$message.warning('请输入信用等级');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
[
|
||||
this.standardForm.scoreRateLower,
|
||||
this.standardForm.scoreRateUpper,
|
||||
this.standardForm.scoreRateIncrease,
|
||||
this.standardForm.creditLimitLower,
|
||||
this.standardForm.creditLimitUpper,
|
||||
this.standardForm.creditLimitIncrease,
|
||||
].some(value => value === undefined || value === null || value === '')
|
||||
) {
|
||||
this.$message.warning('请完整填写评估标准必填项');
|
||||
return;
|
||||
}
|
||||
if (Number(this.standardForm.scoreRateLower) >= Number(this.standardForm.scoreRateUpper)) {
|
||||
this.$message.warning('得分率下限必须小于上限');
|
||||
return;
|
||||
}
|
||||
const standard = JSON.parse(JSON.stringify(this.standardForm));
|
||||
if (this.currentStandardIndex >= 0) {
|
||||
this.configForm.standards.splice(this.currentStandardIndex, 1, standard);
|
||||
} else {
|
||||
this.configForm.standards.push(standard);
|
||||
}
|
||||
this.standardBox = false;
|
||||
},
|
||||
removeStandard(index) {
|
||||
this.configForm.standards.splice(index, 1);
|
||||
},
|
||||
validateConfig() {
|
||||
if (!this.configForm.name || !this.configForm.name.trim()) {
|
||||
this.$message.warning('请输入评定表名称');
|
||||
return false;
|
||||
}
|
||||
const basic = this.configForm.categories.find(item => item.categoryCode === 'basic');
|
||||
if (!basic || basic.items.length === 0) {
|
||||
this.$message.warning('至少配置1个基础得分项目');
|
||||
return false;
|
||||
}
|
||||
for (const category of this.configForm.categories) {
|
||||
for (const item of category.items) {
|
||||
if (!item.options || item.options.length === 0) {
|
||||
this.$message.warning(`${item.itemName}至少存在1条档位选项`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
saveConfig() {
|
||||
if (!this.validateConfig()) return;
|
||||
submit(this.configForm).then(() => {
|
||||
this.configBox = false;
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('仅草稿状态量表可删除,确定删除所选数据?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
handlePublish(row) {
|
||||
this.$confirm('发布前将校验所有评分项目、档位选项和评估标准,是否继续?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => publish(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '发布成功!' });
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const nextStatus = row.status === 1 ? 2 : 1;
|
||||
const actionName = nextStatus === 1 ? '启用' : '停用';
|
||||
this.$confirm(`是否确认${actionName}该量表?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => changeStatus(row.id, nextStatus))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出评分量化表数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob(
|
||||
'/blade-transport/credit-score-quantification/export-credit-score-quantification',
|
||||
this.buildExportParams()
|
||||
)
|
||||
.then(res => {
|
||||
downloadXls(res.data, `评分量化表${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-transport/credit-score-quantification/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '评分量化表模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.score-config {
|
||||
padding: 4px 12px 12px;
|
||||
}
|
||||
|
||||
.base-form {
|
||||
max-width: 1080px;
|
||||
margin: 10px auto 28px;
|
||||
}
|
||||
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 24px 0 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 18px 0 12px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.standard-head {
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.config-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.category-line {
|
||||
margin: 0 0 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.option-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.option-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 1fr) 190px 32px 70px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.range-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.score-unit {
|
||||
margin-left: 8px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
:deep(.score-dialog .el-dialog__body) {
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
1243
src/views/vehicle/customer-archive.vue
Normal file
1243
src/views/vehicle/customer-archive.vue
Normal file
File diff suppressed because it is too large
Load Diff
413
src/views/vehicle/etc-record.vue
Normal file
413
src/views/vehicle/etc-record.vue
Normal file
@@ -0,0 +1,413 @@
|
||||
<template>
|
||||
<basic-container class="etc-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('etc_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('etc_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('etc_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('etc_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
clearable
|
||||
placeholder="输入车牌号模糊查询选择"
|
||||
value-key="value"
|
||||
class="etc-record-page__input"
|
||||
/>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="ETC记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/etc-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/etc-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('etc_record_add'),
|
||||
viewBtn: this.hasPermission('etc_record_view'),
|
||||
delBtn: this.hasPermission('etc_record_delete'),
|
||||
editBtn: this.hasPermission('etc_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
getVehicleList(1, 20, { plateNo: queryString })
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(
|
||||
records.map(item => ({
|
||||
value: item.plateNo,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return (
|
||||
value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个'
|
||||
);
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
isEmpty(value) {
|
||||
return value === undefined || value === null || value === '';
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
values.dataSource = values.dataSource || '手工录入';
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
if (values.etcCardNo) {
|
||||
values.etcCardNo = values.etcCardNo.trim();
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
validateDecimal(value, label, required = false) {
|
||||
if (this.isEmpty(value)) {
|
||||
if (required) {
|
||||
this.$message.warning(`请输入${label}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (Number(value) < 0) {
|
||||
this.$message.warning(`${label}不能小于 0`);
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+(\.\d{1,2})?$/.test(String(value))) {
|
||||
this.$message.warning(`${label}最多保留 2 位小数`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (row.entryTime && row.exitTime && new Date(row.exitTime) <= new Date(row.entryTime)) {
|
||||
this.$message.warning('出口时间应大于入口时间');
|
||||
return false;
|
||||
}
|
||||
if (!this.validateDecimal(row.transactionAmount, '交易金额', true)) return false;
|
||||
if (row.remark && row.remark.length > 200) {
|
||||
this.$message.warning('备注不能超过 200 字');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form = { dataSource: '手工录入' };
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出ETC记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/etc-record/export-etc-record', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `ETC记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(`/blade-transport/etc-record/export-template?${this.website.tokenHeader}=${getToken()}`).then(res => {
|
||||
downloadXls(res.data, 'ETC记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.etc-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
449
src/views/vehicle/insurance-record.vue
Normal file
449
src/views/vehicle/insurance-record.vue
Normal file
@@ -0,0 +1,449 @@
|
||||
<template>
|
||||
<basic-container class="insurance-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('insurance_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('insurance_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('insurance_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('insurance_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
clearable
|
||||
placeholder="输入车牌号/船号模糊查询选择"
|
||||
value-key="value"
|
||||
class="insurance-record-page__vehicle-input"
|
||||
/>
|
||||
</template>
|
||||
<template #policyFileForm>
|
||||
<div class="insurance-record-page__upload">
|
||||
<div class="insurance-record-page__upload-icon">OCR</div>
|
||||
<div class="insurance-record-page__upload-text">
|
||||
<strong>上传图片,自动识别填写</strong>
|
||||
<span>支持JPG、PNG、PDF上传</span>
|
||||
</div>
|
||||
<el-upload
|
||||
:action="recognizeUrl"
|
||||
:data="recognizeData"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:on-success="handleRecognizeSuccess"
|
||||
:on-error="handleRecognizeError"
|
||||
accept=".jpg,.jpeg,.png,.pdf"
|
||||
v-if="hasPermission('insurance_record_ocr')"
|
||||
>
|
||||
<el-button type="primary" plain>上传文件</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="保险记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/insurance-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/insurance-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('insurance_record_add'),
|
||||
viewBtn: this.hasPermission('insurance_record_view'),
|
||||
delBtn: this.hasPermission('insurance_record_delete'),
|
||||
editBtn: this.hasPermission('insurance_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
recognizeUrl() {
|
||||
return '/blade-transport/insurance-record/recognize';
|
||||
},
|
||||
recognizeData() {
|
||||
return {
|
||||
vehicleType: this.form.vehicleType || '车辆',
|
||||
ocrTemplate: this.form.ocrTemplate,
|
||||
};
|
||||
},
|
||||
uploadHeaders() {
|
||||
return {
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
const vehicleType = this.form.vehicleType || '车辆';
|
||||
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
|
||||
const params =
|
||||
vehicleType === '船舶'
|
||||
? { shipIdentifierNo: queryString }
|
||||
: { plateNo: queryString };
|
||||
request(1, 20, params)
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(
|
||||
records.map(item => ({
|
||||
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
if (values.policyNo) {
|
||||
values.policyNo = values.policyNo.trim();
|
||||
}
|
||||
return values;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (row.startDate && row.endDate && row.endDate < row.startDate) {
|
||||
this.$message.warning('结束日期不能早于开始日期');
|
||||
return false;
|
||||
}
|
||||
if (Number(row.insuredAmount) < 0 || Number(row.premium) < 0) {
|
||||
this.$message.warning('保额、保费不能小于 0');
|
||||
return false;
|
||||
}
|
||||
if (row.remark && row.remark.length > 200) {
|
||||
this.$message.warning('备注最多 200 个字');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form.vehicleType = '车辆';
|
||||
this.form.ocrTemplate = '紫金-机动车交强险';
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
this.form = res.data.data;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出保险记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/insurance-record/export-insurance-record', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `保险记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-transport/insurance-record/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '保险记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
handleRecognizeSuccess(res) {
|
||||
if (res && res.success && res.data) {
|
||||
this.form = {
|
||||
...this.form,
|
||||
...res.data,
|
||||
vehicleType: res.data.vehicleType || this.form.vehicleType || '车辆',
|
||||
};
|
||||
this.$message.success('识别完成');
|
||||
} else {
|
||||
this.$message.warning((res && res.msg) || '识别失败,请手动填写');
|
||||
}
|
||||
},
|
||||
handleRecognizeError() {
|
||||
this.$message.warning('识别失败,请手动填写');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.insurance-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__vehicle-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
min-height: 88px;
|
||||
padding: 18px 24px;
|
||||
border: 1px dashed #8c8c8c;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&__upload-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
color: #606266;
|
||||
border: 2px solid #8c8c8c;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__upload-text {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
color: #303133;
|
||||
|
||||
span {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
602
src/views/vehicle/maintenance-plan.vue
Normal file
602
src/views/vehicle/maintenance-plan.vue
Normal file
@@ -0,0 +1,602 @@
|
||||
<template>
|
||||
<basic-container class="maintenance-plan-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('maintenance_plan_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('maintenance_plan_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('maintenance_plan_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('maintenance_plan_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #mileage="{ row }">
|
||||
<span>{{ formatMileage(row.mileage, row.mileageUnit) }}</span>
|
||||
</template>
|
||||
<template #nextMaintenanceMileage="{ row }">
|
||||
<span>{{ formatMileage(row.nextMaintenanceMileage, row.mileageUnit) }}</span>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="保养记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList, getDetail, add, update, remove } from '@/api/vehicle/maintenance-plan';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 220,
|
||||
column: [
|
||||
{
|
||||
label: '车船类型',
|
||||
prop: 'vehicleType',
|
||||
type: 'radio',
|
||||
minWidth: 110,
|
||||
dicData: [
|
||||
{ label: '车辆', value: '车辆' },
|
||||
{ label: '船舶', value: '船舶' },
|
||||
],
|
||||
value: '车辆',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '车牌号/船号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
search: true,
|
||||
minWidth: 130,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '保养人',
|
||||
prop: 'maintainer',
|
||||
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '保养时间',
|
||||
prop: 'maintenanceTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 170,
|
||||
rules: [{ required: true, message: '请选择保养时间', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '里程/航程数',
|
||||
prop: 'mileage',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 130,
|
||||
slot: true,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '里程单位',
|
||||
prop: 'mileageUnit',
|
||||
type: 'select',
|
||||
dicData: [
|
||||
{ label: '公里', value: '公里' },
|
||||
{ label: '海里', value: '海里' },
|
||||
],
|
||||
value: '公里',
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '保养项目',
|
||||
prop: 'maintenanceItem',
|
||||
minWidth: 160,
|
||||
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '费用',
|
||||
prop: 'cost',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
rules: [
|
||||
{ required: true, message: '请输入费用', trigger: 'blur' },
|
||||
{ validator: validateNonNegative, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '店名',
|
||||
prop: 'storeName',
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '联系电话',
|
||||
prop: 'contactPhone',
|
||||
minWidth: 150,
|
||||
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '地址',
|
||||
prop: 'address',
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
span: 24,
|
||||
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '下次保养时间',
|
||||
prop: 'nextMaintenanceTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '下次保养里程/航程',
|
||||
prop: 'nextMaintenanceMileage',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 170,
|
||||
slot: true,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [{ max: 500, message: '最多 500 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
},
|
||||
excelOption: {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/maintenance-plan/import-maintenance-plan',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('maintenance_plan_add'),
|
||||
viewBtn: this.hasPermission('maintenance_plan_view'),
|
||||
delBtn: this.hasPermission('maintenance_plan_delete'),
|
||||
editBtn: this.hasPermission('maintenance_plan_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
formatMileage(value, unit) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
return `${Math.max(Number(value), 0)} ${unit || '公里'}`;
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个';
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
if (!values.mileageUnit) {
|
||||
values.mileageUnit = values.vehicleType === '船舶' ? '海里' : '公里';
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
add(this.normalizeRow(row)).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
update(this.normalizeRow(row)).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出保养记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/maintenance-plan/export-maintenance-plan', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `保养记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-transport/maintenance-plan/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '保养记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.maintenance-plan-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
598
src/views/vehicle/maintenance-record.vue
Normal file
598
src/views/vehicle/maintenance-record.vue
Normal file
@@ -0,0 +1,598 @@
|
||||
<template>
|
||||
<basic-container class="maintenance-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('maintenance_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('maintenance_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('maintenance_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('maintenance_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #mileage="{ row }">
|
||||
<span>{{ formatMileage(row.mileage, row.mileageUnit) }}</span>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="维修记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList, getDetail, add, update, remove } from '@/api/vehicle/maintenance-record';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const validateNonNegative = (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback();
|
||||
} else if (Number(value) < 0) {
|
||||
callback(new Error('不能小于 0'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
option: {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 980,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 220,
|
||||
column: [
|
||||
{
|
||||
label: '车船类型',
|
||||
prop: 'vehicleType',
|
||||
type: 'radio',
|
||||
minWidth: 110,
|
||||
dicData: [
|
||||
{ label: '车辆', value: '车辆' },
|
||||
{ label: '船舶', value: '船舶' },
|
||||
],
|
||||
value: '车辆',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '请选择车船类型', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '车牌号/船号',
|
||||
prop: 'vehicleNo',
|
||||
slot: true,
|
||||
search: true,
|
||||
minWidth: 130,
|
||||
rules: [
|
||||
{ required: true, message: '请输入车牌号/船号', trigger: 'blur' },
|
||||
{ max: 30, message: '最多 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '维修人',
|
||||
prop: 'maintainer',
|
||||
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '维修时间',
|
||||
prop: 'maintenanceTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 170,
|
||||
rules: [{ required: true, message: '请选择维修时间', trigger: 'click' }],
|
||||
},
|
||||
{
|
||||
label: '维修位置',
|
||||
prop: 'location',
|
||||
minWidth: 160,
|
||||
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '更换零件',
|
||||
prop: 'replacedPart',
|
||||
minWidth: 160,
|
||||
overHidden: true,
|
||||
rules: [{ max: 200, message: '最多 200 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '费用',
|
||||
prop: 'cost',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
rules: [
|
||||
{ required: true, message: '请输入费用', trigger: 'blur' },
|
||||
{ validator: validateNonNegative, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '维修单位',
|
||||
prop: 'company',
|
||||
minWidth: 180,
|
||||
rules: [{ max: 50, message: '最多 50 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '联系方式',
|
||||
prop: 'contact',
|
||||
minWidth: 150,
|
||||
rules: [{ max: 20, message: '最多 20 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '出厂时间',
|
||||
prop: 'factoryTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '里程/航程数',
|
||||
prop: 'mileage',
|
||||
type: 'number',
|
||||
precision: 2,
|
||||
minWidth: 130,
|
||||
slot: true,
|
||||
rules: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '里程单位',
|
||||
prop: 'mileageUnit',
|
||||
type: 'select',
|
||||
dicData: [
|
||||
{ label: '公里', value: '公里' },
|
||||
{ label: '海里', value: '海里' },
|
||||
],
|
||||
value: '公里',
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '地址',
|
||||
prop: 'address',
|
||||
minWidth: 180,
|
||||
overHidden: true,
|
||||
span: 24,
|
||||
rules: [{ max: 100, message: '最多 100 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachments',
|
||||
type: 'upload',
|
||||
listType: 'text',
|
||||
multiple: true,
|
||||
accept: '.jpg,.jpeg,.png,.pdf',
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
url: 'link',
|
||||
name: 'name',
|
||||
},
|
||||
action: '/blade-resource/oss/endpoint/put-file',
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
minRows: 4,
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [{ max: 500, message: '最多 500 个字符', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '创建开始时间',
|
||||
prop: 'createTimeStart',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 00:00:00',
|
||||
valueFormat: 'YYYY-MM-DD 00:00:00',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建结束时间',
|
||||
prop: 'createTimeEnd',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD 23:59:59',
|
||||
valueFormat: 'YYYY-MM-DD 23:59:59',
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
prop: 'createDept',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
},
|
||||
checkStrictly: true,
|
||||
search: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '更新人',
|
||||
prop: 'updateUserName',
|
||||
formatter: formatUpdateUserName,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updateTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
display: false,
|
||||
minWidth: 170,
|
||||
},
|
||||
],
|
||||
},
|
||||
excelOption: {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-transport/maintenance-record/import-maintenance-record',
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('maintenance_record_add'),
|
||||
viewBtn: this.hasPermission('maintenance_record_view'),
|
||||
delBtn: this.hasPermission('maintenance_record_delete'),
|
||||
editBtn: this.hasPermission('maintenance_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
formatMileage(value, unit) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
return `${Math.max(Number(value), 0)} ${unit || '公里'}`;
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个';
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
if (!values.mileageUnit) {
|
||||
values.mileageUnit = values.vehicleType === '船舶' ? '海里' : '公里';
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
add(this.normalizeRow(row)).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
update(this.normalizeRow(row)).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出维修记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/maintenance-record/export-maintenance-record', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `维修记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-transport/maintenance-record/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '维修记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.maintenance-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
431
src/views/vehicle/mileage-record.vue
Normal file
431
src/views/vehicle/mileage-record.vue
Normal file
@@ -0,0 +1,431 @@
|
||||
<template>
|
||||
<basic-container class="mileage-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('mileage_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('mileage_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('mileage_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('mileage_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
:disabled="Boolean(form.id)"
|
||||
clearable
|
||||
placeholder="输入车牌号模糊查询选择"
|
||||
value-key="value"
|
||||
class="mileage-record-page__input"
|
||||
/>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="里程记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/mileage-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/mileage-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('mileage_record_add'),
|
||||
viewBtn: this.hasPermission('mileage_record_view'),
|
||||
delBtn: this.hasPermission('mileage_record_delete'),
|
||||
editBtn: this.hasPermission('mileage_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
getVehicleList(1, 20, { plateNo: queryString })
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(records.map(item => ({ value: item.plateNo })));
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return (
|
||||
value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个'
|
||||
);
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
isEmpty(value) {
|
||||
return value === undefined || value === null || value === '';
|
||||
},
|
||||
toNumber(value) {
|
||||
return this.isEmpty(value) ? null : Number(value);
|
||||
},
|
||||
toMoney(value) {
|
||||
return Math.round(Number(value) * 100) / 100;
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
const previousMonthMileage = this.toNumber(values.previousMonthMileage);
|
||||
const currentMonthMileage = this.toNumber(values.currentMonthMileage);
|
||||
if (this.isEmpty(values.monthlyMileage) && previousMonthMileage !== null && currentMonthMileage !== null) {
|
||||
values.monthlyMileage = this.toMoney(currentMonthMileage - previousMonthMileage);
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
validateMileage(value, label) {
|
||||
if (this.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
if (Number(value) < 0) {
|
||||
this.$message.warning(`${label}不能小于 0`);
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+(\.\d{1,2})?$/.test(String(value))) {
|
||||
this.$message.warning(`${label}最多保留 2 位小数`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validateRow(row) {
|
||||
const mileageFields = [
|
||||
['previousMonthMileage', '上月统计里程'],
|
||||
['currentMonthMileage', '本月统计里程'],
|
||||
['monthlyMileage', '本月行驶里程'],
|
||||
['totalMileage', '累计行驶里程'],
|
||||
];
|
||||
if (!mileageFields.every(([prop, label]) => this.validateMileage(row[prop], label))) {
|
||||
return false;
|
||||
}
|
||||
const previousMonthMileage = this.toNumber(row.previousMonthMileage);
|
||||
const currentMonthMileage = this.toNumber(row.currentMonthMileage);
|
||||
const monthlyMileage = this.toNumber(row.monthlyMileage);
|
||||
const totalMileage = this.toNumber(row.totalMileage);
|
||||
if (
|
||||
previousMonthMileage !== null &&
|
||||
currentMonthMileage !== null &&
|
||||
monthlyMileage !== null &&
|
||||
this.toMoney(currentMonthMileage - previousMonthMileage) !== this.toMoney(monthlyMileage)
|
||||
) {
|
||||
this.$message.warning('本月行驶里程应等于本月统计里程减去上月统计里程');
|
||||
return false;
|
||||
}
|
||||
if (totalMileage !== null && currentMonthMileage !== null && totalMileage < currentMonthMileage) {
|
||||
this.$message.warning('累计行驶里程应大于等于本月统计里程');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
|
||||
vehicleNoColumn.disabled = type !== 'add';
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出里程记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/mileage-record/export-mileage-record', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `里程记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(`/blade-transport/mileage-record/export-template?${this.website.tokenHeader}=${getToken()}`).then(
|
||||
res => {
|
||||
downloadXls(res.data, '里程记录模板.xlsx');
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mileage-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
450
src/views/vehicle/oil-electric-record.vue
Normal file
450
src/views/vehicle/oil-electric-record.vue
Normal file
@@ -0,0 +1,450 @@
|
||||
<template>
|
||||
<basic-container class="oil-electric-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('oil_electric_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('oil_electric_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('oil_electric_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('oil_electric_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
clearable
|
||||
placeholder="输入车牌号/船号查询选择"
|
||||
value-key="value"
|
||||
class="oil-electric-record-page__input"
|
||||
/>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="油电记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/oil-electric-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/oil-electric-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('oil_electric_record_add'),
|
||||
viewBtn: this.hasPermission('oil_electric_record_view'),
|
||||
delBtn: this.hasPermission('oil_electric_record_delete'),
|
||||
editBtn: this.hasPermission('oil_electric_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'form.vehicleType': {
|
||||
handler() {
|
||||
this.updateVehicleType();
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
'form.feeType': {
|
||||
handler(value) {
|
||||
this.updateFeeType(value || '加油');
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
updateVehicleType() {
|
||||
if (!this.form.id && this.form.vehicleNo) {
|
||||
this.form.vehicleNo = '';
|
||||
}
|
||||
},
|
||||
updateFeeType(feeType) {
|
||||
const oilProductColumn = this.findColumn(this.option.column, 'oilProduct');
|
||||
const unitPriceColumn = this.findColumn(this.option.column, 'unitPrice');
|
||||
oilProductColumn.display = feeType !== '充电';
|
||||
unitPriceColumn.append = feeType === '充电' ? '元/度' : '元/升';
|
||||
if (feeType === '充电') {
|
||||
this.form.oilProduct = undefined;
|
||||
}
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
const vehicleType = this.form.vehicleType || '车辆';
|
||||
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
|
||||
const params =
|
||||
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
|
||||
request(1, 20, params)
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(
|
||||
records.map(item => ({
|
||||
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return (
|
||||
value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个'
|
||||
);
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
isEmpty(value) {
|
||||
return value === undefined || value === null || value === '';
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
values.vehicleType = values.vehicleType || '车辆';
|
||||
values.dataSource = values.dataSource || '手工录入';
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleType === '船舶' ? values.vehicleNo.trim() : values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
if (values.feeType === '充电') {
|
||||
values.oilProduct = undefined;
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
validateDecimal(value, label, required = false) {
|
||||
if (this.isEmpty(value)) {
|
||||
if (required) {
|
||||
this.$message.warning(`请输入${label}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (Number(value) < 0) {
|
||||
this.$message.warning(`${label}不能小于 0`);
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+(\.\d{1,2})?$/.test(String(value))) {
|
||||
this.$message.warning(`${label}最多保留 2 位小数`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (!this.validateDecimal(row.transactionAmount, '交易金额', true)) return false;
|
||||
if (!this.validateDecimal(row.quantity, '数量')) return false;
|
||||
if (!this.validateDecimal(row.unitPrice, '单价')) return false;
|
||||
if (!this.validateDecimal(row.balance, '余额')) return false;
|
||||
if (row.remark && row.remark.length > 200) {
|
||||
this.$message.warning('备注不能超过 200 字');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form = { vehicleType: '车辆', feeType: '加油', dataSource: '手工录入' };
|
||||
this.updateFeeType('加油');
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
this.updateFeeType(detail.feeType);
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出油电记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/oil-electric-record/export-oil-electric-record', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `油电记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(`/blade-transport/oil-electric-record/export-template?${this.website.tokenHeader}=${getToken()}`).then(
|
||||
res => {
|
||||
downloadXls(res.data, '油电记录模板.xlsx');
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.oil-electric-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
441
src/views/vehicle/other-expense-record.vue
Normal file
441
src/views/vehicle/other-expense-record.vue
Normal file
@@ -0,0 +1,441 @@
|
||||
<template>
|
||||
<basic-container class="other-expense-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('other_expense_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('other_expense_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('other_expense_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('other_expense_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
clearable
|
||||
placeholder="输入车牌号/船号查询选择"
|
||||
value-key="value"
|
||||
class="other-expense-record-page__input"
|
||||
/>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="其他费用记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/other-expense-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/other-expense-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('other_expense_record_add'),
|
||||
viewBtn: this.hasPermission('other_expense_record_view'),
|
||||
delBtn: this.hasPermission('other_expense_record_delete'),
|
||||
editBtn: this.hasPermission('other_expense_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'form.vehicleType': {
|
||||
handler() {
|
||||
this.updateVehicleType();
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
updateVehicleType() {
|
||||
if (!this.form.id && this.form.vehicleNo) {
|
||||
this.form.vehicleNo = '';
|
||||
}
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
const vehicleType = this.form.vehicleType || '车辆';
|
||||
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
|
||||
const params =
|
||||
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
|
||||
request(1, 20, params)
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(
|
||||
records.map(item => ({
|
||||
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return (
|
||||
value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个'
|
||||
);
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
isEmpty(value) {
|
||||
return value === undefined || value === null || value === '';
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
values.vehicleType = values.vehicleType || '车辆';
|
||||
values.dataSource = values.dataSource || '手工录入';
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleType === '船舶' ? values.vehicleNo.trim() : values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
validateDecimal(value, label, required = false) {
|
||||
if (this.isEmpty(value)) {
|
||||
if (required) {
|
||||
this.$message.warning(`请输入${label}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (Number(value) < 0) {
|
||||
this.$message.warning(`${label}不能小于 0`);
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+(\.\d{1,2})?$/.test(String(value))) {
|
||||
this.$message.warning(`${label}最多保留 2 位小数`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (!this.validateDecimal(row.amount, '金额', true)) return false;
|
||||
if (row.remark && row.remark.length > 200) {
|
||||
this.$message.warning('备注不能超过 200 字');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form = {
|
||||
vehicleType: '车辆',
|
||||
expenseDate: this.$dayjs().format('YYYY-MM-DD'),
|
||||
dataSource: '手工录入',
|
||||
};
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
const { expenseDateRange } = this.query;
|
||||
const values = { ...params, ...this.query };
|
||||
if (expenseDateRange) {
|
||||
values.expenseDateStart = expenseDateRange[0];
|
||||
values.expenseDateEnd = expenseDateRange[1];
|
||||
values.expenseDateRange = null;
|
||||
}
|
||||
return values;
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, this.buildQuery(params))
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出其他费用记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/other-expense-record/export-other-expense-record', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `其他费用记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.buildQuery(),
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(`/blade-transport/other-expense-record/export-template?${this.website.tokenHeader}=${getToken()}`).then(
|
||||
res => {
|
||||
downloadXls(res.data, '其他费用记录模板.xlsx');
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.other-expense-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
393
src/views/vehicle/tire-replacement-record.vue
Normal file
393
src/views/vehicle/tire-replacement-record.vue
Normal file
@@ -0,0 +1,393 @@
|
||||
<template>
|
||||
<basic-container class="tire-replacement-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('tire_replacement_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('tire_replacement_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('tire_replacement_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('tire_replacement_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
clearable
|
||||
placeholder="请输入关键字"
|
||||
value-key="value"
|
||||
class="tire-replacement-record-page__input"
|
||||
/>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="换胎记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/tire-replacement-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/tire-replacement-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('tire_replacement_record_add'),
|
||||
viewBtn: this.hasPermission('tire_replacement_record_view'),
|
||||
delBtn: this.hasPermission('tire_replacement_record_delete'),
|
||||
editBtn: this.hasPermission('tire_replacement_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
getVehicleList(1, 20, { plateNo: queryString })
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(records.map(item => ({ value: item.plateNo })));
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return (
|
||||
value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个'
|
||||
);
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (Number(row.replacementCost) < 0) {
|
||||
this.$message.warning('换胎费用不能小于 0');
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
row.tireQuantity !== undefined &&
|
||||
row.tireQuantity !== null &&
|
||||
row.tireQuantity !== '' &&
|
||||
(!Number.isInteger(Number(row.tireQuantity)) || Number(row.tireQuantity) <= 0)
|
||||
) {
|
||||
this.$message.warning('换胎数量必须为正整数');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form = {};
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出换胎记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob(
|
||||
'/blade-transport/tire-replacement-record/export-tire-replacement-record',
|
||||
this.buildExportParams()
|
||||
)
|
||||
.then(res => {
|
||||
downloadXls(res.data, `换胎记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-transport/tire-replacement-record/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '换胎记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tire-replacement-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
411
src/views/vehicle/transport-change-record.vue
Normal file
411
src/views/vehicle/transport-change-record.vue
Normal file
@@ -0,0 +1,411 @@
|
||||
<template>
|
||||
<basic-container class="transport-change-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('transport_change_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('transport_change_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('transport_change_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('transport_change_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button v-if="row.vehicleNo" type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
clearable
|
||||
placeholder="输入车牌号/船号模糊查询选择"
|
||||
value-key="value"
|
||||
class="transport-change-record-page__input"
|
||||
/>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="变更记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/transport-change-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/transport-change-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('transport_change_record_add'),
|
||||
viewBtn: this.hasPermission('transport_change_record_view'),
|
||||
delBtn: this.hasPermission('transport_change_record_delete'),
|
||||
editBtn: this.hasPermission('transport_change_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'form.vehicleType': {
|
||||
handler(value) {
|
||||
this.updateVehicleType(value || '车辆');
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
updateVehicleType() {
|
||||
if (!this.form.id && this.form.vehicleNo) {
|
||||
this.form.vehicleNo = '';
|
||||
}
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
const vehicleType = this.form.vehicleType || '车辆';
|
||||
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
|
||||
const params =
|
||||
vehicleType === '船舶' ? { shipIdentifierNo: queryString } : { plateNo: queryString };
|
||||
request(1, 20, params)
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(
|
||||
records.map(item => ({
|
||||
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return (
|
||||
value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个'
|
||||
);
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
values.vehicleType = values.vehicleType || '车辆';
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleType === '船舶' ? values.vehicleNo.trim() : values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (row.changeContent && row.changeContent.length > 200) {
|
||||
this.$message.warning('变更内容不能超过 200 字');
|
||||
return false;
|
||||
}
|
||||
if (row.remark && row.remark.length > 200) {
|
||||
this.$message.warning('备注不能超过 200 字');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form = { vehicleType: '车辆' };
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出变更记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob(
|
||||
'/blade-transport/transport-change-record/export-transport-change-record',
|
||||
this.buildExportParams()
|
||||
)
|
||||
.then(res => {
|
||||
downloadXls(res.data, `变更记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-transport/transport-change-record/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '变更记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.transport-change-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
490
src/views/vehicle/violation-record.vue
Normal file
490
src/views/vehicle/violation-record.vue
Normal file
@@ -0,0 +1,490 @@
|
||||
<template>
|
||||
<basic-container class="violation-record-page">
|
||||
<avue-crud
|
||||
:option="option"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
:permission="permissionList"
|
||||
:before-open="beforeOpen"
|
||||
v-model="form"
|
||||
ref="crud"
|
||||
@row-update="rowUpdate"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-upload"
|
||||
plain
|
||||
v-if="hasPermission('violation_record_import')"
|
||||
@click="handleImport"
|
||||
>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('violation_record_template')"
|
||||
@click="handleTemplate"
|
||||
>下载模板
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
plain
|
||||
v-if="hasPermission('violation_record_export')"
|
||||
@click="handleExport"
|
||||
>批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission('violation_record_delete')"
|
||||
@click="handleDelete"
|
||||
>批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNo="{ row, index }">
|
||||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||||
{{ row.vehicleNo }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #vehicleNoForm>
|
||||
<el-autocomplete
|
||||
v-model="form.vehicleNo"
|
||||
:fetch-suggestions="fetchVehicleOptions"
|
||||
clearable
|
||||
placeholder="输入车牌号/船号模糊查询选择"
|
||||
value-key="value"
|
||||
class="violation-record-page__input"
|
||||
/>
|
||||
</template>
|
||||
<template #driverNameForm>
|
||||
<el-autocomplete
|
||||
v-if="form.vehicleType !== '船舶'"
|
||||
v-model="form.driverName"
|
||||
:fetch-suggestions="fetchDriverOptions"
|
||||
clearable
|
||||
placeholder="输入驾驶人模糊查询选择"
|
||||
value-key="value"
|
||||
class="violation-record-page__input"
|
||||
/>
|
||||
<el-input v-else v-model="form.driverName" clearable maxlength="20" placeholder="请输入船长" />
|
||||
</template>
|
||||
<template #processStatus="{ row }">
|
||||
<span :class="row.processStatus === '未处理' ? 'violation-record-page__danger' : ''">
|
||||
{{ row.processStatus }}
|
||||
</span>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<el-dialog title="违章记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||||
<avue-form :option="excelOption" v-model="excelForm" :upload-after="uploadAfter">
|
||||
<template #excelTemplate>
|
||||
<el-button type="primary" @click="handleTemplate">
|
||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-form>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/violation-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
|
||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, option } from '@/option/vehicle/violation-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
query: {},
|
||||
loading: true,
|
||||
excelBox: false,
|
||||
excelForm: {},
|
||||
option,
|
||||
excelOption,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo.authority || '';
|
||||
return authority.includes('admin');
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.hasPermission('violation_record_add'),
|
||||
viewBtn: this.hasPermission('violation_record_view'),
|
||||
delBtn: this.hasPermission('violation_record_delete'),
|
||||
editBtn: this.hasPermission('violation_record_edit'),
|
||||
};
|
||||
},
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele.id);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'form.vehicleType': {
|
||||
handler(value) {
|
||||
this.updateVehicleTypeDisplays(value || '车辆');
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
'form.processStatus': {
|
||||
handler(value) {
|
||||
this.updateProcessResultDisplay(value || '已处理');
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
},
|
||||
initDeptTree() {
|
||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
const column = this.findColumn(this.option.column, 'createDept');
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
updateVehicleTypeDisplays(vehicleType) {
|
||||
const vehicleTypeColumn = this.findColumn(this.option.column, 'vehicleType');
|
||||
const driverColumn = this.findColumn(this.option.column, 'driverName');
|
||||
const typeColumn = this.findColumn(this.option.column, 'violationType');
|
||||
const itemColumn = this.findColumn(this.option.column, 'violationItem');
|
||||
driverColumn.label = vehicleType === '船舶' ? '船长' : '驾驶人';
|
||||
typeColumn.display = vehicleType !== '船舶';
|
||||
itemColumn.display = vehicleType === '船舶';
|
||||
vehicleTypeColumn.disabled = Boolean(this.form.id);
|
||||
if (vehicleType === '船舶') {
|
||||
this.form.violationType = undefined;
|
||||
} else {
|
||||
this.form.violationItem = undefined;
|
||||
}
|
||||
},
|
||||
updateProcessResultDisplay(processStatus) {
|
||||
const column = this.findColumn(this.option.column, 'processResult');
|
||||
column.display = processStatus !== '未处理';
|
||||
if (processStatus === '未处理') {
|
||||
this.form.processResult = undefined;
|
||||
}
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
const vehicleType = this.form.vehicleType || '车辆';
|
||||
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
|
||||
const params =
|
||||
vehicleType === '船舶'
|
||||
? { shipIdentifierNo: queryString }
|
||||
: { plateNo: queryString };
|
||||
request(1, 20, params)
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(
|
||||
records.map(item => ({
|
||||
value: vehicleType === '船舶' ? item.shipIdentifierNo || item.shipName : item.plateNo,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
fetchDriverOptions(queryString, callback) {
|
||||
getDriverList(1, 20, { driverName: queryString })
|
||||
.then(res => {
|
||||
const records = res.data.data.records || [];
|
||||
callback(records.map(item => ({ value: item.driverName })));
|
||||
})
|
||||
.catch(() => callback([]));
|
||||
},
|
||||
formatAttachments(value) {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value)) return `${value.length} 个`;
|
||||
if (typeof value !== 'string') return '1 个';
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? `${attachments.length} 个` : '1 个';
|
||||
} catch (error) {
|
||||
return (
|
||||
value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean).length + ' 个'
|
||||
);
|
||||
}
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value || Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
try {
|
||||
const attachments = JSON.parse(value);
|
||||
return Array.isArray(attachments) ? attachments : [];
|
||||
} catch (error) {
|
||||
return value
|
||||
.split(',')
|
||||
.map(item => ({ name: item.trim(), url: item.trim() }))
|
||||
.filter(item => item.url);
|
||||
}
|
||||
},
|
||||
stringifyAttachments(value) {
|
||||
if (!value || typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
normalizeRow(row) {
|
||||
const values = { ...row };
|
||||
values.vehicleType = values.vehicleType || '车辆';
|
||||
values.processStatus = values.processStatus || '已处理';
|
||||
if (values.vehicleNo) {
|
||||
values.vehicleNo = values.vehicleNo.trim().toUpperCase();
|
||||
}
|
||||
if (values.processStatus === '未处理') {
|
||||
values.processResult = undefined;
|
||||
}
|
||||
if (values.vehicleType === '船舶') {
|
||||
values.violationType = undefined;
|
||||
} else {
|
||||
values.violationItem = undefined;
|
||||
}
|
||||
values.attachments = this.stringifyAttachments(values.attachments);
|
||||
return values;
|
||||
},
|
||||
validateRow(row) {
|
||||
if (row.violationTime && new Date(row.violationTime.replace(/-/g, '/')).getTime() > Date.now()) {
|
||||
this.$message.warning('时间不能超过当前时间');
|
||||
return false;
|
||||
}
|
||||
if (Number(row.fineAmount) < 0) {
|
||||
this.$message.warning('被罚金额不能小于 0');
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
row.deductPoints !== undefined &&
|
||||
row.deductPoints !== null &&
|
||||
row.deductPoints !== '' &&
|
||||
(!Number.isInteger(Number(row.deductPoints)) ||
|
||||
Number(row.deductPoints) < 0 ||
|
||||
Number(row.deductPoints) > 15)
|
||||
) {
|
||||
this.$message.warning('被扣分数范围为 0-15 分');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
add(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const values = this.normalizeRow(row);
|
||||
if (!this.validateRow(values)) {
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
update(values).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
loading();
|
||||
}
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form = {
|
||||
vehicleType: '车辆',
|
||||
processStatus: '已处理',
|
||||
};
|
||||
this.updateVehicleTypeDisplays('车辆');
|
||||
this.updateProcessResultDisplay('已处理');
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
getDetail(this.form.id).then(res => {
|
||||
const detail = res.data.data;
|
||||
detail.attachments = this.parseAttachments(detail.attachments);
|
||||
this.form = detail;
|
||||
this.updateVehicleTypeDisplays(detail.vehicleType);
|
||||
this.updateProcessResultDisplay(detail.processStatus);
|
||||
});
|
||||
}
|
||||
done();
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
this.selectionClear();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleImport() {
|
||||
this.excelBox = true;
|
||||
},
|
||||
uploadAfter(res, done) {
|
||||
this.excelBox = false;
|
||||
this.onLoad(this.page);
|
||||
done();
|
||||
},
|
||||
handleExport() {
|
||||
this.$confirm('是否导出违章记录数据?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
NProgress.start();
|
||||
exportBlob('/blade-transport/violation-record/export-violation-record', this.buildExportParams())
|
||||
.then(res => {
|
||||
downloadXls(res.data, `违章记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
},
|
||||
buildExportParams() {
|
||||
return {
|
||||
...this.query,
|
||||
ids: this.ids,
|
||||
[this.website.tokenHeader]: getToken(),
|
||||
};
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
`/blade-transport/violation-record/export-template?${this.website.tokenHeader}=${getToken()}`
|
||||
).then(res => {
|
||||
downloadXls(res.data, '违章记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.violation-record-page {
|
||||
:deep(.el-table th .cell),
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
115
常用地址需求卡.md
Normal file
115
常用地址需求卡.md
Normal file
@@ -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 字段;涉及人员展示时优先使用姓名字段。
|
||||
183
模块需求卡生成规范.md
Normal file
183
模块需求卡生成规范.md
Normal file
@@ -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` 是否都有功能点、异常与边界、反例。
|
||||
- 异常与边界是否包含处理方式,而不只是问题名称。
|
||||
- 反例是否使用“不允许”口径。
|
||||
- 编号是否连续且无重复。
|
||||
- 是否覆盖新增、更新、导入、导出、启停、权限、审计等常见管理功能。
|
||||
- 是否把用户明确禁止的能力写成了可用能力。
|
||||
- 是否保持非表格格式。
|
||||
Reference in New Issue
Block a user