feat(bid): 完成批量业务优化与功能完善

1.  统一所有表格操作列样式,移除固定宽度避免布局溢出
2.  新增报价单自动编号与脏数据清理功能
3.  优化订单状态筛选与展示逻辑,新增closed状态支持
4.  完善操作日志管理,新增统计分析与详情查看功能
5.  优化报价单流程,调整提交审批逻辑与权限控制
6.  修复客户端订单查询SQL,优化关联查询逻辑
7.  新增报价单提交时自动更新提交时间的功能
This commit is contained in:
2026-06-18 20:17:02 +08:00
parent 7b71822a32
commit 41b2e3e772
31 changed files with 1146 additions and 205 deletions

View File

@@ -7,6 +7,7 @@ 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 com.ruoyi.system.service.bid.IBizQuotationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -18,6 +19,7 @@ public class BizApprovalActionController extends BaseController {
@Autowired private IBizApprovalActionService service;
@Autowired private BizApprovalPendingMapper pendingMapper;
@Autowired private IBizQuotationService quotationService;
@GetMapping("/pending")
public AjaxResult pending() {
@@ -27,7 +29,12 @@ public class BizApprovalActionController extends BaseController {
@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()));
int rows = service.submit(bizType, id, getUsername());
// 报价单提交审批时同步更新 submit_time
if ("QUOTATION".equals(bizType) && rows > 0) {
quotationService.updateSubmitTime(id);
}
return toAjax(rows);
}
@Log(title = "审批通过", businessType = BusinessType.UPDATE)

View File

