Merge branch 'develop' of https://e.coding.net/ctcdevteam/hberp/hberp into develop
This commit is contained in:
commit
9592cb5fbc
|
@ -162,6 +162,14 @@ export function ticketAccpet(id,data) {
|
|||
data
|
||||
})
|
||||
}
|
||||
//撤回工单,允许创建人在指定状态撤回工单至初始状态
|
||||
export function getTicketRetreat(id,data) {
|
||||
return request({
|
||||
url: `/wf/ticket/${id}/retreat/`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
//工单详情
|
||||
export function getTicketDetail(id) {
|
||||
return request({
|
||||
|
@ -169,7 +177,8 @@ export function getTicketDetail(id) {
|
|||
method: 'get'
|
||||
})
|
||||
}
|
||||
//工单详情
|
||||
|
||||
//工单流转
|
||||
export function getTicketTransitions(id) {
|
||||
return request({
|
||||
url: `/wf/ticket/${id}/transitions/`,
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<div style="width: 100%;height: 100%;">
|
||||
<div class="testTracking">
|
||||
<canvas style="display:none;" id="myCanvas" ref="canvas" :width="videoWidth" :height="videoHeight"></canvas>
|
||||
<!--图片展示-->
|
||||
<video ref="video" id="myVideo" :width="videoWidth" :height="videoHeight" autoplay style="display: block;margin:0 auto;border: 2px solid #333333;"></video>
|
||||
<!--确认-->
|
||||
<Button type="primary" @click="setImage" class="takePhoto">拍照</Button>
|
||||
<div id="res"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {faceLogin} from "@/api/testModel";
|
||||
export default {
|
||||
props:['src'],
|
||||
data () {
|
||||
return {
|
||||
videoWidth: 500,
|
||||
videoHeight: 400,
|
||||
videoArr:[],//所有的摄像头
|
||||
modelSel:'',//
|
||||
myInterval: null,
|
||||
imgSrc: '',
|
||||
isHasFace:false,//默认没有人脸
|
||||
tracker:null,
|
||||
}
|
||||
},
|
||||
created(){
|
||||
},
|
||||
mounted(){
|
||||
this.callCamera();
|
||||
this.changePhoto();
|
||||
},
|
||||
methods: {
|
||||
// 调用摄像头
|
||||
callCamera () {
|
||||
// H5调用电脑摄像头API
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
video: true
|
||||
}).then(success => {
|
||||
// 摄像头开启成功
|
||||
this.$refs['video'].srcObject = success;
|
||||
// 实时拍照效果
|
||||
this.$refs['video'].play()
|
||||
}).catch(error => {
|
||||
alert('摄像头开启失败,请检查摄像头是否可用!')
|
||||
})
|
||||
},
|
||||
// 拍照
|
||||
setImage () {
|
||||
let canvas = document.getElementById("myCanvas");
|
||||
let context = canvas.getContext('2d');
|
||||
let video = document.getElementById("myVideo");
|
||||
context.drawImage(video,0,0,90,68);
|
||||
let image = new Image();
|
||||
image = canvas.toDataURL('image/png');
|
||||
document.getElementById('res').innerHTML = '<img src="'+image+'">';
|
||||
let imgData = {base64:image};
|
||||
faceLogin(imgData).then((res) => {
|
||||
if (res.code >= 200) {
|
||||
this.$message.success("成功");
|
||||
}
|
||||
});
|
||||
},
|
||||
// 关闭摄像头
|
||||
closeCamera () {
|
||||
if (!this.$refs['video'].srcObject) return;
|
||||
let stream = this.$refs['video'].srcObject;
|
||||
let tracks = stream.getTracks();
|
||||
tracks.forEach(track => {
|
||||
track.stop()
|
||||
});
|
||||
this.$refs['video'].srcObject = null
|
||||
},
|
||||
|
||||
|
||||
//切换本地摄像头
|
||||
changePhoto(){
|
||||
/**得到所有的设备*/
|
||||
navigator.mediaDevices.enumerateDevices()
|
||||
.then((devices)=> {
|
||||
this.videoArr = [];
|
||||
devices.forEach((item)=> {
|
||||
if(item.kind == 'videoinput'){
|
||||
this.videoArr.push({
|
||||
'label': item.kind,
|
||||
'id': item.deviceId
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err=>{
|
||||
});
|
||||
},
|
||||
/**打开摄像头*/
|
||||
getUserMedia(constraints, success, error) {
|
||||
if (navigator.mediaDevices.getUserMedia) {
|
||||
//最新的标准API
|
||||
navigator.mediaDevices.getUserMedia(constraints).then(success=>{
|
||||
// 摄像头开启成功
|
||||
this.$refs['video'].srcObject = success;
|
||||
// 实时拍照效果
|
||||
this.$refs['video'].play()
|
||||
}).catch(error);
|
||||
|
||||
} else if (navigator.webkitGetUserMedia) {
|
||||
//webkit核心浏览器
|
||||
navigator.webkitGetUserMedia(constraints,success, error)
|
||||
} else if (navigator.mozGetUserMedia) {
|
||||
//firfox浏览器
|
||||
navigator.mozGetUserMedia(constraints, success, error);
|
||||
} else if (navigator.getUserMedia) {
|
||||
//旧版API
|
||||
navigator.getUserMedia(constraints, success, error);
|
||||
}
|
||||
},
|
||||
setFace(data){
|
||||
this.isHasFace = data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.testTracking {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.takePhoto{
|
||||
margin: 30px auto;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -53,7 +53,7 @@ const actions = {
|
|||
return new Promise((resolve, reject) => {
|
||||
getInfo(state.token).then(response => {
|
||||
const { data } = response
|
||||
|
||||
sessionStorage.setItem('userId',data.id);
|
||||
if (!data) {
|
||||
reject('验证失败,重新登陆.')
|
||||
}
|
||||
|
|
|
@ -1,273 +1,273 @@
|
|||
|
||||
<template>
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<div class="box-card">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="always">
|
||||
<div class="box-card">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="always">
|
||||
|
||||
<div slot="header" class="clearfix">
|
||||
<span>CPU</span>
|
||||
</div>
|
||||
<el-row class="row">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>CPU</span>
|
||||
</div>
|
||||
<el-row class="row">
|
||||
<el-col :span="8">
|
||||
属性
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
值
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
逻辑核心数
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{cpuData.count}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
物理核心数
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{cpuData.lcount}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
当前使用率
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{cpuData.percent}}%
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
属性
|
||||
<el-card shadow="always">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>内存</span>
|
||||
</div>
|
||||
<el-row class="row">
|
||||
<el-col :span="8">
|
||||
属性
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
值
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
使用率
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{memoryData.percent}}%
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
总内存
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{memoryData.total}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
已用内存
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{memoryData.used}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
值
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
逻辑核心数
|
||||
<el-card shadow="always">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>硬盘</span>
|
||||
</div>
|
||||
<el-row class="row">
|
||||
<el-col :span="8">
|
||||
属性
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
值
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
已用百分比
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{diskData.percent}}%
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
总大小
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{diskData.total}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
已用大小
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{diskData.used}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{cpuData.count}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
物理核心数
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{cpuData.lcount}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
当前使用率
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{cpuData.percent}}%
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="always">
|
||||
|
||||
<div slot="header" class="clearfix">
|
||||
<span>内存</span>
|
||||
</div>
|
||||
<el-row class="row">
|
||||
<el-col :span="8">
|
||||
属性
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
值
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
使用率
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{memoryData.percent}}%
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
总内存
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{memoryData.total}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
已用内存
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{memoryData.used}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="always">
|
||||
|
||||
<div slot="header" class="clearfix">
|
||||
<span>硬盘</span>
|
||||
</div>
|
||||
<el-row class="row">
|
||||
<el-col :span="8">
|
||||
属性
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
值
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
已用百分比
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{diskData.percent}}%
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
总大小
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{diskData.total}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="rowlist">
|
||||
<el-col :span="8">
|
||||
已用大小
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{diskData.used}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</div>
|
||||
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>日志列表</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-input
|
||||
v-model="listQuery.name"
|
||||
placeholder="日志名称"
|
||||
style="width: 300px"
|
||||
class="filter-item"
|
||||
@keyup.enter.native="handleFilter"
|
||||
/>
|
||||
<el-button
|
||||
class="filter-item"
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
@click="handleFilter"
|
||||
>搜索</el-button
|
||||
>
|
||||
<el-button
|
||||
class="filter-item"
|
||||
type="primary"
|
||||
icon="el-icon-refresh-left"
|
||||
@click="resetFilter"
|
||||
>重置</el-button
|
||||
>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%;height:400px">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="日志名称"
|
||||
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>日志列表</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-input
|
||||
v-model="listQuery.name"
|
||||
placeholder="日志名称"
|
||||
style="width: 300px"
|
||||
class="filter-item"
|
||||
@keyup.enter.native="handleFilter"
|
||||
/>
|
||||
<el-button
|
||||
class="filter-item"
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
@click="handleFilter"
|
||||
>搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
class="filter-item"
|
||||
type="primary"
|
||||
icon="el-icon-refresh-left"
|
||||
@click="resetFilter"
|
||||
>重置
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%;height:400px"
|
||||
>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="日志名称"
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="size"
|
||||
label="日志大小"
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="size"
|
||||
label="日志大小"
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
label="操作"
|
||||
width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-button @click="handleClick(scope.row)" type="text" size="small">查看详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-dialog
|
||||
|
||||
:visible.sync="dialogVisible"
|
||||
width="80%"
|
||||
height:300px
|
||||
>
|
||||
|
||||
|
||||
<div v-html="logdec"> </div>
|
||||
|
||||
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
label="操作"
|
||||
width="100"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button @click="handleClick(scope.row)" type="text" size="small">查看详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
width="80%"
|
||||
title="日志详情"
|
||||
>
|
||||
<div v-html="logdec" class="dialogDiv"></div>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import {getlogList,getLog,getServerList} from "@/api/moritor";
|
||||
const defaultCMA = {
|
||||
import {getlogList, getLog, getServerList} from "@/api/moritor";
|
||||
|
||||
}
|
||||
export default {
|
||||
components: { },
|
||||
data() {
|
||||
return {
|
||||
tableData:[],
|
||||
cpuData:[],
|
||||
diskData:[],
|
||||
memoryData:[],
|
||||
dialogVisible: false,
|
||||
logdec:"",
|
||||
text:"",
|
||||
listQuery: {},
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
watch: {},
|
||||
created() {
|
||||
this.getlogList();
|
||||
this.getServerList();
|
||||
},
|
||||
methods: {
|
||||
|
||||
getlogList() {
|
||||
getlogList(this.listQuery).then((response) => {
|
||||
if(response.data) {
|
||||
|
||||
this.tableData=response.data;
|
||||
}
|
||||
|
||||
});
|
||||
const defaultCMA = {}
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
cpuData: [],
|
||||
diskData: [],
|
||||
memoryData: [],
|
||||
dialogVisible: false,
|
||||
logdec: "",
|
||||
text: "",
|
||||
listQuery: {},
|
||||
};
|
||||
},
|
||||
handleFilter() {
|
||||
computed: {},
|
||||
watch: {},
|
||||
created() {
|
||||
this.getlogList();
|
||||
this.getServerList();
|
||||
},
|
||||
resetFilter() {
|
||||
methods: {
|
||||
|
||||
this.getlogList();
|
||||
getlogList() {
|
||||
getlogList(this.listQuery).then((response) => {
|
||||
if (response.data) {
|
||||
|
||||
this.tableData = response.data;
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
handleFilter() {
|
||||
this.getlogList();
|
||||
},
|
||||
resetFilter() {
|
||||
|
||||
this.getlogList();
|
||||
},
|
||||
getServerList() {
|
||||
getServerList().then((response) => {
|
||||
if (response.data) {
|
||||
this.cpuData = response.data.cpu;
|
||||
this.diskData = response.data.disk;
|
||||
this.memoryData = response.data.memory;
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
handleClick(row) {
|
||||
this.dialogVisible = true;
|
||||
getLog(row.name).then((response) => {
|
||||
if (response.data) {
|
||||
this.logdec = response.data.replace(/\n/gm, "<br/>")
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
getServerList() {
|
||||
getServerList().then((response) => {
|
||||
if (response.data) {
|
||||
this.cpuData = response.data.cpu;
|
||||
this.diskData = response.data.disk;
|
||||
this.memoryData = response.data.memory;
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
handleClick(row){
|
||||
this.dialogVisible = true;
|
||||
getLog(row.name).then((response) => {
|
||||
if (response.data) {
|
||||
this.logdec=response.data.replace(/\n/gm,"<br/>")
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-track{
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.2);
|
||||
background-color: #fefefe;
|
||||
border-radius: 7px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb{
|
||||
border-radius: 7px;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.2);
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.dialogDiv{
|
||||
height: 70vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -281,17 +281,28 @@ export default {
|
|||
display: table;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
clear: both
|
||||
}
|
||||
|
||||
.box-card {
|
||||
width: 90%;
|
||||
margin:0 auto;
|
||||
margin-top:30px;
|
||||
margin: 0 auto;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.row{border-bottom: 1px solid #dfe6ec;color: #909399;font-weight: 500;padding: 10px;}
|
||||
.rowlist{
|
||||
border-bottom: 1px solid #dfe6ec;color:#606266;font-weight: 500;padding: 10px;
|
||||
|
||||
.row {
|
||||
border-bottom: 1px solid #dfe6ec;
|
||||
color: #909399;
|
||||
font-weight: 500;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.rowlist {
|
||||
border-bottom: 1px solid #dfe6ec;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -17,13 +17,22 @@
|
|||
<span>保存的图片</span>
|
||||
<div id="res"></div>
|
||||
</div>
|
||||
|
||||
<button @click="takePhoto()">拍照</button>
|
||||
<el-dialog :visible.sync="limitedPhoto" title="人脸登录">
|
||||
<div class="testTracking">
|
||||
<faceLogin name="faceLogin"></faceLogin>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import faceLogin from '@/views/testModel/faceLogin'
|
||||
let preDrawAry = [];
|
||||
let img = new Image();
|
||||
// let img = new Image();
|
||||
export default {
|
||||
components:{
|
||||
faceLogin
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
canvas:null,
|
||||
|
@ -36,6 +45,7 @@
|
|||
lineW:3,
|
||||
colorF:"#e42343",
|
||||
imgData:'',
|
||||
limitedPhoto:false,
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
|
@ -52,25 +62,26 @@
|
|||
setTimeout(function(){
|
||||
_this.draw();
|
||||
},1000)
|
||||
/*
|
||||
this.ctx .clearRect(0, 0, 700, 500);
|
||||
let imgs = new Image();
|
||||
imgs.src ="./../../assets/404_images/404.png";
|
||||
setTimeout(function(){
|
||||
this.draw();
|
||||
this.ctx.drawImage(imgs,0,0,700,500);
|
||||
},1000)*/
|
||||
/* imgs.onload=function(){
|
||||
this.ctx.drawImage(imgs,0,0,700,500);
|
||||
this.ctx.closePath();
|
||||
|
||||
let pattern=this.ctx.createPattern(imgs,"no-repeat")//不加;号
|
||||
this.ctx.fillStyle=pattern;
|
||||
this.ctx.fillRect(0,0,700,500);
|
||||
}*/
|
||||
|
||||
},
|
||||
methods: {
|
||||
takePhoto(){
|
||||
this.limitedPhoto = true;
|
||||
},
|
||||
takeClick(){
|
||||
let canvas = document.getElementById("myCanvas");
|
||||
let context = canvas.getContext('2d');
|
||||
let video = document.getElementById("myVideo");
|
||||
context.drawImage(video,0,0,90,68);
|
||||
let image = new Image();
|
||||
image = canvas.toDataURL('image/png');
|
||||
document.getElementById('res').innerHTML = '<img src="'+image+'">';
|
||||
let imgData = {base64:image};
|
||||
faceLogin(imgData).then((res) => {
|
||||
if (res.code >= 200) {
|
||||
this.$message.success("成功");
|
||||
}
|
||||
});
|
||||
},
|
||||
draw(){
|
||||
debugger;
|
||||
let canvasImg = document.getElementById("canvasImg");
|
||||
|
@ -238,24 +249,6 @@
|
|||
var image = new Image();
|
||||
image = canvas.toDataURL('image/png');
|
||||
document.getElementById('res').innerHTML = '<img style="border: 1px solid #666666;" src="'+image+'">';
|
||||
console.log(image);
|
||||
debugger;
|
||||
/*img.setAttribute('crossOrigin', 'Anonymous') ;// 解决某些图片跨域(有些图片仍不可使用)
|
||||
img.src = 'url';
|
||||
img.onload = (imgs) => {
|
||||
let base64 = this.setBase64(imgs);
|
||||
var arr = base64.split(',');
|
||||
var mime = arr[0].match(/:(.*?);/)[1] ;// 获取图片的类型 (image/jpg)
|
||||
var bstr = atob(arr[1]); // 将base64转码
|
||||
var n = bstr.length ;// 获得转码长度
|
||||
var u8arr = new Uint8Array(n); // 获得length个为0的数组
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n) // 获得unicode码
|
||||
}
|
||||
this.file = new File([u8arr], 'faceImg', { type: mime }); // 生成文件
|
||||
console.log(this.file);
|
||||
this.url = base64
|
||||
}*/
|
||||
},
|
||||
setBase64(img) {
|
||||
let ctx = document.createElement('canvas');
|
||||
|
|
|
@ -61,7 +61,9 @@
|
|||
<el-table-column align="center" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-link v-if="scope.row.state_.distribute_type==1&&scope.row.participant_type==2" type="danger" @click="handleGetTicket(scope)">接单</el-link>
|
||||
<el-link v-else-if="scope.row.act_state==1&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
|
||||
<el-link v-else-if="(scope.row.act_state==1||scope.row.act_state==3)&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
|
||||
<!--如果state_.retreat为可退回,则显示撤回按钮-->
|
||||
<el-link v-if="scope.row.state_.enable_retreat&&userId==scope.row.create_by" type="danger" @click="handleRetreat(scope)">撤回</el-link>
|
||||
<el-link type="success" @click="handlePicture(scope)">查看流程图</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -147,9 +149,10 @@
|
|||
<el-table-column label="创建时间" min-width="100" prop="create_time">
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<template slot-scope="scope" v-if="scope.row.state_">
|
||||
<el-link v-if="scope.row.state_.distribute_type==1&&scope.row.participant_type==2" type="danger" @click="handleGetTicket(scope)">接单</el-link>
|
||||
<el-link v-else-if="scope.row.act_state==1&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
|
||||
<el-link v-else-if="(scope.row.act_state==1||scope.row.act_state==3)&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
|
||||
<el-link v-if="scope.row.state_.enable_retreat" type="danger" @click="handleRetreat(scope)">撤回</el-link>
|
||||
<el-link type="success" @click="handlePicture(scope)">查看流程图</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -225,120 +228,21 @@
|
|||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog :visible.sync="limitedStep" title="工单处理">
|
||||
<el-steps :active="actives" spac="400px" align-center="" style="padding-top: 20px;">
|
||||
<el-step :title="item.name" v-for="item in flowSteps "
|
||||
:key="item.id" @click.native=stepclick(item.id)>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
<el-dialog :visible.sync="limitedRetreat" title="工单撤回">
|
||||
<el-row>
|
||||
<el-form ref="Form" :model="handleForm" label-width="100px" label-position="right" :rules="handleRule">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标题" style="margin-bottom: 0">
|
||||
<span>{{ticketDetail.title}}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工作流" style="margin-bottom: 0">
|
||||
<span v-if="ticketDetail.workflow_">{{ticketDetail.workflow_.name}}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="流水号" style="margin-bottom: 0">
|
||||
<span>{{ticketDetail.sn}}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="创建时间" style="margin-bottom: 0">
|
||||
<span>{{ticketDetail.create_time}}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-for="item in ticketDetail.ticket_data_" :key="item.id" :span="12">
|
||||
<el-form-item :label="item.field_name" v-if="item.field_state==='1'" style="margin-bottom: 0">
|
||||
<span>{{ticketDetail.ticket_data[item.field_key]}}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="item.field_name" v-else>
|
||||
<template v-if="item.field_type=='string'">
|
||||
<el-input v-model="ticketDetail.ticket_data[item.field_key]" :placeholder="item.description"/>
|
||||
</template>
|
||||
<template v-if="item.field_type==='int'">
|
||||
<el-input v-model="ticketDetail.ticket_data[item.field_key]" type="number" :placeholder="item.description" oninput="value=value.replace(/[^\d]/g,'')" />
|
||||
</template>
|
||||
<template v-if="item.field_type==='float'">
|
||||
<el-input v-model="ticketDetail.ticket_data[item.field_key]" type="number" :placeholder="item.description" />
|
||||
</template>
|
||||
<template v-if="item.field_type==='date'">
|
||||
<el-date-picker
|
||||
v-model="ticketDetail.ticket_data[item.field_key]"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 100%"
|
||||
>
|
||||
</el-date-picker>
|
||||
</template>
|
||||
<template v-if="item.field_type==='datetime'">
|
||||
<el-date-picker
|
||||
v-model="ticketDetail.ticket_data[item.field_key]"
|
||||
type="datetime"
|
||||
placeholder="选择日期"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
style="width: 100%"
|
||||
>
|
||||
</el-date-picker>
|
||||
</template>
|
||||
<template v-if="item.field_type==='select'">
|
||||
<el-select style="width: 100%" v-model="ticketDetail.ticket_data[item.field_key]" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item1 in item.field_choice"
|
||||
:key="item1"
|
||||
:label="item1"
|
||||
:value="item1"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
<template v-if="item.field_type==='selects'">
|
||||
<el-select style="width: 100%" multiple v-model="ticketDetail.ticket_data[item.field_key]" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item1 in item.field_choice"
|
||||
:key="item1"
|
||||
:label="item1"
|
||||
:value="item1"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
<template v-if="item.field_type==='textarea'">
|
||||
<el-input type="textarea" :rows="3" v-model="ticketDetail.ticket_data[item.field_key]" placeholder="内容" />
|
||||
</template>
|
||||
<template v-if="item.field_type==='file'">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
:action="upUrl"
|
||||
:on-preview="handlePreview"
|
||||
:on-success="handleUpSuccess"
|
||||
:on-remove="handleRemove"
|
||||
:headers="upHeaders"
|
||||
:file-list="fileList"
|
||||
:limit="1"
|
||||
accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx"
|
||||
>
|
||||
<el-button size="small" type="primary">上传文件</el-button>
|
||||
</el-upload>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-form-item label="审批意见">
|
||||
<el-input v-model="handleForm.suggestion" placeholder="审批意见"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="2" style="height: 1px;"></el-col>
|
||||
<el-col :span="20" style="margin:6vh 0">
|
||||
<el-form-item label="撤回原因">
|
||||
<el-input type="textarea" :rows="3" v-model="handleForm.suggestion" placeholder="撤回原因"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<div style="text-align: center">
|
||||
<el-button v-for="item in operationBtn" :key="item.id" class="filter-item" type="primary" @click="operationSubmit(item.id)">{{item.name}}</el-button>
|
||||
<el-button class="filter-item" type="" @click="retreatCancel">取消</el-button>
|
||||
<el-button class="filter-item" type="primary" @click="retreatSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog :visible.sync="limitedAdd" title="新增工单">
|
||||
|
@ -439,7 +343,7 @@
|
|||
<script>
|
||||
import { upUrl, upHeaders } from "@/api/file";
|
||||
import {getWorkflowList,getWfCustomfieldList,createTicket,getWfStateList,getTickets,ticketAccpet,getWfTransitionList,
|
||||
ticketHandle,getWfFlowSteps,getTicketDetail,getTicketTransitions,getTicketFlowlog } from "@/api/workflow";
|
||||
ticketHandle,getWfFlowSteps,getTicketDetail,getTicketTransitions,getTicketFlowlog,getTicketRetreat} from "@/api/workflow";
|
||||
import Pagination from "@/components/Pagination";
|
||||
import dagreD3 from 'dagre-d3'
|
||||
import * as d3 from 'd3'
|
||||
|
@ -467,8 +371,6 @@
|
|||
transition:''
|
||||
},
|
||||
handleForm:{
|
||||
transition:'',
|
||||
ticket_data:{},
|
||||
suggestion:'',
|
||||
},
|
||||
handleRule:{},
|
||||
|
@ -486,6 +388,7 @@
|
|||
listLoading:false,
|
||||
limitedStep:false,
|
||||
limitedAdd:false,
|
||||
limitedRetreat:false,
|
||||
nodes: [],
|
||||
tooltip:null,
|
||||
edges: [],
|
||||
|
@ -504,6 +407,7 @@
|
|||
},
|
||||
mounted(){
|
||||
debugger;
|
||||
this.userId = sessionStorage.getItem('userId')
|
||||
this.workflow = this.$route.params.workflow;
|
||||
if(this.workflow){//有传参
|
||||
this.pageForm.workflow = parseInt(this.workflow);
|
||||
|
@ -659,6 +563,30 @@
|
|||
}
|
||||
});
|
||||
},
|
||||
retreatCancel(){
|
||||
this.limitedRetreat = false;
|
||||
},
|
||||
retreatSubmit(){
|
||||
this.$confirm("确认撤回吗?", "温馨提示", {
|
||||
confirmButtonText: "确认",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
})
|
||||
.then(async () => {
|
||||
getTicketRetreat(this.retreatId,this.handleForm).then(res=>{
|
||||
this.limitedRetreat = false;
|
||||
this.getList();
|
||||
this.$message.success("成功");
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
handleRetreat(scope){
|
||||
this.limitedRetreat = true;
|
||||
this.retreatId = scope.row.id;
|
||||
},
|
||||
handlePicture(scope){
|
||||
let that = this;
|
||||
that.dialogVisible = true;
|
||||
|
@ -863,15 +791,6 @@
|
|||
handleDetail(scope){
|
||||
this.$router.push({name:"ticketHandle",params:{ticketId:scope.row.id,workflow:scope.row.workflow}})
|
||||
},
|
||||
operationSubmit(id){
|
||||
this.handleForm.transition = id;
|
||||
this.handleForm.ticket_data = this.ticketDetail.ticket_data;
|
||||
ticketHandle(this.ticketId,this.handleForm).then(res=>{
|
||||
if (res.data){
|
||||
this.limitedStep = false;
|
||||
}
|
||||
})
|
||||
},
|
||||
//接单
|
||||
handleGetTicket(scope){
|
||||
let ticketId = scope.row.id;
|
||||
|
|
|
@ -23,6 +23,7 @@ class EquipmentSerializer(ModelSerializer):
|
|||
def get_step_(self, obj):
|
||||
return Step.objects.filter(equipments=obj).values('id', 'name', 'number')
|
||||
|
||||
|
||||
class EquipmentSimpleSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Equipment
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.2.6 on 2021-10-14 01:44
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('em', '0009_auto_20210916_1108'),
|
||||
('mtm', '0021_auto_20211013_0856'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='subproduction',
|
||||
name='process',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='subproduction_process', to='mtm.process', verbose_name='隶属大工序'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='step',
|
||||
name='equipments',
|
||||
field=models.ManyToManyField(related_name='step_equips', to='em.Equipment', verbose_name='使用设备'),
|
||||
),
|
||||
]
|
|
@ -64,7 +64,7 @@ class Step(CommonAModel):
|
|||
number = models.CharField('步骤编号', max_length=100, null=True, blank=True)
|
||||
instruction_content = models.TextField('相应操作指导', null=True, blank=True)
|
||||
sort = models.IntegerField('排序号', default=1)
|
||||
equipments = models.ManyToManyField(Equipment, verbose_name='使用设备')
|
||||
equipments = models.ManyToManyField(Equipment, verbose_name='使用设备', related_name='step_equips')
|
||||
|
||||
class Meta:
|
||||
verbose_name = '工序步骤'
|
||||
|
@ -134,6 +134,7 @@ class SubProduction(CommonAModel):
|
|||
"""
|
||||
name = models.CharField('命名', max_length=50, null=True, blank=True)
|
||||
product = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE)
|
||||
process = models.ForeignKey(Process, verbose_name='隶属大工序', on_delete=models.CASCADE, related_name='subproduction_process')
|
||||
sort = models.IntegerField('排序号', default=1)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -17,8 +17,9 @@ class MaterialDetailSerializer(serializers.ModelSerializer):
|
|||
fields = '__all__'
|
||||
|
||||
def get_processes_(self, obj):
|
||||
steps = UsedStep.objects.filter(subproduction__product=obj).values_list('step', flat=True)
|
||||
objs = Process.objects.filter(step_process__id__in=steps).distinct().order_by('number')
|
||||
# steps = UsedStep.objects.filter(subproduction__product=obj).values_list('step', flat=True)
|
||||
# objs = Process.objects.filter(step_process__id__in=steps).distinct().order_by('number')
|
||||
objs = Process.objects.filter(subproduction_process__product=obj, subproduction_process__is_deleted=False, is_deleted=False).order_by('number')
|
||||
return ProcessSimpleSerializer(instance=objs, many=True).data
|
||||
|
||||
|
||||
|
@ -61,6 +62,7 @@ class StepDetailSerializer(serializers.ModelSerializer):
|
|||
return queryset
|
||||
|
||||
class SubProductionSerializer(serializers.ModelSerializer):
|
||||
process_ = ProcessSimpleSerializer(source='process', read_only=True)
|
||||
class Meta:
|
||||
model = SubProduction
|
||||
fields = '__all__'
|
||||
|
|
|
@ -74,8 +74,8 @@ class SubProductionViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
|||
产品生产分解增删改查
|
||||
"""
|
||||
perms_map={'*':'*'}
|
||||
queryset = SubProduction.objects.all()
|
||||
filterset_fields = ['product']
|
||||
queryset = SubProduction.objects.select_related('process').all()
|
||||
filterset_fields = ['product', 'process']
|
||||
search_fields = ['name']
|
||||
serializer_class = SubProductionSerializer
|
||||
ordering = ['sort']
|
||||
|
|
|
@ -15,3 +15,10 @@ class ProductionPlanSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = ProductionPlan
|
||||
fields ='__all__'
|
||||
|
||||
class ResourceCalSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField(label='产品ID')
|
||||
count = serializers.IntegerField(label='生产数量')
|
||||
|
||||
class ResourceCalListSerializer(serializers.ListSerializer):
|
||||
child = ResourceCalSerializer()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from apps.pm.views import ProductionPlanViewSet
|
||||
from apps.pm.views import ProductionPlanViewSet, ResourceViewSet
|
||||
from django.db.models import base
|
||||
from rest_framework import urlpatterns
|
||||
from django.urls import path, include
|
||||
|
@ -6,7 +6,7 @@ from rest_framework.routers import DefaultRouter
|
|||
|
||||
router = DefaultRouter()
|
||||
router.register('productionplan', ProductionPlanViewSet, basename='productionplan')
|
||||
|
||||
router.register('resource', ResourceViewSet, basename='resource')
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
from rest_framework.views import APIView
|
||||
from apps.em.models import Equipment
|
||||
from apps.em.serializers import EquipmentSerializer
|
||||
from apps.mtm.models import InputMaterial, Step, SubProduction, UsedStep
|
||||
from apps.system.mixins import CreateUpdateModelAMixin
|
||||
from apps.pm.serializers import ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer
|
||||
from apps.pm.serializers import ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer
|
||||
from rest_framework.mixins import CreateModelMixin, ListModelMixin
|
||||
from apps.pm.models import ProductionPlan
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
|
@ -8,6 +11,7 @@ from django.shortcuts import render
|
|||
from apps.sam.models import Order
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import action
|
||||
# Create your views here.
|
||||
|
||||
def updateOrderPlanedCount(order):
|
||||
|
@ -52,5 +56,50 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel
|
|||
updateOrderPlanedCount(instance.order)
|
||||
return Response()
|
||||
|
||||
class ResourceCalculate(APIView):
|
||||
pass
|
||||
class ResourceViewSet(GenericViewSet):
|
||||
|
||||
perms_map = {'*': '*'}
|
||||
@action(methods=['post'], detail=False, perms_map={'get':'*'}, serializer_class=ResourceCalListSerializer)
|
||||
def cal(self, request, pk=None):
|
||||
"""
|
||||
物料消耗计算
|
||||
"""
|
||||
rdata = request.data
|
||||
serializer = self.get_serializer(data=rdata)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
res_d_list = []
|
||||
res = []
|
||||
for i in rdata:
|
||||
materials = InputMaterial.objects.filter(subproduction__product__id=i['id'],
|
||||
subproduction__is_deleted=False, is_deleted=False, material__type__in=[3,4]).order_by('material__number')\
|
||||
.values('material__id', 'material__name', 'material__number', 'material__type', 'count', 'material__count')
|
||||
l_m = list(materials)
|
||||
for m in l_m:
|
||||
if m['material__id'] in res_d_list:
|
||||
index = res_d_list.index(m['material__id'])
|
||||
res[index]['count'] = res[index]['count'] + m['count']*i['count']
|
||||
else:
|
||||
res_d_list.append(m['material__id'])
|
||||
res.append({'id':m['material__id'], 'name':m['material__name'],
|
||||
'type':m['material__type'], 'number':m['material__number'],
|
||||
'count':m['count']*i['count'], 'inv_count':m['material__count']})
|
||||
return Response(res)
|
||||
|
||||
@action(methods=['post'], detail=False, perms_map={'get':'*'}, serializer_class=ResourceCalListSerializer)
|
||||
def cal_equip(self, request, pk=None):
|
||||
"""
|
||||
设备状态查看
|
||||
"""
|
||||
rdata = request.data
|
||||
serializer = self.get_serializer(data=rdata)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
rdata_l = []
|
||||
for i in rdata:
|
||||
rdata_l.append(i['id'])
|
||||
subproductions = SubProduction.objects.filter(product__id__in=rdata_l, is_deleted=False)
|
||||
steps = Step.objects.filter(usedsteps__is_deleted=False, usedsteps__subproduction__in=subproductions)
|
||||
equips = Equipment.objects.filter(step_equips__in=steps, is_deleted=False)
|
||||
serializer = EquipmentSerializer(instance=equips, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.2.6 on 2021-10-14 01:44
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wf', '0012_ticketflow_intervene_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ticketflow',
|
||||
name='transition',
|
||||
field=models.ForeignKey(blank=True, help_text='与worklow.Transition关联, 为空时表示认为干预的操作', null=True, on_delete=django.db.models.deletion.CASCADE, to='wf.transition', verbose_name='流转id'),
|
||||
),
|
||||
]
|
|
@ -201,7 +201,7 @@ class TicketFlow(BaseModel):
|
|||
工单流转日志
|
||||
"""
|
||||
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='关联工单', related_name='ticketflow_ticket')
|
||||
transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联, 为0时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
|
||||
transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联, 为空时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
|
||||
suggestion = models.CharField('处理意见', max_length=10000, default='', blank=True)
|
||||
participant_type = models.IntegerField('处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.state_participanttype_choices)
|
||||
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True, related_name='ticketflow_participant')
|
||||
|
|
|
@ -130,3 +130,13 @@ class TicketHandleSerializer(serializers.Serializer):
|
|||
|
||||
class TicketRetreatSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="撤回原因", required = False)
|
||||
|
||||
class TicketCloseSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="关闭原因", required = False)
|
||||
|
||||
class TicketAddNodeSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="加签说明", required = False)
|
||||
add_node_man = serializers.IntegerField(label='加签人')
|
||||
|
||||
class TicketAddNodeEndSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="加签意见", required = False)
|
|
@ -33,6 +33,17 @@ class WfService(object):
|
|||
except:
|
||||
raise Exception('工作流状态配置错误')
|
||||
|
||||
@staticmethod
|
||||
def get_workflow_end_state(workflow:Workflow):
|
||||
"""
|
||||
获取工作流结束状态
|
||||
"""
|
||||
try:
|
||||
wf_state_obj = State.objects.get(workflow=workflow, type=State.STATE_TYPE_END, is_deleted=False)
|
||||
return wf_state_obj
|
||||
except:
|
||||
raise Exception('工作流状态配置错误')
|
||||
|
||||
@staticmethod
|
||||
def get_workflow_custom_fields(workflow:Workflow):
|
||||
"""
|
||||
|
@ -124,7 +135,7 @@ class WfService(object):
|
|||
"""
|
||||
到达结束状态
|
||||
"""
|
||||
return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
return dict(destination_participant_type=0,
|
||||
destination_participant=0,
|
||||
multi_all_person={})
|
||||
multi_all_person_dict = {}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from apps.system.models import User
|
||||
from apps.wf.filters import TicketFilterSet
|
||||
from django.core.exceptions import AppRegistryNotReady
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import serializers
|
||||
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
|
||||
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
from rest_framework.decorators import action, api_view
|
||||
|
@ -11,6 +12,7 @@ from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, Tic
|
|||
from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, OptimizationMixin
|
||||
from apps.wf.services import WfService
|
||||
from rest_framework.exceptions import APIException, PermissionDenied
|
||||
from rest_framework import status
|
||||
|
||||
# Create your views here.
|
||||
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
||||
|
@ -225,7 +227,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
|
|||
ticket.act_state = Ticket.TICKET_ACT_STATE_ONGOING
|
||||
|
||||
if transition.attribute_type == Transition.TRANSITION_ATTRIBUTE_TYPE_REFUSE:
|
||||
transition.act_state = Ticket.TICKET_ACT_STATE_BACK
|
||||
ticket.act_state = Ticket.TICKET_ACT_STATE_BACK
|
||||
|
||||
# 只更新必填和可选的字段
|
||||
for key, value in ticket.state.state_fields.items():
|
||||
|
@ -312,16 +314,66 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
|
|||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post':'*'})
|
||||
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketAddNodeSerializer)
|
||||
def add_node(self, request, pk=None):
|
||||
"""
|
||||
加签
|
||||
"""
|
||||
ticket = self.get_object()
|
||||
data = request.data
|
||||
add_user = User.objects.get(pk=data['add_node_man'])
|
||||
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
|
||||
ticket.participant = add_user.id
|
||||
ticket.in_add_node = True
|
||||
ticket.add_node_man = request.user
|
||||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 加签说明
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketAddNodeEndSerializer)
|
||||
def add_node_end(self, request, pk=None):
|
||||
"""
|
||||
加签完成
|
||||
"""
|
||||
ticket = self.get_object()
|
||||
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
|
||||
ticket.in_add_node = False
|
||||
ticket.add_node_man = None
|
||||
ticket.participant = ticket.add_node_man.id
|
||||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 加签意见
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketCloseSerializer)
|
||||
def close(self, request, pk=None):
|
||||
"""
|
||||
关闭工单(超级管理员或者创建人在初始状态)
|
||||
关闭工单(创建人在初始状态)
|
||||
"""
|
||||
ticket = self.get_object()
|
||||
if ticket.state.type == State.STATE_TYPE_START and ticket.create_by==request.user:
|
||||
end_state = WfService.get_workflow_end_state(ticket.workflow)
|
||||
ticket.state = end_state
|
||||
ticket.participant_type = 0
|
||||
ticket.participant = 0
|
||||
ticket.act_state = Ticket.TICKET_ACT_STATE_CLOSED
|
||||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 关闭原因
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
else:
|
||||
return Response('工单不可关闭', status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
|
||||
|
|
|
@ -56,7 +56,7 @@ class FitJSONRenderer(JSONRenderer):
|
|||
data = data[prefix]
|
||||
if isinstance(data, list):
|
||||
data = data[0]
|
||||
response_body.msg = prefix + ":" + data # 取一部分放入msg,方便前端alert
|
||||
response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert
|
||||
else:
|
||||
response_body.data = data
|
||||
renderer_context.get("response").status_code = 200 # 统一成200响应,用code区分
|
||||
|
|
Loading…
Reference in New Issue