Merge branch 'develop' of https://e.coding.net/ctcdevteam/hberp/hberp into develop

This commit is contained in:
shilixia 2021-09-06 13:36:32 +08:00
commit 7ed06d8b40
13 changed files with 689 additions and 180 deletions

View File

@ -13,7 +13,7 @@ module.exports = {
// add your custom rules here // add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue //it is base on https://github.com/vuejs/eslint-config-vue
rules: { /* rules: {
"vue/max-attributes-per-line": [2, { "vue/max-attributes-per-line": [2, {
"singleline": 10, "singleline": 10,
"multiline": { "multiline": {
@ -194,5 +194,5 @@ module.exports = {
objectsInObjects: false objectsInObjects: false
}], }],
'array-bracket-spacing': [2, 'never'] 'array-bracket-spacing': [2, 'never']
} }*/
} }

View File

@ -25,6 +25,7 @@
"nprogress": "0.2.0", "nprogress": "0.2.0",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-json-editor": "^1.4.3",
"vue-router": "^3.5.2", "vue-router": "^3.5.2",
"vuex": "^3.6.2", "vuex": "^3.6.2",
"xlsx": "^0.17.1" "xlsx": "^0.17.1"

View File

@ -45,6 +45,11 @@ import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger' import Hamburger from '@/components/Hamburger'
export default { export default {
data(){
return{
name:''
}
},
components: { components: {
Breadcrumb, Breadcrumb,
Hamburger Hamburger

View File

@ -339,6 +339,27 @@ export const asyncRoutes = [
} }
] ]
}, },
{
path: '/testModel',
component: Layout,
redirect: '/testModel/faceLogin/',
name: 'testModel',
meta: { title: '测试模块', icon: 'example' },
children: [
{
path: 'faceLogin',
name: 'faceLogin',
component: () => import('@/views/testModel/faceLogin'),
meta: { title: '人脸识别登录', icon: 'example' }
},
{
path: 'markImage',
name: 'markImage',
component: () => import('@/views/testModel/markImage'),
meta: { title: '图片标记', icon: 'example' }
}
]
},
// 404 page must be placed at the end !!! // 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true } { path: '*', redirect: '/404', hidden: true }
] ]

View File

