feat: 第三方数据库配置 + 库存检查功能
1. 系统设置新增第三方数据库配置管理 - 支持SQL Server/MySQL/PostgreSQL - 支持连接测试、启用/禁用 2. 任务子类型新增「检查库存」选项 - 启用后发布任务需要输入商品条码 - 自动查询聚水潭库存信息 3. 发起任务页面集成库存检查 - 库存>1才允许发布任务 - 库存不足提示先申请样品 库存查询配置: - 数据表:jst_inventoryInfo - 仓库条件:wms_co_id='14138566'
This commit is contained in:
parent
015c425b93
commit
3056f44a6e
@ -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>
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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> {
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
24
database/create_third_party_db_config.sql
Normal file
24
database/create_third_party_db_config.sql
Normal 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;
|
||||
|
||||
94
frontend/src/api/thirdPartyDb.ts
Normal file
94
frontend/src/api/thirdPartyDb.ts
Normal 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}`)
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user