feat(approval): 新增业务审批流程及配置管理

- 新增审批配置主子表(biz_approval_config / biz_approval_config_user),支持或签
- 5 个业务模块接入审批: 采购订单/客户报价/供应商报价/发货单/订单异议
- 统一审批动作接口(提交/通过/驳回),status=10 表示审批中
- 新增"待我审批"聚合页面,按业务类型筛选
- 修复 logback 写本地路径报错,去除文件 appender
- 修复 Redis SSL 配置在 Spring Boot 4 下需对象格式
- 补齐部分业务表缺失的 update_by/update_time 审计列

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 11:14:46 +08:00
parent 0180388a2f
commit 7ffc140cf8
69 changed files with 1563 additions and 446 deletions

View File

@@ -0,0 +1,46 @@
package com.ruoyi.web.controller.bid;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.mapper.bid.BizApprovalPendingMapper;
import com.ruoyi.system.service.bid.IBizApprovalActionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/bid/approval/action")
public class BizApprovalActionController extends BaseController {
@Autowired private IBizApprovalActionService service;
@Autowired private BizApprovalPendingMapper pendingMapper;
@GetMapping("/pending")
public AjaxResult pending() {
return success(pendingMapper.selectPendingForUser(SecurityUtils.getUserId()));
}
@Log(title = "提交审批", businessType = BusinessType.UPDATE)
@PostMapping("/submit/{bizType}/{id}")
public AjaxResult submit(@PathVariable String bizType, @PathVariable Long id) {
return toAjax(service.submit(bizType, id, getUsername()));
}
@Log(title = "审批通过", businessType = BusinessType.UPDATE)
@PostMapping("/approve/{bizType}/{id}")
public AjaxResult approve(@PathVariable String bizType, @PathVariable Long id) {
return toAjax(service.approve(bizType, id, SecurityUtils.getUserId(), getUsername()));
}
@Log(title = "审批驳回", businessType = BusinessType.UPDATE)
@PostMapping("/reject/{bizType}/{id}")
public AjaxResult reject(@PathVariable String bizType, @PathVariable Long id,
@RequestBody(required = false) Map<String, String> body) {
String reason = body != null ? body.get("reason") : null;
return toAjax(service.reject(bizType, id, SecurityUtils.getUserId(), getUsername(), reason));
}
}

View File

@@ -0,0 +1,54 @@
package com.ruoyi.web.controller.bid;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.bid.BizApprovalConfig;
import com.ruoyi.system.service.bid.IBizApprovalConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/bid/approval")
public class BizApprovalConfigController extends BaseController {
@Autowired private IBizApprovalConfigService service;
@PreAuthorize("@ss.hasPermi('bid:approval:list')")
@GetMapping("/list")
public TableDataInfo list(BizApprovalConfig q) {
startPage();
return getDataTable(service.selectList(q));
}
@PreAuthorize("@ss.hasPermi('bid:approval:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
return success(service.selectById(id));
}
@PreAuthorize("@ss.hasPermi('bid:approval:add')")
@Log(title = "审批配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody BizApprovalConfig c) {
c.setCreateBy(getUsername());
return toAjax(service.insert(c));
}
@PreAuthorize("@ss.hasPermi('bid:approval:edit')")
@Log(title = "审批配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody BizApprovalConfig c) {
c.setUpdateBy(getUsername());
return toAjax(service.update(c));
}
@PreAuthorize("@ss.hasPermi('bid:approval:remove')")
@Log(title = "审批配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(service.deleteByIds(ids));
}
}

View File

@@ -72,15 +72,18 @@ spring:
# redis 配置
redis:
# 地址
host: localhost
host: 49.232.154.205
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
password: WANGyu11!
# 连接超时时间
timeout: 10s
timeout: 30s
# 是否开启ssl
ssl:
enabled: false
lettuce:
pool:
# 连接池中的最小空闲连接
@@ -92,6 +95,28 @@ spring:
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
redisson:
# redis key前缀
keyPrefix:
# 线程池数量
threads: 16
# Netty线程池数量
nettyThreads: 32
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 32
# 连接池大小
connectionPoolSize: 64
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
# token配置
token:
# 令牌自定义标识

View File

@@ -1,93 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="/home/ruoyi/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 用户访问日志输出 -->
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.ruoyi" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
<!--系统用户操作日志-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<logger name="com.ruoyi" level="info" />
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
</root>
<logger name="sys-user" level="info">
<appender-ref ref="console" />
</logger>
</configuration>

View File