@ -20,10 +20,10 @@
type="text" type="text"
tabindex="1" tabindex="1"
auto-complete="on" auto-complete="on"
><svg-icon ><svg-icon
slot="prefix" slot="prefix"
icon-class="user" icon-class="user"
class="el-input__icon input-icon" class="el-input__icon input-icon"
/></el-input> /></el-input>
</el-form-item> </el-form-item>
@ -38,10 +38,10 @@
tabindex="2" tabindex="2"
auto-complete="on" auto-complete="on"
@keyup.enter.native="handleLogin" @keyup.enter.native="handleLogin"
><svg-icon ><svg-icon
slot="prefix" slot="prefix"
icon-class="password" icon-class="password"
class="el-input__icon input-icon" class="el-input__icon input-icon"
/></el-input> /></el-input>
<span class="show-pwd" @click="showPwd"> <span class="show-pwd" @click="showPwd">
<svg-icon <svg-icon
@ -54,7 +54,7 @@
type="primary" type="primary"
style="width: 100%; margin-bottom: 30px" style="width: 100%; margin-bottom: 30px"
@click.native.prevent="handleLogin" @click.native.prevent="handleLogin"
>登录</el-button >登录</el-button
> >
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
@ -65,168 +65,168 @@
<script> <script>
export default { export default {
name: "Login", name: "Login",
data() { data() {
// const validateUsername = (rule, value, callback) => { // const validateUsername = (rule, value, callback) => {
// if (!validUsername(value)) { // if (!validUsername(value)) {
// callback(new Error('请输入正确账号!')) // callback(new Error('请输入正确账号!'))
// } else { // } else {
// callback() // callback()
// } // }
// } // }
const validatePassword = (rule, value, callback) => { const validatePassword = (rule, value, callback) => {
if (value.length < 4) { if (value.length < 4) {
callback(new Error("密码长度小于4位!")); callback(new Error("密码长度小于4位!"));
} else {
callback();
}
};
return {
timer: null,
buttonmsg: "发送验证码",
count: 60,
loginForm: {
username: "",
password: "",
},
loginRules: {
username: [{ required: true, trigger: "blur", message: "请输入账户" }],
password: [
{
required: true,
trigger: "blur",
validator: validatePassword,
message: "请输入密码",
},
],
},
loading: false,
passwordType: "password",
redirect: undefined,
};
},
created() {
this.getUP();
},
watch: {
$route: {
handler: function (route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true,
},
},
methods: {
showPwd() {
if (this.passwordType === "password") {
this.passwordType = "";
} else {
this.passwordType = "password";
}
this.$nextTick(() => {
this.$refs.password.focus();
});
},
handleLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true;
this.$store
.dispatch("user/login", this.loginForm)
.then(() => {
this.$router.push({ path: this.redirect || "/" });
this.loading = false;
localStorage.setItem("rem_username", this.loginForm.username);
localStorage.setItem("rem_password", this.loginForm.password);
})
.catch(() => {
this.loading = false;
});
} else { } else {
console.log("error submit!!"); callback();
return false;
} }
}); };
}, return {
timer: null,
buttonmsg: "发送验证码",
count: 60,
loginForm: {
username: "",
password: "",
},
loginRules: {
username: [{ required: true, trigger: "blur", message: "请输入账户" }],
password: [
{
required: true,
trigger: "blur",
validator: validatePassword,
message: "请输入密码",
},
],
},
//读取cookie
getUP() {
this.loginForm.username = localStorage.getItem("rem_username"); loading: false,
this.loginForm.password = localStorage.getItem("rem_password"); passwordType: "password",
redirect: undefined,
};
}, },
}, created() {
}; this.getUP();
},
watch: {
$route: {
handler: function (route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true,
},
},
methods: {
showPwd() {
if (this.passwordType === "password") {
this.passwordType = "";
} else {
this.passwordType = "password";
}
this.$nextTick(() => {
this.$refs.password.focus();
});
},
handleLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true;
this.$store
.dispatch("user/login", this.loginForm)
.then(() => {
this.$router.push({ path: this.redirect || "/" });
this.loading = false;
localStorage.setItem("rem_username", this.loginForm.username);
localStorage.setItem("rem_password", this.loginForm.password);
})
.catch(() => {
this.loading = false;
});
} else {
console.log("error submit!!");
return false;
}
});
},
//读取cookie
getUP() {
this.loginForm.username = localStorage.getItem("rem_username");
this.loginForm.password = localStorage.getItem("rem_password");
},
},
};
</script> </script>
<style lang="scss"> <style lang="scss">
.login { .login {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 100%;
background-image: url("../../assets/beijing.jpg"); background-image: url("../../assets/beijing.jpg");
background-size: cover; background-size: cover;
} }
.title { .title {
margin: 30px auto 30px auto; margin: 30px auto 30px auto;
text-align: center; text-align: center;
color: #0174d7; color: #0174d7;
} }
.logo { .logo {
width: 350px; width: 350px;
height: 140px; height: 140px;
} }
.login-form { .login-form {
border-radius: 6px; border-radius: 6px;
background: #ffffff; background: #ffffff;
width: 400px; width: 400px;
padding: 25px 25px 5px 25px; padding: 25px 25px 5px 25px;
.el-input { .el-input {
height: 45px;
input {
height: 45px; height: 45px;
input {
height: 45px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
} }
} }
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
}
}
.show-pwd { .show-pwd {
height: 39px; height: 39px;
margin-right: 2px; margin-right: 2px;
} }
.login-tip { .login-tip {
font-size: 13px; font-size: 13px;
text-align: center; text-align: center;
color: #bfbfbf; color: #bfbfbf;
} }
.login-code { .login-code {
width: 33%; width: 33%;
height: 38px; height: 38px;
float: right; float: right;
img { img {
cursor: pointer; cursor: pointer;
vertical-align: middle; vertical-align: middle;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
cursor: pointer;
user-select: none;
}
.login-code-img {
height: 38px;
} }
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
cursor: pointer;
user-select: none;
}
.login-code-img {
height: 38px;
}
</style> </style>

View File

@ -0,0 +1,184 @@
<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">拍照</Button>
<div id="res"></div>
</div>
</div>
</template>
<script>
// import "tracking/build/data/face-min.js";
// import "tracking/build/data/mouth-min.js";
// import "tracking/build/data/tracking-min.js";
// import "tracking/examples/assets/stats.min.js";
// import "tracking/examples/assets/stats.min.js";
export default {
props:['src'],
data () {
return {
videoWidth: 900,
videoHeight: 700,
videoArr:[],//所有的摄像头
modelSel:'',//
myInterval: null,
imgSrc: '',
isHasFace:false,//默认没有人脸
tracker:null,
}
},
created(){
},
mounted(){
// this.openUserMedia();
this.callCamera();
this.changePhoto();
// this.checkFace();
if(this.src!='user'){
// this.myInterval = setInterval(()=>{
// this.setImage();
// },2000)
}
},
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');
var video = document.getElementById("myVideo");
context.drawImage(video,0,0,this.videoWidth,this.videoHeight);
var image = new Image();
image = canvas.toDataURL('image/png');
document.getElementById('res').innerHTML = '<img style="border: 1px solid #666666;" src="'+image+'">';
},
// 关闭摄像头
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);
}
},
/**
* 检查取景框是否有人脸
* */
checkFace() {
// var video = document.getElementById("video");
let canvas = document.getElementById("myCanvas");
let context = canvas.getContext('2d');
this.tracker = new tracking.ObjectTracker("face");
this.tracker.setInitialScale(4);
this.tracker.setStepSize(2);
this.tracker.setEdgesDensity(0.1);
this.trackerTask = tracking.track("#video", this.tracker, { camera: true });
let self = this;
this.tracker.on("track", event=> {
if (event.data.length <= 0) {
self.setFace(false);
return;
}
self.setFace(true);
context.clearRect(0, 0, canvas.width, canvas.height);
event.data.forEach(function (rect) {
context.strokeStyle = '#a64ceb';
context.strokeRect(rect.x, rect.y, rect.width, rect.height);
context.font = '11px Helvetica';
context.fillStyle = "#fff";
context.fillText('x: ' + rect.x + 'px', rect.x + rect.width + 5, rect.y + 11);
context.fillText('y: ' + rect.y + 'px', rect.x + rect.width + 5, rect.y + 22);
});
});
},
setFace(data){
this.isHasFace = data;
}
},
beforeDestroy () {
clearInterval(this.myInterval);
// 停止侦测
// this.trackerTask.stop();
}
}
</script>
<style scoped>
.testTracking {
min-height: 700px;
width: 100%;
position: relative;
}
.buttonDiv {
bottom: 10px;
}
</style>

