feat:提交从库数据源管理、多币种管理

This commit is contained in:
Kris 2026-01-26 10:20:55 +08:00
parent 95b944518a
commit 1d5083b243
13 changed files with 1718 additions and 29 deletions

View File

@ -8,11 +8,11 @@
- **模块归属**: datai-salesforce-setting
- **版本号**: v1.0
- **创建日期**: 2026-01-14
- **最后更新**: 2026-01-14
- **最后更新**: 2026-01-24
## 功能描述
查询配置环境列表支持分页查询和多条件过滤。返回的环境数据包含环境ID、环境名称、环境编码、ORG类型、环境描述、是否激活等信息便于用户管理和切换不同环境。
查询配置环境列表支持分页查询和多条件过滤。返回的环境数据包含环境ID、环境名称、环境编码、ORG类型、环境描述、是否激活、是否有效等信息,便于用户管理和切换不同环境。
## 请求参数
@ -20,7 +20,7 @@
| 参数名 | 类型 | 必填 | 描述 | 示例 | 默认值 |
|--------|------|------|------|------|--------|
| pageNum | Integer | 否 | 页码 | 1 | 1 |
| pageNum | Integer | 否 | 页码 |1 | 1 |
| pageSize | Integer | 否 | 每页数量 | 10 | 10 |
| deptId | Long | 否 | 部门ID | 1 | - |
| environmentName | String | 否 | 环境名称 | 生产环境 | - |
@ -28,6 +28,8 @@
| orgType | String | 否 | ORG类型 | source | - |
| description | String | 否 | 环境描述 | 生产环境配置 | - |
| isActive | Boolean | 否 | 是否激活 | true | - |
| isValid | Boolean | 否 | 是否有效 | true | - |
| datasourceId | Long | 否 | 数据源ID | 1 | - |
| remark | String | 否 | 备注 | - | - |
| createBy | String | 否 | 创建人 | admin | - |
| createTime | Date | 否 | 创建时间 | 2026-01-01 00:00:00 | - |
@ -96,6 +98,8 @@
| orgType | String | ORG类型source/target | source |
| description | String | 环境描述 | 生产环境配置 |
| isActive | Boolean | 是否激活 | true |
| isValid | Boolean | 是否有效 | true |
| datasourceId | Long | 数据源ID | 1 |
| remark | String | 备注 | 源ORG生产环境 |
| createBy | String | 创建人 | admin |
| createTime | String | 创建时间 | 2026-01-01 10:00:00 |

View File

@ -8,11 +8,11 @@
- **模块归属**: datai-salesforce-setting
- **版本号**: v1.0
- **创建日期**: 2026-01-14
- **最后更新**: 2026-01-14
- **最后更新**: 2026-01-24
## 功能描述
根据环境ID获取配置环境的详细信息包括环境ID、环境名称、环境编码、ORG类型、环境描述、是否激活等完整信息用于查看或编辑环境配置。
根据环境ID获取配置环境的详细信息包括环境ID、环境名称、环境编码、ORG类型、环境描述、是否激活、是否有效等完整信息,用于查看或编辑环境配置。
## 请求参数
@ -66,6 +66,8 @@
| orgType | String | ORG类型source/target | source |
| description | String | 环境描述 | 生产环境配置 |
| isActive | Boolean | 是否激活 | true |
| isValid | Boolean | 是否有效 | true |
| datasourceId | Long | 数据源ID | 1 |
| remark | String | 备注 | 源ORG生产环境 |
| createBy | String | 创建人 | admin |
| createTime | String | 创建时间 | 2026-01-01 10:00:00 |

View File

@ -8,7 +8,7 @@
- **模块归属**: datai-salesforce-setting
- **版本号**: v1.0
- **创建日期**: 2026-01-14
- **最后更新**: 2026-01-14
- **最后更新**: 2026-01-24
## 功能描述

View File

@ -8,7 +8,7 @@
- **模块归属**: datai-salesforce-setting
- **版本号**: v1.0
- **创建日期**: 2026-01-14
- **最后更新**: 2026-01-14
- **最后更新**: 2026-01-24
## 功能描述

View File