@@ -0,0 +1,29 @@
package com.ruoyi.system.domain.bid;
import com.ruoyi.common.core.domain.BaseEntity;
import java.util.List;
public class BizApprovalConfig extends BaseEntity {
private Long id;
private String bizType;
private String bizName;
private String signType;
private String enabled;
private List<Long> userIds;
private List<String> userNames;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getBizType() { return bizType; }
public void setBizType(String bizType) { this.bizType = bizType; }
public String getBizName() { return bizName; }
public void setBizName(String bizName) { this.bizName = bizName; }
public String getSignType() { return signType; }
public void setSignType(String signType) { this.signType = signType; }
public String getEnabled() { return enabled; }
public void setEnabled(String enabled) { this.enabled = enabled; }
public List<Long> getUserIds() { return userIds; }
public void setUserIds(List<Long> userIds) { this.userIds = userIds; }
public List<String> getUserNames() { return userNames; }
public void setUserNames(List<String> userNames) { this.userNames = userNames; }
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.system.mapper.bid;
import org.apache.ibatis.annotations.Param;
public interface BizApprovalActionMapper {
/** 仅当当前状态在 fromStatuses 中时更新业务单据状态 */
int updateStatus(@Param("table") String table,
@Param("pk") String pk,
@Param("statusCol") String statusCol,
@Param("id") Long id,
@Param("newStatus") String newStatus,
@Param("fromStatuses") java.util.List<String> fromStatuses,
@Param("updateBy") String updateBy);
String selectStatus(@Param("table") String table,
@Param("pk") String pk,
@Param("statusCol") String statusCol,
@Param("id") Long id);
}

View File

@@ -0,0 +1,21 @@
package com.ruoyi.system.mapper.bid;
import com.ruoyi.system.domain.bid.BizApprovalConfig;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface BizApprovalConfigMapper {
List<BizApprovalConfig> selectList(BizApprovalConfig query);
BizApprovalConfig selectById(Long id);
BizApprovalConfig selectByBizType(String bizType);
int insert(BizApprovalConfig record);
int update(BizApprovalConfig record);
int deleteById(Long id);
List<Long> selectUserIds(@Param("configId") Long configId);
List<String> selectUserNames(@Param("configId") Long configId);
int deleteUsers(@Param("configId") Long configId);
int insertUser(@Param("configId") Long configId, @Param("userId") Long userId, @Param("sortNo") Integer sortNo);
int existsUser(@Param("bizType") String bizType, @Param("userId") Long userId);
}

View File

@@ -0,0 +1,10 @@
package com.ruoyi.system.mapper.bid;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface BizApprovalPendingMapper {
/** 查询某用户作为审批人的、所有业务表中 status='10' 的单据 */
List<Map<String, Object>> selectPendingForUser(@Param("userId") Long userId);
}

View File

@@ -0,0 +1,12 @@
package com.ruoyi.system.service.bid;
public interface IBizApprovalActionService {
/** 提交审批: 业务单据 status -> 10 */
int submit(String bizType, Long id, String username);
/** 通过审批(或签): 任一审批人通过即生效, 业务单据 status -> 通过状态 */
int approve(String bizType, Long id, Long userId, String username);
/** 驳回审批: 业务单据 status -> rejected, 备注记录原因 */
int reject(String bizType, Long id, Long userId, String username, String reason);
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.system.service.bid;
import com.ruoyi.system.domain.bid.BizApprovalConfig;
import java.util.List;
public interface IBizApprovalConfigService {
List<BizApprovalConfig> selectList(BizApprovalConfig query);
BizApprovalConfig selectById(Long id);
BizApprovalConfig selectByBizType(String bizType);
int insert(BizApprovalConfig record);
int update(BizApprovalConfig record);
int deleteByIds(Long[] ids);
/** 校验当前用户是否为该业务的审批人(或签) */
boolean canApprove(String bizType, Long userId);
}

View File

@@ -0,0 +1,78 @@
package com.ruoyi.system.service.bid.impl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.mapper.bid.BizApprovalActionMapper;
import com.ruoyi.system.service.bid.IBizApprovalActionService;
import com.ruoyi.system.service.bid.IBizApprovalConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class BizApprovalActionServiceImpl implements IBizApprovalActionService {
@Autowired private BizApprovalActionMapper mapper;
@Autowired private IBizApprovalConfigService configService;
/** 业务类型 -> 表/主键/状态列/审批通过的目标状态/可提交的初始状态 */
static class Meta {
final String table, pk, statusCol, approvedStatus;
final List<String> draftStatuses;
Meta(String t, String pk, String sc, String approved, List<String> drafts) {
this.table = t; this.pk = pk; this.statusCol = sc;
this.approvedStatus = approved; this.draftStatuses = drafts;
}
}
private static final Map<String, Meta> META = new HashMap<>();
static {
META.put("PURCHASE_ORDER", new Meta("biz_purchase_order", "po_id", "status", "confirmed", Arrays.asList("draft")));
META.put("CLIENT_QUOTE", new Meta("biz_client_quote", "quote_id", "status", "confirmed", Arrays.asList("draft")));
META.put("QUOTATION", new Meta("biz_quotation", "quotation_id", "status", "accepted", Arrays.asList("draft", "submitted")));
META.put("DELIVERY_ORDER", new Meta("biz_delivery_order", "do_id", "delivery_status", "confirmed", Arrays.asList("pending")));
META.put("ORDER_OBJECTION", new Meta("biz_order_objection", "objection_id", "status", "resolved", Arrays.asList("pending")));
}
private Meta meta(String bizType) {
Meta m = META.get(bizType);
if (m == null) throw new ServiceException("不支持的业务类型: " + bizType);
return m;
}
@Override
public int submit(String bizType, Long id, String username) {
Meta m = meta(bizType);
int rows = mapper.updateStatus(m.table, m.pk, m.statusCol, id, "10", m.draftStatuses, username);
if (rows == 0) throw new ServiceException("当前状态不允许提交审批");
return rows;
}
@Override
public int approve(String bizType, Long id, Long userId, String username) {
Meta m = meta(bizType);
if (!configService.canApprove(bizType, userId)) {
throw new ServiceException("无权审批: 当前用户不是该业务的审批人");
}
int rows = mapper.updateStatus(m.table, m.pk, m.statusCol, id,
m.approvedStatus, Collections.singletonList("10"), username);
if (rows == 0) throw new ServiceException("单据非审批中, 无法通过");
return rows;
}
@Override
public int reject(String bizType, Long id, Long userId, String username, String reason) {
Meta m = meta(bizType);
if (!configService.canApprove(bizType, userId)) {
throw new ServiceException("无权审批: 当前用户不是该业务的审批人");
}
int rows = mapper.updateStatus(m.table, m.pk, m.statusCol, id,
"rejected", Collections.singletonList("10"), username);
if (rows == 0) throw new ServiceException("单据非审批中, 无法驳回");
return rows;
}
}

View File

@@ -0,0 +1,86 @@
package com.ruoyi.system.service.bid.impl;
import com.ruoyi.system.domain.bid.BizApprovalConfig;
import com.ruoyi.system.mapper.bid.BizApprovalConfigMapper;
import com.ruoyi.system.service.bid.IBizApprovalConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class BizApprovalConfigServiceImpl implements IBizApprovalConfigService {
@Autowired private BizApprovalConfigMapper mapper;
@Override
public List<BizApprovalConfig> selectList(BizApprovalConfig query) {
List<BizApprovalConfig> list = mapper.selectList(query);
for (BizApprovalConfig c : list) {
c.setUserIds(mapper.selectUserIds(c.getId()));
c.setUserNames(mapper.selectUserNames(c.getId()));
}
return list;
}
@Override
public BizApprovalConfig selectById(Long id) {
BizApprovalConfig c = mapper.selectById(id);
if (c != null) {
c.setUserIds(mapper.selectUserIds(id));
c.setUserNames(mapper.selectUserNames(id));
}
return c;
}
@Override
public BizApprovalConfig selectByBizType(String bizType) {
BizApprovalConfig c = mapper.selectByBizType(bizType);
if (c != null) {
c.setUserIds(mapper.selectUserIds(c.getId()));
c.setUserNames(mapper.selectUserNames(c.getId()));
}
return c;
}
@Override
@Transactional
public int insert(BizApprovalConfig r) {
int rows = mapper.insert(r);
saveUsers(r);
return rows;
}
@Override
@Transactional
public int update(BizApprovalConfig r) {
int rows = mapper.update(r);
mapper.deleteUsers(r.getId());
saveUsers(r);
return rows;
}
private void saveUsers(BizApprovalConfig r) {
if (r.getUserIds() == null) return;
int sort = 0;
for (Long uid : r.getUserIds()) {
if (uid != null) mapper.insertUser(r.getId(), uid, sort++);
}
}
@Override
@Transactional
public int deleteByIds(Long[] ids) {
int n = 0;
for (Long id : ids) {
mapper.deleteUsers(id);
n += mapper.deleteById(id);
}
return n;
}
@Override
public boolean canApprove(String bizType, Long userId) {
if (bizType == null || userId == null) return false;
return mapper.existsUser(bizType, userId) > 0;
}
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.bid.BizApprovalActionMapper">
<update id="updateStatus">
UPDATE ${table}
SET ${statusCol} = #{newStatus},
update_by = #{updateBy},
update_time = NOW()
WHERE ${pk} = #{id}
<if test="fromStatuses != null and !fromStatuses.isEmpty()">
AND ${statusCol} IN
<foreach collection="fromStatuses" item="s" open="(" separator="," close=")">#{s}</foreach>
</if>
</update>
<select id="selectStatus" resultType="java.lang.String">
SELECT ${statusCol} FROM ${table} WHERE ${pk}=#{id}
</select>
</mapper>

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.bid.BizApprovalConfigMapper">
<resultMap id="BaseRM" type="com.ruoyi.system.domain.bid.BizApprovalConfig">
<id property="id" column="id"/>
<result property="bizType" column="biz_type"/>
<result property="bizName" column="biz_name"/>
<result property="signType" column="sign_type"/>
<result property="enabled" column="enabled"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<select id="selectList" resultMap="BaseRM">
SELECT * FROM biz_approval_config
<where>
<if test="bizType != null and bizType != ''"> AND biz_type LIKE CONCAT('%',#{bizType},'%')</if>
<if test="bizName != null and bizName != ''"> AND biz_name LIKE CONCAT('%',#{bizName},'%')</if>
<if test="enabled != null and enabled != ''"> AND enabled=#{enabled}</if>
</where>
ORDER BY id ASC
</select>
<select id="selectById" resultMap="BaseRM">
SELECT * FROM biz_approval_config WHERE id=#{id}
</select>
<select id="selectByBizType" resultMap="BaseRM">
SELECT * FROM biz_approval_config WHERE biz_type=#{bizType}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO biz_approval_config(biz_type,biz_name,sign_type,enabled,remark,create_by,create_time)
VALUES(#{bizType},#{bizName},#{signType},#{enabled},#{remark},#{createBy},NOW())
</insert>
<update id="update">
UPDATE biz_approval_config
<set>
<if test="bizName != null">biz_name=#{bizName},</if>
<if test="signType != null">sign_type=#{signType},</if>
<if test="enabled != null">enabled=#{enabled},</if>
<if test="remark != null">remark=#{remark},</if>
update_by=#{updateBy}, update_time=NOW()
</set>
WHERE id=#{id}
</update>
<delete id="deleteById">DELETE FROM biz_approval_config WHERE id=#{id}</delete>
<select id="selectUserIds" resultType="java.lang.Long">
SELECT user_id FROM biz_approval_config_user WHERE config_id=#{configId} ORDER BY sort_no, id
</select>
<select id="selectUserNames" resultType="java.lang.String">
SELECT u.nick_name FROM biz_approval_config_user cu
JOIN sys_user u ON u.user_id=cu.user_id
WHERE cu.config_id=#{configId} ORDER BY cu.sort_no, cu.id
</select>
<delete id="deleteUsers">DELETE FROM biz_approval_config_user WHERE config_id=#{configId}</delete>
<insert id="insertUser">
INSERT INTO biz_approval_config_user(config_id,user_id,sort_no)
VALUES(#{configId},#{userId},#{sortNo})
</insert>
<select id="existsUser" resultType="int">
SELECT COUNT(1) FROM biz_approval_config c
JOIN biz_approval_config_user cu ON cu.config_id=c.id
WHERE c.biz_type=#{bizType} AND c.enabled='1' AND cu.user_id=#{userId}
</select>
</mapper>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.bid.BizApprovalPendingMapper">
<select id="selectPendingForUser" resultType="java.util.LinkedHashMap">
SELECT * FROM (
SELECT 'PURCHASE_ORDER' AS bizType, '采购订单' AS bizName,
po_id AS id, po_no AS bizNo,
CAST(total_amount AS CHAR) AS amount,
create_by AS createBy, create_time AS createTime
FROM biz_purchase_order
WHERE status = '10'
AND EXISTS (SELECT 1 FROM biz_approval_config c
JOIN biz_approval_config_user cu ON cu.config_id = c.id
WHERE c.biz_type = 'PURCHASE_ORDER' AND c.enabled = '1' AND cu.user_id = #{userId})
UNION ALL
SELECT 'CLIENT_QUOTE', '客户报价',
quote_id, quote_no, CAST(total_amount AS CHAR),
create_by, create_time
FROM biz_client_quote
WHERE status = '10'
AND EXISTS (SELECT 1 FROM biz_approval_config c
JOIN biz_approval_config_user cu ON cu.config_id = c.id
WHERE c.biz_type = 'CLIENT_QUOTE' AND c.enabled = '1' AND cu.user_id = #{userId})
UNION ALL
SELECT 'QUOTATION', '供应商报价',
quotation_id, quote_no, CAST(total_amount AS CHAR),
create_by, create_time
FROM biz_quotation
WHERE status = '10'
AND EXISTS (SELECT 1 FROM biz_approval_config c
JOIN biz_approval_config_user cu ON cu.config_id = c.id
WHERE c.biz_type = 'QUOTATION' AND c.enabled = '1' AND cu.user_id = #{userId})
UNION ALL
SELECT 'DELIVERY_ORDER', '发货单',
do_id, do_no, CAST(total_amount AS CHAR),
create_by, create_time
FROM biz_delivery_order
WHERE delivery_status = '10'
AND EXISTS (SELECT 1 FROM biz_approval_config c
JOIN biz_approval_config_user cu ON cu.config_id = c.id
WHERE c.biz_type = 'DELIVERY_ORDER' AND c.enabled = '1' AND cu.user_id = #{userId})
UNION ALL
SELECT 'ORDER_OBJECTION', '订单异议',
objection_id, CAST(objection_id AS CHAR), NULL,
create_by, create_time
FROM biz_order_objection
WHERE status = '10'
AND EXISTS (SELECT 1 FROM biz_approval_config c
JOIN biz_approval_config_user cu ON cu.config_id = c.id
WHERE c.biz_type = 'ORDER_OBJECTION' AND c.enabled = '1' AND cu.user_id = #{userId})
) t
ORDER BY t.createTime DESC
</select>
</mapper>

View File

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#e4393c"/>
<stop offset="1" stop-color="#c81623"/>
</linearGradient>
</defs>
<rect x="1" y="1" width="30" height="30" rx="7" fill="url(#g)"/>
<path d="M10 8 h14 v3.5 h-10 v4 h8.5 v3.5 h-8.5 v5.5 h-4 z" fill="#fff"/>
<circle cx="23" cy="22" r="3" fill="#fff"/>
<circle cx="23" cy="22" r="1.3" fill="#e4393c"/>
</svg>

After

Width:  |  Height:  |  Size: 520 B

View File

@@ -5,7 +5,8 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="icon" type="image/svg+xml" href="<%= BASE_URL %>favicon.svg">
<link rel="alternate icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>

View File

@@ -0,0 +1,7 @@
import request from '@/utils/request'
const baseUrl = '/bid/approval'
export const listApproval = (params) => request({ url: baseUrl + '/list', method: 'get', params })
export const getApproval = (id) => request({ url: baseUrl + '/' + id, method: 'get' })
export const addApproval = (data) => request({ url: baseUrl, method: 'post', data })
export const updateApproval = (data) => request({ url: baseUrl, method: 'put', data })
export const delApproval = (ids) => request({ url: baseUrl + '/' + ids, method: 'delete' })

View File

@@ -0,0 +1,6 @@
import request from '@/utils/request'
const base = '/bid/approval/action'
export const listPending = () => request({ url: `${base}/pending`, method: 'get' })
export const submitApproval = (bizType, id) => request({ url: `${base}/submit/${bizType}/${id}`, method: 'post' })
export const approveBiz = (bizType, id) => request({ url: `${base}/approve/${bizType}/${id}`, method: 'post' })
export const rejectBiz = (bizType, id, reason) => request({ url: `${base}/reject/${bizType}/${id}`, method: 'post', data: { reason } })

View File

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<defs>
<linearGradient id="lg-red" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#e4393c"/>
<stop offset="1" stop-color="#c81623"/>
</linearGradient>
</defs>
<rect x="10" y="10" width="180" height="180" rx="36" fill="url(#lg-red)"/>
<path d="M62 56 h78 v18 h-56 v22 h48 v18 h-48 v32 h-22 z" fill="#ffffff"/>
<circle cx="146" cy="138" r="14" fill="#ffffff"/>
<circle cx="146" cy="138" r="6" fill="#e4393c"/>
</svg>

After

Width:  |  Height:  |  Size: 546 B

View File

@@ -21,7 +21,7 @@ $--border-color-light: #f0f0f0;
$--border-color-lighter: #f0f0f0;
$--table-border: 1px solid #e5e5e5;
$--table-header-background: #f5f5f5;
$--table-header-background: #ffffff;
$--table-header-font-color: #666666;
$--table-row-hover-background: #fff5f5;
@@ -30,7 +30,7 @@ $--button-default-border-color: #e5e5e5;
$--button-default-font-color: #666666;
/* 背景 */
$--background-color-base: #f5f5f5;
$--background-color-base: #ffffff;
/* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';

View File

@@ -6,11 +6,11 @@
// ═══ 京东品牌色 ═══
$jd-red: #e4393c;
$jd-red-dark: #c81623;
$jd-red-light: #fff5f5;
$jd-red-bg: #fff0f0;
$jd-red-light: #fafafa;
$jd-red-bg: #ffffff;
// ═══ 灰色系 ═══
$bg-gray: #f5f5f5;
$bg-gray: #ffffff;
$bg-dark: #333;
$border-gray: #e5e5e5;
$border-light: #f0f0f0;
@@ -21,7 +21,7 @@ $text-medium: #666666;
$text-light: #999999;
// ═══ 功能色 ═══
$link-blue: #005ea7;
$link-blue: #e4393c;
$color-success: #67c23a;
$color-warning: #e6a23c;
$color-danger: #f56c6c;

View File

@@ -299,7 +299,7 @@
color: #333;
border-bottom: 1px solid #ebeef5;
}
.detail-card-title i { margin-right: 5px; color: #409EFF; }
.detail-card-title i { margin-right: 5px; color: #e4393c; }
.detail-row { padding: 0 8px; }
@@ -357,7 +357,7 @@
}
.code-action .el-button:hover {
background: #ffffff;
border-color: #409EFF;
border-color: #e4393c;
}
.code-pre {
margin: 0;
@@ -507,8 +507,8 @@
/* 搜索按钮特殊色(浅蓝) */
.search-btn {
background: #409EFF;
border-color: #409EFF;
background: #e4393c;
border-color: #e4393c;
border-radius: 4px;
color: #fff;
}

View File

@@ -1,6 +1,6 @@
/* ═══════════════════════════════════════════════════════════════
京东风格 — 全局主题CSS 变量 + Element UI 覆盖)
主色:#e4393c 深红:#c81623 背景灰:#f5f5f5
主色:#e4393c 深红:#c81623 背景灰:#ffffff
═══════════════════════════════════════════════════════════════ */
:root {
@@ -8,11 +8,11 @@
--jd-red: #e4393c;
--jd-red-dark: #c81623;
--jd-red-hover: #e55a5d;
--jd-red-light: #fff5f5;
--jd-red-bg: #fff0f0;
--jd-red-light: #fafafa;
--jd-red-bg: #ffffff;
/* ═══ 灰色系 ═══ */
--bg-gray: #f5f5f5;
--bg-gray: #ffffff;
--bg-white: #ffffff;
--border-gray: #e5e5e5;
--border-light: #f0f0f0;
@@ -23,7 +23,7 @@
--text-light: #999999;
/* ═══ 功能色 ═══ */
--link-blue: #005ea7;
--link-blue: #e4393c;
--color-success: #67c23a;
--color-warning: #e6a23c;
--color-danger: #f56c6c;
@@ -61,7 +61,7 @@
--space-xl: 20px;
/* ═══ 表格 ═══ */
--table-header-bg: #f5f5f5;
--table-header-bg: #ffffff;
--table-header-text: #666666;
--table-row-hover: #f5f7fa;
--table-stripe: #fafafa;
@@ -69,12 +69,12 @@
/* ═══ 导航 ═══ */
--nav-height: 50px;
--nav-bg: #f5f5f5;
--nav-bg: #ffffff;
--nav-text: #333333;
--nav-hover: rgba(0, 0, 0, 0.04);
/* ═══ 侧栏 ═══ */
--sidebar-width: 200px;
--sidebar-width: 192px;
--sidebar-bg: #ffffff;
--sidebar-text: #666666;
--sidebar-text-active: #e4393c;
@@ -107,11 +107,21 @@ body {
顶部导航栏
═══════════════════════════════════════════ */
.navbar {
background: #f5f5f5 !important;
border-bottom: 1px solid #e5e5e5 !important;
box-shadow: none !important;
background: #ffffff !important;
border-bottom: 1px solid #ececec !important;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04) !important;
height: var(--nav-height) !important;
color: #333 !important;
position: relative;
/* 顶部红色品牌细条 */
&::before {
content: '';
position: absolute;
left: 0; right: 0; top: 0;
height: 3px;
background: linear-gradient(90deg, #e4393c 0%, #c81623 60%, #e4393c 100%);
}
.hamburger-container svg {
color: #666 !important;
@@ -119,7 +129,8 @@ body {
}
.hamburger-container:hover {
background: rgba(0, 0, 0, 0.04) !important;
background: rgba(228, 57, 60, 0.06) !important;
svg { fill: #e4393c !important; color: #e4393c !important; }
}
.breadcrumb-container {
@@ -127,12 +138,15 @@ body {
.el-breadcrumb__inner a,
.el-breadcrumb__separator {
color: #999 !important;
font-weight: 400 !important;
}
.el-breadcrumb__inner.is-link {
font-weight: 400 !important;
&:hover {
color: #e4393c !important;
}
&:hover { color: #e4393c !important; }
}
.el-breadcrumb__item:last-child .el-breadcrumb__inner {
color: #333 !important;
font-weight: 500 !important;
}
}
@@ -142,36 +156,25 @@ body {
font-size: 16px !important;
&.hover-effect:hover {
color: #333 !important;
background: #f0f0f0 !important;
color: #e4393c !important;
background: rgba(228, 57, 60, 0.06) !important;
}
svg,
i {
color: #666 !important;
}
&:hover svg,
&:hover i {
color: #333 !important;
}
svg, i { color: #666 !important; }
&:hover svg, &:hover i { color: #e4393c !important; }
}
.avatar-container {
.user-nickname {
color: #333 !important;
font-weight: 500 !important;
}
.avatar-wrapper {
margin-top: 6px !important;
}
.user-nickname { color: #333 !important; font-weight: 500 !important; }
.avatar-wrapper { margin-top: 6px !important; }
}
.el-dropdown {
color: #333 !important;
}
.el-dropdown { color: #333 !important; }
.el-badge__content {
border-color: #fff !important;
background: #e4393c !important;
color: #fff !important;
}
}
@@ -183,7 +186,7 @@ body {
.el-menu-item,
.el-submenu__title {
color: #666 !important;
color: #555 !important;
background: transparent !important;
border-bottom: none !important;
height: var(--nav-height) !important;
@@ -191,12 +194,13 @@ body {
&:hover {
color: #e4393c !important;
background: rgba(0, 0, 0, 0.03) !important;
background: rgba(228, 57, 60, 0.04) !important;
}
&.is-active {
color: #e4393c !important;
background: transparent !important;
border-bottom: 2px solid #e4393c !important;
}
}
}
@@ -269,6 +273,21 @@ body {
.el-submenu.is-active > .el-submenu__title {
color: var(--sidebar-text-active) !important;
background: transparent !important;
}
/* 强制清除 element-ui 默认 hover 黄色背景 */
.el-submenu__title:hover,
.el-submenu__title:focus,
.el-menu-item:focus {
background: var(--sidebar-hover-bg) !important;
outline: none !important;
}
/* 子菜单展开时 inline-style 的浅色背景一并覆盖 */
.el-menu .el-menu-item,
.el-menu .el-submenu__title {
background-color: transparent !important;
}
/* 子菜单 */
@@ -277,12 +296,23 @@ body {
}
.el-submenu .el-menu-item {
padding-left: 42px !important;
padding-left: 32px !important;
height: 34px !important;
line-height: 34px !important;
font-size: 12px !important;
color: var(--text-medium) !important;
/* 二级菜单图标隐藏 */
.svg-icon,
> i,
> .el-icon-,
[class^="el-icon-"],
[class*=" el-icon-"] {
display: none !important;
width: 0 !important;
margin: 0 !important;
}
&:hover {
color: var(--text-medium) !important;
background: var(--sidebar-hover-bg) !important;
@@ -322,38 +352,39 @@ body {
/* ═══════════════════════════════════════════
按钮样式 — 银灰风格
═══════════════════════════════════════════ */
/* 主按钮 — 银灰色 */
/* 主按钮 — 京东红 */
.el-button--primary {
background: #909399 !important;
border: 1px solid #909399 !important;
background: var(--jd-red) !important;
border: 1px solid var(--jd-red) !important;
color: #fff !important;
border-radius: var(--radius-base) !important;
transition: all 0.2s;
&:hover,
&:focus {
background: #6b7280 !important;
border-color: #6b7280 !important;
background: var(--jd-red-dark) !important;
border-color: var(--jd-red-dark) !important;
}
&:active {
background: #4b5563 !important;
border-color: #4b5563 !important;
background: #a30f1c !important;
border-color: #a30f1c !important;
}
&.is-disabled,
&.is-disabled:hover {
background: #d1d5db !important;
border-color: #d1d5db !important;
background: #fbcecf !important;
border-color: #fbcecf !important;
color: #fff !important;
}
&.is-plain {
background: #fff !important;
border-color: #909399 !important;
color: #909399 !important;
border-color: var(--jd-red) !important;
color: var(--jd-red) !important;
&:hover {
background: #909399 !important;
background: var(--jd-red) !important;
color: #fff !important;
}
}
@@ -744,8 +775,8 @@ body {
color: #e6a23c !important;
}
.el-tag--primary {
background: #ecf5ff !important;
color: #409eff !important;
background: var(--jd-red-light) !important;
color: var(--jd-red) !important;
}
.el-tag--success {
background: #f0f9eb !important;

View File

@@ -1,27 +1,27 @@
// base color - ERPNext inspired palette
$blue:#4A6FA5;
$light-blue:#6B8FB5;
$red:#C03639;
// base color - 京东红 palette
$blue:#e4393c;
$light-blue:#f88;
$red:#e4393c;
$pink: #E65D6E;
$green: #30B08F;
$tiffany: #4AB7BD;
$yellow:#FEC171;
$panGreen: #30B08F;
// 默认菜单主题风格(ERPNext风格白色边栏蓝色主色
// 默认菜单主题风格(京东风格
$base-menu-color: #555c70;
$base-menu-color-active: #4A6FA5;
$base-menu-color-active: #e4393c;
$base-menu-background: #1a2332;
$base-logo-title-color: #ffffff;
$base-menu-light-color: rgba(0,0,0,.65);
$base-menu-light-background: #ffffff;
$base-logo-light-title-color: #4A6FA5;
$base-logo-light-title-color: #e4393c;
$base-sub-menu-background: #111a27;
$base-sub-menu-hover: rgba(17,113,196,.08);
$base-sub-menu-hover: rgba(228,57,60,.08);
$base-sidebar-width: 200px;
$base-sidebar-width: 192px;
:export {
menuColor: $base-menu-color;

View File

@@ -63,7 +63,7 @@ export default {
<style lang="scss" scoped>
.jd-filter-bar {
background: var(--bg-gray, #f5f5f5);
background: var(--bg-gray, #ffffff);
padding: 12px 16px;
border-radius: var(--radius-base, 2px);
margin-bottom: 12px;

View File

@@ -582,7 +582,7 @@ export default {
transition: all 0.2s;
&:hover {
color: #409eff;
color: #e4393c;
background: #ecf5ff;
}
}
@@ -608,7 +608,7 @@ export default {
gap: 5px;
i {
color: #409eff;
color: #e4393c;
font-size: 14px;
}
}
@@ -630,7 +630,7 @@ export default {
transition: all 0.2s;
&:hover {
color: #409eff;
color: #e4393c;
background: #ecf5ff;
}
}
@@ -668,17 +668,17 @@ export default {
margin-bottom: 1px;
&:hover {
background: #f0f7ff;
background: #fafafa;
}
}
::v-deep .el-tree-node.is-current > .el-tree-node__content {
background: #e6f0fd;
color: #409eff;
color: #e4393c;
font-weight: 600;
.node-icon {
color: #409eff !important;
color: #e4393c !important;
}
}
}

View File

@@ -137,7 +137,7 @@ export default {
}
.notice-popover .notice-mark-all {
font-size: 12px;
color: #409EFF;
color: #e4393c;
font-weight: normal;
cursor: pointer;
}
@@ -155,7 +155,7 @@ export default {
align-items: center;
gap: 8px;
padding: 10px 14px;
border-bottom: 1px solid #f5f5f5;
border-bottom: 1px solid #ffffff;
cursor: pointer;
transition: background 0.15s;
}

View File

@@ -146,7 +146,7 @@ export default {
height: 50px;
overflow: hidden;
position: relative;
background: #f5f5f5;
background: #ffffff;
border-bottom: 1px solid #e5e5e5;
box-shadow: none;
display: flex;

View File

@@ -1,24 +1,19 @@
<template>
<div class="sidebar-logo-container" :class="{ collapse: collapse }"
:style="{ backgroundColor: sideTheme === 'theme-dark' && navType !== 3 ? variables.menuBackground : variables.menuLightBackground }">
<div class="sidebar-logo-container" :class="{ collapse: collapse }">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo"
style="width:32px;height:32px;object-fit:contain;flex-shrink:0;display:block" />
<h1 v-else class="sidebar-title"
:style="{ color: sideTheme === 'theme-dark' && navType !== 3 ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
<img v-if="logo" :src="logo" class="sidebar-logo-img" />
<h1 v-else class="sidebar-title">{{ title }}</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo"
style="width:32px;height:32px;object-fit:contain;flex-shrink:0;margin-right:10px;display:block" />
<h1 class="sidebar-title"
:style="{ color: sideTheme === 'theme-dark' && navType !== 3 ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
<img v-if="logo" :src="logo" class="sidebar-logo-img expand" />
<h1 class="sidebar-title">{{ title }}</h1>
</router-link>
</transition>
</div>
</template>
<script>
import logoImg from "@/assets/logo/logo.png"
import logoImg from "@/assets/logo/logo.svg"
import variables from "@/assets/styles/variables.scss"
export default {
name: "SidebarLogo",
@@ -31,18 +26,57 @@ export default {
data() { return { title: process.env.VUE_APP_TITLE, logo: logoImg } }
}
</script>
<style lang="scss" scoped>
<style lang="scss">
.sidebarLogoFade-enter-active { transition: opacity 1.5s; }
.sidebarLogoFade-enter, .sidebarLogoFade-leave-to { opacity: 0; }
.sidebar-logo-container {
height: 50px; background: #2b2f3a; text-align: center;
height: 50px !important;
background: #ffffff !important;
text-align: left !important;
overflow: hidden !important;
border-bottom: 1px solid #e5e5e5 !important;
white-space: nowrap !important;
.sidebar-logo-link {
height: 100%; width: 100%; display: flex; align-items: center;
justify-content: center; text-decoration: none; padding: 0 12px;
height: 50px !important;
width: 100% !important;
display: flex !important;
flex-wrap: nowrap !important;
align-items: center !important;
justify-content: flex-start !important;
text-decoration: none !important;
padding: 0 12px !important;
box-sizing: border-box !important;
white-space: nowrap !important;
.sidebar-logo-img {
width: 26px !important;
height: 26px !important;
min-width: 26px !important;
object-fit: contain !important;
flex: 0 0 26px !important;
display: inline-block !important;
vertical-align: middle !important;
margin: 0 !important;
&.expand { margin-right: 10px !important; }
}
.sidebar-title {
color: #fff; font-weight: 600; font-size: 14px; margin: 0;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
color: #333 !important;
font-weight: 700 !important;
font-size: 13px !important;
margin: 0 !important;
padding: 0 !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
line-height: 50px !important;
flex: 1 1 auto !important;
min-width: 0 !important;
display: inline-block !important;
vertical-align: middle !important;
letter-spacing: 0.5px !important;
}
}
&.collapse .sidebar-logo-link { justify-content: center !important; padding: 0 !important; }
}
</style>

View File

@@ -105,9 +105,9 @@ export default {
},
chromeVars() {
if (this.tagsViewStyle !== 'chrome') return {}
const primary = this.theme || '#409EFF'
const primary = this.theme || '#e4393c'
return {
'--chrome-tab-active-bg': this.mixHexWithWhite(primary, 0.15),
'--chrome-tab-active-bg': '#ffffff',
'--chrome-tab-text-active': primary,
'--chrome-wing-r': '14px'
}

View File

@@ -0,0 +1,150 @@
<template>
<div class="app-container">
<el-form :model="queryParams" size="small" :inline="true" ref="queryForm">
<el-form-item label="业务名称" prop="bizName">
<el-input v-model="queryParams.bizName" placeholder="业务名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="启用状态" prop="enabled">
<el-select v-model="queryParams.enabled" placeholder="全部" clearable style="width:110px">
<el-option label="启用" value="1"/>
<el-option label="停用" value="0"/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['bid:approval:add']">新增配置</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="list">
<el-table-column label="业务类型" prop="bizType" width="180"/>
<el-table-column label="业务名称" prop="bizName" width="160"/>
<el-table-column label="审批方式" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.signType==='1'?'':'warning'">
{{ scope.row.signType==='1' ? '或签' : '会签' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="审批人" min-width="200">
<template slot-scope="scope">
<el-tag v-for="n in scope.row.userNames" :key="n" size="small" style="margin:2px">{{ n }}</el-tag>
<span v-if="!scope.row.userNames || !scope.row.userNames.length" style="color:#f56c6c">未配置</span>
</template>
</el-table-column>
<el-table-column label="启用" width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.enabled==='1' ? 'success' : 'info'">
{{ scope.row.enabled==='1' ? '启用' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" :show-overflow-tooltip="true"/>
<el-table-column label="操作" align="center" width="160">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['bid:approval:edit']">编辑</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['bid:approval:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<el-dialog :title="title" :visible.sync="open" width="640px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="业务类型" prop="bizType">
<el-input v-model="form.bizType" :disabled="!!form.id" placeholder="如 PURCHASE_ORDER"/>
</el-form-item>
<el-form-item label="业务名称" prop="bizName">
<el-input v-model="form.bizName" placeholder="如 采购订单"/>
</el-form-item>
<el-form-item label="审批方式" prop="signType">
<el-radio-group v-model="form.signType">
<el-radio label="1">或签任一审批人通过即生效</el-radio>
<el-radio label="2" disabled>会签预留</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审批人" prop="userIds">
<el-select v-model="form.userIds" multiple filterable placeholder="选择审批人" style="width:100%">
<el-option v-for="u in userOptions" :key="u.userId" :label="u.nickName" :value="u.userId"/>
</el-select>
</el-form-item>
<el-form-item label="启用" prop="enabled">
<el-radio-group v-model="form.enabled">
<el-radio label="1">启用</el-radio>
<el-radio label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" rows="2"/></el-form-item>
</el-form>
<div slot="footer">
<el-button @click="open=false">取消</el-button>
<el-button type="primary" @click="submitForm">保存</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listApproval, getApproval, addApproval, updateApproval, delApproval } from "@/api/bid/approval";
import { listUser } from "@/api/system/user";
export default {
name: "ApprovalConfig",
data() {
return {
loading: false, total: 0, list: [],
open: false, title: "",
userOptions: [],
queryParams: { pageNum: 1, pageSize: 10, bizName: null, enabled: null },
form: { signType: "1", enabled: "1", userIds: [] },
rules: {
bizType: [{ required: true, message: "请输入业务类型", trigger: "blur" }],
bizName: [{ required: true, message: "请输入业务名称", trigger: "blur" }],
userIds: [{ required: true, type: "array", min: 1, message: "至少选择一个审批人", trigger: "change" }]
}
};
},
created() {
this.getList();
listUser({ pageSize: 500 }).then(r => { this.userOptions = r.rows || []; });
},
methods: {
getList() {
this.loading = true;
listApproval(this.queryParams).then(r => { this.list = r.rows; this.total = r.total; this.loading = false; });
},
handleQuery() { this.queryParams.pageNum = 1; this.getList(); },
resetQuery() { this.resetForm("queryForm"); this.handleQuery(); },
handleAdd() {
this.form = { signType: "1", enabled: "1", userIds: [] };
this.open = true; this.title = "新增审批配置";
},
handleUpdate(row) {
getApproval(row.id).then(r => {
this.form = Object.assign({}, r.data, { userIds: r.data.userIds || [] });
this.open = true; this.title = "编辑审批配置";
});
},
handleDelete(row) {
this.$modal.confirm("确认删除该配置?").then(() => delApproval(row.id))
.then(() => { this.getList(); this.$modal.msgSuccess("删除成功"); });
},
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) return;
const action = this.form.id ? updateApproval : addApproval;
action(this.form).then(() => { this.$modal.msgSuccess("保存成功"); this.open = false; this.getList(); });
});
}
}
};
</script>

View File

@@ -0,0 +1,86 @@
<template>
<div class="app-container">
<div style="margin-bottom:12px;display:flex;align-items:center;gap:8px">
<span style="font-size:16px;font-weight:600">待我审批</span>
<el-tag type="warning" size="small"> {{ list.length }} </el-tag>
<el-button size="mini" icon="el-icon-refresh" @click="getList" style="margin-left:auto">刷新</el-button>
</div>
<el-form :inline="true" size="small">
<el-form-item label="业务类型">
<el-select v-model="filterBizType" placeholder="全部" clearable style="width:160px">
<el-option label="采购订单" value="PURCHASE_ORDER"/>
<el-option label="客户报价" value="CLIENT_QUOTE"/>
<el-option label="供应商报价" value="QUOTATION"/>
<el-option label="发货单" value="DELIVERY_ORDER"/>
<el-option label="订单异议" value="ORDER_OBJECTION"/>
</el-select>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="filteredList" border>
<el-table-column label="业务类型" width="140">
<template slot-scope="s">
<el-tag size="small">{{ s.row.bizName }}</el-tag>
</template>
</el-table-column>
<el-table-column label="单号" prop="bizNo" width="200"/>
<el-table-column label="金额" prop="amount" width="140" align="right">
<template slot-scope="s">
<span v-if="s.row.amount" style="color:#e4393c;font-weight:600">¥{{ s.row.amount }}</span>
<span v-else style="color:#999">-</span>
</template>
</el-table-column>
<el-table-column label="提交人" prop="createBy" width="120"/>
<el-table-column label="提交时间" prop="createTime" width="180"/>
<el-table-column label="状态" width="100" align="center">
<template><el-tag type="warning">审批中</el-tag></template>
</el-table-column>
<el-table-column label="操作" align="center" width="180">
<template slot-scope="s">
<el-button size="mini" type="text" style="color:#67C23A" @click="handleApprove(s.row)">通过</el-button>
<el-button size="mini" type="text" style="color:#F56C6C" @click="handleReject(s.row)">驳回</el-button>
</template>
</el-table-column>
<template slot="empty">
<div style="padding:24px;color:#999">暂无待审批单据</div>
</template>
</el-table>
</div>
</template>
<script>
import { listPending, approveBiz, rejectBiz } from "@/api/bid/approvalAction";
export default {
name: "ApprovalPending",
data() {
return { loading: false, list: [], filterBizType: null };
},
computed: {
filteredList() {
if (!this.filterBizType) return this.list;
return this.list.filter(r => r.bizType === this.filterBizType);
}
},
created() { this.getList(); },
methods: {
getList() {
this.loading = true;
listPending().then(r => { this.list = r.data || []; this.loading = false; })
.catch(() => { this.loading = false; });
},
handleApprove(row) {
this.$modal.confirm(`确认通过【${row.bizName} · ${row.bizNo}】?`)
.then(() => approveBiz(row.bizType, row.id))
.then(() => { this.$modal.msgSuccess("审批通过"); this.getList(); });
},
handleReject(row) {
this.$prompt("请输入驳回原因", "驳回", { inputPattern: /.+/, inputErrorMessage: "请填写原因" })
.then(({ value }) => rejectBiz(row.bizType, row.id, value))
.then(() => { this.$modal.msgSuccess("已驳回"); this.getList(); })
.catch(() => {});
}
}
};
</script>

View File

@@ -277,7 +277,7 @@ export default {
/* ═══════ 整体布局 ═══════ */
.client-manage {
padding: 12px;
background: #f5f5f5;
background: #ffffff;
min-height: calc(100vh - 84px);
}
.client-manage ::v-deep .el-tabs__header {
@@ -342,7 +342,7 @@ export default {
.dl {
width: 90px;
flex-shrink: 0;
background: #f5f5f5;
background: #ffffff;
padding: 10px 12px;
font-size: 12px;
color: #666;
@@ -357,7 +357,7 @@ export default {
}
.detail-remark {
padding: 8px 12px;
background: #fff5f5;
background: #fafafa;
border: 1px solid #fce4e4;
border-radius: 2px;
font-size: 12px;

View File

@@ -17,11 +17,20 @@
<el-table-column label="金额" width="120" align="right"><template slot-scope="s"><span class="amount">¥{{ s.row.totalAmount }}</span></template></el-table-column>
<el-table-column label="交货期" prop="deliveryDate" width="95" align="center" />
<el-table-column label="物料" prop="itemCount" width="55" align="center" />
<el-table-column label="状态" width="90" align="center"><template slot-scope="s"><el-tag type="warning" size="small" effect="dark">待发</el-tag></template></el-table-column>
<el-table-column label="操作" width="180" align="center">
<el-table-column label="状态" width="90" align="center">
<template slot-scope="s">
<el-tag :type="s.row.deliveryStatus==='10'?'warning':(s.row.deliveryStatus==='rejected'?'danger':'warning')" size="small" effect="dark">
{{ {pending:'待发','10':'审批中',rejected:'已驳回',confirmed:'已确认'}[s.row.deliveryStatus] || '待发' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="300" align="center">
<template slot-scope="s">
<el-button size="mini" type="text" @click="handleView(s.row)">详情</el-button>
<el-button size="mini" type="text" style="color:#67C23A" @click="handleShip(s.row)">发货确认</el-button>
<el-button size="mini" type="text" style="color:#E6A23C" @click="handleSubmitApproval(s.row)" v-if="s.row.deliveryStatus==='pending' || s.row.deliveryStatus==='rejected'">提交审批</el-button>
<el-button size="mini" type="text" style="color:#67C23A" @click="handleApprove(s.row)" v-if="s.row.deliveryStatus==='10'">通过</el-button>
<el-button size="mini" type="text" style="color:#F56C6C" @click="handleReject(s.row)" v-if="s.row.deliveryStatus==='10'">驳回</el-button>
<el-button size="mini" type="text" style="color:#67C23A" @click="handleShip(s.row)" v-if="s.row.deliveryStatus==='pending' || s.row.deliveryStatus==='confirmed'">发货确认</el-button>
<el-button size="mini" type="text" style="color:#f56c6c" @click="handleDelete(s.row)">删除</el-button>
</template>
</el-table-column>
@@ -32,7 +41,7 @@
<div class="detail-grid">
<div class="detail-item"><span class="dl">发货单号</span><span class="dv"><b>{{ detailData ? detailData.doNo : '' }}</b></span></div>
<div class="detail-item"><span class="dl">甲方客户</span><span class="dv">{{ (detailData && detailData.clientName) || '-' }}</span></div>
<div class="detail-item"><span class="dl">总金额</span><span class="dv" style="color:#409EFF">¥{{ detailData ? detailData.totalAmount : 0 }}</span></div>
<div class="detail-item"><span class="dl">总金额</span><span class="dv" style="color:#e4393c">¥{{ detailData ? detailData.totalAmount : 0 }}</span></div>
<div class="detail-item"><span class="dl">状态</span><span class="dv"><el-tag type="warning" size="small" effect="dark">待发</el-tag></span></div>
<div class="detail-item"><span class="dl">交货期</span><span class="dv">{{ (detailData && detailData.deliveryDate) || '-' }}</span></div>
<div class="detail-item"><span class="dl">备注</span><span class="dv">{{ (detailData && detailData.remark) || '-' }}</span></div>
@@ -53,11 +62,12 @@
</template>
<script>
import { listDelivery, getDelivery, delDelivery, shipDelivery } from "@/api/bid/delivery"
import { submitApproval, approveBiz, rejectBiz } from "@/api/bid/approvalAction"
export default {
name: "ClientDeliveryPending",
data() { return {
loading: false, list: [], total: 0,
q: { pageNum: 1, pageSize: 20, type: "client", deliveryStatus: "pending", doNo: "", clientName: "" },
q: { pageNum: 1, pageSize: 20, type: "client", doNo: "", clientName: "" },
detailOpen: false, detailData: null
}},
created() { this.getList() },
@@ -66,22 +76,25 @@ export default {
search() { this.q.pageNum=1; this.getList() }, resetSearch() { this.q.doNo=""; this.q.clientName=""; this.search() },
handleView(row) { getDelivery(row.doId).then(r=>{this.detailData=r.data;this.detailOpen=true}).catch(()=>{}) },
handleShip(row) { this.$modal.confirm("确认发货?").then(()=>shipDelivery(row.doId)).then(()=>{this.$modal.msgSuccess("已发货");this.getList()}).catch(()=>{}) },
handleDelete(row) { this.$modal.confirm("确认删除?").then(()=>delDelivery(row.doId)).then(()=>{this.$modal.msgSuccess("已删除");this.getList()}).catch(()=>{}) }
handleDelete(row) { this.$modal.confirm("确认删除?").then(()=>delDelivery(row.doId)).then(()=>{this.$modal.msgSuccess("已删除");this.getList()}).catch(()=>{}) },
handleSubmitApproval(row) { this.$modal.confirm("确认提交审批?").then(()=>submitApproval("DELIVERY_ORDER", row.doId)).then(()=>{this.$modal.msgSuccess("已提交审批");this.getList()}).catch(()=>{}) },
handleApprove(row) { this.$modal.confirm("确认通过该发货单?").then(()=>approveBiz("DELIVERY_ORDER", row.doId)).then(()=>{this.$modal.msgSuccess("审批通过");this.getList()}).catch(()=>{}) },
handleReject(row) { this.$prompt("请输入驳回原因","驳回",{inputPattern:/.+/,inputErrorMessage:"请填写原因"}).then(({value})=>rejectBiz("DELIVERY_ORDER", row.doId, value)).then(()=>{this.$modal.msgSuccess("已驳回");this.getList()}).catch(()=>{}) }
}
}
</script>
<style scoped>
.order-page { background:#f5f7fa; padding:12px; min-height:calc(100vh - 84px); }
.page-header { background:#fff; padding:12px 16px; border-radius:4px; margin-bottom:12px; display:flex; align-items:center; gap:12px; }
.page-title { font-size:16px; font-weight:700; color:#1a2c4e; }
.page-title { font-size:16px; font-weight:700; color:#333333; }
.search-bar { background:#fff; padding:12px 16px; border-radius:4px; margin-bottom:12px; display:flex; align-items:center; gap:8px; }
.sr { margin-left:auto; }
.amount { color:#409EFF; font-weight:700; }
.amount { color:#e4393c; font-weight:700; }
.detail-grid { display:grid; grid-template-columns:1fr 1fr; gap:0; border:1px solid #ebeef5; border-radius:4px; margin-bottom:16px; }
.detail-item { display:flex; border-bottom:1px solid #ebeef5; }
.detail-item:nth-last-child(-n+2) { border-bottom:none; }
.detail-item:nth-child(odd) { border-right:1px solid #ebeef5; }
.dl { width:90px; flex-shrink:0; background:#f5f7fa; padding:10px 12px; font-size:12px; color:#606266; font-weight:600; border-right:1px solid #ebeef5; }
.dv { padding:10px 12px; font-size:13px; color:#303133; flex:1; }
.section-bar { font-size:13px; font-weight:700; color:#1a2c4e; padding:6px 0 6px 10px; margin-bottom:10px; border-left:4px solid #4A6FA5; }
.section-bar { font-size:13px; font-weight:700; color:#333333; padding:6px 0 6px 10px; margin-bottom:10px; border-left:4px solid #4A6FA5; }
</style>

View File

@@ -24,7 +24,7 @@
<div class="detail-grid">
<div class="detail-item"><span class="dl">发货单号</span><span class="dv"><b>{{ detailData ? detailData.doNo : '' }}</b></span></div>
<div class="detail-item"><span class="dl">甲方客户</span><span class="dv">{{ (detailData && detailData.clientName) || '-' }}</span></div>
<div class="detail-item"><span class="dl">总金额</span><span class="dv" style="color:#409EFF">¥{{ detailData ? detailData.totalAmount : 0 }}</span></div>
<div class="detail-item"><span class="dl">总金额</span><span class="dv" style="color:#e4393c">¥{{ detailData ? detailData.totalAmount : 0 }}</span></div>
<div class="detail-item"><span class="dl">状态</span><span class="dv"><el-tag type="success" size="small" effect="dark">已签收</el-tag></span></div>
<div class="detail-item"><span class="dl">交货期</span><span class="dv">{{ (detailData && detailData.deliveryDate) || '-' }}</span></div>
<div class="detail-item"><span class="dl">签收日期</span><span class="dv">{{ (detailData && detailData.actualCloseDate) || '-' }}</span></div>
@@ -63,17 +63,17 @@ export default {
<style scoped>
.order-page { background:#f5f7fa; padding:12px; min-height:calc(100vh - 84px); }
.page-header { background:#fff; padding:12px 16px; border-radius:4px; margin-bottom:12px; display:flex; align-items:center; gap:12px; }
.page-title { font-size:16px; font-weight:700; color:#1a2c4e; }
.page-title { font-size:16px; font-weight:700; color:#333333; }
.search-bar { background:#fff; padding:12px 16px; border-radius:4px; margin-bottom:12px; display:flex; align-items:center; gap:8px; }
.sr { margin-left:auto; }
.amount { color:#409EFF; font-weight:700; }
.amount { color:#e4393c; font-weight:700; }
.detail-grid { display:grid; grid-template-columns:1fr 1fr; gap:0; border:1px solid #ebeef5; border-radius:4px; margin-bottom:16px; }
.detail-item { display:flex; border-bottom:1px solid #ebeef5; }
.detail-item:nth-last-child(-n+2) { border-bottom:none; }
.detail-item:nth-child(odd) { border-right:1px solid #ebeef5; }
.dl { width:90px; flex-shrink:0; background:#f5f7fa; padding:10px 12px; font-size:12px; color:#606266; font-weight:600; border-right:1px solid #ebeef5; }
.dv { padding:10px 12px; font-size:13px; color:#303133; flex:1; }
.section-bar { font-size:13px; font-weight:700; color:#1a2c4e; padding:6px 0 6px 10px; margin-bottom:10px; border-left:4px solid #4A6FA5; }
.section-bar { font-size:13px; font-weight:700; color:#333333; padding:6px 0 6px 10px; margin-bottom:10px; border-left:4px solid #4A6FA5; }
.diff-early { color:#67c23a; font-weight:600; }
.diff-ontime { color:#909399; font-weight:600; }
.diff-late { color:#f56c6c; font-weight:600; }

View File

@@ -28,7 +28,7 @@
<div class="detail-grid">
<div class="detail-item"><span class="dl">发货单号</span><span class="dv"><b>{{ detailData ? detailData.doNo : '' }}</b></span></div>
<div class="detail-item"><span class="dl">甲方客户</span><span class="dv">{{ (detailData && detailData.clientName) || '-' }}</span></div>
<div class="detail-item"><span class="dl">总金额</span><span class="dv" style="color:#409EFF">¥{{ detailData ? detailData.totalAmount : 0 }}</span></div>
<div class="detail-item"><span class="dl">总金额</span><span class="dv" style="color:#e4393c">¥{{ detailData ? detailData.totalAmount : 0 }}</span></div>
<div class="detail-item"><span class="dl">状态</span><span class="dv"><el-tag type="primary" size="small" effect="dark">运输中</el-tag></span></div>
<div class="detail-item"><span class="dl">交货期</span><span class="dv">{{ (detailData && detailData.deliveryDate) || '-' }}</span></div>
<div class="detail-item"><span class="dl">备注</span><span class="dv">{{ (detailData && detailData.remark) || '-' }}</span></div>
@@ -65,15 +65,15 @@ export default {
<style scoped>
.order-page { background:#f5f7fa; padding:12px; min-height:calc(100vh - 84px); }
.page-header { background:#fff; padding:12px 16px; border-radius:4px; margin-bottom:12px; display:flex; align-items:center; gap:12px; }
.page-title { font-size:16px; font-weight:700; color:#1a2c4e; }
.page-title { font-size:16px; font-weight:700; color:#333333; }
.search-bar { background:#fff; padding:12px 16px; border-radius:4px; margin-bottom:12px; display:flex; align-items:center; gap:8px; }
.sr { margin-left:auto; }
.amount { color:#409EFF; font-weight:700; }
.amount { color:#e4393c; font-weight:700; }
.detail-grid { display:grid; grid-template-columns:1fr 1fr; gap:0; border:1px solid #ebeef5; border-radius:4px; margin-bottom:16px; }
.detail-item { display:flex; border-bottom:1px solid #ebeef5; }
.detail-item:nth-last-child(-n+2) { border-bottom:none; }
.detail-item:nth-child(odd) { border-right:1px solid #ebeef5; }
.dl { width:90px; flex-shrink:0; background:#f5f7fa; padding:10px 12px; font-size:12px; color:#606266; font-weight:600; border-right:1px solid #ebeef5; }
.dv { padding:10px 12px; font-size:13px; color:#303133; flex:1; }
.section-bar { font-size:13px; font-weight:700; color:#1a2c4e; padding:6px 0 6px 10px; margin-bottom:10px; border-left:4px solid #4A6FA5; }
.section-bar { font-size:13px; font-weight:700; color:#333333; padding:6px 0 6px 10px; margin-bottom:10px; border-left:4px solid #4A6FA5; }
</style>

View File

@@ -144,7 +144,7 @@
</el-table-column>
<el-table-column label="金额" width="75" align="right">
<template slot-scope="s">
<strong style="color:#409EFF">¥{{ s.row.totalPrice || '0.00' }}</strong>
<strong style="color:#e4393c">¥{{ s.row.totalPrice || '0.00' }}</strong>
</template>
</el-table-column>
<el-table-column label="毛利率" width="60" align="center">
@@ -167,7 +167,7 @@
<div class="total-bar">
<span>合计</span>
<strong style="font-size:20px;color:#409EFF">¥{{ grandTotal }}</strong>
<strong style="font-size:20px;color:#e4393c">¥{{ grandTotal }}</strong>
<span style="margin-left:20px;color:#909399;font-size:13px"> {{ form.items.length }} </span>
</div>
</el-card>
@@ -293,7 +293,7 @@ import { getClientQuote, addClientQuote, updateClientQuote, getClientQuoteHistor
import { listClient } from "@/api/bid/client";
import { createRfqFromQuote } from "@/api/bid/rfq";
import { listMaterial, getSupplierQuoteReference } from "@/api/bid/material";
import logoImg from "@/assets/logo/logo.png";
import logoImg from "@/assets/logo/logo.svg";
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
@@ -577,7 +577,7 @@ export default {
.page-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:20px; padding:16px 0; border-bottom:1px solid #f0f2f5; flex-wrap:wrap; gap:10px; overflow:hidden; }
.page-header-left { display:flex; align-items:center; gap:12px; }
.page-header-right { display:flex; gap:10px; }
.page-title { font-size:18px; font-weight:700; color:#1a2c4e; }
.page-title { font-size:18px; font-weight:700; color:#333333; }
.info-card ::v-deep .el-card__body { padding: 20px; }
.total-bar { display:flex; align-items:center; justify-content:flex-end; padding:16px 20px; border-top:1px solid #f0f2f5; background:#fafbfc; }
@@ -606,7 +606,7 @@ export default {
/* RFQ已生成信息 */
.rfq-generated-info { padding: 4px 0; }
.rfq-item { display:flex; align-items:center; gap:8px; padding:8px 10px; border:1px solid #e4e7ed; border-radius:6px; cursor:pointer; transition:all .2s; }
.rfq-item:hover { border-color:#409eff; background:#f0f7ff; }
.rfq-item:hover { border-color:#e4393c; background:#fafafa; }
.rfq-title { font-size:12px; color:#606266; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:120px; }
/* 物料搜索建议下拉 */
@@ -671,7 +671,7 @@ export default {
}
.ms-brand {
color: #409EFF;
color: #e4393c;
background: #ecf5ff;
padding: 2px 8px;
border-radius: 3px;
@@ -695,21 +695,21 @@ export default {
.pdf-header { display:flex; align-items:flex-start; padding-bottom:12px; }
.pdf-logo { width:52px; height:52px; object-fit:contain; margin-right:14px; }
.pdf-header-text { flex:1; }
.pdf-company { font-size:22px; font-weight:700; color:#1171c4; }
.pdf-company { font-size:22px; font-weight:700; color:#e4393c; }
.pdf-doc-type { font-size:14px; color:#333; margin-top:4px; font-weight:600; }
.pdf-header-meta { font-size:12px; color:#888; text-align:right; }
.pdf-divider { border-top:2px solid #1171c4; margin:0 0 16px; }
.pdf-divider { border-top:2px solid #e4393c; margin:0 0 16px; }
.pdf-meta-table { width:100%; border-collapse:collapse; margin-bottom:16px; td { padding:7px 10px; border:1px solid #e4e7ed; } }
.meta-label { background:#f5f7fa; color:#606266; font-weight:600; width:90px; }
.meta-val { color:#303133; }
.pdf-section-title { font-size:14px; font-weight:700; color:#1a2c4e; margin:0 0 10px; padding-left:8px; border-left:4px solid #1171c4; }
.pdf-section-title { font-size:14px; font-weight:700; color:#333333; margin:0 0 10px; padding-left:8px; border-left:4px solid #e4393c; }
.pdf-items-table { width:100%; border-collapse:collapse; margin-bottom:20px;
th { background:#1171c4; color:#fff; padding:8px; text-align:center; font-size:12px; font-weight:600; }
th { background:#e4393c; color:#fff; padding:8px; text-align:center; font-size:12px; font-weight:600; }
td { border:1px solid #e4e7ed; padding:7px 8px; text-align:center; font-size:12px; }
tbody tr:nth-child(even) td { background:#f9fbff; }
}
.amount-cell { color:#409EFF; font-weight:600; }
.total-cell { font-size:14px; background:#f0f7ff !important; font-weight:700; }
.pdf-note { background:#fffbf0; border:1px solid #faecd8; border-radius:4px; padding:10px 14px; font-size:12px; color:#606266; margin-bottom:16px; }
.amount-cell { color:#e4393c; font-weight:600; }
.total-cell { font-size:14px; background:#fafafa !important; font-weight:700; }
.pdf-note { background:#fafafa; border:1px solid #faecd8; border-radius:4px; padding:10px 14px; font-size:12px; color:#606266; margin-bottom:16px; }
.pdf-footer { text-align:center; font-size:11px; color:#aaa; border-top:1px solid #f0f2f5; padding-top:12px; }
</style>

View File

@@ -91,7 +91,7 @@
</el-table-column>
<el-table-column label="总金额" width="130" align="right">
<template slot-scope="s">
<strong style="color:#409EFF">¥{{ s.row.totalAmount | money }}</strong>
<strong style="color:#e4393c">¥{{ s.row.totalAmount | money }}</strong>
</template>
</el-table-column>
<el-table-column label="币种" prop="currency" width="70" align="center" />
@@ -134,13 +134,13 @@
<el-descriptions-item label="有效期">{{ detailForm.validityDate | date }}</el-descriptions-item>
<el-descriptions-item label="关联询价单">{{ detailForm.rfqNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="总金额" :span="3">
<strong style="color:#409EFF;font-size:16px">¥{{ detailForm.totalAmount | money }}</strong>
<strong style="color:#e4393c;font-size:16px">¥{{ detailForm.totalAmount | money }}</strong>
</el-descriptions-item>
<el-descriptions-item label="备注" :span="3">{{ detailForm.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<!-- 明细表格 -->
<div style="margin-top:16px;font-weight:600;font-size:14px;color:#303133;padding:8px 0;border-bottom:2px solid #409EFF">
<div style="margin-top:16px;font-weight:600;font-size:14px;color:#303133;padding:8px 0;border-bottom:2px solid #e4393c">
报价明细
</div>
<el-table :data="detailForm.items || []" border size="small" style="margin-top:12px">
@@ -157,7 +157,7 @@
<template slot-scope="s">¥{{ s.row.unitPrice | money }}</template>
</el-table-column>
<el-table-column label="金额" width="110" align="right">
<template slot-scope="s"><strong style="color:#409EFF">¥{{ s.row.totalPrice | money }}</strong></template>
<template slot-scope="s"><strong style="color:#e4393c">¥{{ s.row.totalPrice | money }}</strong></template>
</el-table-column>
<el-table-column label="交期(天)" prop="deliveryDays" width="80" align="center" />
</el-table>

View File

@@ -93,6 +93,9 @@
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="quickCreate">快速新建</el-dropdown-item>
<el-dropdown-item command="createRfq">生成RFQ</el-dropdown-item>
<el-dropdown-item command="submitApproval" v-if="s.row.status==='draft' || s.row.status==='rejected'">提交审批</el-dropdown-item>
<el-dropdown-item command="approve" v-if="s.row.status==='10'">审批通过</el-dropdown-item>
<el-dropdown-item command="reject" v-if="s.row.status==='10'">审批驳回</el-dropdown-item>
<el-dropdown-item command="createDelivery" v-if="s.row.status==='confirmed'">生成发货单</el-dropdown-item>
<el-dropdown-item command="delete" v-if="s.row.status==='draft'" divided>删除</el-dropdown-item>
</el-dropdown-menu>
@@ -213,7 +216,7 @@
<el-descriptions-item label="币种">{{ detailData.currency || 'CNY' }}</el-descriptions-item>
<el-descriptions-item label="有效期">{{ detailData.validityDate | dateFmt }}</el-descriptions-item>
<el-descriptions-item label="RFQ数量">
<span v-if="detailRfqList.length > 0" style="color:#409eff;font-weight:600">{{ detailRfqList.length }} </span>
<span v-if="detailRfqList.length > 0" style="color:#e4393c;font-weight:600">{{ detailRfqList.length }} </span>
<span v-else style="color:#c0c4cc">-</span>
</el-descriptions-item>
<el-descriptions-item label="总金额" :span="3">
@@ -269,6 +272,7 @@ import { listClientQuote, getClientQuote, addClientQuote, updateClientQuote, del
import { listRfq, createRfqFromQuote } from "@/api/bid/rfq";
import { listMaterial } from "@/api/bid/material";
import { addDelivery } from "@/api/bid/delivery";
import { submitApproval, approveBiz, rejectBiz } from "@/api/bid/approvalAction";
export default {
name: "ClientQuote",
@@ -331,8 +335,25 @@ export default {
if (cmd === 'quickCreate') this.handleQuickCreate(row);
else if (cmd === 'createRfq') this.handleCreateRfq(row);
else if (cmd === 'createDelivery') this.handleCreateDelivery(row);
else if (cmd === 'submitApproval') this.handleSubmitApproval(row);
else if (cmd === 'approve') this.handleApprove(row);
else if (cmd === 'reject') this.handleReject(row);
else if (cmd === 'delete') this.handleDelete(row);
},
handleSubmitApproval(row) {
this.$modal.confirm("确认提交审批?").then(() => submitApproval("CLIENT_QUOTE", row.quoteId))
.then(() => { this.$modal.msgSuccess("已提交审批"); this.getList(); this.getStats(); });
},
handleApprove(row) {
this.$modal.confirm("确认通过该客户报价?").then(() => approveBiz("CLIENT_QUOTE", row.quoteId))
.then(() => { this.$modal.msgSuccess("审批通过"); this.getList(); this.getStats(); });
},
handleReject(row) {
this.$prompt("请输入驳回原因", "驳回", { inputPattern: /.+/, inputErrorMessage: "请填写原因" })
.then(({ value }) => rejectBiz("CLIENT_QUOTE", row.quoteId, value))
.then(() => { this.$modal.msgSuccess("已驳回"); this.getList(); this.getStats(); })
.catch(() => {});
},
handleDelete(row) { this.$modal.confirm("确认删除【" + row.quoteNo + "】?").then(() => delClientQuote(row.quoteId)).then(() => { this.$modal.msgSuccess("删除成功"); this.getList(); this.getStats(); }); },
loadRfqForDetail(quoteId) { this.detailRfqLoading = true; listRfq({ clientQuoteId: quoteId, pageSize: 50 }).then(r => { this.detailRfqList = r.rows || []; this.detailRfqLoading = false; }).catch(() => { this.detailRfqLoading = false; }); },
viewRfqDetail(rfq) { this.detailOpen = false; this.$router.push({ path: '/bid/rfq/detail', query: { rfqId: rfq.rfqId } }); },
@@ -348,22 +369,22 @@ export default {
itemTotal(row) { return ((parseFloat(row.quantity)||0) * (parseFloat(row.unitPrice)||0)).toFixed(2); },
calcMargin(row) { const c = parseFloat(row.costPrice)||0; const p = parseFloat(row.unitPrice)||0; if (!p) return "0.0"; return (((p-c)/p)*100).toFixed(1); },
marginColor(row) { const m = parseFloat(this.calcMargin(row)); return m >= 20 ? "#67c23a" : m >= 10 ? "#e6a23c" : "#f56c6c"; },
statusType(s) { return { draft:"info", sent:"primary", confirmed:"success", rejected:"danger" }[s] || ""; },
statusLabel(s) { return { draft:"草稿", sent:"已发送", confirmed:"已确认", rejected:"已拒绝" }[s] || s; }
statusType(s) { return { draft:"info", "10":"warning", sent:"primary", confirmed:"success", rejected:"danger" }[s] || ""; },
statusLabel(s) { return { draft:"草稿", "10":"审批中", sent:"已发送", confirmed:"已确认", rejected:"已拒绝" }[s] || s; }
}
};
</script>
<style scoped>
/* ═══════ 页面容器 ═══════ */
.cq-page { background: #f5f5f5; padding: 12px; min-height: calc(100vh - 84px); }
.cq-page { background: #ffffff; padding: 12px; min-height: calc(100vh - 84px); }
/* ═══════ 统计卡片 ═══════ */
.stat-row { margin-bottom: 12px !important; }
/* ═══════ 搜索栏 ═══════ */
.search-bar {
background: #f5f5f5; padding: 10px 16px; border-radius: 2px;
background: #ffffff; padding: 10px 16px; border-radius: 2px;
margin-bottom: 12px;
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
@@ -371,7 +392,7 @@ export default {
/* ═══════ 表格 ═══════ */
.cq-table { }
.link-text { color: #005ea7; cursor: pointer; }
.link-text { color: #e4393c; cursor: pointer; }
.link-text:hover { color: #e4393c; text-decoration: underline; }
.amount { color: #e4393c; font-weight: 700; }

View File

@@ -79,7 +79,7 @@
</el-button>
</div>
</div>
<div class="plan-reason"><i class="el-icon-info" style="color:#409EFF;margin-right:4px"></i>{{ plan.clusterReason }}</div>
<div class="plan-reason"><i class="el-icon-info" style="color:#e4393c;margin-right:4px"></i>{{ plan.clusterReason }}</div>
<el-table :data="plan.items" border size="small" class="plan-table"
@selection-change="(sel) => onItemSelect(plan, sel)">
@@ -93,7 +93,7 @@
<template slot-scope="s">¥{{ s.row.unitPrice }}</template>
</el-table-column>
<el-table-column label="金额()" width="120" align="right">
<template slot-scope="s"><strong style="color:#409EFF">¥{{ s.row.totalPrice }}</strong></template>
<template slot-scope="s"><strong style="color:#e4393c">¥{{ s.row.totalPrice }}</strong></template>
</el-table-column>
<el-table-column label="交期()" prop="deliveryDays" width="85" align="center" />
</el-table>
@@ -150,7 +150,7 @@
<script>
import { compareRfq } from "@/api/bid/comparison";
import logoImg from "@/assets/logo/logo.png";
import logoImg from "@/assets/logo/logo.svg";
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
@@ -243,11 +243,11 @@ export default {
.comparison-detail { padding-bottom: 40px; }
.page-header { display:flex; align-items:center; margin-bottom:20px; padding:16px 0; border-bottom:1px solid #f0f2f5; }
.page-header-left { display:flex; align-items:center; gap:12px; }
.page-title { font-size:18px; font-weight:700; color:#1a2c4e; }
.page-title { font-size:18px; font-weight:700; color:#333333; }
.loading-box { text-align:center; padding:80px; color:#909399; font-size:15px; i { font-size:24px; margin-right:8px; } }
.main-tabs { ::v-deep .el-tabs__content { padding: 20px; } }
.item-block { margin-bottom: 32px; }
.item-header { background:linear-gradient(90deg,#1171c4,#22a4ff); color:#fff; padding:10px 16px; border-radius:6px 6px 0 0; display:flex; align-items:center; gap:10px; }
.item-header { background:linear-gradient(90deg,#e4393c,#e4393c); color:#fff; padding:10px 16px; border-radius:6px 6px 0 0; display:flex; align-items:center; gap:10px; }
.item-badge { background:rgba(255,255,255,.25); padding:2px 10px; border-radius:12px; font-size:12px; }
.item-name { font-size:15px; font-weight:700; }
.item-spec { color:rgba(255,255,255,.8); font-size:13px; }
@@ -256,33 +256,33 @@ export default {
.supplier-cards { display:flex; gap:12px; padding:16px 0; overflow-x:auto; }
.supplier-card { flex:0 0 200px; border:1px solid #e4e7ed; border-radius:8px; padding:14px; background:#fff; &:hover { box-shadow:0 4px 16px rgba(0,0,0,.1); } }
.card-supplier { font-size:13px; font-weight:600; color:#303133; margin-bottom:6px; }
.card-price { font-size:13px; color:#409EFF; font-weight:600; margin-bottom:10px; }
.card-price { font-size:13px; color:#e4393c; font-weight:600; margin-bottom:10px; }
.card-meta { font-size:11px; color:#c0c4cc; margin-top:8px; }
.price-low { color:#67c23a; font-weight:700; }
.plan-card { border:1px solid #e4e7ed; border-radius:8px; margin-bottom:24px; overflow:hidden; background:#fff; position:relative; }
.plan-header { display:flex; align-items:center; justify-content:space-between; padding:14px 20px; background:linear-gradient(90deg,#f0f7ff,#fff); border-bottom:1px solid #e4e7ed; flex-wrap:wrap; gap:10px; }
.plan-header { display:flex; align-items:center; justify-content:space-between; padding:14px 20px; background:linear-gradient(90deg,#fafafa,#fff); border-bottom:1px solid #e4e7ed; flex-wrap:wrap; gap:10px; }
.plan-title { display:flex; align-items:center; gap:10px; }
.plan-rank { background:#1171c4; color:#fff; padding:3px 10px; border-radius:12px; font-size:12px; }
.plan-supplier { font-size:16px; font-weight:700; color:#1a2c4e; }
.plan-rank { background:#e4393c; color:#fff; padding:3px 10px; border-radius:12px; font-size:12px; }
.plan-supplier { font-size:16px; font-weight:700; color:#333333; }
.plan-actions { display:flex; align-items:center; gap:16px; flex-wrap:wrap; }
.plan-total { font-size:14px; color:#606266; strong { font-size:18px; color:#409EFF; } }
.plan-total { font-size:14px; color:#606266; strong { font-size:18px; color:#e4393c; } }
.plan-reason { padding:8px 20px; font-size:13px; color:#606266; background:#f9fbff; border-bottom:1px solid #f0f2f5; }
/* PDF */
.pdf-area { padding:28px; background:#fff; font-family:"Microsoft YaHei","Noto Sans SC",Arial,sans-serif; font-size:13px; color:#222; }
.pdf-header { display:flex; align-items:flex-start; padding-bottom:12px; }
.pdf-logo { width:52px; height:52px; object-fit:contain; margin-right:14px; }
.pdf-header-text { flex:1; }
.pdf-company { font-size:22px; font-weight:700; color:#1171c4; }
.pdf-company { font-size:22px; font-weight:700; color:#e4393c; }
.pdf-doc-type { font-size:13px; color:#666; margin-top:2px; }
.pdf-header-meta { font-size:12px; color:#888; text-align:right; }
.pdf-divider { border-top:2px solid #1171c4; margin:0 0 16px; }
.pdf-divider { border-top:2px solid #e4393c; margin:0 0 16px; }
.pdf-meta-table { width:100%; border-collapse:collapse; margin-bottom:16px; td { padding:7px 10px; border:1px solid #e4e7ed; } }
.meta-label { background:#f5f7fa; color:#606266; font-weight:600; width:90px; }
.meta-val { color:#303133; }
.amount-big { color:#409EFF; font-weight:700; font-size:16px; }
.pdf-section-title { font-size:14px; font-weight:700; color:#1a2c4e; margin:0 0 10px; padding-left:8px; border-left:4px solid #1171c4; }
.pdf-items-table { width:100%; border-collapse:collapse; margin-bottom:20px; th { background:#1171c4; color:#fff; padding:8px; text-align:center; font-weight:600; font-size:12px; } td { border:1px solid #e4e7ed; padding:7px 8px; text-align:center; font-size:12px; } tbody tr:nth-child(even) td { background:#f9fbff; } }
.amount-cell { color:#409EFF; font-weight:600; }
.total-cell { font-size:14px; background:#f0f7ff !important; font-weight:700; }
.amount-big { color:#e4393c; font-weight:700; font-size:16px; }
.pdf-section-title { font-size:14px; font-weight:700; color:#333333; margin:0 0 10px; padding-left:8px; border-left:4px solid #e4393c; }
.pdf-items-table { width:100%; border-collapse:collapse; margin-bottom:20px; th { background:#e4393c; color:#fff; padding:8px; text-align:center; font-weight:600; font-size:12px; } td { border:1px solid #e4e7ed; padding:7px 8px; text-align:center; font-size:12px; } tbody tr:nth-child(even) td { background:#f9fbff; } }
.amount-cell { color:#e4393c; font-weight:600; }
.total-cell { font-size:14px; background:#fafafa !important; font-weight:700; }
.pdf-footer { text-align:center; font-size:11px; color:#aaa; margin-top:16px; border-top:1px solid #f0f2f5; padding-top:12px; }
</style>

View File

@@ -16,8 +16,8 @@
</el-card>
<el-card shadow="never" style="margin-top:12px">
<div slot="header" style="font-weight:700;color:#1a2c4e;font-size:15px">
<i class="el-icon-data-analysis" style="margin-right:6px;color:#1171c4"></i>
<div slot="header" style="font-weight:700;color:#333333;font-size:15px">
<i class="el-icon-data-analysis" style="margin-right:6px;color:#e4393c"></i>
选择询价单进行智慧比价
</div>
<el-table v-loading="loading" :data="rfqList" border>

View File

@@ -418,7 +418,7 @@ export default {
transform: translateY(-50%);
width: 4px;
height: 14px;
background: linear-gradient(180deg, #409eff 0%, #2c3e50 100%);
background: linear-gradient(180deg, #e4393c 0%, #2c3e50 100%);
border-radius: 2px;
}
@@ -508,7 +508,7 @@ export default {
.mat-name {
font-size: 15px;
font-weight: 700;
color: #1a2c4e;
color: #333333;
line-height: 1.3;
}
.mat-subtitle {
@@ -567,7 +567,7 @@ export default {
gap: 4px;
}
.divider-text i {
color: #409eff;
color: #e4393c;
font-size: 14px;
}
@@ -617,7 +617,7 @@ export default {
}
.quote-row.is-fastest {
background: linear-gradient(90deg, #ecf5ff 0%, #d9ecff 100%);
border-left: 3px solid #409eff;
border-left: 3px solid #e4393c;
}
.quote-row.is-lowest.is-fastest {
background: linear-gradient(90deg, #f0f9eb 0%, #e8f5e0 50%, #ecf5ff 100%);

View File

@@ -183,7 +183,7 @@ export default {
.supplier-contact .phone {
margin-left: 8px;
color: #409eff;
color: #e4393c;
}
/* 价格单元格 */

View File

@@ -249,7 +249,7 @@ export default {
<style scoped>
/* ========= 顶部标题 ========= */
.detail-header {
background: linear-gradient(135deg, #1a2c4e 0%, #2c3e50 100%);
background: linear-gradient(135deg, #333333 0%, #2c3e50 100%);
padding: 18px 24px;
border-radius: 6px;
margin-bottom: 16px;
@@ -315,7 +315,7 @@ export default {
transform: translateY(-50%);
width: 4px;
height: 16px;
background: linear-gradient(180deg, #409eff 0%, #2c3e50 100%);
background: linear-gradient(180deg, #e4393c 0%, #2c3e50 100%);
border-radius: 2px;
}
@@ -401,7 +401,7 @@ export default {
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.grid-value-wrap >>> .el-input__inner:focus {
border-color: #409eff;
border-color: #e4393c;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.15);
}
@@ -452,7 +452,7 @@ export default {
}
>>> .el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {
background: #fff;
color: #409eff;
color: #e4393c;
font-weight: 600;
}
>>> .el-tabs--border-card > .el-tabs__content {

View File

@@ -42,7 +42,7 @@
:data="materialList"
@selection-change="handleSelectionChange"
border stripe style="width:100%"
:header-cell-style="{ background: '#f5f5f5', color: '#666', fontWeight: 500, fontSize: '12px' }"
:header-cell-style="{ background: '#ffffff', color: '#666', fontWeight: 500, fontSize: '12px' }"
:cell-style="{ fontSize: '12px', color: '#333' }"
size="small">
<el-table-column type="selection" width="44" align="center" />

View File

@@ -25,8 +25,8 @@
<el-table-column label="异议原因" prop="reason" :show-overflow-tooltip="true" />
<el-table-column label="状态" width="100">
<template slot-scope="scope">
<el-tag :type="{ pending:'warning', processing:'primary', resolved:'success', rejected:'danger' }[scope.row.status]">
{{ { pending:'待处理', processing:'处理中', resolved:'已解决', rejected:'已拒绝' }[scope.row.status] || scope.row.status }}
<el-tag :type="{ pending:'warning', processing:'primary', '10':'warning', resolved:'success', rejected:'danger' }[scope.row.status]">
{{ { pending:'待处理', processing:'处理中', '10':'审批中', resolved:'已解决', rejected:'已拒绝' }[scope.row.status] || scope.row.status }}
</el-tag>
</template>
</el-table-column>
@@ -35,6 +35,9 @@
<el-table-column label="操作" align="center" width="150">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="handleResolve(scope.row)" v-if="scope.row.status==='pending'||scope.row.status==='processing'" style="color:#67C23A">处理</el-button>
<el-button size="mini" type="text" @click="handleSubmitApproval(scope.row)" v-if="scope.row.status==='pending'" style="color:#E6A23C">提交审批</el-button>
<el-button size="mini" type="text" @click="handleApprove(scope.row)" v-if="scope.row.status==='10'" style="color:#67C23A">通过</el-button>
<el-button size="mini" type="text" @click="handleReject(scope.row)" v-if="scope.row.status==='10'" style="color:#F56C6C">驳回</el-button>
</template>
</el-table-column>
</el-table>
@@ -76,6 +79,7 @@
<script>
import { listObjection, addObjection, updateObjection } from "@/api/bid/objection";
import { listSupplier } from "@/api/bid/supplier";
import { submitApproval, approveBiz, rejectBiz } from "@/api/bid/approvalAction";
export default {
name: "Objection",
data() {
@@ -100,6 +104,20 @@ export default {
handleResolve(row) { this.resolveForm = { objectionId: row.objectionId, status: "resolved" }; this.resolveOpen = true; },
submitResolve() {
updateObjection(this.resolveForm).then(() => { this.$modal.msgSuccess("处理成功"); this.resolveOpen = false; this.getList(); });
},
handleSubmitApproval(row) {
this.$modal.confirm("确认提交审批?").then(() => submitApproval("ORDER_OBJECTION", row.objectionId))
.then(() => { this.$modal.msgSuccess("已提交审批"); this.getList(); });
},
handleApprove(row) {
this.$modal.confirm("确认通过该异议?").then(() => approveBiz("ORDER_OBJECTION", row.objectionId))
.then(() => { this.$modal.msgSuccess("审批通过"); this.getList(); });
},
handleReject(row) {
this.$prompt("请输入驳回原因", "驳回", { inputPattern: /.+/, inputErrorMessage: "请填写原因" })
.then(({ value }) => rejectBiz("ORDER_OBJECTION", row.objectionId, value))
.then(() => { this.$modal.msgSuccess("已驳回"); this.getList(); })
.catch(() => {});
}
}
};

View File

@@ -131,7 +131,7 @@ export default {
return map[b] || ("类型" + b)
},
actionColor(b) {
const map = { 1: "#67c23a", 2: "#e6a23c", 3: "#f56c6c", 5: "#409EFF" }
const map = { 1: "#67c23a", 2: "#e6a23c", 3: "#f56c6c", 5: "#e4393c" }
return map[b] || "#909399"
},
formatParam(param) {
@@ -150,7 +150,7 @@ export default {
background: #fff; padding: 12px 16px; border-radius: 4px; margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06); display: flex; align-items: center; gap: 12px;
}
.page-title { font-size: 16px; font-weight: 700; color: #1a2c4e; }
.page-title { font-size: 16px; font-weight: 700; color: #333333; }
.page-subtitle { font-size: 11px; color: #c0c4cc; letter-spacing: 1px; }
.header-right { margin-left: auto; }
.total-info { font-size: 12px; color: #909399; }

View File

@@ -184,7 +184,7 @@ export default {
.stat-row { margin-bottom: 12px !important; }
.jd-filter { display: flex; align-items: center; background: #f5f5f5; padding: 10px 16px; border-radius: 2px; margin-bottom: 12px; flex-wrap: wrap; gap: 8px; }
.jd-filter { display: flex; align-items: center; background: #ffffff; padding: 10px 16px; border-radius: 2px; margin-bottom: 12px; flex-wrap: wrap; gap: 8px; }
.filter-left { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.filter-right { margin-left: auto; }
.filter-input { width: 130px; }
@@ -202,17 +202,17 @@ export default {
.right-section { margin-bottom: 20px; }
.rs-header { font-size: 12px; color: #666; margin-bottom: 8px; }
.rs-list { max-height: 150px; overflow-y: auto; border: 1px solid #e5e5e5; border-radius: 2px; padding: 4px; }
.rs-item { padding: 4px 8px; font-size: 12px; color: #333; border-bottom: 1px solid #f5f5f5; }
.rs-item { padding: 4px 8px; font-size: 12px; color: #333; border-bottom: 1px solid #ffffff; }
.rs-item:last-child { border-bottom: none; }
.rs-empty { text-align: center; padding: 20px; color: #999; font-size: 12px; }
.rs-date-row { display: flex; gap: 6px; margin-bottom: 8px; }
.rs-quick { display: flex; gap: 6px; }
.rs-warn { font-size: 11px; color: #f56c6c; margin-top: 6px; }
.order-link { color: #005ea7; cursor: pointer; }
.order-link { color: #e4393c; cursor: pointer; }
.order-link:hover { color: #e4393c; }
::v-deep .selected-row td { background: #fff5f5 !important; }
::v-deep .selected-row td { background: #fafafa !important; }
.diff-early { color: #67c23a; font-weight: 600; }
.diff-late { color: #f56c6c; font-weight: 600; }

View File

@@ -238,7 +238,7 @@ export default {
.jd-tab.active::after { content: ''; position: absolute; bottom: 0; left: 20px; right: 20px; height: 2px; background: #e4393c; }
.tab-count { margin-left: 4px; font-size: 12px; color: #999; font-weight: 400; }
.jd-filter { display: flex; align-items: center; background: #f5f5f5; padding: 10px 16px; border-radius: 2px; margin-bottom: 12px; flex-wrap: wrap; gap: 8px; }
.jd-filter { display: flex; align-items: center; background: #ffffff; padding: 10px 16px; border-radius: 2px; margin-bottom: 12px; flex-wrap: wrap; gap: 8px; }
.filter-left { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.filter-right { margin-left: auto; }
.filter-input { width: 150px; }
@@ -247,7 +247,7 @@ export default {
.jd-table { border: none !important; }
.amount { color: #e4393c; font-weight: 700; }
.order-link { color: #005ea7; cursor: pointer; transition: color 0.2s; }
.order-link { color: #e4393c; cursor: pointer; transition: color 0.2s; }
.order-link:hover { color: #e4393c; text-decoration: underline; }
.diff-early { color: #67c23a; font-weight: 600; }
@@ -258,7 +258,7 @@ export default {
.detail-item { display: flex; border-bottom: 1px solid #f0f0f0; }
.detail-item:nth-last-child(-n+2) { border-bottom: none; }
.detail-item:nth-child(odd) { border-right: 1px solid #f0f0f0; }
.dl { width: 90px; flex-shrink: 0; background: #f5f5f5; padding: 10px 12px; font-size: 12px; color: #666; font-weight: 600; border-right: 1px solid #f0f0f0; }
.dl { width: 90px; flex-shrink: 0; background: #ffffff; padding: 10px 12px; font-size: 12px; color: #666; font-weight: 600; border-right: 1px solid #f0f0f0; }
.dv { padding: 10px 12px; font-size: 13px; color: #333; flex: 1; }
.section-bar { font-size: 13px; font-weight: 700; color: #333; padding: 6px 0 6px 10px; margin-bottom: 10px; border-left: 4px solid #e4393c; }

View File

@@ -249,7 +249,7 @@ export default {
.jd-filter {
display: flex;
align-items: center;
background: #f5f5f5;
background: #ffffff;
padding: 10px 16px;
border-radius: 2px;
margin-bottom: 12px;
@@ -291,9 +291,9 @@ export default {
.detail-item { display: flex; border-bottom: 1px solid #f0f0f0; }
.detail-item:nth-last-child(-n+2) { border-bottom: none; }
.detail-item:nth-child(odd) { border-right: 1px solid #f0f0f0; }
.dl { width: 90px; flex-shrink: 0; background: #f5f5f5; padding: 10px 12px; font-size: 12px; color: #666; font-weight: 600; border-right: 1px solid #f0f0f0; }
.dl { width: 90px; flex-shrink: 0; background: #ffffff; padding: 10px 12px; font-size: 12px; color: #666; font-weight: 600; border-right: 1px solid #f0f0f0; }
.dv { padding: 10px 12px; font-size: 13px; color: #333; flex: 1; }
.detail-remark { padding: 8px 12px; background: #fff5f5; border: 1px solid #fce4e4; border-radius: 2px; font-size: 12px; color: #e6a23c; margin-bottom: 16px; }
.detail-remark { padding: 8px 12px; background: #fafafa; border: 1px solid #fce4e4; border-radius: 2px; font-size: 12px; color: #e6a23c; margin-bottom: 16px; }
.section-bar { font-size: 13px; font-weight: 700; color: #333; padding: 6px 0 6px 10px; margin-bottom: 10px; border-left: 4px solid #e4393c; }
/* ═══ Amount ═══ */
@@ -303,7 +303,7 @@ export default {
/* ═══ Order Link ═══ */
.order-link {
color: #005ea7;
color: #e4393c;
cursor: pointer;
transition: color 0.2s;
}

View File

@@ -264,7 +264,7 @@ export default {
/* ═══ JD Filter ═══ */
.jd-filter {
display: flex; align-items: center; background: #f5f5f5;
display: flex; align-items: center; background: #ffffff;
padding: 10px 16px; border-radius: 2px; margin-bottom: 12px; flex-wrap: wrap; gap: 8px;
}
.filter-left { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
@@ -283,15 +283,15 @@ export default {
.detail-item { display: flex; border-bottom: 1px solid #f0f0f0; }
.detail-item:nth-last-child(-n+2) { border-bottom: none; }
.detail-item:nth-child(odd) { border-right: 1px solid #f0f0f0; }
.dl { width: 90px; flex-shrink: 0; background: #f5f5f5; padding: 10px 12px; font-size: 12px; color: #666; font-weight: 600; border-right: 1px solid #f0f0f0; }
.dl { width: 90px; flex-shrink: 0; background: #ffffff; padding: 10px 12px; font-size: 12px; color: #666; font-weight: 600; border-right: 1px solid #f0f0f0; }
.dv { padding: 10px 12px; font-size: 13px; color: #333; flex: 1; }
.detail-remark { padding: 8px 12px; background: #fff5f5; border: 1px solid #fce4e4; border-radius: 2px; font-size: 12px; color: #e6a23c; margin-bottom: 16px; }
.detail-remark { padding: 8px 12px; background: #fafafa; border: 1px solid #fce4e4; border-radius: 2px; font-size: 12px; color: #e6a23c; margin-bottom: 16px; }
.section-bar { font-size: 13px; font-weight: 700; color: #333; padding: 6px 0 6px 10px; margin-bottom: 10px; border-left: 4px solid #e4393c; }
/* ═══ Urgency ═══ */
.amount { color: #e4393c; font-weight: 700; }
.urgent-overdue { color: #f56c6c; font-weight: 700; font-size: 12px; }
.urgent-soon { color: #e6a23c; font-weight: 700; font-size: 12px; }
.order-link { color: #005ea7; cursor: pointer; transition: color 0.2s; }
.order-link { color: #e4393c; cursor: pointer; transition: color 0.2s; }
.order-link:hover { color: #e4393c; text-decoration: underline; }
</style>

View File

@@ -7,6 +7,8 @@
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width:110px">
<el-option label="草稿" value="draft"/>
<el-option label="审批中" value="10"/>
<el-option label="已驳回" value="rejected"/>
<el-option label="已确认" value="confirmed"/>
<el-option label="已交付" value="delivered"/>
<el-option label="已关闭" value="closed"/>
@@ -29,7 +31,7 @@
<el-table-column label="供应商" prop="supplierName" />
<el-table-column label="RFQ" prop="rfqTitle" :show-overflow-tooltip="true" />
<el-table-column label="总金额" prop="totalAmount" width="130" align="right">
<template slot-scope="scope"><span style="color:#409EFF;font-weight:bold">¥{{ scope.row.totalAmount }}</span></template>
<template slot-scope="scope"><span style="color:#e4393c;font-weight:bold">¥{{ scope.row.totalAmount }}</span></template>
</el-table-column>
<el-table-column label="交货日期" prop="deliveryDate" width="110" />
<el-table-column label="状态" width="100">
@@ -38,10 +40,13 @@
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" width="160" />
<el-table-column label="操作" align="center" width="220">
<el-table-column label="操作" align="center" width="320">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)">详情</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-if="scope.row.status==='draft'">编辑</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-if="scope.row.status==='draft' || scope.row.status==='rejected'">编辑</el-button>
<el-button size="mini" type="text" style="color:#E6A23C" @click="handleSubmitApproval(scope.row)" v-if="scope.row.status==='draft' || scope.row.status==='rejected'">提交审批</el-button>
<el-button size="mini" type="text" style="color:#67C23A" @click="handleApprove(scope.row)" v-if="scope.row.status==='10'">通过</el-button>
<el-button size="mini" type="text" style="color:#F56C6C" @click="handleReject(scope.row)" v-if="scope.row.status==='10'">驳回</el-button>
<el-button size="mini" type="text" style="color:#67C23A" @click="handleConfirm(scope.row)" v-if="scope.row.status==='draft'">确认</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-if="scope.row.status==='draft'">删除</el-button>
</template>
@@ -90,7 +95,7 @@
</el-table-column>
<el-table-column label="金额" width="120" align="right">
<template slot-scope="scope">
<span style="color:#409EFF">{{ ((scope.row.quantity||0)*(scope.row.unitPrice||0)).toFixed(2) }}</span>
<span style="color:#e4393c">{{ ((scope.row.quantity||0)*(scope.row.unitPrice||0)).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center">
@@ -174,8 +179,9 @@
<script>
import { listPurchaseorder, getPurchaseorder, addPurchaseorder, updatePurchaseorder, confirmPurchaseorder, delPurchaseorder } from "@/api/bid/purchaseorder";
import { submitApproval, approveBiz, rejectBiz } from "@/api/bid/approvalAction";
import { listSupplier } from "@/api/bid/supplier";
import logoImg from "@/assets/logo/logo.png";
import logoImg from "@/assets/logo/logo.svg";
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
@@ -208,6 +214,20 @@ export default {
this.$modal.confirm("确认提交采购单?").then(() => confirmPurchaseorder(row.poId))
.then(() => { this.$modal.msgSuccess("已确认"); this.getList(); });
},
handleSubmitApproval(row) {
this.$modal.confirm("确认提交审批?提交后单据状态变为审批中。").then(() => submitApproval("PURCHASE_ORDER", row.poId))
.then(() => { this.$modal.msgSuccess("已提交审批"); this.getList(); });
},
handleApprove(row) {
this.$modal.confirm("确认通过该采购单?").then(() => approveBiz("PURCHASE_ORDER", row.poId))
.then(() => { this.$modal.msgSuccess("审批通过"); this.getList(); });
},
handleReject(row) {
this.$prompt("请输入驳回原因", "驳回", { inputPattern: /.+/, inputErrorMessage: "请填写原因" })
.then(({ value }) => rejectBiz("PURCHASE_ORDER", row.poId, value))
.then(() => { this.$modal.msgSuccess("已驳回"); this.getList(); })
.catch(() => {});
},
handleDelete(row) {
this.$modal.confirm("确认删除?").then(() => delPurchaseorder(row.poId)).then(() => { this.getList(); this.$modal.msgSuccess("删除成功"); });
},
@@ -240,8 +260,8 @@ export default {
this.pdfLoading = false;
}
},
statusType(s) { return { draft:"info", confirmed:"success", delivered:"", closed:"", disputed:"danger" }[s] || ""; },
statusLabel(s) { return { draft:"草稿", confirmed:"已确认", delivered:"已交付", closed:"已关闭", disputed:"异议中" }[s] || s; }
statusType(s) { return { draft:"info", "10":"warning", rejected:"danger", confirmed:"success", delivered:"", closed:"", disputed:"danger" }[s] || ""; },
statusLabel(s) { return { draft:"草稿", "10":"审批中", rejected:"已驳回", confirmed:"已确认", delivered:"已交付", closed:"已关闭", disputed:"异议中" }[s] || s; }
}
};
</script>
@@ -271,7 +291,7 @@ export default {
.pdf-company {
font-size: 20px;
font-weight: 700;
color: #1171c4;
color: #e4393c;
letter-spacing: 1px;
}
.pdf-doc-type {
@@ -284,7 +304,7 @@ export default {
color: #888;
}
.pdf-divider {
border-top: 2px solid #1171c4;
border-top: 2px solid #e4393c;
margin-bottom: 16px;
}
.pdf-meta-table {
@@ -306,17 +326,17 @@ export default {
color: #303133;
}
.amount {
color: #409EFF;
color: #e4393c;
font-weight: 700;
font-size: 15px;
}
.pdf-section-title {
font-size: 14px;
font-weight: 700;
color: #1a2c4e;
color: #333333;
margin: 0 0 10px;
padding-left: 8px;
border-left: 4px solid #1171c4;
border-left: 4px solid #e4393c;
}
.pdf-items-table {
width: 100%;
@@ -324,7 +344,7 @@ export default {
margin-bottom: 20px;
}
.pdf-items-table th {
background: #1171c4;
background: #e4393c;
color: #fff;
padding: 8px 10px;
text-align: center;
@@ -339,12 +359,12 @@ export default {
background: #f9fbff;
}
.amount-cell {
color: #409EFF;
color: #e4393c;
font-weight: 600;
}
.total-cell {
font-size: 15px;
background: #f0f7ff !important;
background: #fafafa !important;
}
.pdf-footer {
text-align: right;

View File

@@ -120,6 +120,12 @@
v-if="s.row.status==='draft'">编辑</el-button>
<el-button size="mini" type="text" style="color:#67C23A" icon="el-icon-upload2"
@click="handleSubmit(s.row)" v-if="s.row.status==='draft'">提交</el-button>
<el-button size="mini" type="text" style="color:#E6A23C" icon="el-icon-s-check"
@click="handleSubmitApproval(s.row)" v-if="s.row.status==='draft' || s.row.status==='submitted'">提交审批</el-button>
<el-button size="mini" type="text" style="color:#67C23A"
@click="handleApprove(s.row)" v-if="s.row.status==='10'">审批通过</el-button>
<el-button size="mini" type="text" style="color:#F56C6C"
@click="handleApprovalReject(s.row)" v-if="s.row.status==='10'">审批驳回</el-button>
<el-button size="mini" type="text" style="color:#67C23A" icon="el-icon-check"
@click="handleAccept(s.row)" v-if="s.row.status==='submitted' && !isSupplier">采纳</el-button>
<el-button size="mini" type="text" style="color:#F56C6C" icon="el-icon-close"
@@ -185,7 +191,7 @@
</el-row>
<el-divider content-position="left">
<span style="font-weight:700;color:#1a2c4e">报价明细</span>
<span style="font-weight:700;color:#333333">报价明细</span>
<el-tooltip content="选择RFQ后可自动加载物料需求" placement="top">
<i class="el-icon-question" style="margin-left:6px;color:#909399;cursor:pointer"></i>
</el-tooltip>
@@ -372,7 +378,8 @@ import { listQuotation, getQuotation, addQuotation, updateQuotation,
import { listRfq, getRfqItems } from "@/api/bid/rfq";
import { listSupplier } from "@/api/bid/supplier";
import { addDelivery } from "@/api/bid/delivery";
import logoImg from "@/assets/logo/logo.png";
import { submitApproval, approveBiz, rejectBiz } from "@/api/bid/approvalAction";
import logoImg from "@/assets/logo/logo.svg";
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
@@ -495,6 +502,20 @@ export default {
this.$modal.confirm("确认提交报价?提交后不可修改").then(() => submitQuotation(row.quotationId))
.then(() => { this.$modal.msgSuccess("提交成功"); this.getList(); });
},
handleSubmitApproval(row) {
this.$modal.confirm("确认提交审批?").then(() => submitApproval("QUOTATION", row.quotationId))
.then(() => { this.$modal.msgSuccess("已提交审批"); this.getList(); });
},
handleApprove(row) {
this.$modal.confirm("确认通过该报价审批?").then(() => approveBiz("QUOTATION", row.quotationId))
.then(() => { this.$modal.msgSuccess("审批通过"); this.getList(); });
},
handleApprovalReject(row) {
this.$prompt("请输入驳回原因", "驳回", { inputPattern: /.+/, inputErrorMessage: "请填写原因" })
.then(({ value }) => rejectBiz("QUOTATION", row.quotationId, value))
.then(() => { this.$modal.msgSuccess("已驳回"); this.getList(); })
.catch(() => {});
},
handleAccept(row) {
this.$modal.confirm("确认采纳此报价?").then(() => acceptQuotation(row.quotationId))
.then(() => { this.$modal.msgSuccess("已采纳"); this.getList(); });
@@ -591,8 +612,8 @@ export default {
} catch(e) { this.$message.error("导出失败:" + e.message); }
finally { this.pdfLoading = false; }
},
statusType(s) { return { draft: "info", submitted: "warning", accepted: "success", rejected: "danger" }[s] || ""; },
statusLabel(s) { return { draft: "草稿", submitted: "已提交", accepted: "已采纳", rejected: "已拒绝" }[s] || s; },
statusType(s) { return { draft: "info", submitted: "warning", "10": "warning", accepted: "success", rejected: "danger" }[s] || ""; },
statusLabel(s) { return { draft: "草稿", submitted: "已提交", "10": "审批中", accepted: "已采纳", rejected: "已拒绝" }[s] || s; },
statusIcon(s) { return { draft: "el-icon-edit-outline", submitted: "el-icon-time", accepted: "el-icon-circle-check", rejected: "el-icon-circle-close" }[s] || "el-icon-document"; }
}
};
@@ -606,7 +627,7 @@ export default {
/* ── 搜索 ── */
.jd-filter-bar {
background: #f5f5f5;
background: #ffffff;
padding: 12px 16px 4px;
border-radius: 2px;
::v-deep .el-form-item {
@@ -677,6 +698,6 @@ export default {
tbody tr:nth-child(even) td { background: #f9fbff; }
}
.amount-cell { color: #e4393c; font-weight: 600; }
.total-cell { font-size: 15px; background: #fff5f5 !important; font-weight: 700; }
.total-cell { font-size: 15px; background: #fafafa !important; font-weight: 700; }
.pdf-footer { text-align: right; font-size: 11px; color: #aaa; margin-top: 10px; border-top: 1px solid #f0f2f5; padding-top: 8px; }
</style>

View File

@@ -17,7 +17,7 @@
</template>
<script>
const COLORS = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399']
const COLORS = ['#e4393c', '#67C23A', '#E6A23C', '#F56C6C', '#909399']
const CARD_NAMES = ['采购总额', 'RFQ总数', '采购单数', '活跃供应商']
export default {
@@ -59,7 +59,7 @@ export default {
<style scoped>
.kpi-card {
border-left: 4px solid var(--kpi-accent, #409EFF);
border-left: 4px solid var(--kpi-accent, #e4393c);
border-radius: 6px;
transition: transform .2s;
}

View File

@@ -21,7 +21,7 @@
<el-card shadow="hover" style="margin-bottom:16px">
<div slot="header" class="card-header">
<span><i class="el-icon-data-board" style="color:#409EFF"></i> 月度预算 vs 实际成本</span>
<span><i class="el-icon-data-board" style="color:#e4393c"></i> 月度预算 vs 实际成本</span>
</div>
<div ref="costTrendChart" style="height:350px;width:100%"></div>
</el-card>
@@ -104,7 +104,7 @@ export default {
if (!this.data || !this.data.summary) return []
const s = this.data.summary
return [
{ label: '预算总额', value: '¥' + this.formatMoney(s.totalExpected), color: '#409EFF' },
{ label: '预算总额', value: '¥' + this.formatMoney(s.totalExpected), color: '#e4393c' },
{ label: '实际采购', value: '¥' + this.formatMoney(s.totalActual), color: '#67C23A' },
{ label: '节省金额', value: '¥' + this.formatMoney(s.savedAmount), color: Number(s.savedAmount) > 0 ? '#E6A23C' : '#C0C4CC', sub: Number(s.savedAmount) > 0 ? '为您节省了开支' : '' },
{ label: '节省比例', value: (Number(s.savedRate) || 0) + '%', color: '#F56C6C', sub: '相比预算' }
@@ -182,7 +182,7 @@ export default {
{
name: '实际金额', type: 'bar',
data: items.map(d => Number(d.actualAmount) || 0),
itemStyle: { color: '#409EFF', borderRadius: [4,4,0,0] },
itemStyle: { color: '#e4393c', borderRadius: [4,4,0,0] },
barWidth: '35%',
label: { show: true, position: 'top', formatter: p => '¥' + Number(p.value).toLocaleString(), fontSize: 10 }
}
@@ -206,7 +206,7 @@ export default {
center: ['50%', '55%'],
label: { show: true, formatter: '{b}\n{d}%', fontSize: 11 },
data: list.length ? list : [{ name: '暂无', value: 1 }],
color: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#22a4ff']
color: ['#e4393c', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#e4393c']
}]
})
},
@@ -258,12 +258,12 @@ export default {
display: flex; align-items: center; gap: 12px;
margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid #f0f2f5;
}
.page-title { font-size: 20px; font-weight: 700; color: #1a2c4e; }
.page-title { font-size: 20px; font-weight: 700; color: #333333; }
.loading-box { text-align: center; padding: 120px 0; color: #909399; font-size: 15px; i { font-size: 24px; margin-right: 8px; } }
.card-header { font-weight: 600; color: #303133; font-size: 14px; }
.cost-content { min-height: 400px; }
.summary-card {
border-left: 4px solid var(--s-accent, #409EFF);
border-left: 4px solid var(--s-accent, #e4393c);
border-radius: 6px;
.s-label { font-size: 14px; color: #909399; margin-bottom: 8px; }
.s-value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
@@ -271,7 +271,7 @@ export default {
}
.cell-expected { color: #C0C4CC; }
.cell-lowest { color: #E6A23C; font-weight: 600; }
.cell-actual { color: #409EFF; font-weight: 600; }
.cell-actual { color: #e4393c; font-weight: 600; }
.cell-saved { color: #67C23A; font-weight: 700; }
.cell-none { color: #C0C4CC; }
.no-data { text-align: center; color: #C0C4CC; }

View File

@@ -20,7 +20,7 @@
<el-col :xs="24" :sm="14" style="margin-bottom:16px">
<el-card shadow="hover">
<div slot="header" class="card-header">
<span><i class="el-icon-data-line" style="color:#409EFF"></i> 月度采购趋势</span>
<span><i class="el-icon-data-line" style="color:#e4393c"></i> 月度采购趋势</span>
</div>
<div ref="trendChart" style="height:320px;width:100%"></div>
</el-card>
@@ -173,7 +173,7 @@ export default {
series: [
{
name: '采购金额', type: 'bar', data: list.map(d => Number(d.amount) || 0),
itemStyle: { color: '#409EFF', borderRadius: [4,4,0,0] },
itemStyle: { color: '#e4393c', borderRadius: [4,4,0,0] },
barWidth: '40%',
label: { show: true, position: 'top', formatter: p => '¥' + Number(p.value).toLocaleString(), fontSize: 10 }
},
@@ -206,7 +206,7 @@ export default {
label: { show: true, formatter: '{b}\n{d}%', fontSize: 11 },
emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold' } },
data: list.length ? list : [{ name: '暂无数据', value: 1 }],
color: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399']
color: ['#e4393c', '#67C23A', '#E6A23C', '#F56C6C', '#909399']
}]
})
},
@@ -246,8 +246,8 @@ export default {
value: v,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#409EFF' },
{ offset: 1, color: '#22a4ff' }
{ offset: 0, color: '#e4393c' },
{ offset: 1, color: '#e4393c' }
]),
borderRadius: [0, 6, 6, 0]
}
@@ -278,7 +278,7 @@ export default {
display: flex; align-items: center; gap: 12px;
margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid #f0f2f5;
}
.page-title { font-size: 20px; font-weight: 700; color: #1a2c4e; }
.page-title { font-size: 20px; font-weight: 700; color: #333333; }
.page-tip { font-size: 13px; color: #C0C4CC; }
.loading-box { text-align: center; padding: 120px 0; color: #909399; font-size: 15px; i { font-size: 24px; margin-right: 8px; } }
.card-header { font-weight: 600; color: #303133; font-size: 14px; }
@@ -287,7 +287,7 @@ export default {
.activity-list { max-height: 280px; overflow-y: auto; }
.activity-item {
display: flex; align-items: center; gap: 10px;
padding: 10px 0; border-bottom: 1px solid #f5f5f5;
padding: 10px 0; border-bottom: 1px solid #ffffff;
&:last-child { border-bottom: none; }
}
.act-time { font-size: 12px; color: #C0C4CC; white-space: nowrap; min-width: 120px; }

View File

@@ -11,7 +11,7 @@
<div v-show="!loading && data" class="supplier-content">
<el-card shadow="hover" style="margin-bottom:16px">
<div slot="header" class="card-header">
<span><i class="el-icon-s-custom" style="color:#409EFF"></i> 供应商综合评分排名</span>
<span><i class="el-icon-s-custom" style="color:#e4393c"></i> 供应商综合评分排名</span>
</div>
<el-table :data="data.rankings || []" border size="small"
highlight-current-row @current-change="onRowClick" style="cursor:pointer">
@@ -47,7 +47,7 @@
<el-col :xs="24" :sm="12" style="margin-bottom:16px">
<el-card shadow="hover">
<div slot="header" class="card-header">
<span><i class="el-icon-s-marketing" style="color:#409EFF"></i> 供应商评价雷达图</span>
<span><i class="el-icon-s-marketing" style="color:#e4393c"></i> 供应商评价雷达图</span>
<span style="float:right;font-size:12px;color:#C0C4CC">点击表格行切换</span>
</div>
<div ref="radarChart" style="height:320px;width:100%"></div>
@@ -166,7 +166,7 @@ export default {
{ name: '服务', max: 5 },
{ name: '价格', max: 5 }
]
const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#22a4ff']
const colors = ['#e4393c', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#e4393c']
this.radarChart.setOption({
legend: {
@@ -296,7 +296,7 @@ export default {
display: flex; align-items: center; gap: 12px;
margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid #f0f2f5;
}
.page-title { font-size: 20px; font-weight: 700; color: #1a2c4e; }
.page-title { font-size: 20px; font-weight: 700; color: #333333; }
.loading-box { text-align: center; padding: 120px 0; color: #909399; font-size: 15px; i { font-size: 24px; margin-right: 8px; } }
.card-header { font-weight: 600; color: #303133; font-size: 14px; }
.supplier-content { min-height: 400px; }

View File

@@ -177,7 +177,7 @@
</el-table-column>
<el-table-column label="参考来源" width="90" align="center">
<template slot-scope="s">
<span v-if="s.row._fromClient" style="color:#409eff;font-size:12px">甲方报价</span>
<span v-if="s.row._fromClient" style="color:#e4393c;font-size:12px">甲方报价</span>
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center">
@@ -198,7 +198,7 @@
<script>
import { getRfq, updateRfq } from "@/api/bid/rfq";
import { listQuotation } from "@/api/bid/quotation";
import logoImg from "@/assets/logo/logo.png";
import logoImg from "@/assets/logo/logo.svg";
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
@@ -371,7 +371,7 @@ export default {
.pdf-company {
font-size: 22px;
font-weight: 700;
color: #1171c4;
color: #e4393c;
letter-spacing: 1px;
}
.pdf-doc-type {
@@ -381,7 +381,7 @@ export default {
}
.pdf-header-no { font-size: 13px; color: #888; }
.pdf-divider {
border-top: 2px solid #1171c4;
border-top: 2px solid #e4393c;
margin-bottom: 18px;
}
.pdf-meta-table {
@@ -403,10 +403,10 @@ export default {
.pdf-section-title {
font-size: 14px;
font-weight: 700;
color: #1a2c4e;
color: #333333;
margin: 0 0 12px;
padding-left: 8px;
border-left: 4px solid #1171c4;
border-left: 4px solid #e4393c;
}
.pdf-items-table {
width: 100%;
@@ -414,7 +414,7 @@ export default {
margin-bottom: 20px;
}
.pdf-items-table th {
background: #1171c4;
background: #e4393c;
color: #fff;
padding: 9px 12px;
text-align: center;
@@ -426,7 +426,7 @@ export default {
text-align: center;
}
.pdf-items-table tbody tr:nth-child(even) td { background: #f9fbff; }
.amount-cell { color: #409EFF; font-weight: 600; }
.amount-cell { color: #e4393c; font-weight: 600; }
.pdf-footer {
text-align: right;
font-size: 11px;

View File

@@ -88,7 +88,7 @@
<el-tooltip placement="top" effect="light" style="width:100%">
<div slot="content">
<div style="max-width:300px;line-height:1.8">
<strong style="color:#409EFF">💡 使用说明</strong><br/>
<strong style="color:#e4393c">💡 使用说明</strong><br/>
1. 从下拉框选择已存在的甲方报价单<br/>
2. 系统将<strong>自动导入</strong>该报价单中的所有物料明细<br/>
3. 物料名称规格数量预期单价会自动填充<br/>
@@ -156,7 +156,7 @@
</el-table-column>
<el-table-column label="参考来源" width="100">
<template slot-scope="scope">
<span v-if="scope.row._fromClient" style="color:#409eff;font-size:12px">甲方报价</span>
<span v-if="scope.row._fromClient" style="color:#e4393c;font-size:12px">甲方报价</span>
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center" v-if="!viewOnly">
@@ -236,7 +236,7 @@
<el-table-column type="index" width="44" label="#" />
<el-table-column label="供应商" prop="supplierName" min-width="160">
<template slot-scope="s">
<el-avatar :size="26" style="background:#1171c4;margin-right:6px;vertical-align:middle;display:inline-flex">
<el-avatar :size="26" style="background:#e4393c;margin-right:6px;vertical-align:middle;display:inline-flex">
{{ (s.row.supplierName||'?').charAt(0) }}
</el-avatar>
<span style="vertical-align:middle">{{ s.row.supplierName }}</span>
@@ -251,7 +251,7 @@
</el-table-column>
<el-table-column label="金额" width="130" align="right">
<template slot-scope="s">
<strong style="color:#409EFF">¥{{ s.row.totalAmount | money }}</strong>
<strong style="color:#e4393c">¥{{ s.row.totalAmount | money }}</strong>
</template>
</el-table-column>
<el-table-column label="交期" prop="deliveryDays" width="70" align="center">
@@ -494,13 +494,13 @@ export default {
text-align: center;
padding: 14px 10px;
border-radius: 6px;
background: linear-gradient(135deg, #f0f7ff, #e8f4fd);
background: linear-gradient(135deg, #fafafa, #e8f4fd);
border: 1px solid #d9ecff;
}
.summary-stats .stat-num {
font-size: 22px;
font-weight: 700;
color: #1171c4;
color: #e4393c;
line-height: 1.3;
}
.summary-stats .stat-lbl {

View File

@@ -680,7 +680,7 @@ export default {
.supplier-item.active {
background-color: #ecf5ff;
border-left: 3px solid #409eff;
border-left: 3px solid #e4393c;
padding-left: 13px;
}
@@ -830,14 +830,14 @@ export default {
}
.detail-tabs >>> .el-tabs__item.is-active {
color: #409eff;
color: #e4393c;
background: #ecf5ff;
border-radius: 4px 4px 0 0;
font-weight: 500;
}
.detail-tabs >>> .el-tabs__item:hover {
color: #409eff;
color: #e4393c;
}
.detail-tabs >>> .el-tabs__active-bar {

View File

@@ -30,7 +30,7 @@
<el-table-column label="采购单号" prop="poNo" width="150" />
<el-table-column label="金额" prop="amount" width="130" align="right">
<template slot-scope="scope">
<span v-if="scope.row.amount" style="color:#409EFF">¥{{ scope.row.amount }}</span>
<span v-if="scope.row.amount" style="color:#e4393c">¥{{ scope.row.amount }}</span>
<span v-else style="color:#909399">-</span>
</template>
</el-table-column>

View File

@@ -103,7 +103,7 @@ export default {
},
supplierQuickActions() {
return [
{ label: "报价请求", icon: "el-icon-document", color: "#1171c4", path: "/rfq" },
{ label: "报价请求", icon: "el-icon-document", color: "#e4393c", path: "/rfq" },
{ label: "我的报价", icon: "el-icon-money", color: "#67c23a", path: "/quotation" }
];
}
@@ -115,18 +115,18 @@ export default {
recentPos: [],
suppliers: [],
statCards: [
{ key: "suppliers", label: "供应商", icon: "el-icon-s-custom", color: "#1171c4" },
{ key: "materials", label: "物料数", icon: "el-icon-goods", color: "#67c23a" },
{ key: "rfqs", label: "询价单", icon: "el-icon-document", color: "#e6a23c" },
{ key: "pos", label: "采购单", icon: "el-icon-shopping-cart-full", color: "#f56c6c" }
{ key: "suppliers", label: "供应商", icon: "el-icon-s-custom", color: "#e4393c" },
{ key: "materials", label: "物料数", icon: "el-icon-goods", color: "#e4393c" },
{ key: "rfqs", label: "询价单", icon: "el-icon-document", color: "#e4393c" },
{ key: "pos", label: "采购单", icon: "el-icon-shopping-cart-full", color: "#e4393c" }
],
quickActions: [
{ label: "新建询价单", icon: "el-icon-edit", color: "#1171c4", path: "/rfq" },
{ label: "物料管理", icon: "el-icon-goods", color: "#67c23a", path: "/material" },
{ label: "智慧比价", icon: "el-icon-data-analysis", color: "#e6a23c", path: "/comparison" },
{ label: "采购单", icon: "el-icon-shopping-cart-full", color: "#f56c6c", path: "/purchaseorder" },
{ label: "供应商", icon: "el-icon-s-cooperation", color: "#9b59b6", path: "/supplier" },
{ label: "供应商评价", icon: "el-icon-star-off", color: "#e67e22", path: "/evaluation" }
{ label: "新建询价单", icon: "el-icon-edit", color: "#e4393c", path: "/rfq" },
{ label: "物料管理", icon: "el-icon-goods", color: "#e4393c", path: "/material" },
{ label: "智慧比价", icon: "el-icon-data-analysis", color: "#e4393c", path: "/comparison" },
{ label: "采购单", icon: "el-icon-shopping-cart-full", color: "#e4393c", path: "/purchaseorder" },
{ label: "供应商", icon: "el-icon-s-cooperation", color: "#e4393c", path: "/supplier" },
{ label: "供应商评价", icon: "el-icon-star-off", color: "#e4393c", path: "/evaluation" }
]
}
},
@@ -159,10 +159,10 @@ export default {
this.stats.pos = r.total || 0
}).catch(() => {})
},
rfqStatusType(s) { return s === "open" ? "primary" : s === "closed" ? "info" : s === "awarded" ? "success" : "warning" },
rfqStatusText(s) { return s === "open" ? "询价中" : s === "closed" ? "已关闭" : s === "awarded" ? "已定标" : (s || "-") },
poStatusType(s) { return s === "approved" ? "success" : s === "pending" ? "warning" : s === "rejected" ? "danger" : "info" },
poStatusText(s) { return s === "approved" ? "审批" : s === "pending" ? "待审批" : s === "rejected" ? "已拒绝" : (s || "-") },
rfqStatusType(s) { return ({ draft:"info", published:"warning", "10":"warning", closed:"", completed:"success", awarded:"success", open:"primary", rejected:"danger" })[s] || "info" },
rfqStatusText(s) { return ({ draft:"草稿", published:"已发布", "10":"审批中", closed:"已关闭", completed:"已完成", awarded:"已定标", open:"询价中", rejected:"已驳回" })[s] || (s || "-") },
poStatusType(s) { return ({ draft:"info", "10":"warning", rejected:"danger", confirmed:"success", delivered:"", closed:"info", disputed:"danger" })[s] || "info" },
poStatusText(s) { return ({ draft:"草稿", "10":"审批中", rejected:"已驳回", confirmed:"已确认", delivered:"已交付", closed:"已关闭", disputed:"异议中" })[s] || (s || "-") },
gradeType(g) { return g === "A" ? "success" : g === "B" ? "primary" : g === "C" ? "warning" : "info" }
}
}
@@ -184,10 +184,10 @@ export default {
}
.panel-header {
display: flex; align-items: center; justify-content: space-between;
font-size: 14px; font-weight: 600; color: #1a2c4e;
i { margin-right: 6px; color: #1171c4; }
font-size: 14px; font-weight: 600; color: #333;
i { margin-right: 6px; color: #e4393c; }
}
.panel-more { font-size: 12px; color: #1171c4; text-decoration: none; }
.panel-more { font-size: 12px; color: #e4393c; text-decoration: none; }
.panel-more:hover { text-decoration: underline; }
.quick-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; padding: 4px 0; }
.quick-btn { display: flex; flex-direction: column; align-items: center; gap: 8px; text-decoration: none; padding: 16px 8px; border-radius: 8px; transition: background 0.2s; }

View File

@@ -1,10 +1,14 @@
<template>
<div class="login-container">
<!-- Left branding panel -->
<!-- 背景图 + 红色遮罩 -->
<div class="login-bg"></div>
<div class="login-mask"></div>
<!-- 左侧品牌介绍 -->
<div class="login-left">
<div class="brand-wrap">
<div class="brand-logo-box">
<img src="@/assets/logo/logo.png" class="brand-logo-img" alt="logo" />
<img src="@/assets/logo/logo.svg" class="brand-logo-img" alt="logo" />
</div>
<h1 class="brand-name">福安德智慧报价平台</h1>
<p class="brand-slogan">高效 · 精准 · 智能采购管理</p>
@@ -23,19 +27,15 @@
</div>
</div>
</div>
<div class="brand-circles">
<div class="circle c1"></div>
<div class="circle c2"></div>
<div class="circle c3"></div>
</div>
</div>
<!-- Right form panel -->
<!-- 右侧表单 -->
<div class="login-right">
<div class="form-card">
<div class="form-header">
<h2 class="form-title">欢迎登录</h2>
<p class="form-subtitle">福安德智慧报价系统</p>
<div class="title-bar"></div>
</div>
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on">
<el-form-item prop="username">
@@ -77,15 +77,14 @@
</div>
</div>
</el-form-item>
<el-form-item>
<el-form-item class="remember-row">
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
</el-form-item>
<el-form-item>
<el-button
:loading="loading"
type="primary"
size="medium"
style="width: 100%;"
class="login-btn"
@click.native.prevent="handleLogin"
>
<span v-if="!loading">&nbsp;&nbsp;</span>
@@ -192,143 +191,173 @@ export default {
<style lang="scss" scoped>
.login-container {
position: relative;
display: flex;
height: 100vh;
overflow: hidden;
}
/* ---- Left panel ---- */
/* 背景图来源Unsplash 商业摄影) */
.login-bg {
position: absolute;
inset: 0;
background-image: url("https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=80");
background-size: cover;
background-position: center;
filter: blur(1px);
transform: scale(1.02);
}
/* 红色品牌渐变遮罩 */
.login-mask {
position: absolute;
inset: 0;
background: linear-gradient(120deg, rgba(200, 22, 35, 0.88) 0%, rgba(228, 57, 60, 0.78) 55%, rgba(228, 57, 60, 0.35) 100%);
}
/* ---- 左侧 ---- */
.login-left {
flex: 0 0 55%;
position: relative;
background: linear-gradient(135deg, #0d2b6b 0%, #1171c4 55%, #22a4ff 100%);
z-index: 2;
flex: 0 0 55%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
color: #fff;
}
.brand-wrap {
position: relative;
z-index: 2;
text-align: center;
padding: 40px;
}
.brand-logo-box {
width: 90px;
height: 90px;
background: rgba(255, 255, 255, 0.15);
border-radius: 20px;
width: 96px;
height: 96px;
background: rgba(255, 255, 255, 0.18);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 22px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 24px;
backdrop-filter: blur(4px);
margin: 0 auto 26px;
backdrop-filter: blur(6px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.brand-logo-img {
width: 60px;
height: 60px;
width: 64px;
height: 64px;
object-fit: contain;
}
.brand-name {
color: #ffffff;
font-size: 28px;
font-size: 30px;
font-weight: 700;
margin: 0 0 10px;
letter-spacing: 2px;
text-shadow: 0 2px 8px rgba(0,0,0,0.2);
letter-spacing: 3px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}
.brand-slogan {
color: rgba(255, 255, 255, 0.8);
color: rgba(255, 255, 255, 0.85);
font-size: 15px;
margin: 0 0 40px;
letter-spacing: 4px;
margin: 0 0 44px;
letter-spacing: 6px;
}
.brand-features {
display: flex;
flex-direction: column;
gap: 16px;
text-align: left;
display: inline-block;
text-align: left;
}
.feature-row {
color: rgba(255, 255, 255, 0.9);
color: rgba(255, 255, 255, 0.92);
font-size: 14px;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 14px;
gap: 12px;
margin-bottom: 16px;
i {
font-size: 18px;
color: #a8d8ff;
font-size: 20px;
color: #ffd9da;
width: 28px;
height: 28px;
line-height: 28px;
text-align: center;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
}
}
/* Decorative circles */
.brand-circles {
position: absolute;
inset: 0;
pointer-events: none;
}
.circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.06);
}
.c1 { width: 400px; height: 400px; bottom: -120px; left: -100px; }
.c2 { width: 250px; height: 250px; top: -60px; right: -60px; }
.c3 { width: 150px; height: 150px; top: 40%; right: 10%; background: rgba(255,255,255,0.04); }
/* ---- Right panel ---- */
/* ---- 右侧 ---- */
.login-right {
position: relative;
z-index: 2;
flex: 1;
background: #f5f7fa;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.form-card {
width: 400px;
background: #ffffff;
border-radius: 12px;
padding: 40px 40px 32px;
box-shadow: 0 8px 32px rgba(17, 113, 196, 0.10);
background: rgba(255, 255, 255, 0.97);
border-radius: 14px;
padding: 40px 40px 28px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(8px);
}
.form-header {
text-align: center;
margin-bottom: 32px;
margin-bottom: 30px;
position: relative;
}
.form-title {
font-size: 24px;
font-weight: 700;
color: #1a2c4e;
color: #333;
margin: 0 0 6px;
}
.form-subtitle {
font-size: 13px;
color: #8c97a8;
margin: 0;
color: #999;
margin: 0 0 14px;
}
.title-bar {
width: 40px;
height: 3px;
background: linear-gradient(90deg, #e4393c, #c81623);
border-radius: 2px;
margin: 0 auto;
}
.login-form {
.el-form-item {
::v-deep .el-form-item {
margin-bottom: 20px;
}
.el-input__inner {
height: 42px;
::v-deep .el-input__inner {
height: 44px;
line-height: 44px;
border-radius: 8px;
padding-left: 38px;
border: 1px solid #e5e5e5;
transition: all 0.2s;
&:hover { border-color: #e4393c; }
&:focus { border-color: #e4393c; box-shadow: 0 0 0 3px rgba(228, 57, 60, 0.08); }
}
::v-deep .el-input__prefix {
left: 12px;
color: #c0c4cc;
font-size: 16px;
}
}
@@ -336,9 +365,7 @@ export default {
display: flex;
gap: 12px;
align-items: center;
.el-form-item, .el-input {
flex: 1;
}
.el-input { flex: 1; }
}
.code-img-box {
@@ -346,23 +373,66 @@ export default {
cursor: pointer;
border-radius: 6px;
overflow: hidden;
height: 42px;
height: 44px;
display: flex;
align-items: center;
border: 1px solid #e5e5e5;
}
.code-img {
height: 42px;
height: 44px;
width: 120px;
object-fit: cover;
display: block;
}
.remember-row {
::v-deep .el-checkbox__input.is-checked .el-checkbox__inner {
background: #e4393c;
border-color: #e4393c;
}
::v-deep .el-checkbox__input.is-checked + .el-checkbox__label {
color: #666;
}
}
/* 登录按钮 — 京东红渐变 */
.login-btn {
width: 100%;
height: 44px;
font-size: 15px;
font-weight: 600;
letter-spacing: 6px;
border: none !important;
border-radius: 8px;
color: #fff !important;
background: linear-gradient(90deg, #e4393c 0%, #c81623 100%) !important;
box-shadow: 0 4px 12px rgba(228, 57, 60, 0.35);
transition: all 0.2s;
&:hover {
background: linear-gradient(90deg, #c81623 0%, #a30f1c 100%) !important;
box-shadow: 0 6px 16px rgba(228, 57, 60, 0.45);
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
.right-footer {
position: absolute;
bottom: 20px;
font-size: 12px;
color: #b0bac6;
color: rgba(255, 255, 255, 0.75);
letter-spacing: 0.5px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
/* 响应式 */
@media (max-width: 900px) {
.login-left { display: none; }
.login-right { flex: 1; }
}
</style>

View File

@@ -102,7 +102,7 @@ export default {
line-height: 28px;
}
.read-stat strong {
color: #409eff;
color: #e4393c;
font-size: 15px;
margin: 0 2px;
}

View File

@@ -902,7 +902,7 @@ export default {
margin-top: 4px;
}
.select-item.sortable-chosen {
border: 1px dashed #409eff;
border: 1px dashed #e4393c;
}
.select-line-icon {
line-height: 32px;
@@ -929,7 +929,7 @@ export default {
top: 0;
left: 0;
cursor: pointer;
background: #409eff;
background: #e4393c;
z-index: 1;
border-radius: 0 0 6px 0;
text-align: center;

View File

@@ -147,7 +147,7 @@ import { makeUpHtml, vueTemplate, vueScript, cssStyle } from '@/utils/generator/
import { makeUpJs } from '@/utils/generator/js'
import { makeUpCss } from '@/utils/generator/css'
import { drawingDefaultValue, initDrawingDefaultValue, cleanDrawingDefaultValue } from '@/utils/generator/drawingDefault'
import logo from '@/assets/logo/logo.png'
import logo from '@/assets/logo/logo.svg'
import CodeTypeDialog from './CodeTypeDialog'
import DraggableItem from './DraggableItem'
@@ -482,7 +482,7 @@ export default {
margin-left: 6px;
}
.el-icon-plus{
color: #409EFF;
color: #e4393c;
}
.el-icon-delete{
color: #157a0c;
@@ -502,7 +502,7 @@ export default {
}
$selectedColor: #f6f7ff;
$lighterBlue: #409EFF;
$lighterBlue: #e4393c;
.container {
position: relative;

View File

@@ -0,0 +1,55 @@
-- ----------------------------
-- 审批负责人配置(或签)
-- ----------------------------
DROP TABLE IF EXISTS biz_approval_config_user;
DROP TABLE IF EXISTS biz_approval_config;
CREATE TABLE biz_approval_config (
id BIGINT NOT NULL AUTO_INCREMENT,
biz_type VARCHAR(64) NOT NULL COMMENT '业务类型: PURCHASE_ORDER/CLIENT_QUOTE/QUOTATION/DELIVERY_ORDER/ORDER_OBJECTION',
biz_name VARCHAR(64) NOT NULL COMMENT '业务名称',
sign_type CHAR(1) DEFAULT '1' COMMENT '审批方式: 1或签 2会签(预留)',
enabled CHAR(1) DEFAULT '1' COMMENT '是否启用 0停用 1启用',
remark VARCHAR(255) DEFAULT NULL,
create_by VARCHAR(64) DEFAULT '',
create_time DATETIME DEFAULT NULL,
update_by VARCHAR(64) DEFAULT '',
update_time DATETIME DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_biz_type (biz_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审批负责人配置';
CREATE TABLE biz_approval_config_user (
id BIGINT NOT NULL AUTO_INCREMENT,
config_id BIGINT NOT NULL COMMENT 'biz_approval_config.id',
user_id BIGINT NOT NULL COMMENT 'sys_user.user_id',
sort_no INT DEFAULT 0,
PRIMARY KEY (id),
KEY idx_config (config_id),
UNIQUE KEY uk_config_user (config_id, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审批人(或签)';
-- 种子数据
INSERT INTO biz_approval_config(biz_type,biz_name,sign_type,enabled,create_by,create_time) VALUES
('PURCHASE_ORDER', '采购订单', '1','1','admin',NOW()),
('CLIENT_QUOTE', '客户报价', '1','1','admin',NOW()),
('QUOTATION', '供应商报价', '1','1','admin',NOW()),
('DELIVERY_ORDER', '发货单', '1','1','admin',NOW()),
('ORDER_OBJECTION', '订单异议', '1','1','admin',NOW());
-- 字典:审批状态(业务表 status 复用10=审批中)
DELETE FROM sys_dict_type WHERE dict_type='biz_approval_status';
INSERT INTO sys_dict_type(dict_name,dict_type,status,create_by,create_time,remark)
VALUES('审批状态','biz_approval_status','0','admin',NOW(),'业务单据审批中状态');
DELETE FROM sys_dict_data WHERE dict_type='biz_approval_status';
INSERT INTO sys_dict_data(dict_sort,dict_label,dict_value,dict_type,css_class,list_class,is_default,status,create_by,create_time) VALUES
(1,'审批中','10','biz_approval_status','','warning','N','0','admin',NOW()),
(2,'已通过','confirmed','biz_approval_status','','success','N','0','admin',NOW()),
(3,'已驳回','rejected','biz_approval_status','','danger','N','0','admin',NOW());
-- 菜单:审批配置
DELETE FROM sys_menu WHERE menu_name='审批配置' AND parent_id=2000;
INSERT INTO sys_menu(menu_name,parent_id,order_num,path,component,is_frame,is_cache,menu_type,visible,status,perms,icon,create_by,create_time)
VALUES('审批配置',(SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE menu_name='系统管理' AND parent_id=0 LIMIT 1) t),100,
'approval','bid/approval/index',1,0,'C','0','0','bid:approval:list','tree','admin',NOW());

80
sql/menu_reorganize.sql Normal file
View File

@@ -0,0 +1,80 @@
-- ═══════════════════════════════════════════════════════════════════
-- 菜单重组:按业务模块分组到统一父菜单 + 新增审批配置
-- ═══════════════════════════════════════════════════════════════════
SET NAMES utf8mb4;
-- ───────────────────────────────────────────────
-- 1. 新建一级业务父菜单M 类型 = 目录)
-- ───────────────────────────────────────────────
INSERT IGNORE INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES
(2100, '基础数据', 0, 10, 'basedata', NULL, 1, 0, 'M', '0', '0', '', 'tree-table', 'admin', NOW()),
(2110, '报价采购', 0, 20, 'quote', NULL, 1, 0, 'M', '0', '0', '', 'money', 'admin', NOW()),
(2120, '订单履约', 0, 30, 'fulfill', NULL, 1, 0, 'M', '0', '0', '', 'shopping', 'admin', NOW()),
(2130, '系统配置', 0, 90, 'bizconfig',NULL, 1, 0, 'M', '0', '0', '', 'tool', 'admin', NOW());
-- 统计分析 2019 已存在,保持 order_num
UPDATE sys_menu SET order_num = 40 WHERE menu_id = 2019;
-- ───────────────────────────────────────────────
-- 2. 已有子菜单按业务归类到新父菜单
-- 用 perms 标识 ,避免依赖具体 menu_id
-- ───────────────────────────────────────────────
-- 基础数据
UPDATE sys_menu SET parent_id = 2100, order_num = 1 WHERE perms = 'bid:material:list';
UPDATE sys_menu SET parent_id = 2100, order_num = 2 WHERE perms = 'bid:category:list';
UPDATE sys_menu SET parent_id = 2100, order_num = 3 WHERE perms = 'bid:client:list';
UPDATE sys_menu SET parent_id = 2100, order_num = 4 WHERE perms = 'bid:supplier:list';
-- 报价采购
UPDATE sys_menu SET parent_id = 2110, order_num = 1 WHERE perms = 'bid:rfq:list';
UPDATE sys_menu SET parent_id = 2110, order_num = 2 WHERE perms = 'bid:quotation:list';
UPDATE sys_menu SET parent_id = 2110, order_num = 3 WHERE perms = 'bid:comparison:list';
UPDATE sys_menu SET parent_id = 2110, order_num = 4 WHERE perms = 'bid:purchaseorder:list';
UPDATE sys_menu SET parent_id = 2110, order_num = 5 WHERE perms = 'bid:clientquote:list';
-- 订单履约
UPDATE sys_menu SET parent_id = 2120, order_num = 1 WHERE perms = 'bid:order:pending';
UPDATE sys_menu SET parent_id = 2120, order_num = 2 WHERE perms = 'bid:order:transit';
UPDATE sys_menu SET parent_id = 2120, order_num = 3 WHERE perms = 'bid:order:history';
UPDATE sys_menu SET parent_id = 2120, order_num = 4 WHERE perms = 'bid:order:closeDate';
UPDATE sys_menu SET parent_id = 2120, order_num = 5 WHERE perms = 'bid:objection:list';
UPDATE sys_menu SET parent_id = 2120, order_num = 6 WHERE perms = 'bid:clientDelivery:list';
-- 兼容旧 permsorder:list订单履约旧索引页若存在放履约组顶部
UPDATE sys_menu SET parent_id = 2120, order_num = 0 WHERE perms = 'bid:order:list';
-- 系统配置 / 业务配置
UPDATE sys_menu SET parent_id = 2130, order_num = 2 WHERE perms = 'bid:evaluation:list';
UPDATE sys_menu SET parent_id = 2130, order_num = 3 WHERE perms = 'bid:transaction:list';
UPDATE sys_menu SET parent_id = 2130, order_num = 4 WHERE perms = 'bid:operationlog:list';
UPDATE sys_menu SET parent_id = 2130, order_num = 5 WHERE perms = 'bid:tenant:list';
-- ───────────────────────────────────────────────
-- 3. 新增:审批配置(挂在系统配置下)
-- ───────────────────────────────────────────────
DELETE FROM sys_menu WHERE perms = 'bid:approval:list';
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES (2131, '审批配置', 2130, 1, 'approval', 'bid/approval/index', 1, 0, 'C', '0', '0', 'bid:approval:list', 'tree', 'admin', NOW());
-- 审批配置按钮权限
DELETE FROM sys_menu WHERE perms IN ('bid:approval:edit','bid:approval:query');
INSERT INTO sys_menu(menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES
('查询', 2131, 1, '', NULL, 1, 0, 'F', '0', '0', 'bid:approval:query', '#', 'admin', NOW()),
('编辑', 2131, 2, '', NULL, 1, 0, 'F', '0', '0', 'bid:approval:edit', '#', 'admin', NOW());
-- 给 admin 角色role_id=1授权
INSERT IGNORE INTO sys_role_menu(role_id, menu_id)
SELECT 1, menu_id FROM sys_menu
WHERE perms IN ('bid:approval:list','bid:approval:query','bid:approval:edit')
OR menu_id IN (2100, 2110, 2120, 2130);
-- ───────────────────────────────────────────────
-- 4. 验证:列出当前一级菜单
-- ───────────────────────────────────────────────
SELECT menu_id, menu_name, order_num, menu_type, perms
FROM sys_menu
WHERE parent_id = 0 AND visible = '0' AND status = '0'
ORDER BY order_num;