View File

@ -0,0 +1,259 @@
<template>
<div>
<button type="button" class="btn btn-warning" @click="error1">错误</button>
<button @click="word1()">文字</button>
<button @click="restuya()">清除批注</button>
<button @click="back()">回退</button>
<button @click="saveTu()">保存</button>
<hr>
<!--<img src="./../../assets/404_images/404.png">-->
<div style="position: relative;">
<canvas id="canvas" width="700" height="500" >
您的浏览器不支持绘图请升级或更换浏览器
</canvas>
<input type="text" value="" class="hide" id="txt" placeholder="请输入文字">
<textarea class="hide" id="word" cols="30" rows="10" placeholder="请输入文字" autofocus></textarea>
<input id="inputV" type="hidden" value="1">
<span>保存的图片</span>
<div id="res"></div>
</div>
</div>
</template>
<script>
let preDrawAry = [];
export default {
data() {
return {
canvas:null,
ctx:null,
myCanvas_rect:null,
Txt:null,
word:null,
widths:0,
heights:0,
lineW:3,
colorF:"#e42343",
imgData:'',
}
},
mounted(){
this.canvas = document.getElementById('canvas');
this.ctx = this.canvas.getContext('2d');
this.myCanvas_rect = this.canvas.getBoundingClientRect();
this.Txt = document.getElementById('txt');
this.word = document.getElementById('word');
this.widths = this.myCanvas_rect.width;
this.heights = this.myCanvas_rect.height;
let imgs = new Image();
imgs.src = "./../../assets/404_images/404.png";
this.ctx.drawImage(imgs,0,0,this.widths,this.heights);
preDrawAry = [];
},
methods: {
// 叉号
error1(){
let canvas1 = document.getElementById('canvas');
let ctx1 = canvas1.getContext('2d');
this.Txt.style.display="none";
this.word.style.display="none";
ctx1.closePath();
this.canvas.onmousedown=function () {
this.imgData= ctx1.getImageData(0,0,canvas1.width,canvas1.height);
preDrawAry.push(this.imgData);
ctx1.beginPath();
ctx1.strokeStyle = "#e42343";
ctx1.lineWidth = "3";
ctx1.lineJoin="round";
};
//鼠标按下的位置
this.canvas.onmouseup=function (ev) {
let oldX = ev.offsetX;
let oldY = ev.offsetY;
ctx1.moveTo(oldX,oldY);
ctx1.lineTo(ev.offsetX+10,ev.offsetY+10);
ctx1.moveTo(ev.offsetX+10,ev.offsetY);
ctx1.lineTo(ev.offsetX,ev.offsetY+10);
ctx1.stroke();
};
this.ctx.closePath();
},
// 文字先写字
text(){
let canvas2 = document.getElementById('canvas');
let ctx2 = canvas2.getContext('2d');
let Txt2 = document.getElementById('txt');
Txt2.style.display="block";
this.word.style.display="none";
ctx2.font="16px Microsoft Yahei";
canvas2.onmousedown=function (ev) {
this.imgData=ctx2.getImageData(0,0,canvas2.width,canvas2.height);
preDrawAry.push(this.imgData);
var v = Txt2.value;
// console.log(v);
if (v != '') {
var oldX = ev.offsetX;
var oldY = ev.offsetY;
// console.log(oldX,oldY);
ctx2.moveTo(oldX,oldY);
canvas2.onmouseup=function () {
ctx2.fillStyle=this.colorF;
ctx2.fillText(v,oldX,oldY);
canvas2.TextAutoLine(v,canvas2,oldX,oldY,20);
// Txt.value = "";
Txt2.style.display="none";
}
}
}
},
// 文字
word1(){
let canvas3 = document.getElementById('canvas');
let ctx3 = canvas3.getContext('2d');
let Txt3 = document.getElementById('txt');
let word3 = document.getElementById('word');
Txt3.style.display="none";
ctx3.font="16px Microsoft Yahei";
canvas3.onmousedown=function () {
ctx3.closePath();
}
canvas3.onmouseup=function (ev) {
var inputV= document.getElementById('inputV').value;
if(inputV == 1){
document.getElementById('word').focus();
// console.log(ev.offsetX,ev.offsetY);
var oldX = ev.offsetX;
var oldY = ev.offsetY;
word3.style.display="block";
word3.style.left=oldX+'px';
word3.style.top =oldY+'px';
word3.onblur=function () {
let v = word3.value;
if(v != '' && v != ' '){
this.imgData=ctx3.getImageData(0,0,canvas3.width,canvas3.height);
let img = ctx3.getImageData(0,0,canvas3.width,canvas3.height);
debugger;
preDrawAry.push(img);
ctx3.moveTo(oldX,oldY);
ctx3.fillStyle="#e42343";
// ctx3.fillText(v,oldX,oldY+10);
// this.canvasTextAutoLine(v,canvas3,oldX,oldY,20);
let lineWidth = 0;
let canvasWidth = canvas3.width;
let lastSubStrIndex= 0;
for(let i=0;i<v.length;i++){
lineWidth+=ctx3.measureText(v[i]).width;
if(lineWidth>canvasWidth-oldX){
ctx3.fillText(v.substring(lastSubStrIndex,i),oldX,oldY);
oldY+=20;
lineWidth=0;
lastSubStrIndex=i;
}
if(i==v.length-1){
ctx3.fillText(v.substring(lastSubStrIndex,i+1),oldX,oldY);
}
}
inputV ="2";
word3.value = "";
}
}
}
}
},
// 文字过长超出换行toDataURL()
canvasTextAutoLine(str,canvas,initX,initY,lineHeight){
let ctx = canvas.getContext("2d");
let lineWidth = 0;
let canvasWidth = canvas.width;
let lastSubStrIndex= 0;
for(let i=0;i<str.length;i++){
lineWidth+=ctx.measureText(str[i]).width;
if(lineWidth>canvasWidth-initX){
ctx.fillText(str.substring(lastSubStrIndex,i),initX,initY);
initY+=lineHeight;
lineWidth=0;
lastSubStrIndex=i;
}
if(i==str.length-1){
ctx.fillText(str.substring(lastSubStrIndex,i+1),initX,initY);
}
}
},
// 删除批注
restuya(){
this.word.style.display="none";
this.Txt.style.display="none";
this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
window.location.reload();
},
// 撤销
back(){
this.word.style.display="none";
this.Txt.style.display="none";
if(preDrawAry.length>0) {
// console.log(this.preDrawAry)
var popData = preDrawAry.pop();
this.ctx.putImageData(popData, 0, 0);
}
},
// 回退一次
put(){
this.ctx.putImageData(this.imgData,0,0);
},
//保存
saveTu(){
var image = new Image();
image = this.canvas.toDataURL('image/png');
document.getElementById('res').innerHTML = '<img style="border: 1px solid #666666;" src="'+image+'">';
}
}
}
</script>
<style scoped>
canvas{
border:1px solid #000000;
cursor: crosshair;
}
.hide{
display: none;
}
#txt{
position: absolute;
top: 1%;
left: 1%;
width: 575px;
height: 30px;
border:1px solid #e42343;
}
#word{
position: absolute;
width: 220px;
height: 80px;
padding: 0 2px;
background:none;
color:#e42343;
border: 1px dashed #b9b9b9;
}
#word::-webkit-input-placeholder{
color:#e42343;
}
#word::-moz-placeholder{
color:#e42343;
}
#word::placeholder{
color:#e42343;
}
#res{
display: inline-block;
}
</style>