@ -0,0 +1,212 @@
# 接口文档
## 接口信息
- **接口名称**: 初始化环境从库
- **接口路径**: /setting/environment/initSlave
- **请求方法**: POST
- **模块归属**: datai-salesforce-setting
- **版本号**: v1.0
- **创建日期**: 2026-01-24
- **最后更新**: 2026-01-24
## 功能描述
为指定的环境初始化从库数据源。该接口会从配置文件读取主库配置自动生成从库名称slave_环境编码创建数据库写入数据源配置表注册数据源到 DataSourceManager并验证连接有效性。从库一经创建不允许任何修改、删除或重新初始化。
## 请求参数
### 请求参数
| 参数名 | 类型 | 必填 | 描述 | 示例 | 默认值 |
|--------|------|------|------|------|--------|
| environmentId | Long | 是 | 环境ID | 1 | - |
**参数说明**:
- `environmentId`: 要初始化从库的环境ID必须是已存在的环境
## 响应数据
### 成功响应
**HTTP 状态码**: 200 OK
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"dsName": "PROD",
"dbName": "slave_PROD",
"dbHost": "localhost",
"dbPort": 3306,
"dbType": "mysql",
"username": "root",
"password": "******",
"status": "0",
"createBy": "admin",
"createTime": "2026-01-24 10:00:00",
"updateBy": "admin",
"updateTime": "2026-01-24 10:00:00"
}
}
```
| 字段名 | 类型 | 描述 | 示例 |
|--------|------|------|------|
| code | Integer | 响应码 | 200 |
| msg | String | 响应消息 | 操作成功 |
| data | Object | 创建的数据源配置详细信息 | - |
**data 对象字段说明**:
| 字段名 | 类型 | 描述 | 示例 |
|--------|------|------|------|
| id | Long | 数据源ID | 1 |
| dsName | String | 数据源名称(环境编码) | PROD |
| dbName | String | 数据库名称 | slave_PROD |
| dbHost | String | 数据库主机地址 | localhost |
| dbPort | Integer | 数据库端口 | 3306 |
| dbType | String | 数据库类型 | mysql |
| username | String | 数据库用户名 | root |
| password | String | 数据库密码(加密) | ****** |
| status | String | 状态0-正常1-停用) | 0 |
| createBy | String | 创建人 | admin |
| createTime | String | 创建时间 | 2026-01-24 10:00:00 |
| updateBy | String | 更新人 | admin |
| updateTime | String | 更新时间 | 2026-01-24 10:00:00 |
### 失败响应
**HTTP 状态码**: 400/401/403/500
```json
{
"code": 500,
"msg": "数据源名称已存在PROD",
"data": null
}
```
| 错误码 | 错误信息 | 描述 |
|--------|----------|------|
| 400 | 参数错误 | 参数验证失败 |
| 401 | 您没有权限执行此操作 | 权限不足 |
| 403 | 访问被拒绝 | 访问被拒绝 |
| 500 | 数据源名称已存在:{环境编码} | 数据源已存在 |
| 500 | 主库配置不存在 | 主库配置不存在 |
| 500 | 数据库操作失败:{错误信息} | 数据库操作失败 |
| 500 | 数据源配置写入失败 | 数据源配置写入失败 |
| 500 | 数据源连接验证失败 | 数据源连接验证失败 |
## 接口示例
### 请求示例
```bash
curl -X POST "http://localhost:8080/setting/environment/initSlave" \
-H "Authorization: Bearer [token]" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "environmentId=1"
```
### 响应示例
**成功**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"dsName": "PROD",
"dbName": "slave_PROD",
"dbHost": "localhost",
"dbPort": 3306,
"dbType": "mysql",
"username": "root",
"password": "******",
"status": "0"
}
}
```
**失败**:
```json
{
"code": 500,
"msg": "数据源名称已存在PROD",
"data": null
}
```
## 错误处理
1. **权限验证失败**: 检查用户是否具有 setting:environment:initSlave 权限
2. **参数验证失败**: 检查必填参数是否完整,参数格式是否正确
3. **主库配置不存在**: 检查 application-druid.yml 中是否配置了主库信息
4. **数据源已存在**: 检查数据源名称是否已存在,如已存在则返回错误
5. **数据库创建失败**: 检查数据库连接和权限,记录错误日志并返回错误信息
6. **数据源配置写入失败**: 检查数据库连接和权限,记录错误日志并返回错误信息
7. **数据源连接验证失败**: 检查数据源配置和连接,记录错误日志并返回错误信息
8. **系统错误**: 记录错误日志并返回通用错误信息
## 注意事项
1. 接口需要登录认证,需要携带有效的 token
2. 接口需要 setting:environment:initSlave 权限
3. 环境编码为必填项
4. 从库名称自动生成为 slave_环境编码
5. 数据库字符集为 utf8mb4排序规则为 utf8mb4_general_ci
6. 从库一经创建,不允许任何修改、删除或重新初始化
7. 使用 Spring 事务保证原子性,验证失败时自动回滚
8. 初始化操作会记录到操作日志中
9. 初始化成功后会返回创建的数据源配置信息
10. 主库配置从 application-druid.yml 配置文件中读取
11. 初始化过程包括:创建数据库、写入配置、注册数据源、切换到从库、验证连接、切换回主库
12. 建议在初始化前先查询确认环境信息
## 相关接口
- [查询配置环境列表](0001-environment-list.md) - 查询配置环境列表
- [获取配置环境详细信息](0003-get-info.md) - 获取单个环境的详细信息
- [切换当前环境](0007-switch-environment.md) - 切换当前环境
- [获取当前激活的环境](0008-get-current-environment.md) - 获取当前激活的环境
## 实现细节
1. 接收环境ID参数
2. 根据环境ID查询环境信息
3. 从环境信息中获取环境编码
4. 调用 IDataiConfigEnvironmentService 的 initSlaveDatasource 方法
5. 从 application-druid.yml 配置文件中读取主库配置
6. 生成从库名称slave_环境编码
7. 检查数据源名称是否已存在
8. 使用 JDBC 创建数据库字符集utf8mb4排序规则utf8mb4_general_ci
9. 写入数据源配置表
10. 注册数据源到 DataSourceManager
11. 切换到从库并验证连接有效性
12. 切换回主库
13. 使用 Spring 事务保证原子性,验证失败时自动回滚
14. 返回创建的数据源配置信息
## 测试信息
### 测试环境
- **环境**: 本地开发环境
- **版本**: v1.0
### 测试用例
| 测试场景 | 输入参数 | 预期结果 | 实际结果 | 状态 |
|----------|----------|----------|----------|------|
| 初始化生产环境从库 | environmentId=1 | 初始化成功 | 初始化成功 | 通过 |
| 初始化测试环境从库 | environmentId=2 | 初始化成功 | 初始化成功 | 通过 |
| 初始化已存在的从库 | environmentId=1已存在 | 返回数据源已存在 | 返回数据源已存在 | 通过 |
| 初始化不存在的环境 | environmentId=999 | 返回错误 | 返回错误 | 通过 |
| 缺少必填参数 | 缺少environmentId | 返回参数错误 | 返回参数错误 | 通过 |
| 无权限访问 | 无权限token | 返回401错误 | 返回401错误 | 通过 |

View File

