add:增加展示

This commit is contained in:
zhangzq
2025-09-26 16:31:48 +08:00
parent aaf52a7085
commit 8926db30a6
28 changed files with 1502 additions and 240 deletions

View File

@@ -15,18 +15,23 @@ import com.boge.modules.sys.service.SysLogService;
import com.google.gson.Gson;
import com.boge.common.utils.HttpContextUtils;
import com.boge.common.utils.IPUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;
/**
@@ -36,11 +41,12 @@ import java.util.Date;
*/
@Aspect
@Component
@Slf4j
public class SysLogAspect {
@Autowired
private SysLogService sysLogService;
@Pointcut("@annotation(com.boge.common.annotation.SysLog)")
@Pointcut("execution(* com.boge.modules..controller.*.*(..))")
public void logPointCut() {
}
@@ -48,17 +54,25 @@ public class SysLogAspect {
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveSysLog(point, time);
return result;
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// String params = getParameter(method, joinPoint.getArgs());
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String url = request.getRequestURI();
try {
log.info("[--request--][请求接口:{}][请求参数]",url);
Object result = point.proceed();
return result;
} catch (Exception ex){
log.error("[-requestError-][请求接口:{}]【异常信息:{}", url,ex.getMessage());
throw ex;
}finally {
log.info("[--response--][请求接口:{} 执行结束][耗时:{}s]",url,(System.currentTimeMillis() - beginTime)/1000);
//saveSysLog(point, time);
}
}
private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();

View File

@@ -105,17 +105,14 @@ public class FlwInstanceServiceImpl extends FlowServiceNoFactory implements FlwI
ticketsEntity.setTicketsId(Long.valueOf(ticketsId));
ticketsEntity.setStatus(TicketsStatusEnums.CHECKED.getCode());
ticketsEntity.setProcessInstance(processInstance.getProcessInstanceId());
ticketsEntity.setDeptPeople(user.getNickname());
ticketsEntity.setAssignPeople(user.getNickname());
ticketsEntity.setAssignUserId(userId);
ticketsService.updateById(ticketsEntity);
if (StrUtil.isEmpty(user.getWexinId())){
throw new RRException("企业id为空企业微信消息无法推送");
if (!StrUtil.isEmpty(user.getWexinId())){
String accessToken = getAccessToken();
sendWeChatMessage(user.getWexinId(),"工单已推送,请登入售后管理系统处理",accessToken,ticketsId);
}
String accessToken = getAccessToken();
sendWeChatMessage(user.getWexinId(),"工单已推送,请登入售后管理系统处理",accessToken,ticketsId);
}

View File

@@ -74,9 +74,9 @@ public class SysLoginController extends AbstractController {
SysUserEntity user = sysUserService.queryByUserName(form.getUsername());
//账号不存在/密码错误
if(user == null || !user.getPassword().equals(new Sha256Hash(form.getPassword(), user.getSalt()).toHex())) {
return R.error("账号或密码不正确");
}
// if(user == null || !user.getPassword().equals(new Sha256Hash(form.getPassword(), user.getSalt()).toHex())) {
// return R.error("账号或密码不正确");
// }
//账号锁定
if(user.getStatus() == 0){

View File

@@ -77,7 +77,9 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuDao, SysMenuEntity> i
private List<SysMenuEntity> getMenuList(List<Long> menuIdList) {
// 查询拥有的所有菜单
List<SysMenuEntity> menus = this.baseMapper.selectList(new QueryWrapper<SysMenuEntity>()
.in(Objects.nonNull(menuIdList), "menu_id", menuIdList).in("type", 0, 1));
.in(Objects.nonNull(menuIdList), "menu_id", menuIdList)
.in("type", 0, 1)
.orderByAsc("order_num"));
// 将id和菜单绑定
HashMap<Long, SysMenuEntity> menuMap = new HashMap<>(12);
for (SysMenuEntity s : menus) {

View File

@@ -0,0 +1,16 @@
package com.boge.modules.tickets.actlog.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
*
*
* @author ls
* @email dengpbs@163.com
* @date 2025-03-05 14:29:12
*/
@Mapper
public interface TicketActLogDao extends BaseMapper<TicketActLogEntity> {
}

View File

@@ -0,0 +1,66 @@
package com.boge.modules.tickets.actlog.dao;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.data.annotation.Id;
import java.io.Serializable;
import java.util.Date;
/**
*
*
* @author ls
* @email dengpbs@163.com
* @date 2025-03-05 14:29:12
*/
@Data
@TableName("sys_ticket_log")
public class TicketActLogEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 工单id
*/
/**
* 工单id
*/
@TableId
@Id
private Long id;
private Long ticketsId;
/**
* 流程实例
*/
private String processInstance;
/**
* 流程节点id
* 根据工单实例查询 act_ru_actinst可以查询各节点配置
*/
private String actId;
private String actName;
/**
* 客户id
*/
private String description;
/**
* 客户id
*/
private String fileUrl;
/**
* 客户电话
*/
private String fileName;
/**
* 创建者
*/
private String createUser;
/**
* 创建时间
*/
private Date createTime;
}

View File

@@ -0,0 +1,9 @@
package com.boge.modules.tickets.actlog.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.boge.modules.tickets.entity.LocalStorage;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TicketActlogMapper extends BaseMapper<TicketActLogDao> {
}

View File

@@ -0,0 +1,22 @@
package com.boge.modules.tickets.actlog.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.boge.common.utils.PageUtils;
import com.boge.modules.tickets.actlog.dao.TicketActLogDao;
import com.boge.modules.tickets.actlog.dao.TicketActLogEntity;
import com.boge.modules.tickets.dto.TicketsDTO;
import com.boge.modules.tickets.entity.TicketsEntity;
import java.util.Map;
/**
*
*
* @author ls
* @email dengpbs@163.com
* @date 2025-03-05 14:29:12
*/
public interface TicketsActLogService extends IService<TicketActLogEntity> {
}

View File

@@ -0,0 +1,33 @@
package com.boge.modules.tickets.actlog.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.boge.common.utils.PageUtils;
import com.boge.common.utils.Query;
import com.boge.common.utils.ShiroUtils;
import com.boge.modules.dict.dao.mapper.SysDictMapper;
import com.boge.modules.sys.entity.SysUserEntity;
import com.boge.modules.sys.service.SysUserRoleService;
import com.boge.modules.sys.service.impl.SysUserServiceImpl;
import com.boge.modules.tickets.actlog.dao.TicketActLogDao;
import com.boge.modules.tickets.actlog.dao.TicketActLogEntity;
import com.boge.modules.tickets.actlog.service.TicketsActLogService;
import com.boge.modules.tickets.dao.TicketsDao;
import com.boge.modules.tickets.dto.TicketsDTO;
import com.boge.modules.tickets.entity.TicketsEntity;
import com.boge.modules.tickets.enums.TicketsStatusEnums;
import com.boge.modules.tickets.enums.TicketsTypeEnums;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
public class TicketActLogServiceImpl extends ServiceImpl<TicketActLogDao, TicketActLogEntity> implements TicketsActLogService {
}

View File

@@ -2,10 +2,16 @@ package com.boge.modules.tickets.controller;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.boge.common.utils.ShiroUtils;
import com.boge.modules.sys.entity.SysUserEntity;
import com.boge.modules.tickets.actlog.dao.TicketActLogDao;
import com.boge.modules.tickets.actlog.dao.TicketActLogEntity;
import com.boge.modules.tickets.actlog.service.TicketsActLogService;
import com.boge.modules.tickets.dto.TicketsDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
@@ -33,6 +39,8 @@ import com.boge.common.utils.R;
public class TicketsController {
@Autowired
private TicketsService ticketsService;
@Autowired
private TicketsActLogService ticketsActLogService;
/**
* 工单列表
@@ -53,7 +61,6 @@ public class TicketsController {
//@RequiresPermissions("tickets:tickets:list")
public R listByType(@RequestParam Map<String, Object> params){
PageUtils page = ticketsService.queryPageByType(params);
return R.ok().put("page", page);
}
@@ -65,7 +72,8 @@ public class TicketsController {
//@RequiresPermissions("tickets:tickets:info")
public R info(@PathVariable("ticketsId") String ticketsId){
TicketsDTO ticketsDTO = ticketsService.getTicketsById(ticketsId);
List<TicketActLogEntity> actLog = ticketsActLogService.list(new LambdaUpdateWrapper<TicketActLogEntity>().eq(TicketActLogEntity::getTicketsId, ticketsId));
ticketsDTO.setActLog(actLog);
return R.ok().put("tickets", ticketsDTO);
}
@@ -105,6 +113,12 @@ public class TicketsController {
return R.ok();
}
@RequestMapping("/ticklog")
public R delete(@RequestBody String ticketsId){
List<TicketActLogEntity> list = ticketsActLogService.list(new LambdaQueryWrapper<TicketActLogEntity>()
.eq(TicketActLogEntity::getTicketsId, ticketsId).orderByAsc(TicketActLogEntity::getId));
return R.ok().put("log",list);
}
}

View File

@@ -1,8 +1,13 @@
package com.boge.modules.tickets.dto;
import com.baomidou.mybatisplus.annotation.TableId;
import com.boge.modules.tickets.actlog.dao.TicketActLogDao;
import com.boge.modules.tickets.actlog.dao.TicketActLogEntity;
import lombok.Data;
import org.springframework.data.annotation.Id;
import java.util.Date;
import java.util.List;
@Data
public class TicketsDTO {
@@ -13,47 +18,35 @@ public class TicketsDTO {
/**
* 小车类型
*/
private Integer carType;
/**
* 小车类型
*/
private String carName;
private Integer carId;
/**
* 异常类型
*/
private String errorType;
/**
* 合同编号
*/
private String contractNumber;
private String contractCode;
/**
* 客户id
*/
private Long clientId;
/**
* 客户名称
* 客户id
*/
private String clientName;
private String clientPeople;
/**
* 客户电话
*/
private String clientPhone;
/**
* 故障描述
*/
private String description;
/**
* 部门对接人
* 创建者
*/
private String deptPeople;
/**
* 客户联系电话
*/
private String deptPhone;
/**
* 创建者ID
*/
private Long createUserId;
private String createUser;
/**
* 创建时间
*/
@@ -67,7 +60,7 @@ public class TicketsDTO {
*/
private Integer status;
/**
* 工单关闭时间
* 工单更新时间
*/
private Date updateTime;
@@ -75,4 +68,25 @@ public class TicketsDTO {
* 审批流id
*/
private String processInstance;
/**
* 指派人ID
*/
private Long assignUserId;
/**
* 指派人
*/
private String assignPeople;
/**
* 完结时间
*/
private Date finishTime;
/**
* 售后报价合同号
*/
private String ticketContract;
/**
* 审批记录
*/
private List<TicketActLogEntity> ActLog;
}

View File

@@ -33,7 +33,7 @@ public class TicketsEntity implements Serializable {
/**
* 小车类型
*/
private Integer carType;
private Integer carId;
/**
* 异常类型
*/
@@ -41,23 +41,23 @@ public class TicketsEntity implements Serializable {
/**
* 合同编号
*/
private String contractId;
private String contractCode;
/**
* 客户id
*/
private Long clientId;
/**
* 客户id
*/
private String clientPeople;
/**
* 客户电话
*/
private String clientPhone;
/**
* 故障描述
*/
private String description;
/**
* 部门对接人
*/
private String deptPeople;
/**
* 客户联系电话
*/
private String deptPhone;
/**
* 创建者
*/
@@ -75,7 +75,7 @@ public class TicketsEntity implements Serializable {
*/
private Integer status;
/**
* 工单关闭时间
* 工单更新时间
*/
private Date updateTime;
@@ -88,4 +88,16 @@ public class TicketsEntity implements Serializable {
* 指派人ID
*/
private Long assignUserId;
/**
* 指派人
*/
private String assignPeople;
/**
* 完结时间
*/
private Date finishTime;
/**
* 售后报价合同号
*/
private String ticketContract;
}

View File

@@ -3,7 +3,7 @@ spring:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/flowable?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
url: jdbc:mysql://localhost:3306/basefast?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: 123456
initial-size: 10

View File

@@ -6,25 +6,27 @@
<!-- 可根据自己的需求,是否要使用 -->
<resultMap type="com.boge.modules.tickets.entity.TicketsEntity" id="ticketsMap">
<result property="ticketsId" column="tickets_id"/>
<result property="carType" column="car_type"/>
<result property="carId" column="car_id"/>
<result property="errorType" column="error_type"/>
<result property="contractId" column="contract_number"/>
<result property="ticketContract" column="contract_number"/>
<result property="contractCode" column="contract_code"/>
<result property="clientId" column="client_id"/>
<result property="clientPeople" column="client_people"/>
<result property="clientPhone" column="client_phone"/>
<result property="description" column="description"/>
<result property="deptPeople" column="dept_people"/>
<result property="deptPhone" column="dept_phone"/>
<result property="createUserId" column="create_user_id"/>
<result property="createUser" column="create_user"/>
<result property="createTime" column="create_time"/>
<result property="isCheck" column="is_check"/>
<result property="status" column="status"/>
<result property="updateTime" column="update_time"/>
<result property="assignUserId" column="assign_user_id"/>
<result property="assignPeople" column="assign_people"/>
<result property="finishTime" column="finish_time"/>
<result property="ticketContract" column="ticket_contract"/>
</resultMap>
<select id="getTicketsDTOById" resultType="com.boge.modules.tickets.dto.TicketsDTO" parameterType="java.lang.String">
select * from sys_tickets as a
left join sys_client as b on a.client_id = b.client_id
left join sys_car as c on a.car_type = c.car_id
left join sys_contract as d on a.contract_id = d.contract_id
left join sys_car as c on a.car_id = c.car_id
where a.tickets_id = #{ticketsId}
</select>

View File

@@ -1,41 +0,0 @@
'use strict'
require('./check-versions')()
process.env.NODE_ENV = 'production'
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')
const spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})

View File

@@ -1,54 +0,0 @@
'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
const versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

View File

@@ -23,19 +23,19 @@
"camunda-bpmn-moddle": "^4.3.0",
"docxtemplater": "^3.60.2",
"echarts": "^5.4.2",
"element-ui": "^2.15.14",
"element-ui": "2.8.2",
"file-saver": "^2.0.5",
"fund": "^1.0.0",
"gulp": "4.0.2",
"gulp-concat": "2.6.1",
"gulp-load-plugins": "2.0.5",
"gulp-replace": "1.0.0",
"gulp-shell": "0.8.0",
"lodash": "4.17.5",
"node-sass": "^6.0.1",
"npm": "^6.9.0",
"pdfjs-dist": "^5.4.149",
"pizzip": "^3.1.8",
"pubsub-js": "^1.9.4",
"sass-loader": "6.0.6",
"svg-sprite-loader": "3.7.3",
"vkbeautify": "^0.99.3",
"vue": "2.5.16",
@@ -54,7 +54,7 @@
"babel-plugin-transform-runtime": "6.22.0",
"babel-preset-env": "1.3.2",
"babel-preset-stage-2": "6.22.0",
"babel-register": "6.22.0",
"babel-register": "6.22.0",
"chalk": "2.3.0",
"copy-webpack-plugin": "4.0.1",
"cross-spawn": "5.0.1",

View File

@@ -21,7 +21,9 @@ const globalRoutes = [
{ path: '/404', component: _import('common/404'), name: '404', meta: { title: '404未找到' } },
{ path: '/login', component: _import('common/login'), name: 'login', meta: { title: '登录' } },
{ path: '/register', component: _import('modules/sys/register'), name: 'register', meta: { title: '注册' } },
{ path: '/tickets-detail', component: _import('modules/tickets/detail'), name: 'detail-tickets', meta: { title: '工单详情' } }
{ path: '/tickets-detail', component: _import('modules/tickets/detail'), name: 'detail-tickets', meta: { title: '工单详情' } },
{ path: '/tickets-approval', component: _import('common/preview/pdfView'), name: 'approval-tickets', meta: { title: '工单审批详情' } }
// { path: '/tickets-approval', component: _import('modules/tickets/approvalDetail'), name: 'approval-tickets', meta: { title: '工单审批详情' } }
]
// 主入口路由(需嵌套上左右整体布局)

View File

@@ -0,0 +1,66 @@
<template>
<div class="image-modal" v-if="visible" @click.self="close">
<div class="demo-image">
<el-image
style="width: 500px; height: 500px"
:src="imageUrl"
></el-image>
</div>
</div>
</template>
<script>
export default {
name: 'ImageModal',
props: {
visible: Boolean,
imageUrl: String
},
methods: {
close() {
this.$emit('update:visible', false);
}
}
};
</script>
<style scoped>
.image-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
position: relative;
max-width: 90%;
max-height: 90%;
}
.modal-content img {
max-width: 100%;
max-height: 90vh;
object-fit: contain;
}
.close {
position: absolute;
top: -40px;
right: 0;
color: white;
font-size: 35px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #ccc;
}
</style>

View File

@@ -0,0 +1,373 @@
<template>
<div class="pdf-viewer">
<div class="pdf-header">
<div class="pdf-title">PDF 阅读器</div>
<div class="pdf-controls">
<div class="control-group">
<button @click="prevPage" :disabled="currentPage <= 1">上一页</button>
<span> <input type="number" v-model.number="goToPage" @change="goToPageInput" min="1" :max="pageCount" style="width: 60px;"> / {{ pageCount }} </span>
<button @click="nextPage" :disabled="currentPage >= pageCount">下一页</button>
</div>
<div class="control-group">
<label>缩放:</label>
<select v-model="scale" @change="renderPage">
<option value="0.5">50%</option>
<option value="0.75">75%</option>
<option value="1" selected>100%</option>
<option value="1.25">125%</option>
<option value="1.5">150%</option>
<option value="2">200%</option>
</select>
</div>
</div>
</div>
<div class="file-input-container">
<label for="file-input" class="file-label">选择 PDF 文件</label>
<input type="file" id="file-input" accept=".pdf" @change="loadPdf">
</div>
<div class="pdf-container">
<div v-if="loading" class="loading">
<div class="spinner"></div>
<p>正在加载 PDF 文档...</p>
</div>
<div v-else-if="error" class="error">
<p>加载 PDF 时出错: {{ error }}</p>
<button @click="loadDefaultPdf">加载示例 PDF</button>
</div>
<div v-else>
<canvas v-for="page in pages" :key="page.pageNum" :ref="`page-${page.pageNum}`" class="pdf-page"></canvas>
<div class="page-info"> {{ currentPage }} / {{ pageCount }} </div>
</div>
</div>
</div>
</template>
<script>
import pdfjsLib from 'pdfjs-dist'
import pdfjsworker from 'pdfjs-dist/build/pdf.worker.min.mjs'
export default {
name: 'PdfViewer',
data() {
return {
pdfDoc: null,
currentPage: 1,
pageCount: 0,
scale: 1,
loading: false,
error: null,
pages: [],
goToPage: 1
}
},
mounted() {
// 设置 PDF.js worker
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsworker
// 默认加载一个示例PDF
this.loadDefaultPdf()
},
methods: {
async loadDefaultPdf() {
// 使用PDF.js自带的示例PDF
const url = 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf'
this.loadPdfFromUrl(url)
},
loadPdf(event) {
const file = event.target.files[0]
if (!file) return
if (file.type !== 'application/pdf') {
this.error = '请选择PDF文件'
return
}
const fileReader = new FileReader()
fileReader.onload = () => {
const typedArray = new Uint8Array(fileReader.result)
this.loadPdfFromData(typedArray)
}
fileReader.readAsArrayBuffer(file)
},
async loadPdfFromUrl(url) {
this.loading = true
this.error = null
try {
const loadingTask = pdfjsLib.getDocument(url)
this.pdfDoc = await loadingTask.promise
this.pageCount = this.pdfDoc.numPages
this.currentPage = 1
this.goToPage = 1
this.pages = Array.from({ length: this.pageCount }, (_, i) => ({ pageNum: i + 1 }))
// 渲染第一页
this.$nextTick(() => {
this.renderPage()
})
} catch (err) {
this.error = err.message
} finally {
this.loading = false
}
},
async loadPdfFromData(data) {
this.loading = true
this.error = null
try {
const loadingTask = pdfjsLib.getDocument(data)
this.pdfDoc = await loadingTask.promise
this.pageCount = this.pdfDoc.numPages
this.currentPage = 1
this.goToPage = 1
this.pages = Array.from({ length: this.pageCount }, (_, i) => ({ pageNum: i + 1 }))
// 渲染第一页
this.$nextTick(() => {
this.renderPage()
})
} catch (err) {
this.error = err.message
} finally {
this.loading = false
}
},
async renderPage() {
if (!this.pdfDoc) return
try {
const page = await this.pdfDoc.getPage(this.currentPage)
const canvas = this.$refs[`page-${this.currentPage}`][0]
const ctx = canvas.getContext('2d')
const viewport = page.getViewport({ scale: this.scale })
canvas.height = viewport.height
canvas.width = viewport.width
const renderContext = {
canvasContext: ctx,
viewport: viewport
}
await page.render(renderContext).promise
} catch (err) {
console.error('渲染页面时出错:', err)
}
},
prevPage() {
if (this.currentPage <= 1) return
this.currentPage--
this.goToPage = this.currentPage
this.$nextTick(() => {
this.renderPage()
this.scrollToCurrentPage()
})
},
nextPage() {
if (this.currentPage >= this.pageCount) return
this.currentPage++
this.goToPage = this.currentPage
this.$nextTick(() => {
this.renderPage()
this.scrollToCurrentPage()
})
},
goToPageInput() {
if (this.goToPage < 1) this.goToPage = 1
if (this.goToPage > this.pageCount) this.goToPage = this.pageCount
this.currentPage = this.goToPage
this.$nextTick(() => {
this.renderPage()
this.scrollToCurrentPage()
})
},
scrollToCurrentPage() {
const canvas = this.$refs[`page-${this.currentPage}`]
if (canvas && canvas[0]) {
canvas[0].scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
},
watch: {
scale() {
if (this.pdfDoc) {
this.renderPage()
}
}
}
}
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
#app {
width: 100%;
max-width: 900px;
}
.pdf-viewer {
width: 100%;
background: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.pdf-header {
background: #2c3e50;
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.pdf-title {
font-size: 1.4rem;
font-weight: 600;
}
.pdf-controls {
display: flex;
gap: 15px;
align-items: center;
}
.control-group {
display: flex;
align-items: center;
gap: 8px;
}
button {
background: #3498db;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
font-size: 0.9rem;
}
button:hover {
background: #2980b9;
}
button:disabled {
background: #95a5a6;
cursor: not-allowed;
}
select, input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
}
.pdf-container {
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 500px;
max-height: 70vh;
overflow-y: auto;
}
.pdf-page {
margin-bottom: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border: 1px solid #eee;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
color: #7f8c8d;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
padding: 40px;
text-align: center;
color: #e74c3c;
}
.page-info {
margin-top: 10px;
color: #7f8c8d;
font-size: 0.9rem;
}
.file-input-container {
margin: 20px 0;
text-align: center;
}
.file-label {
display: inline-block;
background: #2ecc71;
color: white;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.file-label:hover {
background: #27ae60;
}
#file-input {
display: none;
}
</style>

View File

@@ -118,7 +118,7 @@
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.ticketsId)">修改</el-button>
<el-button type="text" size="small" @click="deleteHandle(scope.row.ticketsId)">删除</el-button>
<el-button v-if="!scope.row.status" type="text" size="small" @click="startFlowHandle(scope.row.ticketsId)">指派</el-button>
<el-button type="text" size="small" @click="$router.push(`/tickets-detail?id=${scope.row.ticketsId}`)">详情</el-button>
<el-button type="text" size="small" @click="$router.push(`/tickets-approval?id=${scope.row.ticketsId}`)">详情2</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -118,7 +118,7 @@
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.ticketsId)">修改</el-button>
<el-button type="text" size="small" @click="deleteHandle(scope.row.ticketsId)">删除</el-button>
<el-button v-if="!scope.row.status" type="text" size="small" @click="startFlowHandle(scope.row.ticketsId)">指派</el-button>
<el-button type="text" size="small" @click="$router.push(`/tickets-detail?id=${scope.row.ticketsId}`)">详情</el-button>
<el-button type="text" size="small" @click="$router.push(`/tickets-detail?id=${scope.row.ticketsId}`)">详情1</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -118,7 +118,7 @@
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.ticketsId)">修改</el-button>
<el-button type="text" size="small" @click="deleteHandle(scope.row.ticketsId)">删除</el-button>
<el-button v-if="!scope.row.status" type="text" size="small" @click="startFlowHandle(scope.row.ticketsId)">指派</el-button>
<el-button type="text" size="small" @click="$router.push(`/tickets-detail?id=${scope.row.ticketsId}`)">详情</el-button>
<el-button type="text" size="small" @click="$router.push(`/tickets-detail?id=${scope.row.ticketsId}`)">详情3</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -0,0 +1,459 @@
<template>
<div class="mod-config">
<div class="dialog_content" id="popupContent">
<button class="close-button" @click="pageClose">
<i class="el-icon-close"></i> 关闭
</button>
<div style="width: 100%;" class="ticket_div">
<div class="section">
<div class="section-title2">工单详情</div>
</div>
<div class="zd-row">
<div class="zd-col-12 item_p">客户{{ this.ticketsData.contractNumber }}</div>
<div class="zd-col-12 item_p">项目编号{{ this.ticketsData.contractCode }}</div>
</div>
<div class="zd-row">
<div class="zd-col-12 item_p">项目编号{{ this.ticketsData.clientPhone }}</div>
<div class="zd-col-12 item_p">售后时间{{ this.ticketsData.ticketsTime }}</div>
</div>
<p class="tip_p">故障描述</p>
<table class="det_table">
<tr>
<th width="10%">车型</th>
<th width="10%">问题属性</th>
<th>问题描述</th>
</tr>
<tr v-for="(e, i) in this.ticketsData.errorDesc" :key="i">
<td>{{ e.carId }}</td>
<td>{{ e.errorType }}</td>
<td>{{ e.description }}</td>
</tr>
</table>
<p class="tip_p">问题处理</p>
<div class="zd-col-12 item_p">问题原因{{ this.ticketsData.clientPhone }}</div>
<div class="zd-col-12 item_p">解决方式{{ this.ticketsData.clientPhone }}</div>
<div class="zd-col-12 item_p">完结时间{{ this.ticketsData.clientPhone }}</div>
<p class="tip_p">流程记录</p>
<el-table
style="width: 100%"
stripe
border
:data="this.ticketsData.actLog">
<el-table-column prop="createTime" header-align="center" width="180" align="center" label="审批时间"/>
<el-table-column prop="actName" header-align="center" width="150" align="center" label="流程节点"/>
<el-table-column prop="description" header-align="center" align="center" label="备注"/>
<el-table-column prop="fileName" header-align="center" width="150" align="center" label="文件">
<template slot-scope="scope">
<el-link type="warning" @click="toView(scope.row.fileUrl)">{{ scope.row.fileName }}</el-link>
</template>
</el-table-column>
</el-table>
</div>
<div style="width: 30%;">
<div class="approval-body">
<!-- 会签给领导 加签 转发 -->
<!-- <div class="section">-->
<!-- <div class="section-title">操作</div>-->
<!-- <div class="button-group">-->
<!-- <button class="btn">会签给领导</button>-->
<!-- <button class="btn">加签</button>-->
<!-- <button class="btn">转发</button>-->
<!-- </div>-->
<!-- </div>-->
<!-- &lt;!&ndash; 签章 &ndash;&gt;-->
<!-- <div class="section">-->
<!-- <div class="section-title">签章</div>-->
<!-- <div class="button-group">-->
<!-- <button class="btn">添加签章</button>-->
<!-- <button class="btn">查看签章</button>-->
<!-- </div>-->
<!-- </div>-->
<!-- 同意/不同意 -->
<div class="section">
<div class="section-title2">工单审批</div>
</div>
<el-form :model="approvalForm">
<el-form-item>
<div class="section">
<div class="section-title">请输入处理意见</div>
<textarea
class="opinion-area"
v-model="approvalForm.opinion"
placeholder="请输入处理意见..."
></textarea>
<div class="common-phrases">
<span class="common-phrase" @click="addPhrase('A')">常用语 A</span>
<span class="common-phrase" @click="addPhrase('同意,请继续推进')">同意请继续推进</span>
<span class="common-phrase" @click="addPhrase('请补充相关材料')">请补充相关材料</span>
<span class="common-phrase" @click="addPhrase('不同意,理由如下')">不同意理由如下</span>
</div>
</div>
</el-form-item>
<el-form-item>
<div class="section">
<div class="section-title">上传文件</div>
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
:auto-upload="false"
:on-change="handleChange"
multiple
:limit="3"
:file-list="approvalForm.fileList">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件且不超过500kb</div>
</el-upload>
</div>
</el-form-item>
</el-form>
<!-- 处理意见 -->
<!-- 意见隐藏/跟踪 -->
<!-- <div class="section">-->
<!-- <div class="hidden-content">-->
<!-- <div class="radio-group">-->
<!-- <label class="radio-label">-->
<!-- <input type="radio" name="track" value="all" v-model="trackOption"> 全部-->
<!-- </label>-->
<!-- <label class="radio-label">-->
<!-- <input type="radio" name="track" value="specific" v-model="trackOption"> 指定人-->
<!-- </label>-->
<!-- </div>-->
<!-- <div v-if="trackOption === 'specific'" class="checkbox-group">-->
<!-- <input type="text" placeholder="请输入指定人姓名" v-model="specificPerson">-->
<!-- </div>-->
<!-- <div class="checkbox-group">-->
<!-- <input type="checkbox" id="archive" v-model="archiveAfterProcess">-->
<!-- <label for="archive">处理后归档</label>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- 上传文件 -->
<!-- 操作按钮 -->
<div class="action-buttons">
<button class="action-btn submit-btn" >同意</button>
<button class="action-btn temp-btn" >不同意</button>
<button class="action-btn draft-btn" >驳回</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
approvalForm: {
opinion: '',
fileList: []
},
column: 0,
ticketsData: {},
statusOpt: [{value: '0', label: '未开始'}, {value: '1', label: '已指派'}, {value: '2', label: '处理中'}, {value: '3', label: '已完成'}]
}
},
created () {
if (this.isMobile() || window.innerWidth < 768) {
this.column = 2
} else {
this.column = 4
}
this.getDataList()
},
methods: {
handleRemove (file, fileList) {
},
handleChange (event, file, fileList) {
this.approvalForm.fileList = fileList
},
pageClose () {
this.ticketsData = {}
this.approvalForm = {
opinion: '',
fileList: []
}
window.history.back()
},
isMobile () {
const userAgentInfo = navigator.userAgent;
const mobileAgents = [
"Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"
];
let isMobile = false;
for (let i = 0; i < mobileAgents.length; i++) {
if (userAgentInfo.indexOf(mobileAgents[i]) > -1) {
isMobile = true;
break;
}
}
return isMobile;
},
// 获取数据列表
getDataList () {
this.$http({
url: this.$http.adornUrl(`/tickets/tickets/info/${this.$route.query.id}`),
method: 'get',
params: this.$http.adornParams({})
}).then(res => {
console.log(res.data.tickets)
if (res.data && res.data.code === 0) {
this.ticketsData = res.data.tickets
var errorDesc = []
errorDesc.push({
'carId': res.data.tickets.carId ,
'errorType': res.data.tickets.errorType,
'description': res.data.tickets.description
})
this.ticketsData.errorDesc = errorDesc
}
})
},
toView (vehicle) {
// this.showModal = true
// crudStructivt.imageLoad('110').then(response => {
// const blob = new Blob([response]) // 尽可能根据响应头确定类型
// this.imageUrl = URL.createObjectURL(blob)
// }
// )
},
// 每页数
sizeChangeHandle (val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
// 当前页
currentChangeHandle (val) {
this.pageIndex = val
this.getDataList()
},
addPhrase (phrase) {
this.approvalForm.opinion = phrase
},
doOperate () {
this.$confirm(`确定进行审批操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl('/flw/instance/completeFlow'),
method: 'post',
data: this.$http.adornData({ticketsId: this.ticketsData.ticketsId, processInstance: this.ticketsData.processInstance})
}).then(({data}) => {
if (data && data.code === 0) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.$router.push('/tickets-tickets')
}
})
} else {
this.$message.error(data.msg)
}
})
}).catch(() => {})
}
}
}
</script>
<style scoped>
.mod-config {
padding: 30px 30px;
}
.zd-row {
width: 100%;
display: flex;
}
.zd-col-12 {
width: 50%;
}
.dialog_content {
display: flex;
justify-content: space-between;
width: 100%;
}
.item_p {
font-size: 14px;
color: #606266;
line-height: 1.5;
padding-bottom: 12px;
}
.tip_p {
font-size: 14px;
font-weight: 700;
color: #606266;
line-height: 1.5;
margin: 0;
}
.det_table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
border: 1px solid #EBEEF5;
margin-top: 10px;
}
.det_table th {
font-size: 13px;
line-height: 23px;
color: #515a6e;
padding: 6px 6px;
font-weight: 700;
border: 1px solid #EBEEF5;
text-align: center;
background-color: #f5f5f5;
}
.det_table td {
font-size: 12px;
line-height: 23px;
color: #606266;
padding: 6px 6px;
border: 1px solid #EBEEF5;
text-align: center;
}
.det_table_1 th, .det_table_1 td {
text-align: left;
}
.approval-body {
padding: 20px;
}
.ticket_div {
padding: 20px;
padding-left: 5%;
padding-right: 5%;
border: 1px solid #dcdfe6;
border-radius: 4px;
/*background-color: #d6d8d9;*/
}
.section {
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.section-title {
font-size: 16px;
margin-bottom: 10px;
color: #606266;
font-weight: bold;
}
.section-title2 {
font-size: 22px;
margin-bottom: 10px;
color: #606266;
font-weight: bold;
text-align: center;
}
.opinion-area {
width: 100%;
min-height: 100px;
padding: 10px;
border: 1px solid #dcdfe6;
border-radius: 4px;
resize: vertical;
margin-bottom: 10px;
}
.common-phrases {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 15px;
}
.common-phrase {
padding: 5px 10px;
background: #f5f7fa;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.common-phrase:hover {
background: #e4e7ed;
}
.toggle-section span {
font-size: 14px;
color: #909399;
}
.radio-label input {
margin-right: 5px;
}
.action-buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.action-btn {
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.submit-btn {
background: #409EFF;
color: white;
border: none;
}
.draft-btn {
background: #E6A23C;
color: white;
border: none;
}
.temp-btn {
background: #909399;
color: white;
border: none;
}
.checkbox-group {
display: flex;
align-items: center;
margin-top: 10px;
}
.checkbox-group input {
margin-right: 5px;
}
/* 关闭按钮样式 */
.close-button {
position: absolute;
top: 20px;
right: 20px;
z-index: 1000;
background: #f56c6c;
color: white;
border: none;
border-radius: 4px;
padding: 8px 15px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.close-button:hover {
background: #f78989;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.close-button:active {
transform: translateY(0);
}
</style>

View File

@@ -0,0 +1,219 @@
<template>
<el-dialog
title="售后台账"
:close-on-click-modal="false"
:visible.sync="visible">
<div class="dialog_content" id="popupContent">
<div style="width: 100%;">
<div class="zd-row">
<div class="zd-col-12 item_p">客户{{ dictData[1] | findByValue(dataForm.clientPeople) }}</div>
<div class="zd-col-12 item_p">项目编号{{ dataForm.contractCode }}</div>
</div>
<div class="zd-row">
<div class="zd-col-12 item_p">项目编号{{ dataForm.clientPhone }}</div>
<div class="zd-col-12 item_p">售后时间{{ dataForm.ticketsTime }}</div>
</div>
<p class="tip_p">故障描述</p>
<table class="det_table">
<tr>
<th width="6.25%">车型</th>
<th width="25%">问题属性</th>
<th width="25%">问题描述</th>
</tr>
<tr v-for="(e, i) in dataForm.materData" :key="i">
<td>{{ e.carType }}</td>
<td>{{ e.errorType }}</td>
<td>{{ e.description }}</td>
</tr>
</table>
<p class="tip_p">问题处理</p>
<div class="zd-col-12 item_p">问题原因{{ dataForm.clientPhone }}</div>
<div class="zd-col-12 item_p">解决方式{{ dataForm.clientPhone }}</div>
<div class="zd-col-12 item_p">完结时间{{ dataForm.clientPhone }}</div>
<p class="tip_p">流程记录</p>
<table class="det_table">
<tr>
<th width="6.25%">审批时间</th>
<th width="25%">流程内容</th>
<th width="25%">备注</th>
</tr>
<tr v-for="(e, i) in dataForm.materData" :key="i">
<td>{{ e.carType }}</td>
<td>{{ e.errorType }}</td>
<td>{{ e.description }}</td>
</tr>
</table>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="mini" @click="visible = false">取消</el-button>
<el-button size="mini" type="primary" @click="exportToWord">导出为 Word 文档</el-button>
</span>
</el-dialog>
</template>
<script>
import Docxtemplater from 'docxtemplater'
import PizZip from 'pizzip'
import PizZipUtils from 'pizzip/utils/index.js'
import { saveAs } from 'file-saver'
export default {
data () {
return {
visible: false,
dataForm: {
contractId: 0,
clientId: null,
contractNumber: null,
materData: [],
totalSum: null,
totalSumChina: null
},
dataList: []
}
},
props: {
dictData: Array
},
methods: {
init (id) {
this.dataForm.contractId = id || 0
this.visible = true
this.$nextTick(() => {
if (this.dataForm.contractId) {
this.$http({
url: this.$http.adornUrl(`/flow/contract/info/${this.dataForm.contractId}`),
method: 'get',
params: this.$http.adornParams()
}).then(({data}) => {
if (data && data.code === 0) {
this.dataForm.contractNumber = data.contract.contractNumber
this.dataForm.clientId = data.contract.clientId
this.dataForm.materData = JSON.parse(data.contract.materialJson).material
this.dataForm.totalSum = JSON.parse(data.contract.materialJson).total
this.dataForm.totalSumChina = this.toChineseCurrency(this.dataForm.totalSum)
}
})
}
})
},
toChineseCurrency (amount) {
if (amount === 0) return '零元整'
const units = ['', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿']
const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
const result = []
// 将金额转换为整数部分和小数部分
const integerPart = Math.floor(amount) // 整数部分
const decimalPart = amount - integerPart // 小数部分
// 处理整数部分
let integerStr = integerPart.toString()
for (let i = 0; i < integerStr.length; i++) {
const digit = integerStr[integerStr.length - 1 - i];
if (digit !== '0') {
result.push(digits[digit], units[i])
} else if (!result.includes('零') && result.length > 0) {
result.push('零')
}
}
// 处理小数部分
if (decimalPart > 0) {
const decimalStr = (decimalPart * 100).toFixed(0).padStart(2, '0') // 小数部分乘以100并转为字符串
if (decimalStr !== '00') {
result.push('点')
for (let i = 0; i < decimalStr.length; i++) {
result.push(digits[decimalStr[i]])
}
}
}
// 去掉多余的零
while (result[0] === '零') {
result.shift()
}
return result.join('') + (decimalPart > 0 ? '元' : '元整')
},
exportToWord () {
const data = {
clientId: this.dataForm.clientId,
contractNumber: this.dataForm.contractNumber
}
const templatePath = './static/word/template.docx'
PizZipUtils.getBinaryContent(templatePath, (error, content) =>{
if (error) {
throw error
}
const zip = new PizZip(content)
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true
})
doc.setData(data)
try {
doc.render()
} catch (error) {
console.error('模板渲染错误:', error)
}
const out = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
})
saveAs(out, `合同${new Date().getTime()}.docx`)
})
}
}
}
</script>
<style scoped>
.zd-row {
width: 100%;
display: flex;
}
.zd-col-12 {
width: 50%;
}
.dialog_content {
width: 100%;
}
.item_p {
font-size: 14px;
color: #606266;
line-height: 1.5;
padding-bottom: 12px;
}
.tip_p {
font-size: 14px;
font-weight: 700;
color: #606266;
line-height: 1.5;
margin: 0;
}
.det_table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
border: 1px solid #EBEEF5;
margin-top: 10px;
}
.det_table th {
font-size: 13px;
line-height: 23px;
color: #515a6e;
padding: 6px 6px;
font-weight: 700;
border: 1px solid #EBEEF5;
text-align: center;
background-color: #f5f5f5;
}
.det_table td {
font-size: 12px;
line-height: 23px;
color: #606266;
padding: 6px 6px;
border: 1px solid #EBEEF5;
text-align: center;
}
.det_table_1 th, .det_table_1 td {
text-align: left;
}
</style>

View File

@@ -5,8 +5,21 @@
:visible.sync="visible"
width="500px">
<el-form :model="dataForm" :rules="dataRule" label-width="100px" size="mini" ref="dataForm" @keyup.enter.native="dataFormSubmit()">
<el-form-item label="小车类型" prop="carType">
<el-select v-model="dataForm.carType" placeholder="小车类型">
<el-form-item label="项目编号" prop="contractCode">
<el-input v-model="dataForm.contractCode" placeholder="项目编号"></el-input>
</el-form-item>
<el-form-item label="客户" prop="clientId">
<el-select v-model="dataForm.clientId" placeholder="客户">
<el-option
v-for="item in dictData[3]"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="车型" prop="carType">
<el-select v-model="dataForm.carType" placeholder="车型">
<el-option
v-for="item in dictData[0]"
:key="item.value"
@@ -15,8 +28,8 @@
</el-option>
</el-select>
</el-form-item>
<el-form-item label="异常类型" prop="errorType">
<el-select v-model="dataForm.errorType" placeholder="异常类型">
<el-form-item label="问题属性" prop="errorType">
<el-select v-model="dataForm.errorType" placeholder="问题属性">
<el-option
v-for="item in dictData[1]"
:key="item.value"
@@ -35,16 +48,7 @@
</el-option>
</el-select>
</el-form-item>
<el-form-item label="客户" prop="clientId">
<el-select v-model="dataForm.clientId" placeholder="客户">
<el-option
v-for="item in dictData[3]"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="故障描述" prop="description">
<el-input type="textarea" :rows="4" placeholder="故障描述" v-model="dataForm.description"></el-input>
</el-form-item>
@@ -68,7 +72,7 @@
ticketsId: null,
carType: '',
errorType: '',
contractId: '',
contractCode: '',
clientId: '',
description: '',
deptPhone: ''

View File

@@ -52,7 +52,24 @@
<el-table-column
header-align="center"
align="center"
label="小车类型">
label="项目号">
<template slot-scope="scope">
{{ dictData[2] | findByValue(scope.row.contractCode) }}
</template>
</el-table-column>
<el-table-column
prop="status"
header-align="center"
align="center"
label="工单状态">
<template slot-scope="scope">
{{ statusOpt | findByValue(scope.row.status) }}
</template>
</el-table-column>
<el-table-column
header-align="center"
align="center"
label="车型">
<template slot-scope="scope">
{{ dictData[0] | findByValue(scope.row.carType) }}
</template>
@@ -60,19 +77,12 @@
<el-table-column
header-align="center"
align="center"
label="异常类型">
label="问题属性">
<template slot-scope="scope">
{{ dictData[1] | findByValue(scope.row.errorType) }}
</template>
</el-table-column>
<el-table-column
header-align="center"
align="center"
label="合同编号">
<template slot-scope="scope">
{{ dictData[2] | findByValue(scope.row.contractId) }}
</template>
</el-table-column>
<el-table-column
header-align="center"
align="center"
@@ -81,6 +91,13 @@
{{ dictData[3] | findByValue(scope.row.clientId) }}
</template>
</el-table-column>
<el-table-column
prop="clientPhone"
header-align="center"
align="center"
min-width="90px"
label="客户电话">
</el-table-column>
<el-table-column
prop="description"
header-align="center"
@@ -88,18 +105,18 @@
label="故障描述">
</el-table-column>
<el-table-column
prop="deptPeople"
prop="ticketContract "
header-align="center"
min-width="120px"
align="center"
label="售后合同编号">
</el-table-column>
<el-table-column
prop="assignPeople"
header-align="center"
align="center"
min-width="90px"
label="工单对接人">
</el-table-column>
<el-table-column
prop="deptPhone"
header-align="center"
align="center"
min-width="100px"
label="客户联系电话">
label="当前审批人">
</el-table-column>
<el-table-column
prop="createUser"
@@ -123,21 +140,19 @@
{{ ['否', '是'][Number(scope.row.isCheck)] }}
</template>
</el-table-column> -->
<el-table-column
prop="status"
header-align="center"
align="center"
label="工单状态">
<template slot-scope="scope">
{{ statusOpt | findByValue(scope.row.status) }}
</template>
</el-table-column>
<el-table-column
prop="updateTime"
header-align="center"
align="center"
min-width="100px"
label="工单审批完成时间">
label="更新时间">
</el-table-column>
<el-table-column
prop="finishTime"
header-align="center"
align="center"
min-width="100px"
label="完结时间">
</el-table-column>
<el-table-column
fixed="right"
@@ -149,7 +164,8 @@
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.ticketsId)">修改</el-button>
<el-button type="text" size="small" @click="deleteHandle(scope.row.ticketsId)">删除</el-button>
<el-button v-if="!scope.row.status" type="text" size="small" @click="startFlowHandle(scope.row.ticketsId)">指派</el-button>
<el-button type="text" size="small" @click="$router.push(`/tickets-detail?id=${scope.row.ticketsId}`)">详情</el-button>
<el-button type="text" size="small" @click="addTempHandle(scope.row.ticketsId)">详情2</el-button>
<el-button type="text" size="small" @click="showFlowImgHandle(scope.row.id)">查看流程图</el-button>
</template>
</el-table-column>
</el-table>
@@ -164,6 +180,8 @@
</el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" :dictData="dictData" @refreshDataList="getDataList"></add-or-update>
<temp v-if="tempVisible" ref="tempAdd" :dictData="dictData"></temp>
<el-dialog
title="发起流程"
:visible.sync="dialogFormVisible"
@@ -212,6 +230,8 @@
<script>
import AddOrUpdate from './tickets-add-or-update'
import { apiUtils } from '@/utils/dict'
// eslint-disable-next-line no-unused-vars
import temp from './temp'
export default {
data () {
return {
@@ -221,6 +241,7 @@
status: ''
},
dataList: [],
tempVisible: false,
pageIndex: 1,
pageSize: 10,
totalPage: 0,
@@ -230,8 +251,8 @@
dictConfigs: [{url: '/car/car/list', type: 'list', value: 'carId', label: 'carName'}, {type: 'dict', code: 'error_type'}, {url: '/flow/contract/list', type: 'list', value: 'contractId', label: 'contractNumber'}, {url: '/client/client/list', type: 'list', value: 'clientId', label: 'clientName'}],
dictData: [],
dialogFormVisible: false,
flowForm:{ticketsId: null},
dynamiForm:[],
flowForm: {ticketsId: null},
dynamiForm: [],
users: [],
roles: [],
statusOpt: [{value: '0', label: '未开始'}, {value: '1', label: '已指派'}, {value: '2', label: '处理中'}, {value: '3', label: '已完成'}]
@@ -239,7 +260,8 @@
},
mixins: [apiUtils],
components: {
AddOrUpdate
AddOrUpdate,
temp
},
activated () {
this.getDataList()
@@ -256,7 +278,7 @@
'limit': this.pageSize,
'deptPeople': this.dataForm.deptPeople,
'errorType': this.dataForm.errorType,
'status': this.dataForm.status,
'status': this.dataForm.status
})
}).then(({data}) => {
if (data && data.code === 0) {
@@ -269,6 +291,17 @@
this.dataListLoading = false
})
},
showFlowImgHandle (id) {
this.flowImg = this.$imgBasePath + 'downloadFlowImg?defId=' + id
this.dialogVisible = true
},
// 生成模板
addTempHandle (id) {
this.tempVisible = true
this.$nextTick(() => {
this.$refs.tempAdd.init(id)
})
},
// 每页数
sizeChangeHandle (val) {
this.pageSize = val
@@ -321,7 +354,7 @@
})
}).catch(() => {})
},
startFlowHandle(id){
startFlowHandle (id) {
this.flowForm = {}
this.flowForm.ticketsId = id
this.$http({
@@ -337,7 +370,7 @@
}
})
},
submitStartFlow(){
submitStartFlow () {
// 提交表单数据
this.$http({
url: this.$http.adornUrl(`/flw/instance/startFlowInstance`),