feat: 第三方数据库配置 + 库存检查功能

1. 系统设置新增第三方数据库配置管理
   - 支持SQL Server/MySQL/PostgreSQL
   - 支持连接测试、启用/禁用

2. 任务子类型新增「检查库存」选项
   - 启用后发布任务需要输入商品条码
   - 自动查询聚水潭库存信息

3. 发起任务页面集成库存检查
   - 库存>1才允许发布任务
   - 库存不足提示先申请样品

库存查询配置:
- 数据表:jst_inventoryInfo
- 仓库条件:wms_co_id='14138566'
This commit is contained in:
Your Name 2026-01-05 14:58:12 +08:00
parent 015c425b93
commit 3056f44a6e
10 changed files with 1036 additions and 5 deletions

View File

@ -174,6 +174,13 @@
<artifactId>thumbnailator</artifactId>
<version>0.4.20</version>
</dependency>
<!-- SQL Server JDBC Driver - 第三方数据库查询 -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>12.4.2.jre11</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,88 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.ThirdPartyDbConfig;
import com.designhub.service.ThirdPartyDbService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 第三方数据库配置控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/third-party-db")
@RequiredArgsConstructor
@Tag(name = "第三方数据库配置", description = "第三方数据库配置和库存查询相关接口")
public class ThirdPartyDbController {
private final ThirdPartyDbService thirdPartyDbService;
@Operation(summary = "获取所有配置")
@GetMapping("/configs")
public Result<List<ThirdPartyDbConfig>> getAllConfigs() {
return Result.success(thirdPartyDbService.getAllConfigs());
}
@Operation(summary = "获取配置详情")
@GetMapping("/configs/{id}")
public Result<ThirdPartyDbConfig> getConfigById(@PathVariable Long id) {
return Result.success(thirdPartyDbService.getConfigById(id));
}
@Operation(summary = "获取启用的配置")
@GetMapping("/configs/enabled")
public Result<ThirdPartyDbConfig> getEnabledConfig() {
return Result.success(thirdPartyDbService.getEnabledConfig());
}
@Operation(summary = "创建配置")
@PostMapping("/configs")
public Result<ThirdPartyDbConfig> createConfig(@RequestBody ThirdPartyDbConfig config) {
return Result.success(thirdPartyDbService.createConfig(config));
}
@Operation(summary = "更新配置")
@PutMapping("/configs/{id}")
public Result<ThirdPartyDbConfig> updateConfig(@PathVariable Long id, @RequestBody ThirdPartyDbConfig config) {
config.setId(id);
return Result.success(thirdPartyDbService.updateConfig(config));
}
@Operation(summary = "删除配置")
@DeleteMapping("/configs/{id}")
public Result<Void> deleteConfig(@PathVariable Long id) {
thirdPartyDbService.deleteConfig(id);
return Result.success();
}
@Operation(summary = "测试数据库连接")
@PostMapping("/configs/test")
public Result<Boolean> testConnection(@RequestBody ThirdPartyDbConfig config) {
boolean success = thirdPartyDbService.testConnection(config);
if (success) {
return Result.success(true);
} else {
return Result.error("数据库连接失败,请检查配置");
}
}
@Operation(summary = "查询商品库存")
@GetMapping("/inventory/{barcode}")
public Result<Map<String, Object>> checkInventory(@PathVariable String barcode) {
return Result.success(thirdPartyDbService.checkInventory(barcode));
}
@Operation(summary = "检查库存是否满足发布任务条件")
@GetMapping("/inventory/check-for-task/{barcode}")
public Result<Map<String, Object>> checkInventoryForTask(@PathVariable String barcode) {
return Result.success(thirdPartyDbService.checkInventoryForTask(barcode));
}
}

View File