@@ -1,10 +1,19 @@
package com.ruoyi.web.controller.bid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
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.common.utils.poi.ExcelUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.service.ISysOperLogService;
@@ -18,6 +27,67 @@ public class BizOperationLogController extends BaseController {
@GetMapping("/list")
public TableDataInfo list(SysOperLog log) {
startPage();
return getDataTable(operLogService.selectOperLogList(log));
List<SysOperLog> list = operLogService.selectOperLogList(log);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('bid:operationlog:query')")
@GetMapping("/{operId}")
public AjaxResult getInfo(@PathVariable("operId") Long operId) {
return success(operLogService.selectOperLogById(operId));
}
@PreAuthorize("@ss.hasPermi('bid:operationlog:remove')")
@Log(title = "操作日志", businessType = BusinessType.DELETE)
@DeleteMapping("/{operIds}")
public AjaxResult remove(@PathVariable Long[] operIds) {
return toAjax(operLogService.deleteOperLogByIds(operIds));
}
@PreAuthorize("@ss.hasPermi('bid:operationlog:remove')")
@Log(title = "操作日志", businessType = BusinessType.CLEAN)
@DeleteMapping("/clean")
public AjaxResult clean() {
operLogService.cleanOperLog();
return success();
}
@PreAuthorize("@ss.hasPermi('bid:operationlog:export')")
@Log(title = "操作日志", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysOperLog operLog) {
List<SysOperLog> list = operLogService.selectOperLogList(operLog);
ExcelUtil<SysOperLog> util = new ExcelUtil<SysOperLog>(SysOperLog.class);
util.exportExcel(response, list, "操作日志");
}
@PreAuthorize("@ss.hasPermi('bid:operationlog:list')")
@GetMapping("/stats")
public AjaxResult stats(SysOperLog log) {
Map<String, Object> result = new HashMap<>();
Map<String, Object> basic = operLogService.selectOperLogStats(log);
if (basic != null) {
result.put("basic", basic);
} else {
Map<String, Object> empty = new HashMap<>();
empty.put("total_count", 0);
empty.put("error_count", 0);
empty.put("today_count", 0);
empty.put("user_count", 0);
empty.put("avg_cost_time", 0);
empty.put("module_count", 0);
result.put("basic", empty);
}
result.put("modules", operLogService.selectModuleStats(log));
result.put("businessTypes", operLogService.selectBusinessTypeStats(log));
result.put("trend", operLogService.selectDailyTrend(log));
result.put("errors", operLogService.selectErrorStats(log));
return success(result);
}
@PreAuthorize("@ss.hasPermi('bid:operationlog:list')")
@GetMapping("/modules")
public AjaxResult getModules() {
return success(operLogService.selectModuleStats(new SysOperLog()));
}
}

View File

@@ -50,6 +50,8 @@ public class BizQuotationController extends BaseController {
@PostMapping
public AjaxResult add(@RequestBody BizQuotation q) {
q.setCreateBy(getUsername());
Long tenantId = getDeptId();
q.setTenantId(tenantId != null ? tenantId : 1L);
// 供应商新建报价时自动设置 supplier_id
if (SecurityUtils.hasRole("supplier")) {
BizSupplier supplier = supplierService.selectBizSupplierByUserId(SecurityUtils.getUserId());
@@ -86,13 +88,24 @@ public class BizQuotationController extends BaseController {
return toAjax(service.rejectQuotation(quotationId));
}
@PreAuthorize("@ss.hasPermi('bid:quotation:remove') || @ss.hasRole('supplier')")
@PreAuthorize("@ss.hasPermi('bid:quotation:remove')")
@Log(title = "报价单", businessType = BusinessType.DELETE)
@DeleteMapping("/{quotationIds}")
public AjaxResult remove(@PathVariable Long[] quotationIds) {
return toAjax(service.deleteBizQuotationByIds(quotationIds));
}
/**
* 清理无编号的脏数据历史遗留数据quote_no IS NULL 或空字符串)
*/
@PreAuthorize("@ss.hasPermi('bid:quotation:remove')")
@Log(title = "报价单", businessType = BusinessType.CLEAN)
@DeleteMapping("/clean-null-quote")
public AjaxResult cleanNullQuote() {
int n = service.deleteBizQuotationByNoQuoteNo();
return success(n > 0 ? "已清理 " + n + " 条无编号报价单" : "没有需要清理的无编号报价单");
}
/**
* 按供应商ID查询报价明细展开为每行一条物料支持搜索过滤
* 用于供应商管理页面的"报价历史"Tab

View File

@@ -45,4 +45,44 @@ public interface SysOperLogMapper
* 清空操作日志
*/
public void cleanOperLog();
/**
* 查询操作日志统计信息
*
* @param operLog 操作日志对象(带筛选条件)
* @return 统计结果
*/
public java.util.Map<String, Object> selectOperLogStats(SysOperLog operLog);
/**
* 按模块统计操作日志
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各模块统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectModuleStats(SysOperLog operLog);
/**
* 按操作类型统计
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各操作类型统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectBusinessTypeStats(SysOperLog operLog);
/**
* 查询每日操作趋势
*
* @param operLog 操作日志对象(带筛选条件)
* @return 每日趋势列表
*/
public java.util.List<java.util.Map<String, Object>> selectDailyTrend(SysOperLog operLog);
/**
* 按模块统计异常日志
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各模块异常统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectErrorStats(SysOperLog operLog);
}

View File

@@ -10,4 +10,8 @@ public interface BizQuotationMapper {
int updateBizQuotation(BizQuotation record);
int deleteBizQuotationById(Long id);
int deleteBizQuotationByIds(Long[] ids);
/** 自动生成报价单号Q-YYYYMM-NNN */
String selectNextQuoteNo();
/** 查询所有无编号报价单ID用于清理脏数据 */
List<Long> selectIdsByQuoteNoNull();
}

View File

@@ -45,4 +45,44 @@ public interface ISysOperLogService
* 清空操作日志
*/
public void cleanOperLog();
/**
* 查询操作日志统计信息
*
* @param operLog 操作日志对象(带筛选条件)
* @return 统计结果
*/
public java.util.Map<String, Object> selectOperLogStats(SysOperLog operLog);
/**
* 按模块统计
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各模块统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectModuleStats(SysOperLog operLog);
/**
* 按操作类型统计
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各类型统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectBusinessTypeStats(SysOperLog operLog);
/**
* 查询每日操作趋势
*
* @param operLog 操作日志对象(带筛选条件)
* @return 每日趋势列表
*/
public java.util.List<java.util.Map<String, Object>> selectDailyTrend(SysOperLog operLog);
/**
* 按模块统计异常日志
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各模块异常统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectErrorStats(SysOperLog operLog);
}

View File

@@ -14,5 +14,7 @@ public interface IBizQuotationService {
int rejectQuotation(Long quotationId);
int deleteBizQuotationById(Long id);
int deleteBizQuotationByIds(Long[] ids);
int deleteBizQuotationByNoQuoteNo();
void updateSubmitTime(Long quotationId);
List<BizQuotationItem> selectItemsByQuotationId(Long quotationId);
}

View File

@@ -30,6 +30,12 @@ public class BizQuotationServiceImpl implements IBizQuotationService {
@Override
@Transactional
public int insertBizQuotation(BizQuotation q) {
if (q.getQuoteNo() == null || q.getQuoteNo().isBlank()) {
q.setQuoteNo(mapper.selectNextQuoteNo());
}
if (q.getStatus() == null) {
q.setStatus("draft");
}
int rows = mapper.insertBizQuotation(q);
saveItems(q);
return rows;
@@ -38,6 +44,13 @@ public class BizQuotationServiceImpl implements IBizQuotationService {
@Override
@Transactional
public int updateBizQuotation(BizQuotation q) {
// 对历史遗留的空编号记录,编辑时自动补号
if (q.getQuoteNo() == null || q.getQuoteNo().isBlank()) {
BizQuotation existing = mapper.selectBizQuotationById(q.getQuotationId());
if (existing != null && (existing.getQuoteNo() == null || existing.getQuoteNo().isBlank())) {
q.setQuoteNo(mapper.selectNextQuoteNo());
}
}
itemMapper.deleteByQuotationId(q.getQuotationId());
saveItems(q);
return mapper.updateBizQuotation(q);
@@ -48,6 +61,7 @@ public class BizQuotationServiceImpl implements IBizQuotationService {
BigDecimal total = BigDecimal.ZERO;
for (BizQuotationItem item : q.getItems()) {
item.setQuotationId(q.getQuotationId());
if (item.getRfqItemId() == null) item.setRfqItemId(0L);
if (item.getUnitPrice() != null && item.getQuantity() != null) {
item.setTotalPrice(item.getUnitPrice().multiply(item.getQuantity()));
total = total.add(item.getTotalPrice());
@@ -85,12 +99,31 @@ public class BizQuotationServiceImpl implements IBizQuotationService {
return mapper.updateBizQuotation(q);
}
@Override
public void updateSubmitTime(Long quotationId) {
BizQuotation q = new BizQuotation();
q.setQuotationId(quotationId);
q.setSubmitTime(new Date());
mapper.updateBizQuotation(q);
}
@Override
public int deleteBizQuotationById(Long id) { return mapper.deleteBizQuotationById(id); }
@Override
public int deleteBizQuotationByIds(Long[] ids) { return mapper.deleteBizQuotationByIds(ids); }
@Override
@Transactional
public int deleteBizQuotationByNoQuoteNo() {
List<Long> ids = mapper.selectIdsByQuoteNoNull();
if (ids.isEmpty()) return 0;
for (Long id : ids) {
itemMapper.deleteByQuotationId(id);
}
return mapper.deleteBizQuotationByIds(ids.toArray(new Long[0]));
}
@Override
public List<BizQuotationItem> selectItemsByQuotationId(Long quotationId) {
return itemMapper.selectItemsByQuotationId(quotationId);

View File

@@ -73,4 +73,34 @@ public class SysOperLogServiceImpl implements ISysOperLogService
{
operLogMapper.cleanOperLog();
}
@Override
public java.util.Map<String, Object> selectOperLogStats(SysOperLog operLog)
{
return operLogMapper.selectOperLogStats(operLog);
}
@Override
public java.util.List<java.util.Map<String, Object>> selectModuleStats(SysOperLog operLog)
{
return operLogMapper.selectModuleStats(operLog);
}
@Override
public java.util.List<java.util.Map<String, Object>> selectBusinessTypeStats(SysOperLog operLog)
{
return operLogMapper.selectBusinessTypeStats(operLog);
}
@Override
public java.util.List<java.util.Map<String, Object>> selectDailyTrend(SysOperLog operLog)
{
return operLogMapper.selectDailyTrend(operLog);
}
@Override
public java.util.List<java.util.Map<String, Object>> selectErrorStats(SysOperLog operLog)
{
return operLogMapper.selectErrorStats(operLog);
}
}

View File

@@ -76,19 +76,21 @@
</delete>
<select id="selectClientDeliveryOrders" resultType="java.util.Map">
SELECT d.do_no,
d.delivery_date,
d.delay_date,
d.actual_close_date,
d.delivery_status,
d.total_amount,
s.supplier_name,
(SELECT COUNT(*) FROM biz_delivery_order_item WHERE do_id = d.do_id) AS item_count
FROM biz_client_quote cq
JOIN biz_rfq r ON r.client_quote_id = cq.quote_id
JOIN biz_delivery_order d ON d.rfq_id = r.rfq_id
SELECT d.do_id AS doId,
d.do_no AS doNo,
d.delivery_date AS deliveryDate,
d.delay_date AS delayDate,
d.actual_close_date AS actualCloseDate,
d.delivery_status AS deliveryStatus,
d.total_amount AS totalAmount,
d.remark,
s.supplier_name AS supplierName,
(SELECT COUNT(*) FROM biz_delivery_order_item WHERE do_id = d.do_id) AS itemCount
FROM biz_delivery_order d
LEFT JOIN biz_supplier s ON d.supplier_id = s.supplier_id
WHERE cq.client_id = #{clientId}
WHERE d.client_quote_id IN (
SELECT cq.quote_id FROM biz_client_quote cq WHERE cq.client_id = #{clientId}
)
ORDER BY d.create_time DESC
</select>
</mapper>

View File

@@ -41,7 +41,7 @@
<if test="query.type != null and query.type != ''"> AND d.type=#{query.type}</if>
<if test="query.doNo != null and query.doNo != ''"> AND d.do_no LIKE CONCAT('%',#{query.doNo},'%')</if>
<if test="query.supplierId != null"> AND d.supplier_id=#{query.supplierId}</if>
<if test="query.deliveryStatus != null and query.deliveryStatus != ''"> AND d.delivery_status=#{query.deliveryStatus}</if>
<if test="query.deliveryStatus != null and query.deliveryStatus != ''"> AND FIND_IN_SET(d.delivery_status, #{query.deliveryStatus})</if>
<if test="query.supplierName != null and query.supplierName != ''"> AND s.supplier_name LIKE CONCAT('%',#{query.supplierName},'%')</if>
<if test="query.clientName != null and query.clientName != ''"> AND cl.client_name LIKE CONCAT('%',#{query.clientName},'%')</if>
</where>

View File

@@ -66,11 +66,21 @@
<if test="status != null">status=#{status},</if>
<if test="note != null">note=#{note},</if>
<if test="submitTime != null">submit_time=#{submitTime},</if>
<if test="quoteNo != null and quoteNo != ''">quote_no=#{quoteNo},</if>
update_time=NOW()
</set>
WHERE quotation_id=#{quotationId}
</update>
<select id="selectNextQuoteNo" resultType="String">
SELECT CONCAT('Q-', DATE_FORMAT(NOW(),'%Y%m'), '-', LPAD(IFNULL(MAX(CAST(SUBSTRING_INDEX(quote_no,'-',-1) AS UNSIGNED)),0)+1,3,'0'))
FROM biz_quotation WHERE quote_no LIKE CONCAT('Q-', DATE_FORMAT(NOW(),'%Y%m'), '%')
</select>
<select id="selectIdsByQuoteNoNull" resultType="Long">
SELECT quotation_id FROM biz_quotation WHERE quote_no IS NULL OR quote_no = ''
</select>
<delete id="deleteBizQuotationById">DELETE FROM biz_quotation WHERE quotation_id=#{id}</delete>
<delete id="deleteBizQuotationByIds">
DELETE FROM biz_quotation WHERE quotation_id IN

View File

@@ -82,6 +82,110 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="cleanOperLog">
truncate table sys_oper_log
</update>
</update>
<select id="selectOperLogStats" resultType="java.util.Map">
SELECT
COUNT(*) AS total_count,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS error_count,
SUM(CASE WHEN DATE(oper_time) = CURDATE() THEN 1 ELSE 0 END) AS today_count,
COUNT(DISTINCT oper_name) AS user_count,
ROUND(AVG(cost_time), 0) AS avg_cost_time,
COUNT(DISTINCT title) AS module_count
FROM sys_oper_log
<where>
<if test="operName != null and operName != ''">
AND (title LIKE CONCAT('%', #{title}, '%') OR oper_name LIKE CONCAT('%', #{operName}, '%'))
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
</where>
</select>
<select id="selectModuleStats" resultType="java.util.Map">
SELECT title AS module_name, COUNT(*) AS count
FROM sys_oper_log
<where>
<if test="operName != null and operName != ''">
AND oper_name LIKE CONCAT('%', #{operName}, '%')
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND oper_time >= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
AND oper_time &lt;= #{params.endTime}
</if>
</where>
GROUP BY title
ORDER BY count DESC
LIMIT 20
</select>
<select id="selectBusinessTypeStats" resultType="java.util.Map">
SELECT business_type AS type, COUNT(*) AS count
FROM sys_oper_log
<where>
<if test="operName != null and operName != ''">
AND oper_name LIKE CONCAT('%', #{operName}, '%')
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND oper_time >= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
AND oper_time &lt;= #{params.endTime}
</if>
</where>
GROUP BY business_type
ORDER BY count DESC
</select>
<select id="selectDailyTrend" resultType="java.util.Map">
SELECT DATE_FORMAT(oper_time, '%Y-%m-%d') AS day, COUNT(*) AS count
FROM sys_oper_log
<where>
<if test="operName != null and operName != ''">
AND oper_name LIKE CONCAT('%', #{operName}, '%')
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND oper_time >= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
AND oper_time &lt;= #{params.endTime}
</if>
</where>
GROUP BY DATE_FORMAT(oper_time, '%Y-%m-%d')
ORDER BY day DESC
LIMIT 14
</select>
<select id="selectErrorStats" resultType="java.util.Map">
SELECT title AS module_name, COUNT(*) AS error_count
FROM sys_oper_log
WHERE status = 1
<if test="operName != null and operName != ''">
AND oper_name LIKE CONCAT('%', #{operName}, '%')
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND oper_time >= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
AND oper_time &lt;= #{params.endTime}
</if>
GROUP BY title
ORDER BY error_count DESC
LIMIT 10
</select>
</mapper>

View File

@@ -1,3 +1,16 @@
import request from '@/utils/request'
const baseUrl = '/bid/operationLog'
export const listOperationLog = (params) => request({ url: baseUrl + '/list', method: 'get', params })
export const getOperationLog = (operId) => request({ url: baseUrl + '/' + operId, method: 'get' })
export const deleteOperationLog = (operIds) => request({ url: baseUrl + '/' + operIds, method: 'delete' })
export const cleanOperationLog = () => request({ url: baseUrl + '/clean', method: 'delete' })
export const exportOperationLog = (params) => request({ url: baseUrl + '/export', method: 'post', data: params, responseType: 'blob' })
export const getOperationLogStats = (params) => request({ url: baseUrl + '/stats', method: 'get', params })
export const getOperationLogModules = () => request({ url: baseUrl + '/modules', method: 'get' })

View File

@@ -9,6 +9,9 @@ export const acceptQuotation = (id) => request({ url: baseUrl + '/accept/' + id,
export const rejectQuotation = (id) => request({ url: baseUrl + '/reject/' + id, method: 'put' })
export const delQuotation = (ids) => request({ url: baseUrl + '/' + ids, method: 'delete' })
// 清理无报价单号的脏数据admin用
export const cleanNullQuoteNo = () => request({ url: baseUrl + '/clean-null-quote', method: 'delete' })
// 按供应商ID查询报价明细展开为每行一条物料支持搜索过滤参数materialName, quoteNo, quoteStatus, beginTime, endTime
export const getSupplierQuoteItems = (params) => {
const { supplierId, ...query } = params;

View File

@@ -47,7 +47,7 @@
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" :show-overflow-tooltip="true"/>
<el-table-column label="操作" align="center" width="160">
<el-table-column label="操作" class-name="col-ops" align="center">
<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>

View File

@@ -36,7 +36,7 @@
<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">
<el-table-column label="操作" class-name="col-ops" align="center">
<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>

View File

@@ -18,7 +18,7 @@
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200">
<el-table-column label="操作" class-name="col-ops" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-plus" @click="handleAdd(scope.row)">新增子类</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>

View File

@@ -29,7 +29,7 @@
<el-table-column label="城市" prop="city" width="100" />
<el-table-column label="订单数" prop="orderCount" width="70" align="center" />
<el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" width="130" align="center" fixed="right">
<el-table-column label="操作" class-name="col-ops" align="center" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#f56c6c" @click="handleDelete(scope.row)">删除</el-button>
@@ -274,18 +274,15 @@ export default {
const c = this.clientOptions.find(o => o.clientId === clientId)
this.orderClientName = c ? c.clientName : ""
getClientOrders(clientId).then(r => {
this.orderList = (r.data || []).map(o => ({
...o, totalAmount: o.totalAmount || o.total_amount, deliveryDate: o.deliveryDate || o.delivery_date,
actualCloseDate: o.actualCloseDate || o.actual_close_date, deliveryStatus: o.deliveryStatus || o.delivery_status, itemCount: o.itemCount || o.item_count
}))
this.orderList = r.data || []
this.orderLoading = false
}).catch(() => { this.orderLoading = false })
},
showOrderDetail(row) {
getDelivery(row.doId || row.do_id).then(r => { this.detailData = r.data; this.detailOpen = true }).catch(() => {})
},
statusType(s) { return { pending: "warning", transit: "primary", history: "success" }[s] || "" },
statusLabel(s) { return { pending: "待发", transit: "在途", history: "已收货" }[s] || s || "-" }
statusType(s) { return { pending: "warning", transit: "primary", history: "success", closed: "info" }[s] || "" },
statusLabel(s) { return { pending: "待发", transit: "在途", history: "已签收", closed: "已结单" }[s] || s || "-" }
}
}
</script>

View File

@@ -24,7 +24,7 @@
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="300" align="center">
<el-table-column label="操作" class-name="col-ops" 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:#E6A23C" @click="handleSubmitApproval(s.row)" v-if="s.row.deliveryStatus==='pending' || s.row.deliveryStatus==='rejected'">提交审批</el-button>

View File

@@ -16,7 +16,7 @@
<el-table-column label="配送差异" width="90" align="center"><template slot-scope="s"><span :class="diffClass(s)">{{ diffLabel(s.row) }}</span></template></el-table-column>
<el-table-column label="物料" prop="itemCount" width="55" align="center" />
<el-table-column label="状态" width="85" align="center"><el-tag type="success" size="small" effect="dark">已签收</el-tag></el-table-column>
<el-table-column label="操作" width="120" align="center"><template slot-scope="s"><el-button size="mini" type="text" @click="handleView(s.row)">详情</el-button><el-button size="mini" type="text" @click="handleRecall(s.row)">撤回</el-button></template></el-table-column>
<el-table-column label="操作" class-name="col-ops" align="center"><template slot-scope="s"><el-button size="mini" type="text" @click="handleView(s.row)">详情</el-button><el-button size="mini" type="text" @click="handleRecall(s.row)">撤回</el-button></template></el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="q.pageNum" :limit.sync="q.pageSize" @pagination="getList" />
<el-dialog title="发货单详情" :visible.sync="detailOpen" width="780px" append-to-body>
@@ -47,7 +47,7 @@
import { listDelivery, getDelivery, recallDelivery } from "@/api/bid/delivery"
export default {
name: "ClientDeliverySigned",
data() { return { loading: false, list: [], total: 0, q: { pageNum: 1, pageSize: 20, type: "client", deliveryStatus: "history", doNo: "", clientName: "" }, detailOpen: false, detailData: null }},
data() { return { loading: false, list: [], total: 0, q: { pageNum: 1, pageSize: 20, type: "client", deliveryStatus: "history,closed", doNo: "", clientName: "" }, detailOpen: false, detailData: null }},
created() { this.getList() },
methods: {
getList() { this.loading=true; listDelivery(this.q).then(r=>{this.list=(r.rows||[]).map(d=>({...d,deliveryDate:d.deliveryDate?d.deliveryDate.substring(0,10):'',actualCloseDate:d.actualCloseDate?d.actualCloseDate.substring(0,10):''}));this.total=r.total||0;this.loading=false}).catch(()=>{this.loading=false}) },

View File

@@ -14,7 +14,7 @@
<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"><el-tag type="primary" size="small" effect="dark">运输中</el-tag></el-table-column>
<el-table-column label="操作" width="180" align="center">
<el-table-column label="操作" class-name="col-ops" 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="handleSign(s.row)">甲方签收</el-button>

View File

@@ -34,7 +34,7 @@
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" width="160" align="center" />
<el-table-column label="操作" align="center" width="180">
<el-table-column label="操作" class-name="col-ops" align="center">
<template slot-scope="s">
<el-button type="primary" size="mini" icon="el-icon-data-analysis"
@click="goCompare(s.row)">进入比价</el-button>

View File

@@ -32,7 +32,7 @@
</el-table-column>
<el-table-column label="处理结果" prop="resolution" :show-overflow-tooltip="true" />
<el-table-column label="提交时间" prop="createTime" width="160" />
<el-table-column label="操作" align="center" width="150">
<el-table-column label="操作" class-name="col-ops" align="center">
<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>

View File

@@ -0,0 +1,180 @@
<template>
<el-dialog title="操作日志详情" :visible.sync="visible" width="780px" append-to-body>
<div v-if="form.operId" class="detail-wrap">
<!-- 基本信息 -->
<div class="detail-card">
<div class="detail-card-title"><i class="el-icon-info" style="color:#409EFF"></i> 基本信息</div>
<el-row :gutter="16">
<el-col :span="12">
<div class="detail-item"><span class="detail-label">日志编号</span><span class="detail-value">{{ form.operId }}</span></div>
<div class="detail-item"><span class="detail-label">操作模块</span><span class="detail-value"><el-tag size="mini">{{ form.title }}</el-tag></span></div>
<div class="detail-item"><span class="detail-label">业务类型</span><span class="detail-value"><span :style="{color:typeColor(form.businessType),fontWeight:600}">{{ typeLabel(form.businessType) }}</span></span></div>
</el-col>
<el-col :span="12">
<div class="detail-item"><span class="detail-label">操作时间</span><span class="detail-value">{{ formatDate(form.operTime) }}</span></div>
<div class="detail-item"><span class="detail-label">执行状态</span>
<el-tag :type="form.status === 0 ? 'success' : 'danger'" size="mini">{{ form.status === 0 ? '正常' : '异常' }}</el-tag>
</div>
<div class="detail-item"><span class="detail-label">请求耗时</span><span class="detail-value">{{ form.costTime }} 毫秒</span></div>
</el-col>
</el-row>
</div>
<!-- 操作人员 -->
<div class="detail-card">
<div class="detail-card-title"><i class="el-icon-user" style="color:#E6A23C"></i> 操作人员</div>
<el-row :gutter="16">
<el-col :span="12">
<div class="detail-item"><span class="detail-label">用户名</span><span class="detail-value">{{ form.operName || '-' }}</span></div>
<div class="detail-item" v-if="form.deptName"><span class="detail-label">所属部门:</span><span class="detail-value">{{ form.deptName }}</span></div>
</el-col>
<el-col :span="12">
<div class="detail-item"><span class="detail-label">IP地址</span><span class="detail-value">{{ form.operIp || '-' }}</span></div>
<div class="detail-item"><span class="detail-label">访问地点</span><span class="detail-value">{{ form.operLocation || '-' }}</span></div>
</el-col>
</el-row>
</div>
<!-- 请求信息 -->
<div class="detail-card">
<div class="detail-card-title"><i class="el-icon-sort" style="color:#67C23A"></i> 请求信息</div>
<el-row :gutter="16">
<el-col :span="24">
<div class="detail-item">
<span class="detail-label">请求地址</span>
<span class="detail-value">
<el-tag :type="methodType(form.requestMethod)" size="mini">{{ form.requestMethod || '-' }}</el-tag>
{{ form.operUrl || '-' }}
</span>
</div>
</el-col>
<el-col :span="24">
<div class="detail-item"><span class="detail-label">执行方法</span><span class="detail-value mono">{{ form.method || '-' }}</span></div>
</el-col>
</el-row>
</div>
<!-- 请求参数 -->
<div class="detail-card">
<div class="detail-card-title"><i class="el-icon-upload2" style="color:#9B59B6"></i> 请求参数</div>
<div class="code-body">
<div class="code-action">
<el-button size="mini" icon="el-icon-copy-document" @click="copyText(form.operParam)">复制</el-button>
</div>
<pre class="code-pre">{{ formatJson(form.operParam) }}</pre>
</div>
</div>
<!-- 返回参数 -->
<div class="detail-card">
<div class="detail-card-title"><i class="el-icon-download" style="color:#1890FF"></i> 返回参数</div>
<div class="code-body">
<div class="code-action">
<el-button size="mini" icon="el-icon-copy-document" @click="copyText(form.jsonResult)">复制</el-button>
</div>
<pre class="code-pre">{{ formatJson(form.jsonResult) }}</pre>
</div>
</div>
<!-- 异常信息 -->
<div class="detail-card" v-if="form.status !== 0 && form.errorMsg">
<div class="detail-card-title error-title"><i class="el-icon-warning"></i> 异常信息</div>
<div class="error-body">
<div class="error-msg">{{ form.errorMsg }}</div>
</div>
</div>
</div>
<div v-else class="empty-tip">
<i class="el-icon-document" style="font-size:48px;color:#c0c4cc"></i>
<p>暂无详细数据</p>
</div>
<div slot="footer">
<el-button size="mini" @click="visible = false"> </el-button>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'LogDetailDialog',
props: {
visible: { type: Boolean, default: false },
row: { type: Object, default: () => ({}) }
},
computed: {
form() { return this.row || {} }
},
methods: {
formatDate(t) {
if (!t) return '-'
return this.parseTime(t)
},
typeLabel(t) {
const map = { 0: '其它', 1: '新增', 2: '修改', 3: '删除', 4: '授权', 5: '导出', 6: '导入', 7: '强退', 8: '生成代码', 9: '清空' }
return map[t] || '类型' + t
},
typeColor(t) {
const map = { 0: '#909399', 1: '#67C23A', 2: '#E6A23C', 3: '#F56C6C', 5: '#409EFF', 6: '#9B59B6' }
return map[t] || '#909399'
},
methodType(m) {
const map = { GET: 'success', POST: 'primary', PUT: 'warning', DELETE: 'danger' }
return map[m] || 'info'
},
formatJson(str) {
if (!str) return '(无数据)'
try { return JSON.stringify(JSON.parse(str), null, 2) } catch { return str }
},
copyText(str) {
const text = this.formatJson(str)
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => this.$message({ message: '已复制', type: 'success', duration: 1500 }))
} else {
const ta = document.createElement('textarea')
ta.value = text
document.body.appendChild(ta)
ta.select()
document.execCommand('copy')
document.body.removeChild(ta)
this.$message({ message: '已复制', type: 'success', duration: 1500 })
}
}
}
}
</script>
<style scoped>
.detail-wrap { padding: 4px; }
.detail-card {
border: 1px solid #ebeef5; border-radius: 4px;
margin-bottom: 12px; padding: 12px 14px; background: #fafafa;
}
.detail-card-title {
font-size: 13px; font-weight: 600; color: #303133;
margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5;
display: flex; align-items: center; gap: 6px;
}
.detail-row { margin-bottom: 0; }
.detail-item {
display: flex; align-items: center; padding: 4px 0;
font-size: 12px; line-height: 1.6;
}
.detail-label { color: #909399; min-width: 70px; flex-shrink: 0; }
.detail-value { color: #303133; flex: 1; word-break: break-all; }
.detail-value.mono { font-family: 'Courier New', monospace; font-size: 11px; }
.code-body { background: #2d2d2d; border-radius: 4px; overflow: hidden; }
.code-action { background: #3a3a3a; padding: 6px 10px; display: flex; justify-content: flex-end; }
.code-pre {
margin: 0; padding: 12px; color: #e0e0e0; font-size: 11px;
font-family: 'Consolas', monospace; line-height: 1.6; white-space: pre-wrap;
max-height: 300px; overflow: auto;
}
.error-title { color: #f56c6c; }
.error-body { background: #fef0f0; padding: 12px; border-radius: 4px; border: 1px solid #fde2e2; }
.error-msg { color: #f56c6c; font-size: 12px; line-height: 1.6; white-space: pre-wrap; word-break: break-all; max-height: 200px; overflow: auto; }
.empty-tip { padding: 40px 0; text-align: center; color: #909399; }
</style>

View File

@@ -1,143 +1,472 @@
<template>
<div class="log-page">
<!-- 标题栏 -->
<!-- 顶部标题栏 -->
<div class="page-header">
<span class="page-title">操作记录</span>
<span class="page-subtitle">OPERATION AUDIT LOG</span>
<div class="header-left">
<span class="page-title">
<i class="el-icon-document-checked" style="color:#409EFF"></i>
操作记录
</span>
<span class="page-subtitle">OPERATION AUDIT LOG</span>
</div>
<div class="header-right">
<span class="total-info"> <b>{{ total }}</b> 条记录</span>
<el-button size="mini" icon="el-icon-refresh" @click="refreshAll">刷新数据</el-button>
<el-button size="mini" icon="el-icon-download" @click="handleExport" v-hasPermi="['bid:operationlog:export']">导出</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete" @click="handleClean" v-hasPermi="['bid:operationlog:remove']">清空</el-button>
</div>
</div>
<!-- 筛选栏 -->
<!-- KPI 统计卡片 -->
<el-row :gutter="12" class="kpi-row" v-if="stats.basic">
<el-col :xs="12" :sm="6" :md="4" v-for="card in kpiCards" :key="card.label">
<div class="kpi-card" :style="{ borderTop: '3px solid ' + card.color }">
<div class="kpi-label">{{ card.label }}</div>
<div class="kpi-value" :style="{ color: card.color }">{{ card.value }}</div>
<div class="kpi-icon"><i :class="card.icon" :style="{ color: card.color }"></i></div>
</div>
</el-col>
</el-row>
<!-- 图表区域 -->
<el-row :gutter="12" class="chart-row">
<el-col :xs="24" :sm="12" :md="8">
<div class="chart-card">
<div class="chart-title">
<i class="el-icon-pie-chart" style="color:#67C23A"></i>
<span>模块分布</span>
</div>
<div ref="moduleChart" class="chart-container" style="height:280px"></div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<div class="chart-card">
<div class="chart-title">
<i class="el-icon-data-analysis" style="color:#E6A23C"></i>
<span>操作类型分布</span>
</div>
<div ref="typeChart" class="chart-container" style="height:280px"></div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="8">
<div class="chart-card">
<div class="chart-title">
<i class="el-icon-warning" style="color:#F56C6C"></i>
<span>异常模块 Top5</span>
</div>
<div ref="errorChart" class="chart-container" style="height:280px"></div>
</div>
</el-col>
</el-row>
<!-- 趋势图表 -->
<div class="chart-card trend-card">
<div class="chart-title">
<i class="el-icon-data-line" style="color:#409EFF"></i>
<span> 14 日操作趋势</span>
</div>
<div ref="trendChart" class="chart-container" style="height:280px"></div>
</div>
<!-- 筛选栏 -->
<div class="search-bar">
<el-input v-model="q.searchKey" placeholder="搜索目标 / 详情" clearable size="small" style="width:200px"
prefix-icon="el-icon-search" @keyup.enter.native="handleSearch" />
<el-select v-model="q.title" placeholder="全部模块" clearable size="small" style="width:130px" @change="handleSearch">
<el-option label="全部模块" value="" />
<el-option label="甲方客户" value="甲方客户" />
<el-option label="物料管理" value="物料管理" />
<el-option label="供应商管理" value="供应商管理" />
<el-option label="报价请求" value="报价请求" />
<el-option label="供应商报价" value="报价单" />
<el-option label="采购单" value="采购单" />
<el-option label="发货管理" value="发货管理" />
<el-option label="结单时间" value="结单时间" />
<el-option label="订单异议" value="订单异议" />
<el-option label="供应商评价" value="供应商评价" />
<el-option label="租户管理" value="租户管理" />
<el-input
v-model="queryParams.operParam"
placeholder="搜索关键词(模块/操作人/请求URL/参数)"
clearable
size="small"
prefix-icon="el-icon-search"
style="width:260px"
@keyup.enter.native="handleQuery"
/>
<el-select
v-model="queryParams.title"
placeholder="模块筛选"
clearable
size="small"
filterable
style="width:140px"
@change="handleQuery"
>
<el-option
v-for="m in moduleOptions"
:key="m.module_name"
:label="m.module_name"
:value="m.module_name"
/>
</el-select>
<el-select v-model="q.businessType" placeholder="全部操作" clearable size="small" style="width:120px" @change="handleSearch">
<el-option label="全部操作" value="" />
<el-option label="新增" value="1" />
<el-option label="修改" value="2" />
<el-option label="删除" value="3" />
<el-option label="导出" value="5" />
<el-select
v-model="queryParams.businessType"
placeholder="操作类型"
clearable
size="small"
style="width:120px"
@change="handleQuery"
>
<el-option label="新增" :value="1" />
<el-option label="修改" :value="2" />
<el-option label="删除" :value="3" />
<el-option label="授权" :value="4" />
<el-option label="导出" :value="5" />
<el-option label="导入" :value="6" />
<el-option label="强退" :value="7" />
<el-option label="生成代码" :value="8" />
<el-option label="清空" :value="9" />
<el-option label="其它" :value="0" />
</el-select>
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始"
end-placeholder="结束" value-format="yyyy-MM-dd" size="small" style="width:210px" clearable />
<el-button type="primary" size="small" icon="el-icon-search" @click="handleSearch">搜索</el-button>
<el-button size="small" icon="el-icon-refresh" @click="resetSearch">重置</el-button>
<div class="search-right">
<el-button size="small" icon="el-icon-refresh" @click="getList">刷新</el-button>
</div>
<el-select
v-model="queryParams.status"
placeholder="操作状态"
clearable
size="small"
style="width:110px"
@change="handleQuery"
>
<el-option label="正常" :value="0" />
<el-option label="异常" :value="1" />
</el-select>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始"
end-placeholder="结束"
value-format="yyyy-MM-dd"
size="small"
style="width:240px"
clearable
/>
<el-button type="primary" size="small" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button size="small" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</div>
<!-- 表格 -->
<el-table v-loading="loading" :data="list" border stripe size="small" class="log-table" style="width:100%">
<el-table-column label="时间" prop="operTime" width="155" />
<el-table-column label="操作人" prop="operName" width="90" />
<el-table-column label="模块" width="100">
<template slot-scope="s">
<el-tag :type="moduleType(s.row.title)" size="small" effect="plain">{{ s.row.title }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作类型" width="85" align="center">
<template slot-scope="s">
<span :style="{ color: actionColor(s.row.businessType), fontWeight:600 }">{{ actionLabel(s.row.businessType) }}</span>
</template>
</el-table-column>
<el-table-column label="请求地址" prop="operUrl" min-width="200" show-overflow-tooltip />
<el-table-column label="参数" min-width="180" show-overflow-tooltip>
<template slot-scope="s">{{ formatParam(s.row.operParam) }}</template>
</el-table-column>
<el-table-column label="状态" width="65" align="center">
<template slot-scope="s">
<el-tag :type="s.row.status === 0 ? 'success' : 'danger'" size="mini" effect="dark">
{{ s.row.status === 0 ? '正常' : '异常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="耗时" prop="costTime" width="70" align="center">
<template slot-scope="s">{{ s.row.costTime || '-' }}ms</template>
</el-table-column>
</el-table>
<!-- 日志列表 -->
<div v-loading="loading" class="log-table-wrap">
<el-table
ref="logTable"
:data="list"
border
stripe
size="small"
height="500"
style="width:100%"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="45" align="center" />
<el-table-column label="编号" prop="operId" width="70" align="center" />
<el-table-column label="时间" prop="operTime" width="155" align="center">
<template slot-scope="scope">{{ formatDate(scope.row.operTime) }}</template>
</el-table-column>
<el-table-column label="模块" min-width="110">
<template slot-scope="scope">
<el-tag size="mini" effect="plain">{{ scope.row.title }}</el-tag>
</template>
</el-table-column>
<el-table-column label="类型" width="80" align="center">
<template slot-scope="scope">
<span :style="{ color: typeColor(scope.row.businessType), fontWeight: 600 }">{{ typeLabel(scope.row.businessType) }}</span>
</template>
</el-table-column>
<el-table-column label="操作人" prop="operName" width="100" align="center" />
<el-table-column label="请求路径" prop="operUrl" min-width="220" show-overflow-tooltip />
<el-table-column label="状态" width="75" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 0 ? 'success' : 'danger'" size="mini">
{{ scope.row.status === 0 ? '正常' : '异常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="耗时" prop="costTime" width="75" align="center">
<template slot-scope="scope">{{ scope.row.costTime }}ms</template>
</el-table-column>
<el-table-column label="操作" class-name="col-ops" align="center" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>详情</el-button>
<el-button
size="mini"
type="text"
style="color:#F56C6C"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['bid:operationlog:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="q.pageNum" :limit.sync="q.pageSize"
@pagination="getList" />
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
<!-- 详情弹窗 -->
<log-detail-dialog :visible.sync="detailVisible" :row="detailRow" />
</div>
</template>
<script>
import { listOperationLog } from "@/api/bid/operation-log"
import * as echarts from 'echarts'
require('echarts/theme/macarons')
import {
listOperationLog,
deleteOperationLog,
cleanOperationLog,
getOperationLogStats
} from '@/api/bid/operation-log'
import LogDetailDialog from './detail'
export default {
name: "OperationLog",
name: 'OperationLog',
components: { LogDetailDialog },
data() {
return {
loading: false, list: [], total: 0,
dateRange: null,
q: {
pageNum: 1, pageSize: 20,
title: "", businessType: "", searchKey: "",
loading: false,
list: [],
total: 0,
dateRange: [],
moduleOptions: [],
stats: { basic: null, modules: [], businessTypes: [], trend: [], errors: [] },
charts: { module: null, type: null, trend: null, error: null },
detailVisible: false,
detailRow: {},
queryParams: {
pageNum: 1,
pageSize: 20,
operParam: undefined,
title: undefined,
businessType: undefined,
status: undefined,
operName: undefined,
params: { beginTime: null, endTime: null }
}
}
},
created() { this.getList() },
computed: {
kpiCards() {
const b = this.stats.basic || {}
return [
{ label: '总操作数', value: b.total_count || 0, color: '#409EFF', icon: 'el-icon-document' },
{ label: '今日操作', value: b.today_count || 0, color: '#67C23A', icon: 'el-icon-time' },
{ label: '异常次数', value: b.error_count || 0, color: '#F56C6C', icon: 'el-icon-warning' },
{ label: '操作人数', value: b.user_count || 0, color: '#E6A23C', icon: 'el-icon-user-solid' },
{ label: '平均耗时', value: (b.avg_cost_time || 0) + 'ms', color: '#909399', icon: 'el-icon-alarm-clock' },
{ label: '模块总数', value: b.module_count || 0, color: '#9B59B6', icon: 'el-icon-menu' }
]
}
},
mounted() {
this.loadStats()
this.getList()
this.initCharts()
const that = this
this.$nextTick(() => {
window.addEventListener('resize', that.resizeAll)
})
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeAll)
this.disposeAll()
},
methods: {
loadStats() {
const p = this.buildQueryParams()
delete p.pageNum
delete p.pageSize
getOperationLogStats(p).then(res => {
if (res.data) {
this.stats = res.data
this.moduleOptions = (res.data.modules || []).slice(0, 30)
this.$nextTick(() => this.renderCharts())
}
})
},
getList() {
this.loading = true
const p = {
pageNum: this.q.pageNum, pageSize: this.q.pageSize,
title: this.q.title || undefined,
businessType: this.q.businessType || undefined,
params: {}
}
const p = this.buildQueryParams()
listOperationLog(p).then(res => {
this.list = res.rows || []
this.total = res.total || 0
this.loading = false
}).catch(() => {
this.loading = false
})
},
buildQueryParams() {
const p = JSON.parse(JSON.stringify(this.queryParams))
p.params = {}
if (this.dateRange && this.dateRange.length === 2) {
p.params.beginTime = this.dateRange[0] + ' 00:00:00'
p.params.endTime = this.dateRange[1] + ' 23:59:59'
}
if (this.q.searchKey) p.operParam = this.q.searchKey
listOperationLog(p).then(r => {
this.list = r.rows || []; this.total = r.total || 0; this.loading = false
}).catch(() => { this.loading = false })
return p
},
handleSearch() { this.q.pageNum = 1; this.getList() },
resetSearch() {
this.q.title = ""; this.q.businessType = ""; this.q.searchKey = ""
this.dateRange = null; this.q.params = {}
this.handleSearch()
handleQuery() {
this.queryParams.pageNum = 1
this.loadStats()
this.getList()
},
moduleType(title) {
const map = { "物料管理": "", "供应商管理": "", "报价单": "warning", "报价请求": "warning",
"采购单": "", "发货管理": "success", "甲方客户": "primary", "结单时间": "warning",
"订单异议": "danger", "供应商评价": "success", "租户管理": "info" }
return map[title] || ""
resetQuery() {
this.dateRange = []
this.queryParams = {
pageNum: 1, pageSize: 20,
operParam: undefined, title: undefined,
businessType: undefined, status: undefined,
params: { beginTime: null, endTime: null }
}
this.handleQuery()
},
actionLabel(b) {
const map = { 1: "新增", 2: "修改", 3: "删除", 4: "授权", 5: "导出", 6: "导入" }
return map[b] || ("类型" + b)
refreshAll() {
this.loadStats()
this.getList()
},
actionColor(b) {
const map = { 1: "#67c23a", 2: "#e6a23c", 3: "#f56c6c", 5: "#e4393c" }
return map[b] || "#909399"
handleSelectionChange(sel) {
this.ids = sel.map(x => x.operId)
},
formatParam(param) {
if (!param) return "-"
if (param.length > 80) return param.substring(0, 80) + "..."
return param
handleDetail(row) {
this.detailRow = row
this.detailVisible = true
},
handleDelete(row) {
const ids = row.operId ? [row.operId] : this.ids
if (!ids || ids.length === 0) {
this.$message.warning('请选择要删除的日志')
return
}
this.$modal.confirm('确定删除选中的日志记录吗?').then(() => {
return deleteOperationLog(ids.join(','))
}).then(() => {
this.getList()
this.loadStats()
this.$message.success('删除成功')
}).catch(() => {})
},
handleClean() {
this.$modal.confirm('确定清空所有操作日志吗?此操作不可恢复。').then(() => {
return cleanOperationLog()
}).then(() => {
this.getList()
this.loadStats()
this.$message.success('清空成功')
}).catch(() => {})
},
handleExport() {
const p = this.buildQueryParams()
delete p.pageNum
delete p.pageSize
this.download('/bid/operationLog/export', p, '操作日志_' + Date.now())
},
formatDate(t) {
if (!t) return '-'
return this.parseTime(t)
},
typeLabel(t) {
const map = { 0: '其它', 1: '新增', 2: '修改', 3: '删除', 4: '授权', 5: '导出', 6: '导入', 7: '强退', 8: '生成代码', 9: '清空' }
return map[t] || '类型' + t
},
typeColor(t) {
const map = { 0: '#909399', 1: '#67C23A', 2: '#E6A23C', 3: '#F56C6C', 5: '#409EFF', 6: '#9B59B6' }
return map[t] || '#909399'
},
initCharts() {
if (this.$refs.moduleChart) this.charts.module = echarts.init(this.$refs.moduleChart, 'macarons')
if (this.$refs.typeChart) this.charts.type = echarts.init(this.$refs.typeChart, 'macarons')
if (this.$refs.trendChart) this.charts.trend = echarts.init(this.$refs.trendChart, 'macarons')
if (this.$refs.errorChart) this.charts.error = echarts.init(this.$refs.errorChart, 'macarons')
},
renderCharts() {
this.renderModuleChart()
this.renderTypeChart()
this.renderTrendChart()
this.renderErrorChart()
},
renderModuleChart() {
const data = (this.stats.modules || []).slice(0, 10)
if (!this.charts.module || data.length === 0) return
const option = {
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { type: 'scroll', bottom: 0, textStyle: { fontSize: 10 } },
series: [{
type: 'pie',
radius: ['35%', '65%'],
center: ['50%', '40%'],
avoidLabelOverlap: true,
label: { show: true, formatter: '{b}\n{c}', fontSize: 10 },
data: data.map(d => ({ name: d.module_name, value: d.count }))
}]
}
this.charts.module.setOption(option)
},
renderTypeChart() {
const data = this.stats.businessTypes || []
if (!this.charts.type || data.length === 0) return
const option = {
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { type: 'scroll', bottom: 0, textStyle: { fontSize: 10 } },
series: [{
type: 'pie',
radius: '60%',
center: ['50%', '40%'],
label: { show: true, formatter: '{b}\n{c}', fontSize: 10 },
data: data.map(d => ({ name: this.typeLabel(d.type), value: d.count }))
}]
}
this.charts.type.setOption(option)
},
renderTrendChart() {
const data = (this.stats.trend || []).slice().reverse()
if (!this.charts.trend || data.length === 0) return
const option = {
tooltip: { trigger: 'axis' },
grid: { left: '3%', right: '4%', bottom: '10%', top: '10%', containLabel: true },
xAxis: { type: 'category', data: data.map(d => d.day), axisLabel: { fontSize: 10, rotate: 30 } },
yAxis: { type: 'value', name: '次数' },
series: [{
type: 'line',
smooth: true,
areaStyle: { opacity: 0.3 },
lineStyle: { width: 2 },
symbol: 'circle',
symbolSize: 6,
itemStyle: { color: '#409EFF' },
data: data.map(d => d.count)
}]
}
this.charts.trend.setOption(option)
},
renderErrorChart() {
const data = (this.stats.errors || []).slice(0, 5)
if (!this.charts.error || data.length === 0) return
const option = {
tooltip: { trigger: 'axis' },
grid: { left: '3%', right: '8%', bottom: '10%', top: '10%', containLabel: true },
xAxis: { type: 'value', name: '次数' },
yAxis: { type: 'category', data: data.map(d => d.module_name), axisLabel: { fontSize: 10 } },
series: [{
type: 'bar',
data: data.map(d => d.count),
itemStyle: { color: '#F56C6C' },
label: { show: true, position: 'right', fontSize: 10 }
}]
}
this.charts.error.setOption(option)
},
resizeAll() {
Object.keys(this.charts).forEach(k => {
if (this.charts[k] && this.charts[k].resize) this.charts[k].resize()
})
},
disposeAll() {
Object.keys(this.charts).forEach(k => {
if (this.charts[k] && this.charts[k].dispose) this.charts[k].dispose()
})
}
}
}
@@ -147,20 +476,42 @@ export default {
.log-page { background: #f5f7fa; padding: 12px; min-height: calc(100vh - 84px); }
.page-header {
background: #fff; padding: 12px 16px; border-radius: 4px; margin-bottom: 12px;
background: #fff; padding: 14px 18px; 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: #333333; }
.header-left { display: flex; align-items: center; gap: 10px; flex: 1; }
.page-title { font-size: 16px; font-weight: 700; color: #333; }
.page-subtitle { font-size: 11px; color: #c0c4cc; letter-spacing: 1px; }
.header-right { margin-left: auto; }
.total-info { font-size: 12px; color: #909399; }
.header-right { display: flex; gap: 8px; }
.kpi-row { margin-bottom: 12px; }
.kpi-card {
background: #fff; border-radius: 4px; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,0.06);
position: relative; overflow: hidden; margin-bottom: 12px;
}
.kpi-label { font-size: 12px; color: #909399; margin-bottom: 8px; }
.kpi-value { font-size: 26px; font-weight: 700; }
.kpi-icon { position: absolute; right: 16px; top: 50%; transform: translateY(-50%); font-size: 36px; opacity: 0.15; }
.chart-row { margin-bottom: 12px; }
.chart-card {
background: #fff; border-radius: 4px; padding: 14px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
margin-bottom: 12px;
}
.chart-title {
font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 10px;
display: flex; align-items: center; gap: 6px;
}
.chart-container { width: 100%; }
.trend-card { margin-bottom: 12px; }
.search-bar {
background: #fff; padding: 12px 16px; border-radius: 4px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06); margin-bottom: 12px;
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.search-right { margin-left: auto; }
.log-table { box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.log-table-wrap { background: #fff; border-radius: 4px; padding: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
</style>

View File

@@ -131,7 +131,7 @@ export default {
{ key: 'transit', label: '在途', count: 0 },
{ key: 'history', label: '历史', count: 0 },
],
q: { pageNum: 1, pageSize: 20, deliveryStatus: "history", doNo: "", supplierName: "" },
q: { pageNum: 1, pageSize: 20, deliveryStatus: "history,closed", doNo: "", supplierName: "" },
detailOpen: false, detailData: null,
statCards: [
{ key: "totalHistory", label: "历史订单总数", icon: "el-icon-document-copy", color: "#e4393c" },

View File

@@ -328,33 +328,37 @@ export default {
}
this.chart.setOption(option)
// 点击跳转到对应详情self 已在上方定义)
// 点击甘特条或节点 → 跳转到对应订单详情
this.chart.on('click', function(params) {
// custom series: params.value = [startDate, endDate, idx]
// scatter series: params.data.order 或 params.value = [date, idx]
let o = null
// scatter节点order 保存在 data 中
if (params.data && params.data.order) {
o = params.data.order
} else if (params.value && params.value.length >= 3) {
const idx = Math.round(params.value[2])
o = self.orders[idx]
} else if (params.value && params.value.length >= 2) {
// scatter的坐标: [date, idx]
const idx = Math.round(params.value[1])
o = self.orders[idx]
}
// custom甘特条通过 dataIndex 映射到 orders
if (!o && params.dataIndex != null) {
if (params.seriesIndex === 0) {
o = self.orders[params.dataIndex]
} else if (params.seriesIndex === 1 && params.data && params.data.order) {
o = params.data.order
}
}
if (!o) return
// 根据类型和状态跳转
// 根据类型和状态跳转到对应的订单列表页
if (o.type === 'client') {
const path = o.deliveryStatus === 'pending' ? '/bid/clientDelivery/pending'
: o.deliveryStatus === 'transit' ? '/bid/clientDelivery/transit'
: '/bid/clientDelivery/signed'
self.$router.push({ path, query: { doNo: o.doNo } })
self.$router.push({
path: o.deliveryStatus === 'pending' ? '/bid/clientDelivery/pending'
: o.deliveryStatus === 'transit' ? '/bid/clientDelivery/transit'
: '/bid/clientDelivery/signed',
query: { doNo: o.doNo }
})
} else {
const path = o.deliveryStatus === 'pending' ? '/bid/order/pending'
: o.deliveryStatus === 'transit' ? '/bid/order/transit'
: '/bid/order/history'
self.$router.push({ path, query: { doNo: o.doNo } })
self.$router.push({
path: o.deliveryStatus === 'pending' ? '/bid/order/pending'
: o.deliveryStatus === 'transit' ? '/bid/order/transit'
: '/bid/order/history',
query: { doNo: o.doNo }
})
}
})
}

View File

@@ -72,11 +72,20 @@
<el-col :span="1.5">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">新建报价</el-button>
</el-col>
<el-col :span="1.5" v-if="!isSupplier">
<el-button type="danger" plain icon="el-icon-delete" size="mini" @click="handleCleanNullQuote">清理无编号数据</el-button>
</el-col>
</el-row>
<!-- 报价列表 -->
<el-table v-loading="loading" :data="list" border stripe>
<el-table-column label="报价单号" prop="quoteNo" width="155" />
<el-table-column label="报价单号" width="155">
<template slot-scope="s">
<span :style="{ color: s.row.quoteNo ? '#333' : '#c0c4cc' }">
{{ s.row.quoteNo || '(待编号)' }}
</span>
</template>
</el-table-column>
<el-table-column label="关联询价单" width="200">
<template slot-scope="s">
<div style="font-weight:600;color:#333">{{ s.row.rfqNo }}</div>
@@ -113,27 +122,42 @@
<span v-else style="color:#c0c4cc;font-size:12px">未提交</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="220" fixed="right">
<el-table-column label="操作" align="center" width="260" fixed="right" class-name="col-ops">
<template slot-scope="s">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(s.row)">查看</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(s.row)"
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"
@click="handleReject(s.row)" v-if="s.row.status==='submitted' && !isSupplier">拒绝</el-button>
<el-button size="mini" type="text" style="color:#4A6FA5" icon="el-icon-s-order"
@click="handleCreateDelivery(s.row)" v-if="s.row.status==='accepted'">生成发货单</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#f56c6c"
@click="handleDelete(s.row)" v-if="s.row.status==='draft'">删除</el-button>
<!-- 草稿提交审批是唯一送审入口 -->
<template v-if="s.row.status==='draft'">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(s.row)">编辑</el-button>
<el-button size="mini" type="text" style="color:#E6A23C" icon="el-icon-s-check"
@click="handleSubmitApproval(s.row)">提交审批</el-button>
<el-dropdown trigger="click" @command="c => handleDropdown(c, s.row)">
<el-button size="mini" type="text">更多<i class="el-icon-arrow-down el-icon--right" /></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<!-- 已提交历史遗留允许补交审批 -->
<template v-if="s.row.status==='submitted'">
<el-dropdown trigger="click" @command="c => handleDropdown(c, s.row)">
<el-button size="mini" type="text">更多<i class="el-icon-arrow-down el-icon--right" /></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="submitApproval" icon="el-icon-s-check">提交审批</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<!-- 审批中审批人操作 -->
<template v-if="s.row.status==='10'">
<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="handleApprovalReject(s.row)">审批驳回</el-button>
</template>
<!-- 已采纳生成发货单 -->
<el-button v-if="s.row.status==='accepted'" size="mini" type="text" style="color:#4A6FA5"
icon="el-icon-s-order" @click="handleCreateDelivery(s.row)">生成发货单</el-button>
</template>
</el-table-column>
</el-table>
@@ -273,7 +297,7 @@
<div slot="footer">
<el-button @click="dialogOpen = false">取消</el-button>
<el-button type="success" @click="submitForm('draft')" :loading="saving">保存草稿</el-button>
<el-button type="primary" @click="submitForm('submit')" :loading="submitting">保存并提交</el-button>
<el-button type="primary" @click="submitForm('approval')" :loading="submitting">保存并提交审批</el-button>
</div>
</el-dialog>
@@ -304,12 +328,12 @@
<div class="pdf-company">福安德综合报价系统</div>
<div class="pdf-doc-type">供应商报价单</div>
</div>
<div class="pdf-header-no">{{ detailData.quoteNo }}</div>
<div class="pdf-header-no">{{ detailData.quoteNo || '(待编号)' }}</div>
</div>
<div class="pdf-divider"></div>
<table class="pdf-meta-table">
<tr>
<td class="meta-label">报价单号</td><td class="meta-val">{{ detailData.quoteNo }}</td>
<td class="meta-label">报价单号</td><td class="meta-val">{{ detailData.quoteNo || '(待编号)' }}</td>
<td class="meta-label">供应商</td><td class="meta-val"><strong>{{ detailData.supplierName }}</strong></td>
</tr>
<tr>
@@ -374,7 +398,7 @@
<script>
import { listQuotation, getQuotation, addQuotation, updateQuotation,
submitQuotation, acceptQuotation, rejectQuotation, delQuotation } from "@/api/bid/quotation";
delQuotation, cleanNullQuoteNo } from "@/api/bid/quotation";
import { listRfq, getRfqItems } from "@/api/bid/rfq";
import { listSupplier } from "@/api/bid/supplier";
import { addDelivery } from "@/api/bid/delivery";
@@ -426,6 +450,10 @@ export default {
listSupplier({ pageSize: 200 }).then(r => { this.supplierOptions = r.rows || []; });
}
},
/** keep-alive 缓存激活时自动刷新(解决跨页面审批后状态不更新问题) */
activated() {
this.getList();
},
methods: {
getList() {
this.loading = true;
@@ -451,6 +479,13 @@ export default {
this.dialogTitle = "新建报价单";
this.dialogOpen = true;
},
/** 清理历史遗留的无编号报价单脏数据 */
handleCleanNullQuote() {
this.$modal.confirm("确认清理所有无报价单号的脏数据?此操作不可恢复!", "危险操作", { type: "warning" })
.then(() => cleanNullQuoteNo())
.then(r => { this.$modal.msgSuccess(r.msg || "清理成功"); this.getList(); })
.catch(() => {});
},
handleUpdate(row) {
getQuotation(row.quotationId).then(r => {
this.form = { ...r.data, items: r.data.items || [] };
@@ -498,10 +533,6 @@ export default {
itemTotal(row) {
return ((parseFloat(row.quantity) || 0) * (parseFloat(row.unitPrice) || 0)).toFixed(2);
},
handleSubmit(row) {
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(); });
@@ -516,18 +547,18 @@ export default {
.then(() => { this.$modal.msgSuccess("已驳回"); this.getList(); })
.catch(() => {});
},
handleAccept(row) {
this.$modal.confirm("确认采纳此报价?").then(() => acceptQuotation(row.quotationId))
.then(() => { this.$modal.msgSuccess("已采纳"); this.getList(); });
},
handleReject(row) {
this.$modal.confirm("确认拒绝此报价?").then(() => rejectQuotation(row.quotationId))
.then(() => { this.$modal.msgSuccess("已拒绝"); this.getList(); });
},
handleDelete(row) {
this.$modal.confirm("确认删除?").then(() => delQuotation(row.quotationId))
.then(() => { this.$modal.msgSuccess("删除成功"); this.getList(); });
},
/** 「更多」下拉菜单统一路由 */
handleDropdown(command, row) {
const map = {
submitApproval:() => this.handleSubmitApproval(row),
delete: () => this.handleDelete(row),
};
if (map[command]) map[command]();
},
handleCreateDelivery(row) {
this.$modal.confirm("确认基于此报价单生成发货单?").then(() => {
return getQuotation(row.quotationId);
@@ -572,14 +603,14 @@ export default {
submitForm(mode) {
this.$refs.form.validate(valid => {
if (!valid) return;
if (mode === "submit") this.submitting = true;
if (mode === "approval") this.submitting = true;
else this.saving = true;
const action = this.form.quotationId ? updateQuotation : addQuotation;
action(this.form).then(res => {
const id = (res.data && res.data.quotationId) || this.form.quotationId;
if (mode === "submit" && id) {
return submitQuotation(id).then(() => {
this.$modal.msgSuccess("提交成功");
if (mode === "approval" && id) {
return submitApproval("QUOTATION", id).then(() => {
this.$modal.msgSuccess("提交审批");
this.dialogOpen = false;
this.getList();
});
@@ -700,4 +731,11 @@ export default {
.amount-cell { color: #e4393c; font-weight: 600; }
.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; }
/* 操作列:禁止溢出省略,确保所有按钮完整显示 */
::v-deep .col-ops .cell {
overflow: visible !important;
text-overflow: clip !important;
white-space: nowrap;
}
</style>

View File

@@ -69,7 +69,7 @@
</template>
</el-table-column>
<el-table-column label="参与供应商" width="100" align="center" prop="supplierCount" />
<el-table-column label="操作" width="80" align="center">
<el-table-column label="操作" class-name="col-ops" align="center">
<template slot-scope="s">
<el-button type="text" size="mini" icon="el-icon-data-analysis"
@click="goCompare(s.row.rfqId)">比价</el-button>

View File

@@ -17,7 +17,7 @@
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" width="160" />
<el-table-column label="操作" align="center" width="160">
<el-table-column label="操作" class-name="col-ops" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>