@ -38,11 +38,11 @@ export function delEnvironment(id) {
})
}
export function switchEnvironment(data) {
export function switchEnvironment(params) {
return request({
url: '/setting/environment/switch',
method: 'post',
data: data
params: params
})
}
@ -53,3 +53,11 @@ export function getCurrentEnvironment(orgType) {
params: { orgType }
})
}
export function initSlaveDatasource(environmentId) {
return request({
url: '/setting/environment/initSlave',
method: 'post',
params: { environmentId }
})
}

View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询数据源管理列表
export function listDatasourceConfig(query) {
return request({
url: '/system/datasourceConfig/list',
method: 'get',
params: query
})
}
// 查询数据源管理详细
export function getDatasourceConfig(id) {
return request({
url: '/system/datasourceConfig/' + id,
method: 'get'
})
}
// 新增数据源管理
export function addDatasourceConfig(data) {
return request({
url: '/system/datasourceConfig',
method: 'post',
data: data
})
}
// 修改数据源管理
export function updateDatasourceConfig(data) {
return request({
url: '/system/datasourceConfig',
method: 'put',
data: data
})
}
// 删除数据源管理
export function delDatasourceConfig(id) {
return request({
url: '/system/datasourceConfig/' + id,
method: 'delete'
})
}

View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询汇率管理列表
export function listExchangerate(query) {
return request({
url: '/system/exchangerate/list',
method: 'get',
params: query
})
}
// 查询汇率管理详细
export function getExchangerate(id) {
return request({
url: '/system/exchangerate/' + id,
method: 'get'
})
}
// 新增汇率管理
export function addExchangerate(data) {
return request({
url: '/system/exchangerate',
method: 'post',
data: data
})
}
// 修改汇率管理
export function updateExchangerate(data) {
return request({
url: '/system/exchangerate',
method: 'put',
data: data
})
}
// 删除汇率管理
export function delExchangerate(id) {
return request({
url: '/system/exchangerate/' + id,
method: 'delete'
})
}

View File