@ -45,6 +45,12 @@ public class TaskSubType implements Serializable {
*/
private Integer isEnabled;
/**
* 是否检查库存:0-否,1-是
* 如果为1,发布任务时需要输入商品条码并检查库存
*/
private Integer checkInventory;
/**
* 创建时间
*/
@ -106,6 +112,14 @@ public class TaskSubType implements Serializable {
this.isEnabled = isEnabled;
}
public Integer getCheckInventory() {
return checkInventory;
}
public void setCheckInventory(Integer checkInventory) {
this.checkInventory = checkInventory;
}
public LocalDateTime getCreateTime() {
return createTime;
}

View File

@ -0,0 +1,85 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 第三方数据库配置实体类
*/
@Data
@TableName("third_party_db_config")
public class ThirdPartyDbConfig implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 配置名称
*/
private String name;
/**
* 数据库类型:sqlserver/mysql/postgresql等
*/
private String dbType;
/**
* 服务器地址
*/
private String host;
/**
* 端口号
*/
private Integer port;
/**
* 数据库名称
*/
private String database;
/**
* 用户名
*/
private String username;
/**
* 密码(加密存储)
*/
private String password;
/**
* 是否启用:0-禁用 1-启用
*/
private Integer enabled;
/**
* 连接超时时间(秒)
*/
private Integer connectionTimeout;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,13 @@
package com.designhub.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.designhub.entity.ThirdPartyDbConfig;
import org.apache.ibatis.annotations.Mapper;
/**
* 第三方数据库配置Mapper
*/
@Mapper
public interface ThirdPartyDbConfigMapper extends BaseMapper<ThirdPartyDbConfig> {
}

View File

@ -0,0 +1,245 @@
package com.designhub.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.designhub.entity.ThirdPartyDbConfig;
import com.designhub.exception.BusinessException;
import com.designhub.mapper.ThirdPartyDbConfigMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 第三方数据库服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ThirdPartyDbService {
private final ThirdPartyDbConfigMapper configMapper;
/**
* 获取所有配置
*/
public List<ThirdPartyDbConfig> getAllConfigs() {
return configMapper.selectList(
new LambdaQueryWrapper<ThirdPartyDbConfig>()
.orderByDesc(ThirdPartyDbConfig::getCreateTime)
);
}
/**
* 获取配置详情
*/
public ThirdPartyDbConfig getConfigById(Long id) {
return configMapper.selectById(id);
}
/**
* 获取启用的配置
*/
public ThirdPartyDbConfig getEnabledConfig() {
return configMapper.selectOne(
new LambdaQueryWrapper<ThirdPartyDbConfig>()
.eq(ThirdPartyDbConfig::getEnabled, 1)
.last("LIMIT 1")
);
}
/**
* 创建配置
*/
@Transactional(rollbackFor = Exception.class)
public ThirdPartyDbConfig createConfig(ThirdPartyDbConfig config) {
config.setCreateTime(LocalDateTime.now());
config.setUpdateTime(LocalDateTime.now());
configMapper.insert(config);
return config;
}
/**
* 更新配置
*/
@Transactional(rollbackFor = Exception.class)
public ThirdPartyDbConfig updateConfig(ThirdPartyDbConfig config) {
config.setUpdateTime(LocalDateTime.now());
configMapper.updateById(config);
return config;
}
/**
* 删除配置
*/
@Transactional(rollbackFor = Exception.class)
public void deleteConfig(Long id) {
configMapper.deleteById(id);
}
/**
* 测试数据库连接
*/
public boolean testConnection(ThirdPartyDbConfig config) {
Connection conn = null;
try {
conn = getConnection(config);
return conn != null && !conn.isClosed();
} catch (Exception e) {
log.error("数据库连接测试失败: {}", e.getMessage());
return false;
} finally {
closeConnection(conn);
}
}
/**
* 获取数据库连接
*/
private Connection getConnection(ThirdPartyDbConfig config) throws SQLException {
String url;
String driverClass;
switch (config.getDbType().toLowerCase()) {
case "sqlserver":
driverClass = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
url = String.format("jdbc:sqlserver://%s:%d;databaseName=%s;encrypt=false;trustServerCertificate=true",
config.getHost(), config.getPort(), config.getDatabase());
break;
case "mysql":
driverClass = "com.mysql.cj.jdbc.Driver";
url = String.format("jdbc:mysql://%s:%d/%s?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai",
config.getHost(), config.getPort(), config.getDatabase());
break;
case "postgresql":
driverClass = "org.postgresql.Driver";
url = String.format("jdbc:postgresql://%s:%d/%s",
config.getHost(), config.getPort(), config.getDatabase());
break;
default:
throw new BusinessException("不支持的数据库类型: " + config.getDbType());
}
try {
Class.forName(driverClass);
} catch (ClassNotFoundException e) {
throw new SQLException("数据库驱动未找到: " + driverClass);
}
int timeout = config.getConnectionTimeout() != null ? config.getConnectionTimeout() : 10;
DriverManager.setLoginTimeout(timeout);
return DriverManager.getConnection(url, config.getUsername(), config.getPassword());
}
/**
* 关闭连接
*/
private void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
log.error("关闭数据库连接失败", e);
}
}
}
/**
* 查询商品库存信息
* @param barcode 商品条码
* @return 库存数量,如果未找到返回0
*/
public Map<String, Object> checkInventory(String barcode) {
ThirdPartyDbConfig config = getEnabledConfig();
if (config == null) {
throw new BusinessException("未配置第三方数据库或配置未启用");
}
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
Map<String, Object> result = new HashMap<>();
result.put("barcode", barcode);
result.put("quantity", 0);
result.put("found", false);
try {
conn = getConnection(config);
// 查询聚水潭库存信息表
// 条件:wms_co_id='14138566',根据商品条码查询
String sql = "SELECT sku_id, i_id, name, qty, lock_qty, qty - ISNULL(lock_qty, 0) AS available_qty " +
"FROM jst_inventoryInfo " +
"WHERE wms_co_id = '14138566' AND (sku_id = ? OR i_id = ?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, barcode);
pstmt.setString(2, barcode);
rs = pstmt.executeQuery();
if (rs.next()) {
result.put("found", true);
result.put("skuId", rs.getString("sku_id"));
result.put("itemId", rs.getString("i_id"));
result.put("name", rs.getString("name"));
result.put("quantity", rs.getInt("qty"));
result.put("lockQty", rs.getInt("lock_qty"));
result.put("availableQty", rs.getInt("available_qty"));
log.info("查询到商品库存: barcode={}, name={}, qty={}, available={}",
barcode, rs.getString("name"), rs.getInt("qty"), rs.getInt("available_qty"));
} else {
log.info("未找到商品库存信息: barcode={}", barcode);
}
} catch (SQLException e) {
log.error("查询库存失败: barcode={}, error={}", barcode, e.getMessage());
throw new BusinessException("查询库存失败: " + e.getMessage());
} finally {
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
} catch (SQLException e) {
log.error("关闭资源失败", e);
}
closeConnection(conn);
}
return result;
}
/**
* 检查库存是否足够(库存大于1才能发布任务)
* @param barcode 商品条码
* @return 检查结果
*/
public Map<String, Object> checkInventoryForTask(String barcode) {
Map<String, Object> inventoryResult = checkInventory(barcode);
Map<String, Object> result = new HashMap<>(inventoryResult);
boolean found = (boolean) inventoryResult.get("found");
int availableQty = found ? (int) inventoryResult.getOrDefault("availableQty", 0) : 0;
// 库存大于1才允许发布
boolean canPublish = found && availableQty > 1;
result.put("canPublish", canPublish);
if (!found) {
result.put("message", "未找到该商品库存信息,请先申请样品");
} else if (availableQty <= 1) {
result.put("message", "商品库存不足(可用库存: " + availableQty + "),请先申请样品");
} else {
result.put("message", "库存充足(可用库存: " + availableQty + "),可以发布任务");
}
return result;
}
}