View File

@ -18,7 +18,7 @@ class MaterialDetailSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
def get_processes_(self, obj): def get_processes_(self, obj):
objs = Process.objects.filter(id__in=obj.processes) objs = Process.objects.filter(id__in=obj.processes).order_by('number')
return ProcessSimpleSerializer(instance=objs, many=True).data return ProcessSimpleSerializer(instance=objs, many=True).data

View File

@ -53,16 +53,16 @@ class ProcessViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):
ordering_fields = ['number'] ordering_fields = ['number']
ordering = ['number'] ordering = ['number']
@action(methods=['get'], detail=True, perms_map={'get':'process_update'}, pagination_class=None, serializer_class=StepSerializer) @action(methods=['get'], detail=True, perms_map={'get':'process_update'}, pagination_class=None, serializer_class=StepDetailSerializer)
def steps(self, request, pk=None): def steps(self, request, pk=None):
""" """
工序下的子工序 工序下的子工序
""" """
process = self.get_object() process = self.get_object()
serializer = self.serializer_class(instance=Step.objects.filter(process=process, is_deleted=False), many=True) serializer = self.serializer_class(instance=Step.objects.prefetch_related('equipments').filter(process=process, is_deleted=False), many=True)
return Response(serializer.data) return Response(serializer.data)
class StepViewSet(CreateUpdateModelAMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): class StepViewSet(OptimizationMixin, CreateUpdateModelAMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
""" """
子工序-增删改查 子工序-增删改查
""" """
@ -157,6 +157,15 @@ class RecordFormViewSet(OptimizationMixin, CreateUpdateModelAMixin, ModelViewSet
return RecordFormUpdateSerializer return RecordFormUpdateSerializer
return RecordFormSerializer return RecordFormSerializer
@action(methods=['get'], detail=True, perms_map={'get':'*'}, pagination_class=None, serializer_class=RecordFormFieldSerializer)
def fields(self, request, pk=None):
"""
表格下的字段
"""
instance = self.get_object()
serializer = self.serializer_class(instance=RecordFormField.objects.filter(form=instance, is_deleted=False), many=True)
return Response(serializer.data)
class RecordFormFieldViewSet(OptimizationMixin, CreateUpdateModelAMixin, ModelViewSet): class RecordFormFieldViewSet(OptimizationMixin, CreateUpdateModelAMixin, ModelViewSet):
""" """

View File

@ -79,6 +79,9 @@ class Transition(CommonAModel):
""" """
工作流流转定时器条件(允许跳过) 条件流转与定时器不可同时存在 工作流流转定时器条件(允许跳过) 条件流转与定时器不可同时存在
""" """
TRANSITION_ATTRIBUTE_TYPE_ACCEPT = 1 # 同意
TRANSITION_ATTRIBUTE_TYPE_REFUSE = 2 # 拒绝
TRANSITION_ATTRIBUTE_TYPE_OTHER = 3 # 其他
attribute_type_choices = ( attribute_type_choices = (
(1, '同意'), (1, '同意'),
(2, '拒绝'), (2, '拒绝'),

View File

@ -1,7 +1,8 @@
from apps.system.serializers import UserSimpleSerializer
import rest_framework import rest_framework
from rest_framework import serializers from rest_framework import serializers
from .models import State, Ticket, Workflow, Transition, CustomField from .models import State, Ticket, TicketFlow, Workflow, Transition, CustomField
class WorkflowSerializer(serializers.ModelSerializer): class WorkflowSerializer(serializers.ModelSerializer):
@ -42,6 +43,10 @@ class CustomFieldSerializer(serializers.ModelSerializer):
model = CustomField model = CustomField
fields = '__all__' fields = '__all__'
class TicketSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = '__all__'
class TicketCreateSerializer(serializers.ModelSerializer): class TicketCreateSerializer(serializers.ModelSerializer):
transition = serializers.IntegerField(label='流转ID') transition = serializers.IntegerField(label='流转ID')
@ -65,3 +70,12 @@ class TicketSerializer(serializers.ModelSerializer):
def setup_eager_loading(queryset): def setup_eager_loading(queryset):
queryset = queryset.select_related('workflow','state') queryset = queryset.select_related('workflow','state')
return queryset return queryset
class TicketFlowSerializer(serializers.ModelSerializer):
participant_ = UserSimpleSerializer(source='participant', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True)
class Meta:
model = TicketFlow
fields = '__all__'

View File

@ -1,3 +1,4 @@
from apps.wf.serializers import TicketSerializer, TicketSimpleSerializer
from typing import Tuple from typing import Tuple
from apps.system.models import User from apps.system.models import User
from apps.wf.models import CustomField, State, Ticket, Transition, Workflow from apps.wf.models import CustomField, State, Ticket, Transition, Workflow
@ -198,7 +199,7 @@ class WfService(object):
:return: :return:
""" """
# 获取工单基础表中的字段中的字段信息 # 获取工单基础表中的字段中的字段信息
field_info_dict = ticket.get_dict() field_info_dict = TicketSimpleSerializer(instance=ticket).data
# 获取自定义字段的值 # 获取自定义字段的值
custom_fields_queryset = cls.get_workflow_custom_fields(ticket.workflow) custom_fields_queryset = cls.get_workflow_custom_fields(ticket.workflow)
for i in custom_fields_queryset: for i in custom_fields_queryset:

View File

@ -2,7 +2,7 @@ from django.core.exceptions import AppRegistryNotReady
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import serializers from rest_framework import serializers
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.decorators import action, api_view from rest_framework.decorators import action, api_view
@ -116,7 +116,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
if value == State.STATE_FIELD_REQUIRED: if value == State.STATE_FIELD_REQUIRED:
if key not in ticket_data or not ticket_data[key]: if key not in ticket_data or not ticket_data[key]:
raise APIException('字段{}必填'.format(key)) raise APIException('字段{}必填'.format(key))
ticket = serializer.save(state=start_state) # 先创建出来 ticket = serializer.save(state=start_state, create_by=request.user) # 先创建出来
next_state = WfService.get_next_state_by_transition_and_ticket_info(ticket=ticket, transition=transition) next_state = WfService.get_next_state_by_transition_and_ticket_info(ticket=ticket, transition=transition)
participant_info = WfService.get_ticket_state_participant_info(state=next_state, ticket=ticket, ticket_data=ticket.ticket_data) participant_info = WfService.get_ticket_state_participant_info(state=next_state, ticket=ticket, ticket_data=ticket.ticket_data)
destination_participant_type = participant_info.get('destination_participant_type', 0) destination_participant_type = participant_info.get('destination_participant_type', 0)
@ -134,7 +134,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
if title_template: if title_template:
all_ticket_data = {**rdata, **rdata['ticket_data']} all_ticket_data = {**rdata, **rdata['ticket_data']}
title = title_template.format(**all_ticket_data) title = title_template.format(**all_ticket_data)
# 更新工单 # 更新一下信息
ticket.sn=sn ticket.sn=sn
ticket.title=title ticket.title=title
ticket.state=next_state ticket.state=next_state
@ -146,7 +146,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
# 新增流转记录 # 新增流转记录
TicketFlow.objects.create(ticket=ticket, state=start_state, ticket_data=WfService.get_ticket_all_field_value(ticket), TicketFlow.objects.create(ticket=ticket, state=start_state, ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=rdata.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL, suggestion=rdata.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
participant=ticket.create_by, transition=transition, create_by=request.user) participant=ticket.create_by, transition=transition)
return Response(TicketSerializer(instance=ticket).data) return Response(TicketSerializer(instance=ticket).data)
@ -204,14 +204,14 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
ticket.participant = destination_participant ticket.participant = destination_participant
ticket.multi_all_person = multi_all_person ticket.multi_all_person = multi_all_person
if destination_state.type == State.STATE_TYPE_END: if destination_state.type == State.STATE_TYPE_END:
ticket.act_state = State.TICKET_ACT_STATE_FINISH ticket.act_state = Ticket.TICKET_ACT_STATE_FINISH
elif destination_state.type == State.STATE_TYPE_START: elif destination_state.type == State.STATE_TYPE_START:
ticket.act_state = State.TICKET_ACT_STATE_DRAFT ticket.act_state = Ticket.TICKET_ACT_STATE_DRAFT
else: else:
ticket.act_state = State.TICKET_ACT_STATE_ONGOING ticket.act_state = Ticket.TICKET_ACT_STATE_ONGOING
if transition.attribute_type == State.TRANSITION_ATTRIBUTE_TYPE_REFUSE: if transition.attribute_type == Transition.TRANSITION_ATTRIBUTE_TYPE_REFUSE:
transition.act_state = State.TICKET_ACT_STATE_BACK transition.act_state = Ticket.TICKET_ACT_STATE_BACK
# 只更新必填和可选的字段 # 只更新必填和可选的字段
for key, value in ticket.state.state_fields.items(): for key, value in ticket.state.state_fields.items():
@ -223,7 +223,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
# 更新工单流转记录 # 更新工单流转记录
TicketFlow.objects.create(ticket=ticket, state=source_state, ticket_data=WfService.get_ticket_all_field_value(ticket), TicketFlow.objects.create(ticket=ticket, state=source_state, ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=data.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL, suggestion=data.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
participant=request.user, transition=transition, create_by=request.user) participant=request.user, transition=transition)
return Response(TicketSerializer(instance=ticket).data) return Response(TicketSerializer(instance=ticket).data)
@ -244,3 +244,15 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
ticket = self.get_object() ticket = self.get_object()
transitions = WfService.get_ticket_transitions(ticket) transitions = WfService.get_ticket_transitions(ticket)
return Response(TransitionSerializer(instance=transitions, many=True).data) return Response(TransitionSerializer(instance=transitions, many=True).data)
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
"""
工单日志
"""
perms_map = {'*':'*'}
queryset = TicketFlow.objects.all()
serializer_class = TicketFlowSerializer
search_fields = ['suggestion']
filterset_fields = ['paticipant', 'state', 'ticket']
ordering = ['-create_time']