@ -4,7 +4,10 @@
<template #header>
<div class="card-header">
<span>当前源环境</span>
<el-tag v-if="currentEnvironment" type="success">{{ currentEnvironment.environmentName }}</el-tag>
<el-tag v-if="currentEnvironment" type="success" effect="dark">
<el-icon class="tag-icon"><Monitor /></el-icon>
{{ currentEnvironment.environmentName }}
</el-tag>
<el-tag v-else type="info">未设置</el-tag>
</div>
</template>
@ -24,39 +27,107 @@
{{ currentEnvironment.isActive ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="是否有效">
<el-tag :type="currentEnvironment.isValid ? 'success' : 'danger'">
{{ currentEnvironment.isValid ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="数据源ID" v-if="currentEnvironment.datasourceId">
{{ currentEnvironment.datasourceId }}
</el-descriptions-item>
<el-descriptions-item label="备注" :span="2" v-if="currentEnvironment.remark">
{{ currentEnvironment.remark }}
</el-descriptions-item>
<el-descriptions-item label="创建人" v-if="currentEnvironment.createBy">
{{ currentEnvironment.createBy }}
</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ parseTime(currentEnvironment.createTime) }}
</el-descriptions-item>
<el-descriptions-item label="更新人" v-if="currentEnvironment.updateBy">
{{ currentEnvironment.updateBy }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" v-if="currentEnvironment.updateTime">
{{ parseTime(currentEnvironment.updateTime) }}
</el-descriptions-item>
</el-descriptions>
<el-empty v-else description="暂无当前源环境" />
<el-empty v-else description="暂无当前源环境" :image-size="60">
<template #description>
<p class="empty-tip">请先配置并激活源环境</p>
</template>
</el-empty>
</el-card>
<el-card shadow="never" class="mt10">
<template #header>
<div class="card-header">
<span>源环境列表</span>
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['setting:environment:add']">新增源环境</el-button>
<div class="header-actions">
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['setting:environment:add']">新增源环境</el-button>
<el-button type="success" icon="Files" @click="handleInitSlave" v-hasPermi="['setting:environment:initSlave']">初始化从库</el-button>
</div>
</div>
</template>
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px" v-show="showSearch">
<el-form-item label="环境名称" prop="environmentName">
<el-input v-model="queryParams.environmentName" placeholder="请输入环境名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="环境编码" prop="environmentCode">
<el-input v-model="queryParams.environmentCode" placeholder="请输入环境编码" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="是否激活" prop="isActive">
<el-select v-model="queryParams.isActive" placeholder="是否激活" clearable style="width: 120px">
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="isValid">
<el-select v-model="queryParams.isValid" placeholder="是否有效" clearable style="width: 120px">
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="environmentList">
<el-table-column label="环境名称" align="center" prop="environmentName" />
<el-table-column label="环境编码" align="center" prop="environmentCode" />
<el-table-column label="环境描述" align="center" prop="description" show-overflow-tooltip />
<el-table-column label="是否激活" align="center" prop="isActive">
<el-table-column label="环境名称" align="center" prop="environmentName" min-width="120" />
<el-table-column label="环境编码" align="center" prop="environmentCode" min-width="100" />
<el-table-column label="环境描述" align="center" prop="description" show-overflow-tooltip min-width="150" />
<el-table-column label="是否激活" align="center" prop="isActive" width="100">
<template #default="scope">
<el-tag :type="scope.row.isActive ? 'success' : 'danger'">
<el-tag :type="scope.row.isActive ? 'success' : 'danger'" size="small">
{{ scope.row.isActive ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="是否有效" align="center" prop="isValid" width="100">
<template #default="scope">
<el-tag :type="scope.row.isValid ? 'success' : 'danger'" size="small">
{{ scope.row.isValid ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="数据源ID" align="center" prop="datasourceId" width="100" />
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip min-width="120" />
<el-table-column label="创建人" align="center" prop="createBy" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="240" fixed="right">
<el-table-column label="更新人" align="center" prop="updateBy" width="100" />
<el-table-column label="更新时间" align="center" prop="updateTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.updateTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="280" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.isActive && (!currentEnvironment || currentEnvironment.id !== scope.row.id)"
@ -73,10 +144,12 @@
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="environmentRef" :model="form" :rules="rules" label-width="100px">
<el-dialog :title="title" v-model="open" width="700px" append-to-body>
<el-form ref="environmentRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="环境名称" prop="environmentName">
<el-input v-model="form.environmentName" placeholder="请输入环境名称" />
</el-form-item>
@ -84,13 +157,19 @@
<el-input v-model="form.environmentCode" placeholder="请输入环境编码dev、test、prod" />
</el-form-item>
<el-form-item label="环境描述" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入环境描述" />
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入环境描述" />
</el-form-item>
<el-form-item label="是否激活" prop="isActive">
<el-switch v-model="form.isActive" />
<el-switch v-model="form.isActive" active-text="激活" inactive-text="停用" />
</el-form-item>
<el-form-item label="是否有效" prop="isValid">
<el-switch v-model="form.isValid" active-text="有效" inactive-text="无效" />
</el-form-item>
<el-form-item label="数据源ID" prop="datasourceId">
<el-input-number v-model="form.datasourceId" placeholder="请输入数据源ID" :min="1" style="width: 100%" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
@ -107,7 +186,7 @@
<el-input v-model="switchForm.environmentCode" disabled />
</el-form-item>
<el-form-item label="切换原因" prop="switchReason">
<el-input v-model="switchForm.switchReason" type="textarea" placeholder="请输入切换原因" />
<el-input v-model="switchForm.switchReason" type="textarea" :rows="3" placeholder="请输入切换原因" />
</el-form-item>
</el-form>
<template #footer>
@ -117,12 +196,43 @@
</div>
</template>
</el-dialog>
<el-dialog title="初始化从库" v-model="initSlaveOpen" width="500px" append-to-body>
<el-alert title="注意事项" type="warning" :closable="false" style="margin-bottom: 20px">
<template #default>
<ul style="margin: 0; padding-left: 20px;">
<li>从库一经创建不允许任何修改删除或重新初始化</li>
<li>从库名称自动生成为 slave_环境编码</li>
<li>初始化过程包括创建数据库写入配置注册数据源验证连接</li>
</ul>
</template>
</el-alert>
<el-form ref="initSlaveRef" :model="initSlaveForm" :rules="initSlaveRules" label-width="100px">
<el-form-item label="环境" prop="environmentId">
<el-select v-model="initSlaveForm.environmentId" placeholder="请选择环境" style="width: 100%" filterable>
<el-option
v-for="env in environmentList"
:key="env.id"
:label="`${env.environmentName} (${env.environmentCode})`"
:value="env.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitInitSlave" :loading="initSlaveLoading"> </el-button>
<el-button @click="initSlaveOpen = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { listEnvironment, getEnvironment, addEnvironment, updateEnvironment, delEnvironment, switchEnvironment, getCurrentEnvironment } from "@/api/setting/environment";
import { listEnvironment, getEnvironment, addEnvironment, updateEnvironment, delEnvironment, switchEnvironment, getCurrentEnvironment, initSlaveDatasource } from "@/api/setting/environment";
import { ElMessage, ElMessageBox } from 'element-plus';
import { Monitor, Files } from '@element-plus/icons-vue';
const props = defineProps({
currentEnvironment: {
@ -136,8 +246,12 @@
const environmentList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const open = ref(false);
const switchOpen = ref(false);
const initSlaveOpen = ref(false);
const initSlaveLoading = ref(false);
const title = ref("");
const form = ref({});
@ -146,6 +260,19 @@
switchReason: '手动切换',
orgType: 'source'
});
const initSlaveForm = ref({
environmentId: ''
});
const queryParams = ref({
pageNum: 1,
pageSize: 10,
orgType: 'source',
environmentName: null,
environmentCode: null,
isActive: null,
isValid: null
});
const rules = {
environmentName: [
@ -158,12 +285,19 @@
const switchRules = {};
const initSlaveRules = {
environmentId: [
{ required: true, message: "请选择环境", trigger: "change" }
]
};
const getList = async () => {
loading.value = true;
try {
const response = await listEnvironment({ orgType: 'source' });
const response = await listEnvironment(queryParams.value);
if (response.code === 200) {
environmentList.value = response.rows || [];
total.value = response.total || 0;
}
} catch (error) {
console.error('获取源环境列表失败:', error);
@ -172,12 +306,69 @@
}
};
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
proxy.resetForm("queryRef");
queryParams.value = {
pageNum: 1,
pageSize: 10,
orgType: 'source',
environmentName: null,
environmentCode: null,
isActive: null,
isValid: null
};
handleQuery();
};
const handleAdd = () => {
reset();
open.value = true;
title.value = "新增源环境";
};
const handleInitSlave = () => {
initSlaveForm.value = {
environmentId: ''
};
initSlaveOpen.value = true;
};
const submitInitSlave = async () => {
proxy.$refs.initSlaveRef.validate(async (valid) => {
if (valid) {
try {
const selectedEnv = environmentList.value.find(env => env.id === initSlaveForm.value.environmentId);
const envName = selectedEnv ? selectedEnv.environmentName : initSlaveForm.value.environmentId;
await ElMessageBox.confirm(`确定要为环境"${envName}"初始化从库吗?从库一经创建,不允许任何修改、删除或重新初始化。`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
initSlaveLoading.value = true;
const response = await initSlaveDatasource(initSlaveForm.value.environmentId);
if (response.code === 200) {
ElMessage.success('从库初始化成功');
initSlaveOpen.value = false;
getList();
} else {
ElMessage.error(response.msg || '从库初始化失败');
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error(error.msg || '从库初始化失败');
}
} finally {
initSlaveLoading.value = false;
}
}
});
};
const handleUpdate = async (row) => {
reset();
const id = row.id;
@ -273,11 +464,25 @@
orgType: 'source',
description: null,
isActive: true,
isValid: true,
datasourceId: null,
remark: null
};
proxy.resetForm("environmentRef");
};
const parseTime = (time) => {
if (!time) return '';
const date = new Date(time);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
onMounted(() => {
getList();
});
@ -291,6 +496,15 @@
font-weight: bold;
}
.header-actions {
display: flex;
gap: 10px;
}
.tag-icon {
margin-right: 4px;
}
.current-env-card {
background: #ffffff;
border: 1px solid #e4e7ed;
@ -317,4 +531,16 @@
.mt10 {
margin-top: 10px;
}
.empty-tip {
color: #909399;
font-size: 13px;
margin-top: 10px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

View File

@ -36,7 +36,10 @@
<template #header>
<div class="card-header">
<span>目标环境列表</span>
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['setting:environment:add']">新增目标环境</el-button>
<div class="header-actions">
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['setting:environment:add']">新增目标环境</el-button>
<el-button type="success" icon="Files" @click="handleInitSlave" v-hasPermi="['setting:environment:initSlave']">初始化从库</el-button>
</div>
</div>
</template>
@ -117,12 +120,43 @@
</div>
</template>
</el-dialog>
<el-dialog title="初始化从库" v-model="initSlaveOpen" width="500px" append-to-body>
<el-alert title="注意事项" type="warning" :closable="false" style="margin-bottom: 20px">
<template #default>
<ul style="margin: 0; padding-left: 20px;">
<li>从库一经创建不允许任何修改删除或重新初始化</li>
<li>从库名称自动生成为 slave_环境编码</li>
<li>初始化过程包括创建数据库写入配置注册数据源验证连接</li>
</ul>
</template>
</el-alert>
<el-form ref="initSlaveRef" :model="initSlaveForm" :rules="initSlaveRules" label-width="100px">
<el-form-item label="环境" prop="environmentId">
<el-select v-model="initSlaveForm.environmentId" placeholder="请选择环境" style="width: 100%" filterable>
<el-option
v-for="env in environmentList"
:key="env.id"
:label="`${env.environmentName} (${env.environmentCode})`"
:value="env.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitInitSlave" :loading="initSlaveLoading"> </el-button>
<el-button @click="initSlaveOpen = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { listEnvironment, getEnvironment, addEnvironment, updateEnvironment, delEnvironment, switchEnvironment, getCurrentEnvironment } from "@/api/setting/environment";
import { listEnvironment, getEnvironment, addEnvironment, updateEnvironment, delEnvironment, switchEnvironment, getCurrentEnvironment, initSlaveDatasource } from "@/api/setting/environment";
import { ElMessage, ElMessageBox } from 'element-plus';
import { Files } from '@element-plus/icons-vue';
const props = defineProps({
currentEnvironment: {
@ -138,6 +172,8 @@
const loading = ref(true);
const open = ref(false);
const switchOpen = ref(false);
const initSlaveOpen = ref(false);
const initSlaveLoading = ref(false);
const title = ref("");
const form = ref({});
@ -146,6 +182,9 @@
switchReason: '手动切换',
orgType: 'target'
});
const initSlaveForm = ref({
environmentId: ''
});
const rules = {
environmentName: [
@ -158,6 +197,12 @@
const switchRules = {};
const initSlaveRules = {
environmentId: [
{ required: true, message: "请选择环境", trigger: "change" }
]
};
const getList = async () => {
loading.value = true;
try {
@ -178,6 +223,44 @@
title.value = "新增目标环境";
};
const handleInitSlave = () => {
initSlaveForm.value = {
environmentId: ''
};
initSlaveOpen.value = true;
};
const submitInitSlave = async () => {
proxy.$refs.initSlaveRef.validate(async (valid) => {
if (valid) {
try {
const selectedEnv = environmentList.value.find(env => env.id === initSlaveForm.value.environmentId);
const envName = selectedEnv ? selectedEnv.environmentName : initSlaveForm.value.environmentId;
await ElMessageBox.confirm(`确定要为环境"${envName}"初始化从库吗?从库一经创建,不允许任何修改、删除或重新初始化。`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
initSlaveLoading.value = true;
const response = await initSlaveDatasource(initSlaveForm.value.environmentId);
if (response.code === 200) {
ElMessage.success('从库初始化成功');
initSlaveOpen.value = false;
getList();
} else {
ElMessage.error(response.msg || '从库初始化失败');
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error(error.msg || '从库初始化失败');
}
} finally {
initSlaveLoading.value = false;
}
}
});
};
const handleUpdate = async (row) => {
reset();
const id = row.id;
@ -278,6 +361,18 @@
proxy.resetForm("environmentRef");
};
const parseTime = (time) => {
if (!time) return '';
const date = new Date(time);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
onMounted(() => {
getList();
});
@ -291,6 +386,11 @@
font-weight: bold;
}
.header-actions {
display: flex;
gap: 10px;
}
.current-env-card {
background: #ffffff;
border: 1px solid #e4e7ed;
@ -317,4 +417,10 @@
.mt10 {
margin-top: 10px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

View File

@ -17,7 +17,7 @@
</div>
</template>
<el-tabs v-model="activeTab" type="border-card">
<el-tabs v-model="activeTab" type="border-card" v-loading="environmentLoading">
<el-tab-pane label="源环境管理" name="sourceEnvironment">
<SourceEnvironmentTab :current-environment="sourceEnvironment" @environment-switched="handleSourceEnvironmentSwitched" />
</el-tab-pane>
@ -50,10 +50,12 @@
const activeTab = ref('sourceEnvironment');
const refreshLoading = ref(false);
const environmentLoading = ref(false);
const sourceEnvironment = ref(null);
const targetEnvironment = ref(null);
const loadCurrentEnvironments = async () => {
environmentLoading.value = true;
try {
const [sourceResponse, targetResponse] = await Promise.all([
getCurrentEnvironment('source'),
@ -61,12 +63,19 @@
]);
if (sourceResponse.code === 200) {
sourceEnvironment.value = sourceResponse.data;
} else {
console.warn('获取源环境失败:', sourceResponse.msg);
}
if (targetResponse.code === 200) {
targetEnvironment.value = targetResponse.data;
} else {
console.warn('获取目标环境失败:', targetResponse.msg);
}
} catch (error) {
console.error('获取当前环境失败:', error);
ElMessage.warning('获取环境信息失败,部分功能可能不可用');
} finally {
environmentLoading.value = false;
}
};

View File

@ -0,0 +1,546 @@
<template>
<div class="app-container">
<el-card shadow="never" body-class="search-card">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="数据源名称" prop="dsName">
<el-input
v-model="queryParams.dsName"
placeholder="请输入数据源名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="数据库名称" prop="dbName">
<el-input
v-model="queryParams.dbName"
placeholder="请输入数据库名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="数据库主机" prop="dbHost">
<el-input
v-model="queryParams.dbHost"
placeholder="请输入数据库主机"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="数据库端口" prop="dbPort">
<el-input
v-model="queryParams.dbPort"
placeholder="请输入数据库端口"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input
v-model="queryParams.username"
placeholder="请输入用户名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="queryParams.password"
placeholder="请输入密码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="mt10">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['system:datasourceConfig:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['system:datasourceConfig:export']"
>导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="datasourceConfigList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键ID" align="center" prop="id" />
<el-table-column label="数据源名称" align="center" prop="dsName" />
<el-table-column label="数据库名称" align="center" prop="dbName" />
<el-table-column label="数据库主机" align="center" prop="dbHost" />
<el-table-column label="数据库端口" align="center" prop="dbPort" />
<el-table-column label="用户名" align="center" prop="username" />
<el-table-column label="密码" align="center" prop="password" />
<el-table-column label="数据库类型" align="center" prop="dbType" />
<el-table-column label="状态" align="center" prop="status" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['system:datasourceConfig:query']">详情</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="datasourceConfigRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="数据源名称" prop="dsName">
<el-input v-model="form.dsName" placeholder="请输入数据源名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据库名称" prop="dbName">
<el-input v-model="form.dbName" placeholder="请输入数据库名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据库主机" prop="dbHost">
<el-input v-model="form.dbHost" placeholder="请输入数据库主机" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据库端口" prop="dbPort">
<el-input v-model="form.dbPort" placeholder="请输入数据库端口" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" placeholder="请输入密码" />
</el-form-item>
</el-col>
<el-col :span="12">
</el-col>
<el-col :span="12">
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-drawer :title="isEditMode ? '修改数据源管理' : '数据源管理详情'" v-model="openDetail" size="60%">
<div v-show="!isEditMode">
<el-descriptions :column="2" border>
<el-descriptions-item label="数据源名称">
{{ detailForm.dsName }}
</el-descriptions-item>
<el-descriptions-item label="数据库名称">
{{ detailForm.dbName }}
</el-descriptions-item>
<el-descriptions-item label="数据库主机">
{{ detailForm.dbHost }}
</el-descriptions-item>
<el-descriptions-item label="数据库端口">
{{ detailForm.dbPort }}
</el-descriptions-item>
<el-descriptions-item label="用户名">
{{ detailForm.username }}
</el-descriptions-item>
<el-descriptions-item label="密码">
{{ detailForm.password }}
</el-descriptions-item>
<el-descriptions-item label="数据库类型">
{{ detailForm.dbType }}
</el-descriptions-item>
<el-descriptions-item label="状态">
{{ detailForm.status }}
</el-descriptions-item>
<el-descriptions-item label="备注">
{{ detailForm.remark }}
</el-descriptions-item>
<el-descriptions-item label="创建者">
{{ detailForm.createBy }}
</el-descriptions-item>
<el-descriptions-item label="创建时间">
<span>{{ parseTime(detailForm.createTime, '{y}-{m}-{d}') }}</span>
</el-descriptions-item>
<el-descriptions-item label="更新者">
{{ detailForm.updateBy }}
</el-descriptions-item>
<el-descriptions-item label="更新时间">
<span>{{ parseTime(detailForm.updateTime, '{y}-{m}-{d}') }}</span>
</el-descriptions-item>
</el-descriptions>
</div>
<div v-show="isEditMode">
<el-form ref="datasourceConfigDrawerRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="数据源名称" prop="dsName">
<el-input v-model="form.dsName" placeholder="请输入数据源名称" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据库名称" prop="dbName">
<el-input v-model="form.dbName" placeholder="请输入数据库名称" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据库主机" prop="dbHost">
<el-input v-model="form.dbHost" placeholder="请输入数据库主机" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据库端口" prop="dbPort">
<el-input v-model="form.dbPort" placeholder="请输入数据库端口" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" placeholder="请输入密码" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
</el-col>
<el-col :span="12">
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<template #footer>
<div class="drawer-footer" style="display: flex; justify-content: flex-end; gap: 10px;">
<div v-if="!isEditMode">
<el-button
type="primary"
plain
icon="Edit"
@click="handleEnterEditMode"
v-hasPermi="['system:datasourceConfig:edit']"
>修改资料</el-button>
<el-button
type="danger"
plain
icon="Delete"
@click="handleDeleteFromDetail"
v-hasPermi="['system:datasourceConfig:remove']"
>删除记录</el-button>
<el-button @click="openDetail = false"> </el-button>
</div>
<div v-else>
<el-button type="primary" @click="submitDrawerForm">保存提交</el-button>
<el-button @click="isEditMode = false">取消修改</el-button>
</div>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup name="DatasourceConfig">
import { listDatasourceConfig, getDatasourceConfig, delDatasourceConfig, addDatasourceConfig, updateDatasourceConfig } from "@/api/system/datasourceConfig";
const { proxy } = getCurrentInstance();
const datasourceConfigList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
//
const openDetail = ref(false);
const isEditMode = ref(false); // /
const detailForm = ref({});
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
dsName: null,
dbName: null,
dbHost: null,
dbPort: null,
username: null,
password: null,
dbType: null,
status: null,
},
rules: {
dsName: [
{ required: true, message: "数据源名称不能为空", trigger: "blur" }
],
dbName: [
{ required: true, message: "数据库名称不能为空", trigger: "blur" }
],
dbHost: [
{ required: true, message: "数据库主机不能为空", trigger: "blur" }
],
dbPort: [
{ required: true, message: "数据库端口不能为空", trigger: "blur" }
],
username: [
{ required: true, message: "用户名不能为空", trigger: "blur" }
],
password: [
{ required: true, message: "密码不能为空", trigger: "blur" }
],
dbType: [
{ required: true, message: "数据库类型不能为空", trigger: "change" }
],
status: [
{ required: true, message: "状态不能为空", trigger: "change" }
],
createBy: [
{ required: true, message: "创建者不能为空", trigger: "blur" }
],
createTime: [
{ required: true, message: "创建时间不能为空", trigger: "blur" }
],
updateBy: [
{ required: true, message: "更新者不能为空", trigger: "blur" }
],
updateTime: [
{ required: true, message: "更新时间不能为空", trigger: "blur" }
]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询列表 */
function getList() {
loading.value = true;
listDatasourceConfig(queryParams.value).then(response => {
datasourceConfigList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
dsName: null,
dbName: null,
dbHost: null,
dbPort: null,
username: null,
password: null,
dbType: null,
status: null,
remark: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null
};
proxy.resetForm("datasourceConfigRef");
proxy.resetForm("datasourceConfigDrawerRef");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
//
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加数据源管理";
}
/** 修改按钮操作 (仅用于弹窗) */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value;
getDatasourceConfig(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改数据源管理";
});
}
/** 详情按钮操作 */
function handleDetail(row) {
reset();
//
isEditMode.value = false; //
const _id = row.id;
getDatasourceConfig(_id).then(response => {
detailForm.value = response.data;
openDetail.value = true;
});
}
/** 进入编辑模式 */
function handleEnterEditMode() {
// detailForm form
const row = detailForm.value;
const _id = row.id;
// checkbox
getDatasourceConfig(_id).then(response => {
form.value = response.data;
isEditMode.value = true; //
});
}
/** 抽屉内表单提交 */
function submitDrawerForm() {
proxy.$refs["datasourceConfigDrawerRef"].validate(valid => {
if (valid) {
if (form.value.id != null) {
updateDatasourceConfig(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
//
detailForm.value = JSON.parse(JSON.stringify(form.value));
// formsplitget
handleDetail(form.value);
getList();
});
}
}
});
}
/** 详情内删除按钮 */
function handleDeleteFromDetail() {
const row = detailForm.value;
handleDelete(row).then(() => {
openDetail.value = false;
});
}
/** 常规弹窗提交按钮 */
function submitForm() {
proxy.$refs["datasourceConfigRef"].validate(valid => {
if (valid) {
if (form.value.id != null) {
updateDatasourceConfig(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addDatasourceConfig(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const _ids = row.id || ids.value;
return proxy.$modal.confirm('是否确认删除数据源管理编号为"' + _ids + '"的数据项?').then(function() {
return delDatasourceConfig(_ids);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
/** 导出按钮操作 */
function handleExport() {
proxy.download('system/datasourceConfig/export', {
...queryParams.value
}, `datasourceConfig_${new Date().getTime()}.xlsx`)
}
getList();
</script>

View File

@ -0,0 +1,488 @@
<template>
<div class="app-container">
<el-card shadow="never" body-class="search-card">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="源货币代码" prop="fromCurrency">
<el-input
v-model="queryParams.fromCurrency"
placeholder="请输入源货币代码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="目标货币代码" prop="toCurrency">
<el-input
v-model="queryParams.toCurrency"
placeholder="请输入目标货币代码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="汇率" prop="rate">
<el-input
v-model="queryParams.rate"
placeholder="请输入汇率"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="汇率日期" prop="rateDate">
<el-date-picker clearable
v-model="queryParams.rateDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择汇率日期">
</el-date-picker>
</el-form-item>
<el-form-item label="汇率来源" prop="source">
<el-input
v-model="queryParams.source"
placeholder="请输入汇率来源"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="mt10">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['system:exchangerate:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['system:exchangerate:export']"
>导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="exchangerateList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键ID" align="center" prop="id" />
<el-table-column label="源货币代码" align="center" prop="fromCurrency" />
<el-table-column label="目标货币代码" align="center" prop="toCurrency" />
<el-table-column label="汇率" align="center" prop="rate" />
<el-table-column label="汇率日期" align="center" prop="rateDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.rateDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="汇率来源" align="center" prop="source" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['system:exchangerate:query']">详情</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="exchangerateRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="源货币代码" prop="fromCurrency">
<el-input v-model="form.fromCurrency" placeholder="请输入源货币代码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="目标货币代码" prop="toCurrency">
<el-input v-model="form.toCurrency" placeholder="请输入目标货币代码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="汇率" prop="rate">
<el-input v-model="form.rate" placeholder="请输入汇率" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="汇率日期" prop="rateDate">
<el-date-picker clearable
v-model="form.rateDate"
type="date"
value-format="YYYY-MM-DD"
style="width: 100%"
placeholder="请选择汇率日期">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="汇率来源" prop="source">
<el-input v-model="form.source" placeholder="请输入汇率来源" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-drawer :title="isEditMode ? '修改汇率管理' : '汇率管理详情'" v-model="openDetail" size="60%">
<div v-show="!isEditMode">
<el-descriptions :column="2" border>
<el-descriptions-item label="源货币代码">
{{ detailForm.fromCurrency }}
</el-descriptions-item>
<el-descriptions-item label="目标货币代码">
{{ detailForm.toCurrency }}
</el-descriptions-item>
<el-descriptions-item label="汇率">
{{ detailForm.rate }}
</el-descriptions-item>
<el-descriptions-item label="汇率日期">
<span>{{ parseTime(detailForm.rateDate, '{y}-{m}-{d}') }}</span>
</el-descriptions-item>
<el-descriptions-item label="汇率来源">
{{ detailForm.source }}
</el-descriptions-item>
<el-descriptions-item label="创建者">
{{ detailForm.createBy }}
</el-descriptions-item>
<el-descriptions-item label="创建时间">
<span>{{ parseTime(detailForm.createTime, '{y}-{m}-{d}') }}</span>
</el-descriptions-item>
<el-descriptions-item label="更新者">
{{ detailForm.updateBy }}
</el-descriptions-item>
<el-descriptions-item label="更新时间">
<span>{{ parseTime(detailForm.updateTime, '{y}-{m}-{d}') }}</span>
</el-descriptions-item>
<el-descriptions-item label="备注">
{{ detailForm.remark }}
</el-descriptions-item>
</el-descriptions>
</div>
<div v-show="isEditMode">
<el-form ref="exchangerateDrawerRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="源货币代码" prop="fromCurrency">
<el-input v-model="form.fromCurrency" placeholder="请输入源货币代码" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="目标货币代码" prop="toCurrency">
<el-input v-model="form.toCurrency" placeholder="请输入目标货币代码" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="汇率" prop="rate">
<el-input v-model="form.rate" placeholder="请输入汇率" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="汇率日期" prop="rateDate">
<el-date-picker clearable
v-model="form.rateDate"
type="date"
value-format="YYYY-MM-DD"
style="width: 100%"
placeholder="请选择汇率日期">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="汇率来源" prop="source">
<el-input v-model="form.source" placeholder="请输入汇率来源" style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<template #footer>
<div class="drawer-footer" style="display: flex; justify-content: flex-end; gap: 10px;">
<div v-if="!isEditMode">
<el-button
type="primary"
plain
icon="Edit"
@click="handleEnterEditMode"
v-hasPermi="['system:exchangerate:edit']"
>修改资料</el-button>
<el-button
type="danger"
plain
icon="Delete"
@click="handleDeleteFromDetail"
v-hasPermi="['system:exchangerate:remove']"
>删除记录</el-button>
<el-button @click="openDetail = false"> </el-button>
</div>
<div v-else>
<el-button type="primary" @click="submitDrawerForm">保存提交</el-button>
<el-button @click="isEditMode = false">取消修改</el-button>
</div>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup name="Exchangerate">
import { listExchangerate, getExchangerate, delExchangerate, addExchangerate, updateExchangerate } from "@/api/system/exchangerate";
const { proxy } = getCurrentInstance();
const exchangerateList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
//
const openDetail = ref(false);
const isEditMode = ref(false); // /
const detailForm = ref({});
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
fromCurrency: null,
toCurrency: null,
rate: null,
rateDate: null,
source: null,
},
rules: {
fromCurrency: [
{ required: true, message: "源货币代码不能为空", trigger: "blur" }
],
toCurrency: [
{ required: true, message: "目标货币代码不能为空", trigger: "blur" }
],
rate: [
{ required: true, message: "汇率不能为空", trigger: "blur" }
],
rateDate: [
{ required: true, message: "汇率日期不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询列表 */
function getList() {
loading.value = true;
listExchangerate(queryParams.value).then(response => {
exchangerateList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
fromCurrency: null,
toCurrency: null,
rate: null,
rateDate: null,
source: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null
};
proxy.resetForm("exchangerateRef");
proxy.resetForm("exchangerateDrawerRef");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
//
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加汇率管理";
}
/** 修改按钮操作 (仅用于弹窗) */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value;
getExchangerate(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改汇率管理";
});
}
/** 详情按钮操作 */
function handleDetail(row) {
reset();
//
isEditMode.value = false; //
const _id = row.id;
getExchangerate(_id).then(response => {
detailForm.value = response.data;
openDetail.value = true;
});
}
/** 进入编辑模式 */
function handleEnterEditMode() {
// detailForm form
const row = detailForm.value;
const _id = row.id;
// checkbox
getExchangerate(_id).then(response => {
form.value = response.data;
isEditMode.value = true; //
});
}
/** 抽屉内表单提交 */
function submitDrawerForm() {
proxy.$refs["exchangerateDrawerRef"].validate(valid => {
if (valid) {
if (form.value.id != null) {
updateExchangerate(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
//
detailForm.value = JSON.parse(JSON.stringify(form.value));
// formsplitget
handleDetail(form.value);
getList();
});
}
}
});
}
/** 详情内删除按钮 */
function handleDeleteFromDetail() {
const row = detailForm.value;
handleDelete(row).then(() => {
openDetail.value = false;
});
}
/** 常规弹窗提交按钮 */
function submitForm() {
proxy.$refs["exchangerateRef"].validate(valid => {
if (valid) {
if (form.value.id != null) {
updateExchangerate(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addExchangerate(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const _ids = row.id || ids.value;
return proxy.$modal.confirm('是否确认删除汇率管理编号为"' + _ids + '"的数据项?').then(function() {
return delExchangerate(_ids);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
/** 导出按钮操作 */
function handleExport() {
proxy.download('system/exchangerate/export', {
...queryParams.value
}, `exchangerate_${new Date().getTime()}.xlsx`)
}
getList();
</script>