View File

@ -0,0 +1,24 @@
-- 创建第三方数据库配置表
CREATE TABLE IF NOT EXISTS third_party_db_config (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
name VARCHAR(100) NOT NULL COMMENT '配置名称',
db_type VARCHAR(50) NOT NULL DEFAULT 'sqlserver' COMMENT '数据库类型:sqlserver/mysql/postgresql',
host VARCHAR(255) NOT NULL COMMENT '服务器地址',
port INT NOT NULL DEFAULT 1433 COMMENT '端口号',
`database` VARCHAR(100) NOT NULL COMMENT '数据库名称',
username VARCHAR(100) NOT NULL COMMENT '用户名',
password VARCHAR(255) NOT NULL COMMENT '密码',
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用:0-禁用 1-启用',
connection_timeout INT DEFAULT 10 COMMENT '连接超时时间(秒)',
remark VARCHAR(500) COMMENT '备注',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方数据库配置表';
-- 插入默认配置
INSERT INTO third_party_db_config (name, db_type, host, port, `database`, username, password, enabled, connection_timeout, remark)
VALUES ('聚水潭数据中台', 'sqlserver', '218.93.191.16', 1433, 'JSDataMiddlePlatform', 'nanting', 'yq1_xL?:=E>G', 1, 10, '聚水潭商品库存查询');
-- 在任务子类型表中添加检查库存字段
ALTER TABLE task_sub_type ADD COLUMN check_inventory TINYINT(1) DEFAULT 0 COMMENT '是否检查库存:0-否 1-是' AFTER enabled;

View File

@ -0,0 +1,94 @@
import request from '@/utils/request'
export interface ThirdPartyDbConfig {
id?: number
name: string
dbType: string
host: string
port: number
database: string
username: string
password: string
enabled: number
connectionTimeout?: number
remark?: string
createTime?: string
updateTime?: string
}
export interface InventoryResult {
barcode: string
found: boolean
quantity: number
availableQty?: number
skuId?: string
itemId?: string
name?: string
lockQty?: number
canPublish?: boolean
message?: string
}
/**
* 获取所有第三方数据库配置
*/
export const getAllConfigs = () => {
return request.get<ThirdPartyDbConfig[]>('/third-party-db/configs')
}
/**
* 获取配置详情
*/
export const getConfigById = (id: number) => {
return request.get<ThirdPartyDbConfig>(`/third-party-db/configs/${id}`)
}
/**
* 获取启用的配置
*/
export const getEnabledConfig = () => {
return request.get<ThirdPartyDbConfig>('/third-party-db/configs/enabled')
}
/**
* 创建配置
*/
export const createConfig = (config: ThirdPartyDbConfig) => {
return request.post<ThirdPartyDbConfig>('/third-party-db/configs', config)
}
/**
* 更新配置
*/
export const updateConfig = (id: number, config: ThirdPartyDbConfig) => {
return request.put<ThirdPartyDbConfig>(`/third-party-db/configs/${id}`, config)
}
/**
* 删除配置
*/
export const deleteConfig = (id: number) => {
return request.delete<void>(`/third-party-db/configs/${id}`)
}
/**
* 测试数据库连接
*/
export const testConnection = (config: ThirdPartyDbConfig) => {
return request.post<boolean>('/third-party-db/configs/test', config)
}
/**
* 查询商品库存
*/
export const checkInventory = (barcode: string) => {
return request.get<InventoryResult>(`/third-party-db/inventory/${barcode}`)
}
/**
* 检查库存是否满足发布任务条件
*/
export const checkInventoryForTask = (barcode: string) => {
return request.get<InventoryResult>(`/third-party-db/inventory/check-for-task/${barcode}`)
}

View File

@ -202,6 +202,54 @@
</a-radio-group>
</a-form-item>
<!-- 商品条码(当任务子类型需要检查库存时显示) -->
<a-form-item
v-if="selectedSubTypeNeedsInventoryCheck"
label="商品条码"
name="productBarcode"
required
>
<a-input-group compact>
<a-input
v-model:value="formData.productBarcode"
placeholder="请输入商品条码用于库存检查"
size="large"
style="width: calc(100% - 100px)"
@pressEnter="handleCheckInventory"
/>
<a-button
type="primary"
size="large"
@click="handleCheckInventory"
:loading="checkingInventory"
>
检查库存
</a-button>
</a-input-group>
<!-- 库存检查结果 -->
<div v-if="inventoryCheckResult" class="inventory-result" :class="{ 'success': inventoryCheckResult.canPublish, 'error': !inventoryCheckResult.canPublish }">
<a-alert
:type="inventoryCheckResult.canPublish ? 'success' : 'error'"
:message="inventoryCheckResult.canPublish ? '库存充足' : '库存不足'"
:description="inventoryCheckResult.message"
show-icon
/>
<div v-if="inventoryCheckResult.found" class="inventory-details">
<div class="detail-item">
<span class="label">商品名称:</span>
<span class="value">{{ inventoryCheckResult.name || '-' }}</span>
</div>
<div class="detail-item">
<span class="label">可用库存:</span>
<span class="value" :class="{ 'low-stock': inventoryCheckResult.availableQty <= 1 }">
{{ inventoryCheckResult.availableQty }}
</span>
</div>
</div>
</div>
</a-form-item>
<!-- 品牌和平台 -->
<a-row :gutter="16">
<a-col :span="12">
@ -437,6 +485,7 @@ import { getSystemConfigsMapByGroup } from '@/api/system'
import { uploadFilesToS3 } from '@/api/file'
import type { FormInstance } from 'ant-design-vue'
import type { DemandType, Brand, Platform, Category, TaskSubType } from '@/api/config'
import { checkInventoryForTask, type InventoryResult } from '@/api/thirdPartyDb'
import { useAuthStore } from '@/stores/auth'
import { useConfigStore } from '@/stores/config'
@ -518,10 +567,49 @@ const formData = reactive({
previewImage: '', // 预览图URL
remark: '', // 备注信息
releaseType: 'default',
productBarcode: '', // 商品条码(库存检查用)
// receiverId: undefined, // 已移除,改用子任务指定
isUrgent: false
})
// 库存检查相关
const checkingInventory = ref(false)
const inventoryCheckResult = ref<InventoryResult | null>(null)
// 判断当前选择的任务子类型是否需要检查库存
const selectedSubTypeNeedsInventoryCheck = computed(() => {
if (!formData.taskSubType) return false
const selectedSubType = taskSubTypes.value.find(st => st.name === formData.taskSubType)
return selectedSubType?.checkInventory === 1
})
// 检查库存
const handleCheckInventory = async () => {
if (!formData.productBarcode) {
message.warning('请输入商品条码')
return
}
checkingInventory.value = true
inventoryCheckResult.value = null
try {
const response = await checkInventoryForTask(formData.productBarcode)
inventoryCheckResult.value = response.data
if (response.data.canPublish) {
message.success('库存检查通过,可以发布任务')
} else {
message.error(response.data.message || '库存不足,请先申请样品')
}
} catch (error: any) {
console.error('检查库存失败:', error)
message.error(error.response?.data?.message || '检查库存失败')
} finally {
checkingInventory.value = false
}
}
// 表单验证规则
const formRules = {
title: [
@ -826,6 +914,22 @@ const handleSubmit = async () => {
return
}
// 检查库存(如果需要)
if (selectedSubTypeNeedsInventoryCheck.value) {
if (!formData.productBarcode) {
message.error('请输入商品条码')
return
}
if (!inventoryCheckResult.value) {
message.error('请先检查库存')
return
}
if (!inventoryCheckResult.value.canPublish) {
message.error('库存不足,请先申请样品后再发布任务')
return
}
}
// 检查积分余额(管理员跳过)
if (!isAdmin.value && estimatedScore.value > teamScore.value) {
message.error('团队积分余额不足')
@ -1917,5 +2021,42 @@ onUnmounted(() => {
}
}
}
// 库存检查结果样式
.inventory-result {
margin-top: 12px;
.inventory-details {
margin-top: 12px;
padding: 12px;
background: rgba(255, 255, 255, 0.02);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
.detail-item {
display: flex;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.label {
color: $text-tertiary;
width: 100px;
flex-shrink: 0;
}
.value {
color: $text-primary;
&.low-stock {
color: #ff4d4f;
font-weight: 600;
}
}
}
}
}
</style>

View File

@ -1042,6 +1042,81 @@
</a-table>
</div>
</a-tab-pane>
<!-- 第三方数据库配置 -->
<a-tab-pane key="third-party-db" tab="第三方数据库">
<div class="settings-section">
<div class="section-header">
<h2 class="section-title">第三方数据库配置</h2>
<a-button type="primary" @click="showThirdPartyDbModal()">
<template #icon><Icon name="plus" size="sm" /></template>
添加配置
</a-button>
</div>
<a-alert
message="使用说明"
description="第三方数据库用于查询商品库存信息。当任务子类型设置为需要检查库存时,发布任务时会根据商品条码查询库存,库存不足则无法发布任务。"
type="info"
show-icon
style="margin-bottom: 16px;"
/>
<a-table
:columns="thirdPartyDbColumns"
:data-source="thirdPartyDbConfigs"
:loading="loadingThirdPartyDb"
row-key="id"
:pagination="{ pageSize: 10 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'enabled'">
<a-tag :color="record.enabled ? 'green' : 'default'">
{{ record.enabled ? '已启用' : '未启用' }}
</a-tag>
</template>
<template v-else-if="column.key === 'password'">
<span>******</span>
</template>
<template v-else-if="column.key === 'connection'">
<span>{{ record.host }}:{{ record.port }}/{{ record.database }}</span>
</template>
<template v-else-if="column.key === 'actions'">
<a-space>
<a-button type="link" size="small" @click="handleTestThirdPartyDb(record)">
测试连接
</a-button>
<a-button type="link" size="small" @click="showThirdPartyDbModal(record)">
编辑
</a-button>
<a-button
v-if="!record.enabled"
type="link"
size="small"
@click="handleEnableThirdPartyDb(record.id)"
>
启用
</a-button>
<a-button
v-else
type="link"
size="small"
@click="handleDisableThirdPartyDb(record.id)"
>
禁用
</a-button>
<a-popconfirm
title="确定删除?"
@confirm="handleDeleteThirdPartyDb(record.id)"
>
<a-button type="link" danger size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</div>
</a-tab-pane>
</a-tabs>
<!-- 需求类型弹窗 -->
@ -1132,6 +1207,13 @@
placeholder="排序值越小越靠前"
/>
</a-form-item>
<a-form-item label="是否检查库存">
<a-switch v-model:checked="formData.taskSubType.checkInventory" />
<div class="form-tip" style="margin-top: 8px;">
<Icon name="info" size="sm" color="#6B6B6B" style="margin-right: 4px;" />
开启后,发布此类型任务时需要输入商品条码并检查库存,库存不足则无法发布任务
</div>
</a-form-item>
</a-form>
</a-modal>
@ -1478,6 +1560,88 @@
</a-row>
</a-form>
</a-modal>
<!-- 第三方数据库配置弹窗 -->
<a-modal
v-model:open="modals.thirdPartyDb"
:title="thirdPartyDbForm.id ? '编辑数据库配置' : '添加数据库配置'"
width="700px"
@ok="handleSaveThirdPartyDb"
@cancel="modals.thirdPartyDb = false"
>
<a-form :model="thirdPartyDbForm" layout="vertical">
<a-form-item label="配置名称" required>
<a-input v-model:value="thirdPartyDbForm.name" placeholder="例如:聚水潭数据中台" />
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="数据库类型" required>
<a-select v-model:value="thirdPartyDbForm.dbType" placeholder="请选择数据库类型">
<a-select-option value="sqlserver">SQL Server</a-select-option>
<a-select-option value="mysql">MySQL</a-select-option>
<a-select-option value="postgresql">PostgreSQL</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="端口号" required>
<a-input-number
v-model:value="thirdPartyDbForm.port"
:min="1"
:max="65535"
style="width: 100%"
placeholder="1433"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="服务器地址" required>
<a-input v-model:value="thirdPartyDbForm.host" placeholder="218.93.191.16" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="数据库名称" required>
<a-input v-model:value="thirdPartyDbForm.database" placeholder="JSDataMiddlePlatform" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="用户名" required>
<a-input v-model:value="thirdPartyDbForm.username" placeholder="用户名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="密码" required>
<a-input-password v-model:value="thirdPartyDbForm.password" placeholder="密码" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="连接超时(秒)">
<a-input-number
v-model:value="thirdPartyDbForm.connectionTimeout"
:min="1"
:max="120"
style="width: 100%"
placeholder="10"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="是否启用">
<a-switch v-model:checked="thirdPartyDbForm.enabled" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="备注">
<a-textarea v-model:value="thirdPartyDbForm.remark" :rows="2" placeholder="备注信息" />
</a-form-item>
</a-form>
</a-modal>
</div>
</div>
</DefaultLayout>
@ -1535,6 +1699,14 @@ import {
activateS3Config,
type S3Config
} from '@/api/s3'
import {
getAllConfigs as getAllThirdPartyDbConfigs,
createConfig as createThirdPartyDbConfig,
updateConfig as updateThirdPartyDbConfig,
deleteConfig as deleteThirdPartyDbConfig,
testConnection as testThirdPartyDbConnection,
type ThirdPartyDbConfig
} from '@/api/thirdPartyDb'
import request from '@/utils/request'
type LimitPeriod = 'DAILY' | 'WEEKLY' | 'MONTHLY'
@ -1597,7 +1769,8 @@ const modals = reactive({
category: false,
department: false,
scorePool: false,
s3Config: false
s3Config: false,
thirdPartyDb: false
})
const demandTypes = ref<DemandType[]>([])
@ -1651,6 +1824,33 @@ const s3ConfigColumns = [
{ title: '操作', key: 'actions', width: 200 }
]
// 第三方数据库配置相关
const thirdPartyDbConfigs = ref<ThirdPartyDbConfig[]>([])
const loadingThirdPartyDb = ref(false)
const thirdPartyDbForm = reactive<ThirdPartyDbConfig & { enabled: boolean }>({
name: '',
dbType: 'sqlserver',
host: '',
port: 1433,
database: '',
username: '',
password: '',
enabled: true,
connectionTimeout: 10,
remark: ''
})
const thirdPartyDbColumns = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 60 },
{ title: '配置名称', dataIndex: 'name', key: 'name' },
{ title: '数据库类型', dataIndex: 'dbType', key: 'dbType', width: 120 },
{ title: '连接信息', key: 'connection' },
{ title: '用户名', dataIndex: 'username', key: 'username', width: 100 },
{ title: '密码', key: 'password', width: 100 },
{ title: '状态', key: 'enabled', width: 100 },
{ title: '操作', key: 'actions', width: 260 }
]
const defaultDepartmentForm = (): DepartmentForm => ({
deptName: '',
deptCode: '',
@ -2188,8 +2388,8 @@ const showDemandTypeModal = async (item?: DemandType) => {
const showTaskSubTypeModal = (item?: TaskSubType) => {
editingItem.value = item
formData.taskSubType = item
? { ...item }
: { sortOrder: 0, isEnabled: 1 }
? { ...item, checkInventory: item.checkInventory === 1 }
: { sortOrder: 0, isEnabled: 1, checkInventory: false }
modals.taskSubType = true
}
@ -2292,12 +2492,13 @@ const saveDemandType = async () => {
const saveTaskSubType = async () => {
try {
const saveData: Partial<TaskSubType> = {
const saveData: Partial<TaskSubType> & { checkInventory?: number } = {
name: formData.taskSubType.name,
code: formData.taskSubType.code,
description: formData.taskSubType.description,
sortOrder: formData.taskSubType.sortOrder ?? 0,
isEnabled: formData.taskSubType.isEnabled ?? 1
isEnabled: formData.taskSubType.isEnabled ?? 1,
checkInventory: (formData.taskSubType as any).checkInventory ? 1 : 0
}
if (editingItem.value) {
@ -3174,6 +3375,121 @@ const handleActivateS3Config = async (id: number) => {
}
}
// 第三方数据库配置相关方法
const loadThirdPartyDbConfigs = async () => {
loadingThirdPartyDb.value = true
try {
const response = await getAllThirdPartyDbConfigs()
thirdPartyDbConfigs.value = response.data || []
} catch (error: any) {
console.error('加载第三方数据库配置失败:', error)
message.error(error.response?.data?.message || '加载第三方数据库配置失败')
} finally {
loadingThirdPartyDb.value = false
}
}
const showThirdPartyDbModal = (config?: ThirdPartyDbConfig) => {
if (config) {
Object.assign(thirdPartyDbForm, {
...config,
enabled: config.enabled === 1,
password: '' // 编辑时不显示密码
})
} else {
Object.assign(thirdPartyDbForm, {
id: undefined,
name: '',
dbType: 'sqlserver',
host: '',
port: 1433,
database: '',
username: '',
password: '',
enabled: true,
connectionTimeout: 10,
remark: ''
})
}
modals.thirdPartyDb = true
}
const handleSaveThirdPartyDb = async () => {
try {
const data = {
...thirdPartyDbForm,
enabled: thirdPartyDbForm.enabled ? 1 : 0
}
if (thirdPartyDbForm.id) {
await updateThirdPartyDbConfig(thirdPartyDbForm.id, data)
message.success('更新数据库配置成功')
} else {
await createThirdPartyDbConfig(data)
message.success('创建数据库配置成功')
}
modals.thirdPartyDb = false
loadThirdPartyDbConfigs()
} catch (error: any) {
console.error('保存数据库配置失败:', error)
message.error(error.response?.data?.message || '保存数据库配置失败')
}
}
const handleDeleteThirdPartyDb = async (id: number) => {
try {
await deleteThirdPartyDbConfig(id)
message.success('删除数据库配置成功')
loadThirdPartyDbConfigs()
} catch (error: any) {
console.error('删除数据库配置失败:', error)
message.error(error.response?.data?.message || '删除数据库配置失败')
}
}
const handleTestThirdPartyDb = async (config: ThirdPartyDbConfig) => {
try {
message.loading({ content: '正在测试连接...', key: 'testConn' })
const response = await testThirdPartyDbConnection(config)
if (response.data) {
message.success({ content: '数据库连接成功', key: 'testConn' })
} else {
message.error({ content: '数据库连接失败', key: 'testConn' })
}
} catch (error: any) {
console.error('测试连接失败:', error)
message.error({ content: error.response?.data?.message || '连接测试失败', key: 'testConn' })
}
}
const handleEnableThirdPartyDb = async (id: number) => {
try {
const config = thirdPartyDbConfigs.value.find(c => c.id === id)
if (config) {
await updateThirdPartyDbConfig(id, { ...config, enabled: 1 })
message.success('已启用配置')
loadThirdPartyDbConfigs()
}
} catch (error: any) {
console.error('启用配置失败:', error)
message.error(error.response?.data?.message || '启用配置失败')
}
}
const handleDisableThirdPartyDb = async (id: number) => {
try {
const config = thirdPartyDbConfigs.value.find(c => c.id === id)
if (config) {
await updateThirdPartyDbConfig(id, { ...config, enabled: 0 })
message.success('已禁用配置')
loadThirdPartyDbConfigs()
}
} catch (error: any) {
console.error('禁用配置失败:', error)
message.error(error.response?.data?.message || '禁用配置失败')
}
}
watch(activeTab, (value) => {
// 切换到用户与部门 tab 时,始终刷新数据以获取最新状态
if (value === 'users') {
@ -3195,6 +3511,10 @@ watch(activeTab, (value) => {
if (value === 's3-config') {
loadS3Configs()
}
// 切换到第三方数据库配置 tab 时,加载配置
if (value === 'third-party-db') {
loadThirdPartyDbConfigs()
}
})
</script>