mirror of
https://github.com/Teamlinker/Teamlinker.git
synced 2025-06-03 03:00:17 +00:00
0.1.1
This commit is contained in:
parent
1aadc3173d
commit
07b9fd7a7a
@ -8,6 +8,7 @@
|
|||||||
,日历,calendar,video conference,teamlinker,collaboration">
|
,日历,calendar,video conference,teamlinker,collaboration">
|
||||||
<link rel="icon" href="public/favicon.ico"/>
|
<link rel="icon" href="public/favicon.ico"/>
|
||||||
<title>TeamLinker</title>
|
<title>TeamLinker</title>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
overflowY:'visible',
|
overflowY:'visible',
|
||||||
height:rect.height+(gap??0)*2+15+'px'
|
height:rect.height+(gap??0)*2+15+'px'
|
||||||
})}">
|
})}">
|
||||||
<div v-if="props.type==='fixed' && !multiRow && $slots.pinHeader" style="position: sticky;z-index: 1;left: 0px;background-color: white;" :style="{height:rect.height+'px',width:(rect.width+(gap??0))+'px'}">
|
<div v-if="props.type==='fixed' && !multiRow && $slots.pinHeader" style="position: sticky;z-index: 10;left: 0px;background-color: white;" :style="{height:rect.height+'px',width:(rect.width+(gap??0))+'px'}">
|
||||||
<div style="height: 100%;background-color: rgb(244, 245, 247);border-radius: 5px;;position: relative;z-index: 1" :style="{marginLeft:(gap??0)+'px',width:rect.width+'px',top:(gap??0)+'px'}">
|
<div style="height: 100%;background-color: rgb(244, 245, 247);border-radius: 5px;;position: relative;z-index: 1" :style="{marginLeft:(gap??0)+'px',width:rect.width+'px',top:(gap??0)+'px'}">
|
||||||
<div style="width: 100%;height: 30px;position: sticky;top: 0px;background-color: rgb(244, 245, 247);z-index: 1">
|
<div style="width: 100%;height: 30px;position: sticky;top: 0px;background-color: rgb(244, 245, 247);z-index: 1">
|
||||||
<slot name="pinHeader"></slot>
|
<slot name="pinHeader"></slot>
|
||||||
|
@ -31,7 +31,8 @@ export class Dialog {
|
|||||||
events,
|
events,
|
||||||
onOk,
|
onOk,
|
||||||
onClose,
|
onClose,
|
||||||
loading
|
loading,
|
||||||
|
parentNode:el
|
||||||
});
|
});
|
||||||
el.appendChild(ele);
|
el.appendChild(ele);
|
||||||
async function onOk(){
|
async function onOk(){
|
||||||
@ -64,7 +65,8 @@ export class Dialog {
|
|||||||
let destroyFunc=renderComponent(ele,DialogView,appContext,{
|
let destroyFunc=renderComponent(ele,DialogView,appContext,{
|
||||||
onOk,
|
onOk,
|
||||||
onClose,
|
onClose,
|
||||||
title:content
|
title:content,
|
||||||
|
parentNode:el
|
||||||
});
|
});
|
||||||
el.appendChild(ele);
|
el.appendChild(ele);
|
||||||
async function onOk(){
|
async function onOk(){
|
||||||
@ -90,7 +92,8 @@ export class Dialog {
|
|||||||
onOk,
|
onOk,
|
||||||
onClose,
|
onClose,
|
||||||
title:title,
|
title:title,
|
||||||
input
|
input,
|
||||||
|
parentNode:el
|
||||||
});
|
});
|
||||||
el.appendChild(ele);
|
el.appendChild(ele);
|
||||||
async function onOk(){
|
async function onOk(){
|
||||||
@ -123,7 +126,8 @@ export class Dialog {
|
|||||||
events,
|
events,
|
||||||
onOk,
|
onOk,
|
||||||
onClose,
|
onClose,
|
||||||
loading
|
loading,
|
||||||
|
parentNode:el
|
||||||
});
|
});
|
||||||
el.appendChild(ele);
|
el.appendChild(ele);
|
||||||
async function onOk(){
|
async function onOk(){
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="position: absolute;left: 0;top: 0;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;background-color: rgba(29,33,41,0.6);z-index: 1000" ref="root">
|
<div style="position: absolute;left: 0;top: 0;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;background-color: rgba(29,33,41,0.6);z-index: 1000" ref="root">
|
||||||
<div style="background-color: white;width: 60%;height:auto;max-height: 80%;border-radius: 5px;display: flex;flex-direction: column">
|
<div style="background-color: white;height:auto;max-height: 80%;border-radius: 5px;display: flex;flex-direction: column" :style="{
|
||||||
|
width:(parentNode.tagName==='BODY' || parentNode.id==='teamOS')?'40%':'60%'
|
||||||
|
}">
|
||||||
<div style="height: 35px;line-height: 35px;width: 100%;text-align: center;color: rgb(93,93,93);border-bottom: 1px solid gainsboro;flex: 1 1 auto">
|
<div style="height: 35px;line-height: 35px;width: 100%;text-align: center;color: rgb(93,93,93);border-bottom: 1px solid gainsboro;flex: 1 1 auto">
|
||||||
<b>{{component?title:input?$t("util.input"):$t("util.alert")}}</b>
|
<b>{{component?title:input?$t("util.input"):$t("util.alert")}}</b>
|
||||||
</div>
|
</div>
|
||||||
@ -27,6 +29,7 @@
|
|||||||
import {provide, ref} from "vue";
|
import {provide, ref} from "vue";
|
||||||
|
|
||||||
const props=defineProps<{
|
const props=defineProps<{
|
||||||
|
parentNode:HTMLElement,
|
||||||
title:string,
|
title:string,
|
||||||
component?:any,
|
component?:any,
|
||||||
props?:object,
|
props?:object,
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<template v-if="!isEdit">
|
||||||
|
{{showValue}}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-space size="mini">
|
||||||
|
<a-input-number v-model="editValue" :min="1" :max="100" :precision="0"></a-input-number>
|
||||||
|
<a-button type="text" @click="onClick">
|
||||||
|
<template #icon>
|
||||||
|
<icon-check></icon-check>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
<a-button type="text" @click="onBlur">
|
||||||
|
<template #icon>
|
||||||
|
<icon-close style="color: red"></icon-close>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import {ref, watch} from "vue";
|
||||||
|
import {apiIssue} from "../../../request/request";
|
||||||
|
|
||||||
|
const props=defineProps<{
|
||||||
|
isEdit:boolean,
|
||||||
|
showValue?:number,
|
||||||
|
projectIssueId:string
|
||||||
|
}>()
|
||||||
|
const emit=defineEmits<{
|
||||||
|
cancel:[],
|
||||||
|
update:[value:number]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const editValue=ref<number>()
|
||||||
|
|
||||||
|
const assignValue=()=>{
|
||||||
|
editValue.value=props.showValue
|
||||||
|
}
|
||||||
|
watch(()=>props.showValue,()=>{
|
||||||
|
assignValue()
|
||||||
|
},{
|
||||||
|
immediate:true,
|
||||||
|
deep:true
|
||||||
|
})
|
||||||
|
|
||||||
|
const onClick=async ()=>{
|
||||||
|
let res=await apiIssue.editBasicField({
|
||||||
|
projectIssueId:props.projectIssueId,
|
||||||
|
manDay:editValue.value
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
emit("update",editValue.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBlur=()=>{
|
||||||
|
emit('cancel')
|
||||||
|
assignValue()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<template v-if="!isEdit">
|
||||||
|
<a-space wrap size="mini" v-if="(showValue as ICommon_Model_Plan[]).length>0">
|
||||||
|
<a-link href="javascript:void(0)" v-for="item in (showValue as ICommon_Model_Plan[])" :key="item.id" @click="onOpenPlan(item.id)">{{item.name}}</a-link>
|
||||||
|
</a-space>
|
||||||
|
<span v-else style="line-height: 30px;width: 100%;color: grey">{{$t("util.none")}}</span>
|
||||||
|
</template>
|
||||||
|
<a-row style="padding-right: 10px" v-else>
|
||||||
|
<a-space size="mini" wrap>
|
||||||
|
<a-tag v-for="(item,index) in (editValue as {id:string,name:string}[])" :closable="true" @close="onCloseLabelTag(index)" :key="item.id">
|
||||||
|
{{item.name}}
|
||||||
|
</a-tag>
|
||||||
|
<a-select v-model="addValue" allow-search @search="onSearchPlan" v-if="showInput" @change="onAddChange">
|
||||||
|
<a-option v-for="item in labelList" :label="item.name" :value="item.id"></a-option>
|
||||||
|
</a-select>
|
||||||
|
<a-tag v-else :style="{backgroundColor: 'var(--color-fill-2)',border: '1px dashed var(--color-fill-3)',cursor: 'pointer',}" @click="showInput=true">
|
||||||
|
<template #icon>
|
||||||
|
<icon-plus />
|
||||||
|
</template>
|
||||||
|
{{$t("util.add")}}
|
||||||
|
</a-tag>
|
||||||
|
<a-button type="text" @click="onClick">
|
||||||
|
<template #icon>
|
||||||
|
<icon-check></icon-check>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
<a-button type="text" @click="onBlur">
|
||||||
|
<template #icon>
|
||||||
|
<icon-close style="color: red"></icon-close>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import {inject, ref, watch} from "vue";
|
||||||
|
import {injectProjectInfo} from "../../../util/symbol";
|
||||||
|
import {apiPlan} from "../../../request/request";
|
||||||
|
import {DCSType} from "../../../../../../../common/types";
|
||||||
|
import {ICommon_Model_Plan} from "../../../../../../../common/model/plan";
|
||||||
|
import {EClient_EVENTBUS_TYPE, eventBus} from "@/business/common/event/event";
|
||||||
|
|
||||||
|
const props=defineProps<{
|
||||||
|
isEdit:boolean,
|
||||||
|
showValue?:DCSType<ICommon_Model_Plan>[],
|
||||||
|
projectIssueId:string
|
||||||
|
}>()
|
||||||
|
const emit=defineEmits<{
|
||||||
|
cancel:[],
|
||||||
|
update:[value:DCSType<ICommon_Model_Plan>[]]
|
||||||
|
}>()
|
||||||
|
const labelList=ref<{
|
||||||
|
id:string,
|
||||||
|
name:string
|
||||||
|
}[]>([])
|
||||||
|
const editValue=ref<{
|
||||||
|
id:string,
|
||||||
|
name:string
|
||||||
|
}[]>()
|
||||||
|
const showInput=ref(false)
|
||||||
|
const addValue=ref("")
|
||||||
|
const projectId=inject(injectProjectInfo).id;
|
||||||
|
|
||||||
|
const assignValue=()=>{
|
||||||
|
editValue.value=props.showValue.length>0?props.showValue.map(item=>{
|
||||||
|
return {
|
||||||
|
id:item.id,
|
||||||
|
name:item.name
|
||||||
|
}
|
||||||
|
}):[]
|
||||||
|
showInput.value=false
|
||||||
|
addValue.value=""
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(()=>props.showValue,()=>{
|
||||||
|
assignValue()
|
||||||
|
},{
|
||||||
|
immediate:true,
|
||||||
|
deep:true
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const onCloseLabelTag=(index:number)=>{
|
||||||
|
editValue.value.splice(index,1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAddChange=()=>{
|
||||||
|
let arr=editValue.value as {
|
||||||
|
id:string,
|
||||||
|
name:string
|
||||||
|
}[]
|
||||||
|
let index=labelList.value.findIndex((item)=>{
|
||||||
|
if(item.id==addValue.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
arr.push({
|
||||||
|
id:labelList.value[index].id,
|
||||||
|
name:labelList.value[index].name
|
||||||
|
})
|
||||||
|
addValue.value=""
|
||||||
|
showInput.value=false
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSearchPlan=async (keyword:string)=>{
|
||||||
|
let res=await apiPlan.listPlan({
|
||||||
|
projectId,
|
||||||
|
keyword:keyword,
|
||||||
|
page:0,
|
||||||
|
size:10
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
labelList.value=res.data.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClick=async ()=>{
|
||||||
|
let arr=editValue.value as {
|
||||||
|
id:string,
|
||||||
|
name:string
|
||||||
|
}[]
|
||||||
|
let arrId=Array.from(new Set(arr.map(item=>item.id)));
|
||||||
|
let res=await apiPlan.issuePlanEdit({
|
||||||
|
projectIssueId:props.projectIssueId,
|
||||||
|
planList:arrId
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
emit("update",res.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOpenPlan=(planId:string)=>{
|
||||||
|
eventBus.emit(EClient_EVENTBUS_TYPE.OPEN_PROJECT_PLAN_PROFILE,projectId,planId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBlur=()=>{
|
||||||
|
emit('cancel')
|
||||||
|
assignValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -7,5 +7,7 @@ export enum EClient_Field_Basic_Type {
|
|||||||
MODULE,
|
MODULE,
|
||||||
PRIORITY,
|
PRIORITY,
|
||||||
FIXVERSIONS,
|
FIXVERSIONS,
|
||||||
SPRINT
|
SPRINT,
|
||||||
|
MANDAY,
|
||||||
|
PLANS
|
||||||
}
|
}
|
@ -1,17 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<span style="display: inline-block;width: 100%;cursor: text" @mouseenter="onEnter" @mouseleave="onLeave" tabindex="-1" @focus="onFocus" ref="element">
|
<span style="display: inline-block;width: 100%;cursor: text" @mouseenter="onEnter" @mouseleave="onLeave" tabindex="-1"
|
||||||
<FieldEditBasicName :is-edit="isEdit" :show-value="showValue as string" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.NAME" @update="onUpdate" @cancel="onBlur"></FieldEditBasicName>
|
@focus="onFocus" ref="element">
|
||||||
<FieldEditBasicDescription :is-edit="isEdit" :show-value="showValue as string" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.DESCRIPTION" @update="onUpdate" @cancel="onBlur"></FieldEditBasicDescription>
|
<FieldEditBasicName :is-edit="isEdit" :show-value="showValue as string" :project-issue-id="projectIssueId"
|
||||||
<FieldEditBasicAssigner :is-edit="isEdit" :show-value="showValue as User" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.ASSIGNER" @update="onUpdate" @cancel="onBlur"></FieldEditBasicAssigner>
|
v-if="type==EClient_Field_Basic_Type.NAME" @update="onUpdate"
|
||||||
<FieldEditBasicReporter :is-edit="isEdit" :show-value="showValue as User" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.REPORTER" @update="onUpdate" @cancel="onBlur"></FieldEditBasicReporter>
|
@cancel="onBlur"></FieldEditBasicName>
|
||||||
<FieldEditBasicPriority :is-edit="isEdit" :show-value="showValue as ECommon_Model_Project_Issue_Priority" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.PRIORITY" @update="onUpdate" @cancel="onBlur"></FieldEditBasicPriority>
|
<FieldEditBasicDescription :is-edit="isEdit" :show-value="showValue as string" :project-issue-id="projectIssueId"
|
||||||
<FieldEditBasicLabel :is-edit="isEdit" :show-value="showValue as ICommon_Model_Project_Label[]" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.LABEL" @update="onUpdate" @cancel="onBlur"></FieldEditBasicLabel>
|
v-if="type==EClient_Field_Basic_Type.DESCRIPTION" @update="onUpdate"
|
||||||
<FieldEditBasicModule :is-edit="isEdit" :show-value="showValue as ICommon_Model_Project_Module[]" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.MODULE" @update="onUpdate" @cancel="onBlur"></FieldEditBasicModule>
|
@cancel="onBlur"></FieldEditBasicDescription>
|
||||||
<FieldEditBasicFixVersion :is-edit="isEdit" :show-value="showValue as DCSType<ICommon_Model_Project_Release>[]" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.FIXVERSIONS" @update="onUpdate" @cancel="onBlur"></FieldEditBasicFixVersion>
|
<FieldEditBasicAssigner :is-edit="isEdit" :show-value="showValue as User" :project-issue-id="projectIssueId"
|
||||||
|
v-if="type==EClient_Field_Basic_Type.ASSIGNER" @update="onUpdate"
|
||||||
|
@cancel="onBlur"></FieldEditBasicAssigner>
|
||||||
|
<FieldEditBasicReporter :is-edit="isEdit" :show-value="showValue as User" :project-issue-id="projectIssueId"
|
||||||
|
v-if="type==EClient_Field_Basic_Type.REPORTER" @update="onUpdate"
|
||||||
|
@cancel="onBlur"></FieldEditBasicReporter>
|
||||||
|
<FieldEditBasicPriority :is-edit="isEdit" :show-value="showValue as ECommon_Model_Project_Issue_Priority"
|
||||||
|
:project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.PRIORITY"
|
||||||
|
@update="onUpdate" @cancel="onBlur"></FieldEditBasicPriority>
|
||||||
|
<FieldEditBasicLabel :is-edit="isEdit" :show-value="showValue as ICommon_Model_Project_Label[]"
|
||||||
|
:project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.LABEL"
|
||||||
|
@update="onUpdate" @cancel="onBlur"></FieldEditBasicLabel>
|
||||||
|
<FieldEditBasicModule :is-edit="isEdit" :show-value="showValue as ICommon_Model_Project_Module[]"
|
||||||
|
:project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.MODULE"
|
||||||
|
@update="onUpdate" @cancel="onBlur"></FieldEditBasicModule>
|
||||||
|
<FieldEditBasicFixVersion :is-edit="isEdit" :show-value="showValue as DCSType<ICommon_Model_Project_Release>[]"
|
||||||
|
:project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.FIXVERSIONS"
|
||||||
|
@update="onUpdate" @cancel="onBlur"></FieldEditBasicFixVersion>
|
||||||
<FieldEditBasicSprint :is-edit="isEdit" :show-value="showValue as {
|
<FieldEditBasicSprint :is-edit="isEdit" :show-value="showValue as {
|
||||||
id:string,
|
id:string,
|
||||||
name:string
|
name:string
|
||||||
}" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.SPRINT" @update="onUpdate" @cancel="onBlur"></FieldEditBasicSprint>
|
}" :project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.SPRINT" @update="onUpdate"
|
||||||
|
@cancel="onBlur"></FieldEditBasicSprint>
|
||||||
|
<FieldEditBasicManDay :is-edit="isEdit" :show-value="showValue as number" :project-issue-id="projectIssueId"
|
||||||
|
v-if="type==EClient_Field_Basic_Type.MANDAY" @update="onUpdate"
|
||||||
|
@cancel="onBlur"></FieldEditBasicManDay>
|
||||||
|
<FieldEditBasicPlan :is-edit="isEdit" :show-value="showValue as DCSType<ICommon_Model_Plan>[]"
|
||||||
|
:project-issue-id="projectIssueId" v-if="type==EClient_Field_Basic_Type.PLANS"
|
||||||
|
@update="onUpdate" @cancel="onBlur"></FieldEditBasicPlan>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -35,78 +59,93 @@ import FieldEditBasicModule from "./basic/fieldEditBasicModule.vue";
|
|||||||
import FieldEditBasicFixVersion from "./basic/fieldEditBasicFixVersion.vue";
|
import FieldEditBasicFixVersion from "./basic/fieldEditBasicFixVersion.vue";
|
||||||
import FieldEditBasicSprint from "@/business/common/component/field/basic/fieldEditBasicSprint.vue";
|
import FieldEditBasicSprint from "@/business/common/component/field/basic/fieldEditBasicSprint.vue";
|
||||||
import {DCSType} from "../../../../../../common/types";
|
import {DCSType} from "../../../../../../common/types";
|
||||||
|
import FieldEditBasicManDay from "@/business/common/component/field/basic/fieldEditBasicManDay.vue";
|
||||||
|
import {ICommon_Model_Plan} from "../../../../../../common/model/plan";
|
||||||
|
import FieldEditBasicPlan from "@/business/common/component/field/basic/fieldEditBasicPlan.vue";
|
||||||
|
|
||||||
type User={
|
type User = {
|
||||||
id:string,
|
id: string,
|
||||||
organizationUserId?:string,
|
organizationUserId?: string,
|
||||||
photo?:string,
|
photo?: string,
|
||||||
nickname?:string
|
nickname?: string
|
||||||
}
|
}
|
||||||
type Value=string|ECommon_Model_Project_Issue_Priority|User|ICommon_Model_Project_Label[]|ICommon_Model_Project_Module[]|number|ICommon_Model_Project_Release[]
|
type Value =
|
||||||
type Props={
|
string
|
||||||
projectIssueId:string
|
| ECommon_Model_Project_Issue_Priority
|
||||||
|
| User
|
||||||
|
| ICommon_Model_Project_Label[]
|
||||||
|
| ICommon_Model_Project_Module[]
|
||||||
|
| number
|
||||||
|
| ICommon_Model_Project_Release[]
|
||||||
|
type Props = {
|
||||||
|
projectIssueId: string
|
||||||
} & (
|
} & (
|
||||||
{
|
{
|
||||||
type:EClient_Field_Basic_Type.NAME | EClient_Field_Basic_Type.DESCRIPTION,
|
type: EClient_Field_Basic_Type.NAME | EClient_Field_Basic_Type.DESCRIPTION,
|
||||||
value?:string
|
value?: string
|
||||||
} | {
|
} | {
|
||||||
type:EClient_Field_Basic_Type.PRIORITY,
|
type: EClient_Field_Basic_Type.PRIORITY,
|
||||||
value?:ECommon_Model_Project_Issue_Priority
|
value?: ECommon_Model_Project_Issue_Priority
|
||||||
} | {
|
} | {
|
||||||
type:EClient_Field_Basic_Type.ASSIGNER | EClient_Field_Basic_Type.REPORTER,
|
type: EClient_Field_Basic_Type.ASSIGNER | EClient_Field_Basic_Type.REPORTER,
|
||||||
value?:User
|
value?: User
|
||||||
} | {
|
} | {
|
||||||
type:EClient_Field_Basic_Type.LABEL,
|
type: EClient_Field_Basic_Type.LABEL,
|
||||||
value?:ICommon_Model_Project_Label[]
|
value?: ICommon_Model_Project_Label[]
|
||||||
} | {
|
} | {
|
||||||
type:EClient_Field_Basic_Type.MODULE,
|
type: EClient_Field_Basic_Type.MODULE,
|
||||||
value?:ICommon_Model_Project_Module[]
|
value?: ICommon_Model_Project_Module[]
|
||||||
} | {
|
} | {
|
||||||
type:EClient_Field_Basic_Type.FIXVERSIONS,
|
type: EClient_Field_Basic_Type.FIXVERSIONS,
|
||||||
value?:ICommon_Model_Project_Release[]
|
value?: ICommon_Model_Project_Release[]
|
||||||
} | {
|
} | {
|
||||||
type:EClient_Field_Basic_Type.SPRINT,
|
type: EClient_Field_Basic_Type.SPRINT,
|
||||||
value?:{
|
value?: {
|
||||||
id:string,
|
id: string,
|
||||||
name:string
|
name: string
|
||||||
|
}
|
||||||
|
} | {
|
||||||
|
type: EClient_Field_Basic_Type.MANDAY,
|
||||||
|
value?: number
|
||||||
|
} | {
|
||||||
|
type: EClient_Field_Basic_Type.PLANS,
|
||||||
|
value?: ICommon_Model_Plan[]
|
||||||
|
})
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const element = ref<HTMLSpanElement>(null)
|
||||||
|
const showValue = ref(props.value)
|
||||||
|
const permission = inject(injectProjectInfo).permission
|
||||||
|
const isEdit = ref(false)
|
||||||
|
watch(() => props.value, (newValue, oldValue) => {
|
||||||
|
showValue.value = props.value
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
})
|
||||||
|
const onEnter = (event: MouseEvent) => {
|
||||||
|
if (!isEdit.value) {
|
||||||
|
const ele = event.currentTarget as HTMLElement
|
||||||
|
ele.style.backgroundColor = "rgb(230,231,237)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
const onLeave = (event: MouseEvent) => {
|
||||||
const props=defineProps<Props>()
|
const ele = event.currentTarget as HTMLElement
|
||||||
const element=ref<HTMLSpanElement>(null)
|
ele.style.backgroundColor = ""
|
||||||
const showValue=ref(props.value)
|
|
||||||
const permission=inject(injectProjectInfo).permission
|
|
||||||
const isEdit=ref(false)
|
|
||||||
watch(()=>props.value,(newValue,oldValue)=>{
|
|
||||||
showValue.value=props.value
|
|
||||||
},{
|
|
||||||
immediate:true,
|
|
||||||
deep:true
|
|
||||||
})
|
|
||||||
const onEnter=(event:MouseEvent)=>{
|
|
||||||
if(!isEdit.value) {
|
|
||||||
const ele=event.currentTarget as HTMLElement
|
|
||||||
ele.style.backgroundColor="rgb(230,231,237)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const onLeave=(event:MouseEvent)=>{
|
const onFocus = async (event: MouseEvent) => {
|
||||||
const ele=event.currentTarget as HTMLElement
|
if (checkPermission(permission.value, Permission_Types.Project.EDIT)) {
|
||||||
ele.style.backgroundColor=""
|
isEdit.value = true
|
||||||
|
const ele = event.currentTarget as HTMLElement
|
||||||
|
ele.style.backgroundColor = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const onFocus=async (event:MouseEvent)=>{
|
const onBlur = async () => {
|
||||||
if(checkPermission(permission.value,Permission_Types.Project.EDIT)) {
|
isEdit.value = false
|
||||||
isEdit.value=true
|
|
||||||
const ele=event.currentTarget as HTMLElement
|
|
||||||
ele.style.backgroundColor=""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const onBlur=async ()=>{
|
|
||||||
isEdit.value=false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUpdate=(value)=>{
|
const onUpdate = (value) => {
|
||||||
showValue.value=value
|
showValue.value = value
|
||||||
isEdit.value=false
|
isEdit.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
792
code/client/src/business/common/component/gantt/gantt.vue
Normal file
792
code/client/src/business/common/component/gantt/gantt.vue
Normal file
@ -0,0 +1,792 @@
|
|||||||
|
<template>
|
||||||
|
<a-split min="700px" v-model:size="size" style="width: 100%;height: 100%;border: 1px solid lightgrey;box-sizing: border-box">
|
||||||
|
<template #first>
|
||||||
|
<div style="width: 100%;height: 100%;overflow: auto" ref="tableEle">
|
||||||
|
<a-table :columns="column" v-model:expanded-keys="expandedKeys" show-empty-tree hide-expand-button-on-empty :data="data" row-class="ganttRow" ref="dataEle" :bordered="{
|
||||||
|
headerCell:true,
|
||||||
|
bodyCell:true
|
||||||
|
}" :pagination="false" :scroll="{
|
||||||
|
y:'100%'
|
||||||
|
}" column-resizable stripe @cell-dblclick="onCellDbClick">
|
||||||
|
<template #type="{record}">
|
||||||
|
<slot name="type" :record="record as GanttDataItem"></slot>
|
||||||
|
</template>
|
||||||
|
<template #name="{record}">
|
||||||
|
<slot name="name" :record="record as GanttDataItem"></slot>
|
||||||
|
</template>
|
||||||
|
<template #manDay="{record}">
|
||||||
|
<slot name="manDay" :record="record as GanttDataItem"></slot>
|
||||||
|
</template>
|
||||||
|
<template #progress="{record}">
|
||||||
|
<slot name="progress" :record="record as GanttDataItem"></slot>
|
||||||
|
</template>
|
||||||
|
<template #depend="{record}">
|
||||||
|
<slot name="depend" :record="record as GanttDataItem"></slot>
|
||||||
|
</template>
|
||||||
|
<template #delay="{record}">
|
||||||
|
<slot name="delay" :record="record as GanttDataItem"></slot>
|
||||||
|
</template>
|
||||||
|
<template #startDate="{record}">
|
||||||
|
<slot name="startDate" :record="record as GanttDataItem"></slot>
|
||||||
|
</template>
|
||||||
|
<template #endDate="{record}">
|
||||||
|
<slot name="endDate" :record="record as GanttDataItem"></slot>
|
||||||
|
</template>
|
||||||
|
<template #operation="{record}">
|
||||||
|
<slot name="operation" :record="record as GanttDataItem"></slot>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #second>
|
||||||
|
<a-scrollbar style="width: 100%;height: 100%;overflow-x:auto;" :outer-style="{
|
||||||
|
height:'100%'
|
||||||
|
}" ref="scrollEle">
|
||||||
|
<div style="height: 82px;display: flex;">
|
||||||
|
<template v-if="type==='day'">
|
||||||
|
<div style="height: 100%;" v-for="item in monthList">
|
||||||
|
<div style="height: 41px;position: sticky;left: 0px;z-index: 1;display: flex;align-items: center;justify-content: center;width: 500px">
|
||||||
|
{{item.year()}}.{{item.month()+1}}
|
||||||
|
</div>
|
||||||
|
<div style="height: 41px;display: flex;box-sizing: border-box;border-top: 1px lightgrey solid;">
|
||||||
|
<div v-for="item1 in item.daysInMonth()" style="display: flex;justify-content: center;align-items: center;box-sizing: border-box" :style="{
|
||||||
|
borderRight:item1==item.daysInMonth()?'1px lightgray solid':'',
|
||||||
|
width:dayWidth+'px'
|
||||||
|
}">
|
||||||
|
{{item1}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type==='month'">
|
||||||
|
<div style="height: 100%;" v-for="item in yearList">
|
||||||
|
<div style="height: 41px;position: sticky;left: 0px;z-index: 1;display: flex;align-items: center;justify-content: center;width: 500px">
|
||||||
|
{{item.year()}}
|
||||||
|
</div>
|
||||||
|
<div style="height: 41px;display: flex;box-sizing: border-box;border-top: 1px lightgrey solid;">
|
||||||
|
<div v-for="n in 12" style="display: flex;justify-content: center;align-items: center;box-sizing: border-box" :style="{
|
||||||
|
borderRight:n<12?'1px lightgray solid':'',
|
||||||
|
width:dayWidth*item.clone().set('month',n-1).daysInMonth()+'px'
|
||||||
|
}">
|
||||||
|
{{n}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div style="height: 1px;background-color: lightgrey" :style="{
|
||||||
|
width:width+'px'
|
||||||
|
}"></div>
|
||||||
|
<div style="height: calc(100% - 83px);overflow-y:auto;position: relative" :style="{
|
||||||
|
width:width+'px'
|
||||||
|
}" ref="linesEle" @scroll="onScroll" @mousemove="onMouseMove" @mouseup="onLineMouseUp">
|
||||||
|
<div v-for="(line,index) in lines" style="width: 100%;position: relative;box-sizing: border-box;border-bottom: 1px lightgrey solid;height: 41px" :style="{
|
||||||
|
backgroundColor:index%2==0?'':'rgb(247,248,250)'
|
||||||
|
}" class="line">
|
||||||
|
<template v-if="line.type===ECommon_Model_Plan_Table.STAGE || line.type===ECommon_Model_Plan_Table.ISSUE">
|
||||||
|
<a-popover position="bottom">
|
||||||
|
<div style="height: 15px;top:13px;position: absolute;border-radius: 3px;z-index: 1;overflow: hidden;" :id="line.key" :style="{
|
||||||
|
left:line.left+'px',
|
||||||
|
width:line.width+'px',
|
||||||
|
resize: (line.type===ECommon_Model_Plan_Table.ISSUE && !line.hasChild)?'horizontal':'none',
|
||||||
|
cursor:'move',
|
||||||
|
...(line.showProgress!=null?{
|
||||||
|
background:`linear-gradient(to right,${line.color} 0,${line.color} ${line.showProgress.toFixed(0)+'%'},${line.colorUndone} ${line.showProgress.toFixed(0)+'%'},${line.colorUndone} 100%)`
|
||||||
|
}:{
|
||||||
|
backgroundColor:line.color
|
||||||
|
})
|
||||||
|
}" @mousedown="onLineMouseDown(line,$event)" ></div>
|
||||||
|
<template #content>
|
||||||
|
<slot name="shortView" :data="findObj(data,line.key)"></slot>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="line.type===ECommon_Model_Plan_Table.MILESTONE">
|
||||||
|
<div style="top:13px;position: absolute;border-radius: 3px;transform: rotate(45deg)" :style="{
|
||||||
|
left:line.left+(type==='day'?30:10)/4+'px',
|
||||||
|
width:type==='day'?'15px':'10px',
|
||||||
|
backgroundColor:line.color,
|
||||||
|
height: type==='day'?'15px':'10px'
|
||||||
|
}" :id="line.key"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-for="item in lines.filter(item=>item.type===ECommon_Model_Plan_Table.MILESTONE)" style="position: absolute;width: 3px;" :style="{
|
||||||
|
top:(item.parentKey?((lines.findIndex(obj=>obj.key===item.parentKey)+1)*41):0)+'px',
|
||||||
|
backgroundColor:item.color,
|
||||||
|
left:item.left+(type==='day'?30:10)-3+'px',
|
||||||
|
height:((lines.findIndex(obj=>obj.key===item.key)-(item.parentKey?lines.findIndex(obj=>obj.key===item.parentKey):-1))*41)+'px'
|
||||||
|
}"></div>
|
||||||
|
<div style="position: absolute;width: 1px;top: 0px;background-color: purple" :style="{
|
||||||
|
left:moment(startDate).startOf('day').diff(startDay,'day')*dayWidth+'px',
|
||||||
|
height:(lines.length*41)+'px'
|
||||||
|
}"></div>
|
||||||
|
</div>
|
||||||
|
</a-scrollbar>
|
||||||
|
</template>
|
||||||
|
</a-split>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch} from "vue";
|
||||||
|
import {GanttDataItem, GanttLine} from "./types";
|
||||||
|
import moment from "moment";
|
||||||
|
import {TableData} from "@arco-design/web-vue";
|
||||||
|
import {ECommon_Model_Plan_Table} from "../../../../../../common/model/plan_table";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
|
||||||
|
const emit=defineEmits<{
|
||||||
|
change:[item:GanttDataItem,originalStartDate:number,originalEndDate:number,originalDalay:number],
|
||||||
|
move:[key:string,destKey:string,type:"in"|"top"|"bottom"]
|
||||||
|
}>()
|
||||||
|
const props=defineProps<{
|
||||||
|
data:GanttDataItem[]
|
||||||
|
startDate:number,
|
||||||
|
type:"day"|"month"
|
||||||
|
}>()
|
||||||
|
const tableEle=ref<HTMLElement>()
|
||||||
|
const tableScrollEle=ref<HTMLElement>()
|
||||||
|
const scrollEle=ref()
|
||||||
|
const linesEle=ref<HTMLElement>()
|
||||||
|
const isRightScrollByCode=ref(true)
|
||||||
|
const isLeftScrollByCode=ref(true)
|
||||||
|
let selectedItem:GanttDataItem=null
|
||||||
|
let selectType:"move"|"adjust"
|
||||||
|
let selectedElement:HTMLElement
|
||||||
|
let selectedStartDate:number,selectedDependEndDate:number
|
||||||
|
let originalStartDate:number,originalEndDate:number,originalDalay:number
|
||||||
|
let offsetX=0
|
||||||
|
const dayWidth=ref(0)
|
||||||
|
let tipElement:HTMLElement=null
|
||||||
|
const {t}=useI18n()
|
||||||
|
let dragInfo:{
|
||||||
|
dropAction:"in"|"top"|"bottom",
|
||||||
|
containerElement:HTMLElement,
|
||||||
|
key:string,
|
||||||
|
type:ECommon_Model_Plan_Table,
|
||||||
|
parentKey:string
|
||||||
|
}=null
|
||||||
|
const startMonth=computed(()=>{
|
||||||
|
return moment(props.startDate).startOf("month")
|
||||||
|
})
|
||||||
|
|
||||||
|
const startYear = computed(()=>{
|
||||||
|
return startMonth.value.clone().startOf("year")
|
||||||
|
})
|
||||||
|
|
||||||
|
const startDay=computed(()=>{
|
||||||
|
if(props.type==="day") {
|
||||||
|
return startMonth.value
|
||||||
|
} else if(props.type==="month") {
|
||||||
|
return startYear.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const endYear=computed(()=>{
|
||||||
|
return endMonth.value.clone().endOf("year")
|
||||||
|
})
|
||||||
|
|
||||||
|
const yearList=computed(()=>{
|
||||||
|
let ret=[startYear.value]
|
||||||
|
while (1) {
|
||||||
|
let obj=ret.at(-1).clone().add(1,"year")
|
||||||
|
if(obj.year()<=endYear.value.year()) {
|
||||||
|
ret.push(obj)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
})
|
||||||
|
|
||||||
|
const endMonth=computed(()=>{
|
||||||
|
let max=0;
|
||||||
|
function _max(data:GanttDataItem[]) {
|
||||||
|
for(let obj of data) {
|
||||||
|
if(obj.endDate>max) {
|
||||||
|
max=obj.endDate
|
||||||
|
}
|
||||||
|
if(obj.children?.length>0) {
|
||||||
|
_max(obj.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(props.data?.length>0) {
|
||||||
|
_max(props.data)
|
||||||
|
return moment(max).add(1,"month").endOf("month")
|
||||||
|
} else {
|
||||||
|
return moment(props.startDate).endOf("date")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const width=computed(()=>{
|
||||||
|
if(props.type==="day") {
|
||||||
|
return monthList.value.reduce((previousValue, currentValue) => {
|
||||||
|
return previousValue+currentValue.daysInMonth()*dayWidth.value
|
||||||
|
},0)
|
||||||
|
} else if(props.type==="month") {
|
||||||
|
return yearList.value.reduce((previousValue, currentValue) => {
|
||||||
|
return previousValue+currentValue.clone().endOf("year").dayOfYear()*dayWidth.value
|
||||||
|
},0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const monthList=computed(()=>{
|
||||||
|
let ret=[startMonth.value]
|
||||||
|
let obj=ret.at(-1)
|
||||||
|
while(true) {
|
||||||
|
obj=obj.clone().add(1,"month")
|
||||||
|
if(obj.year()<endMonth.value.year() || (obj.year()==endMonth.value.year() && obj.month()<=endMonth.value.month())) {
|
||||||
|
ret.push(obj)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
})
|
||||||
|
const expandedKeys=ref([])
|
||||||
|
|
||||||
|
const column=[
|
||||||
|
{
|
||||||
|
title:t("util.name"),
|
||||||
|
slotName:"name",
|
||||||
|
headerCellStyle:{
|
||||||
|
height:"82px",
|
||||||
|
width:"200px",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
},
|
||||||
|
bodyCellStyle:{
|
||||||
|
overflow:"hidden",
|
||||||
|
width:"200px",
|
||||||
|
textOverflow:"ellipsis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:t("util.type"),
|
||||||
|
slotName:"type",
|
||||||
|
headerCellStyle:{
|
||||||
|
height:"82px",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:t("util.manDay"),
|
||||||
|
slotName: "manDay",
|
||||||
|
headerCellStyle:{
|
||||||
|
width:"60px",
|
||||||
|
height:"82px",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
},
|
||||||
|
bodyCellStyle:{
|
||||||
|
width:"60px",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:t("util.progress"),
|
||||||
|
slotName: "progress",
|
||||||
|
headerCellStyle:{
|
||||||
|
height:"82px",
|
||||||
|
width:"80px",
|
||||||
|
wordBreak:"break-all",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
},
|
||||||
|
bodyCellStyle:{
|
||||||
|
width:"80px",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow:"ellipsis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:t("util.depend"),
|
||||||
|
slotName: "depend",
|
||||||
|
headerCellStyle:{
|
||||||
|
height:"82px",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
},
|
||||||
|
bodyCellStyle:{
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow:"ellipsis",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:t("util.delay"),
|
||||||
|
slotName: "delay",
|
||||||
|
headerCellStyle:{
|
||||||
|
height:"82px",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:t("util.startDate"),
|
||||||
|
slotName: "startDate",
|
||||||
|
headerCellStyle:{
|
||||||
|
height:"82px",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("util.endDate"),
|
||||||
|
slotName: "endDate",
|
||||||
|
headerCellStyle:{
|
||||||
|
height:"82px",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("util.operation"),
|
||||||
|
slotName: "operation",
|
||||||
|
headerCellStyle:{
|
||||||
|
height:"82px",
|
||||||
|
overflow:"hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const size=ref(0.3)
|
||||||
|
|
||||||
|
const lines=ref<GanttLine[]>([])
|
||||||
|
watch(()=>props.type,()=>{
|
||||||
|
nextTick(()=>{
|
||||||
|
setTimeout(()=>{
|
||||||
|
if(lines.value.length>0) {
|
||||||
|
let ele=document.getElementById(lines.value[0].key)
|
||||||
|
if(ele) {
|
||||||
|
ele.scrollIntoView({
|
||||||
|
behavior:"smooth",
|
||||||
|
inline:"center"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},100)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
watch(()=>[expandedKeys,props.data,props.type],()=>{
|
||||||
|
function _handle(data:GanttDataItem[],parentKey:string,startDate:number) {
|
||||||
|
for(let i=0;i<data.length;i++) {
|
||||||
|
let obj=data[i]
|
||||||
|
if(obj.type===ECommon_Model_Plan_Table.STAGE || obj.type===ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
lines.value.push({
|
||||||
|
key:obj.key,
|
||||||
|
left:moment(obj.startDate).endOf("day").diff(startDay.value,"day")*dayWidth.value,
|
||||||
|
width:(moment(obj.endDate).endOf("day").diff(obj.startDate,"day")+1)*dayWidth.value,
|
||||||
|
color:obj.type===ECommon_Model_Plan_Table.ISSUE?"rgb(85,171,251)":"green",
|
||||||
|
colorUndone:obj.type===ECommon_Model_Plan_Table.ISSUE?"rgba(85,171,251,0.5)":"rgb(156,215,176)",
|
||||||
|
type:obj.type,
|
||||||
|
depend:obj.depend,
|
||||||
|
parentKey,
|
||||||
|
progress:obj.progress,
|
||||||
|
showProgress:obj.showProgress,
|
||||||
|
hasChild:obj.children?.length>0,
|
||||||
|
})
|
||||||
|
if(obj.children?.length>0) {
|
||||||
|
if(expandedKeys.value.includes(obj.key)) {
|
||||||
|
_handle(obj.children,obj.key,obj.startDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(obj.type===ECommon_Model_Plan_Table.MILESTONE) {
|
||||||
|
let maxEndDate=0;
|
||||||
|
for(let j=0;j<i;j++) {
|
||||||
|
maxEndDate=Math.max(data[j].endDate,maxEndDate)
|
||||||
|
}
|
||||||
|
lines.value.push({
|
||||||
|
key:obj.key,
|
||||||
|
left:moment(maxEndDate==0?startDate:maxEndDate).endOf("day").diff(startDay.value,"day")*dayWidth.value,
|
||||||
|
color:obj.completed?"#03ad03":"orange",
|
||||||
|
type:obj.type,
|
||||||
|
parentKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(props.type==="day") {
|
||||||
|
dayWidth.value=30
|
||||||
|
} else if(props.type==="month") {
|
||||||
|
dayWidth.value=10
|
||||||
|
}
|
||||||
|
lines.value=[]
|
||||||
|
_handle(props.data,null,props.startDate)
|
||||||
|
nextTick(()=>{
|
||||||
|
if(tableEle.value) {
|
||||||
|
let rows=document.querySelectorAll(".ganttRow")
|
||||||
|
if(rows.length>0) {
|
||||||
|
rows.forEach((value, key, parent) => {
|
||||||
|
let ele=value as HTMLElement
|
||||||
|
ele.draggable=true
|
||||||
|
let id=lines.value[key].key
|
||||||
|
let type=lines.value[key].type
|
||||||
|
let parentKey=lines.value[key].parentKey
|
||||||
|
ele.setAttribute("type",String(type))
|
||||||
|
ele.ondragstart=ev => {
|
||||||
|
dragInfo={} as any
|
||||||
|
dragInfo.containerElement=document.createElement("div")
|
||||||
|
dragInfo.containerElement.style.position="absolute"
|
||||||
|
dragInfo.containerElement.style.pointerEvents="none"
|
||||||
|
dragInfo.containerElement.style.zIndex="1000"
|
||||||
|
document.body.appendChild(dragInfo.containerElement)
|
||||||
|
dragInfo.key=id
|
||||||
|
dragInfo.type=type
|
||||||
|
dragInfo.parentKey=parentKey
|
||||||
|
ev.dataTransfer.setDragImage(ele,0,0)
|
||||||
|
}
|
||||||
|
ele.ondragover=ev => {
|
||||||
|
dragInfo.dropAction=null
|
||||||
|
dragInfo.containerElement.style.display="none"
|
||||||
|
if(id===dragInfo.key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let tempKey=parentKey
|
||||||
|
while (tempKey) {
|
||||||
|
if(tempKey===dragInfo.key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tempKey=lines.value.find(item=>item.key===tempKey)?.parentKey
|
||||||
|
}
|
||||||
|
ev.preventDefault()
|
||||||
|
ev.stopPropagation()
|
||||||
|
let currentEle=ev.currentTarget as HTMLElement
|
||||||
|
let rect=currentEle.getBoundingClientRect()
|
||||||
|
let tempEle=currentEle.querySelector(".arco-table-td-content") as HTMLElement
|
||||||
|
let firstTdRect=tempEle.getBoundingClientRect()
|
||||||
|
dragInfo.containerElement.style.backgroundColor=""
|
||||||
|
dragInfo.containerElement.style.border=""
|
||||||
|
if(dragInfo.type===ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
let isDragChildIssue=lines.value.find(item=>item.key===dragInfo.parentKey)?.type===ECommon_Model_Plan_Table.ISSUE
|
||||||
|
if(isDragChildIssue) {
|
||||||
|
if(type===ECommon_Model_Plan_Table.ISSUE && parentKey===dragInfo.parentKey) {
|
||||||
|
dragInfo.containerElement.style.backgroundColor="dodgerblue"
|
||||||
|
dragInfo.containerElement.style.height="2px"
|
||||||
|
dragInfo.containerElement.style.width=rect.width-(firstTdRect.left-rect.left)+"px"
|
||||||
|
dragInfo.containerElement.style.left=firstTdRect.left+"px"
|
||||||
|
dragInfo.containerElement.style.display="block"
|
||||||
|
if(ev.y-rect.top<10) {
|
||||||
|
dragInfo.dropAction="top"
|
||||||
|
dragInfo.containerElement.style.top=rect.top+"px"
|
||||||
|
} else if(rect.top+rect.height-ev.y<10) {
|
||||||
|
dragInfo.dropAction="bottom"
|
||||||
|
dragInfo.containerElement.style.top=rect.top+rect.height+"px"
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dragInfo.containerElement.style.display="block"
|
||||||
|
if(type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
if(ev.y-rect.top<10) {
|
||||||
|
dragInfo.dropAction="top"
|
||||||
|
dragInfo.containerElement.style.top=rect.top+"px"
|
||||||
|
dragInfo.containerElement.style.backgroundColor="dodgerblue"
|
||||||
|
dragInfo.containerElement.style.height="2px"
|
||||||
|
dragInfo.containerElement.style.width=rect.width-(firstTdRect.left-rect.left)+"px"
|
||||||
|
dragInfo.containerElement.style.left=firstTdRect.left+"px"
|
||||||
|
} else if(rect.top+rect.height-ev.y<10) {
|
||||||
|
dragInfo.dropAction="bottom"
|
||||||
|
dragInfo.containerElement.style.top=rect.top+rect.height+"px"
|
||||||
|
dragInfo.containerElement.style.backgroundColor="dodgerblue"
|
||||||
|
dragInfo.containerElement.style.height="2px"
|
||||||
|
dragInfo.containerElement.style.width=rect.width-(firstTdRect.left-rect.left)+"px"
|
||||||
|
dragInfo.containerElement.style.left=firstTdRect.left+"px"
|
||||||
|
} else {
|
||||||
|
let tempKey=dragInfo.parentKey
|
||||||
|
while (tempKey) {
|
||||||
|
if(tempKey===id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tempKey=lines.value.find(item=>item.key===tempKey)?.parentKey
|
||||||
|
}
|
||||||
|
dragInfo.dropAction="in"
|
||||||
|
dragInfo.containerElement.style.top=rect.top+"px"
|
||||||
|
dragInfo.containerElement.style.border="2px solid dodgerblue"
|
||||||
|
dragInfo.containerElement.style.height=rect.height+"px"
|
||||||
|
dragInfo.containerElement.style.width=rect.width+"px"
|
||||||
|
dragInfo.containerElement.style.left=rect.left+"px"
|
||||||
|
}
|
||||||
|
} else if(type===ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
let isChildIssue=lines.value.find(item=>item.key===parentKey)?.type===ECommon_Model_Plan_Table.ISSUE
|
||||||
|
if(isChildIssue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dragInfo.containerElement.style.backgroundColor="dodgerblue"
|
||||||
|
dragInfo.containerElement.style.height="2px"
|
||||||
|
dragInfo.containerElement.style.width=rect.width-(firstTdRect.left-rect.left)+"px"
|
||||||
|
dragInfo.containerElement.style.left=firstTdRect.left+"px"
|
||||||
|
if(ev.y-rect.top<10) {
|
||||||
|
dragInfo.dropAction="top"
|
||||||
|
dragInfo.containerElement.style.top=rect.top+"px"
|
||||||
|
} else if(rect.top+rect.height-ev.y<10) {
|
||||||
|
dragInfo.dropAction="bottom"
|
||||||
|
dragInfo.containerElement.style.top=rect.top+rect.height+"px"
|
||||||
|
} else {
|
||||||
|
dragInfo.containerElement.style.display="none"
|
||||||
|
}
|
||||||
|
} else if(type===ECommon_Model_Plan_Table.MILESTONE) {
|
||||||
|
dragInfo.containerElement.style.backgroundColor="dodgerblue"
|
||||||
|
dragInfo.containerElement.style.height="2px"
|
||||||
|
dragInfo.containerElement.style.width=rect.width-(firstTdRect.left-rect.left)+"px"
|
||||||
|
dragInfo.containerElement.style.left=firstTdRect.left+"px"
|
||||||
|
if(ev.y-rect.top<10) {
|
||||||
|
dragInfo.dropAction="top"
|
||||||
|
dragInfo.containerElement.style.top=rect.top+"px"
|
||||||
|
} else if(rect.top+rect.height-ev.y<10) {
|
||||||
|
dragInfo.dropAction="bottom"
|
||||||
|
dragInfo.containerElement.style.top=rect.top+rect.height+"px"
|
||||||
|
} else {
|
||||||
|
dragInfo.containerElement.style.display="none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ele.ondrop=ev => {
|
||||||
|
if(dragInfo.dropAction) {
|
||||||
|
emit("move",dragInfo.key,id,dragInfo.dropAction)
|
||||||
|
}
|
||||||
|
if(dragInfo?.containerElement) {
|
||||||
|
dragInfo.containerElement.remove()
|
||||||
|
dragInfo.containerElement=null
|
||||||
|
}
|
||||||
|
dragInfo=null;
|
||||||
|
}
|
||||||
|
ele.ondragend=ev => {
|
||||||
|
if(dragInfo?.containerElement) {
|
||||||
|
dragInfo.containerElement.remove()
|
||||||
|
dragInfo.containerElement=null
|
||||||
|
}
|
||||||
|
dragInfo=null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},{
|
||||||
|
immediate:true,
|
||||||
|
deep:true
|
||||||
|
})
|
||||||
|
|
||||||
|
const findObj=(data:GanttDataItem[],key:string):GanttDataItem=>{
|
||||||
|
for(let obj of data) {
|
||||||
|
if(obj.key===key) {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if(obj.children?.length>0) {
|
||||||
|
let ret=findObj(obj.children,key)
|
||||||
|
if(ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScroll=(ev:MouseEvent)=>{
|
||||||
|
if(isRightScrollByCode.value) {
|
||||||
|
isRightScrollByCode.value=false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let ele=ev.currentTarget as HTMLElement
|
||||||
|
if(tableScrollEle.value) {
|
||||||
|
isLeftScrollByCode.value=true
|
||||||
|
tableScrollEle.value.scrollTo({
|
||||||
|
top:ele.scrollTop,
|
||||||
|
behavior:"smooth"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCellDbClick=(record:TableData)=>{
|
||||||
|
if(lines.value.length>0) {
|
||||||
|
let ele=document.getElementById(record.key)
|
||||||
|
if(ele) {
|
||||||
|
ele.scrollIntoView({
|
||||||
|
behavior:"smooth",
|
||||||
|
inline:"start"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLineMouseDown=(line:GanttLine,event:MouseEvent)=>{
|
||||||
|
let ele=event.currentTarget as HTMLElement
|
||||||
|
let obj=findObj(props.data,line.key)
|
||||||
|
selectedItem=obj
|
||||||
|
selectedElement=ele
|
||||||
|
if(ele.offsetWidth-event.offsetX<20 && ele.style.resize!=="none") {
|
||||||
|
selectType="adjust"
|
||||||
|
} else {
|
||||||
|
selectType="move"
|
||||||
|
offsetX=event.offsetX
|
||||||
|
}
|
||||||
|
originalStartDate=selectedItem.startDate
|
||||||
|
originalEndDate=selectedItem.endDate
|
||||||
|
originalDalay=selectedItem.delay
|
||||||
|
if(line.parentKey) {
|
||||||
|
let obj=findObj(props.data,line.parentKey)
|
||||||
|
selectedStartDate=obj.startDate
|
||||||
|
if(line.depend) {
|
||||||
|
let obj=findObj(props.data,line.depend)
|
||||||
|
selectedDependEndDate=obj.endDate
|
||||||
|
} else {
|
||||||
|
selectedDependEndDate=obj.startDate
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedStartDate=props.startDate
|
||||||
|
selectedDependEndDate=props.startDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLineMouseUp=(event:MouseEvent)=>{
|
||||||
|
if(selectedItem) {
|
||||||
|
if(selectType==="adjust") {
|
||||||
|
let rect=selectedElement.getBoundingClientRect()
|
||||||
|
let days=Math.floor(rect.width/dayWidth.value)
|
||||||
|
let endDate=moment(selectedItem.startDate).clone().add(days,"day").toDate().getTime()
|
||||||
|
selectedItem.endDate=endDate
|
||||||
|
selectedItem.manDay=moment(endDate).endOf("day").diff(selectedItem.startDate,"day")+1
|
||||||
|
}
|
||||||
|
selectedElement=null
|
||||||
|
offsetX=0
|
||||||
|
emit("change",selectedItem,originalStartDate,originalEndDate,originalDalay)
|
||||||
|
selectedItem=null
|
||||||
|
originalStartDate=null
|
||||||
|
originalEndDate=null
|
||||||
|
if(tipElement) {
|
||||||
|
tipElement.remove()
|
||||||
|
tipElement=null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseMove=(event:MouseEvent)=>{
|
||||||
|
if(selectedItem) {
|
||||||
|
if(selectType==="move") {
|
||||||
|
let ele=event.currentTarget as HTMLElement
|
||||||
|
let rect=ele.getBoundingClientRect()
|
||||||
|
let left=event.x-offsetX-rect.x
|
||||||
|
let realLeft=left+ele.offsetLeft
|
||||||
|
let days=Math.floor(realLeft/dayWidth.value)
|
||||||
|
let startDate=startDay.value.clone().add(days,"day").startOf("day")
|
||||||
|
days=moment(selectedItem.endDate).endOf("day").diff(selectedItem.startDate,"day")
|
||||||
|
if(startDate.diff(selectedStartDate,"days")>=0) {
|
||||||
|
selectedItem.startDate=startDate.toDate().getTime()
|
||||||
|
}
|
||||||
|
selectedItem.endDate=moment(selectedItem.startDate).add(days,"days").toDate().getTime()
|
||||||
|
selectedItem.delay=moment(selectedItem.startDate).endOf("day").diff(selectedDependEndDate,"day")
|
||||||
|
let rectScroll=scrollEle.value.$el.getBoundingClientRect()
|
||||||
|
left=event.x-rectScroll.left
|
||||||
|
if(left<20) {
|
||||||
|
scrollEle.value.$el.children[0].scrollBy(-10,0)
|
||||||
|
} else if(left>rectScroll.width-20) {
|
||||||
|
scrollEle.value.$el.children[0].scrollBy(10,0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(props.type=="month") {
|
||||||
|
if(!tipElement) {
|
||||||
|
tipElement=document.createElement("div")
|
||||||
|
tipElement.style.position="absolute"
|
||||||
|
tipElement.style.background="rgba(0,0,255,0.1)"
|
||||||
|
tipElement.style.height=Math.floor(selectedElement.parentElement.offsetTop/41+1)*41+"px"
|
||||||
|
tipElement.style.top="0px"
|
||||||
|
linesEle.value.appendChild(tipElement)
|
||||||
|
let left=document.createElement("div")
|
||||||
|
left.style.paddingRight="10px"
|
||||||
|
left.setAttribute("start_date","")
|
||||||
|
left.style.boxSizing="border-box"
|
||||||
|
left.style.position="absolute"
|
||||||
|
left.style.height="20px"
|
||||||
|
left.style.left="-70px"
|
||||||
|
left.style.bottom="10px"
|
||||||
|
left.style.width="70px"
|
||||||
|
left.style.zIndex="100"
|
||||||
|
left.style.overflow="visible"
|
||||||
|
left.style.color="blue"
|
||||||
|
left.style.textAlign="right"
|
||||||
|
left.style.zIndex="1000"
|
||||||
|
tipElement.appendChild(left)
|
||||||
|
let right=document.createElement("div")
|
||||||
|
right.style.boxSizing="border-box"
|
||||||
|
right.style.paddingLeft="10px"
|
||||||
|
right.setAttribute("end_date","")
|
||||||
|
right.style.position="absolute"
|
||||||
|
right.style.height="20px"
|
||||||
|
right.style.right="-70px"
|
||||||
|
right.style.bottom="10px"
|
||||||
|
right.style.width="70px"
|
||||||
|
right.style.zIndex="100"
|
||||||
|
right.style.overflow="visible"
|
||||||
|
right.style.color="blue"
|
||||||
|
right.style.textAlign="left"
|
||||||
|
tipElement.appendChild(right)
|
||||||
|
}
|
||||||
|
nextTick(()=>{
|
||||||
|
tipElement.style.left=selectedElement.offsetLeft+"px";
|
||||||
|
tipElement.style.width=selectedElement.offsetWidth+"px";
|
||||||
|
(tipElement.querySelector("[start_date]") as HTMLElement).innerText=moment(selectedItem.startDate).format("MM-DD");
|
||||||
|
if(selectType==="move") {
|
||||||
|
(tipElement.querySelector("[end_date]") as HTMLElement).innerText=moment(selectedItem.endDate).format("MM-DD");
|
||||||
|
} else if(selectType==="adjust") {
|
||||||
|
(tipElement.querySelector("[end_date]") as HTMLElement).innerText=moment(selectedItem.startDate).add(Math.floor(selectedElement.offsetWidth/dayWidth.value),"day").format("MM-DD")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableScrollFunc=(ev:MouseEvent) => {
|
||||||
|
if(isLeftScrollByCode.value) {
|
||||||
|
isLeftScrollByCode.value=false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let ele = ev.currentTarget as HTMLElement
|
||||||
|
isRightScrollByCode.value=true
|
||||||
|
linesEle.value.scrollTo({
|
||||||
|
top: ele.scrollTop,
|
||||||
|
behavior: "smooth"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
tableScrollEle.value=tableEle.value.querySelectorAll(".arco-scrollbar-container.arco-table-body").item(0) as HTMLElement
|
||||||
|
if(tableScrollEle.value) {
|
||||||
|
tableScrollEle.value.addEventListener("scroll",tableScrollFunc)
|
||||||
|
}
|
||||||
|
if(lines.value.length>0) {
|
||||||
|
let ele=document.getElementById(lines.value[0].key)
|
||||||
|
if(ele) {
|
||||||
|
setTimeout(()=>{
|
||||||
|
ele.scrollIntoView({
|
||||||
|
behavior:"smooth",
|
||||||
|
inline:"center"
|
||||||
|
})
|
||||||
|
},100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(()=>{
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:global(.ganttRow) {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
cursor: move;
|
||||||
|
height: 41px;
|
||||||
|
}
|
||||||
|
.line:hover {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
:deep thead span {
|
||||||
|
padding: 0px 5px!important;
|
||||||
|
}
|
||||||
|
</style>
|
34
code/client/src/business/common/component/gantt/types.ts
Normal file
34
code/client/src/business/common/component/gantt/types.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {ECommon_Model_Plan_Table} from "../../../../../../common/model/plan_table";
|
||||||
|
|
||||||
|
export type GanttDataItem={
|
||||||
|
key:string,
|
||||||
|
type:ECommon_Model_Plan_Table,
|
||||||
|
uniqueId?:string, // issue
|
||||||
|
projectIssueId?:string // issue
|
||||||
|
name:string,
|
||||||
|
manDay?:number, //set :issue,calc:plan
|
||||||
|
depend?:string,
|
||||||
|
delay?:number,
|
||||||
|
progress?:number, //issue,plan
|
||||||
|
showProgress?:number
|
||||||
|
completed?:boolean, // milestone
|
||||||
|
startDate?:number, //plan,issue
|
||||||
|
endDate?:number,
|
||||||
|
children?:GanttDataItem[] //issue,plan
|
||||||
|
parentId:string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GanttLine={
|
||||||
|
key:string,
|
||||||
|
left:number,
|
||||||
|
width?:number,
|
||||||
|
color:string,
|
||||||
|
colorUndone?:string,
|
||||||
|
type:ECommon_Model_Plan_Table,
|
||||||
|
parentKey:string,
|
||||||
|
progress?:number,
|
||||||
|
showProgress?:number
|
||||||
|
depend?:string,
|
||||||
|
delay?:number,
|
||||||
|
hasChild?:boolean,
|
||||||
|
}
|
@ -26,6 +26,7 @@ export class MeetingClient {
|
|||||||
private defaultVideo=true
|
private defaultVideo=true
|
||||||
private defaultAudio=true
|
private defaultAudio=true
|
||||||
private defaultCameraId:string
|
private defaultCameraId:string
|
||||||
|
private defaultAudioId:string
|
||||||
onProducerStateChange:(state:"new"|"close"|"pause"|"resume", kind: mediaSoup.types.MediaKind, businessId:string,type:"data"|"camera"|"screen",stream?: MediaStream,producerId?:string)=>void
|
onProducerStateChange:(state:"new"|"close"|"pause"|"resume", kind: mediaSoup.types.MediaKind, businessId:string,type:"data"|"camera"|"screen",stream?: MediaStream,producerId?:string)=>void
|
||||||
onLocalProducerInit:(stream:MediaStream)=>void
|
onLocalProducerInit:(stream:MediaStream)=>void
|
||||||
onLocalProducerStart:(kind:MediaKind)=>void
|
onLocalProducerStart:(kind:MediaKind)=>void
|
||||||
@ -96,6 +97,13 @@ export class MeetingClient {
|
|||||||
name:item.label
|
name:item.label
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
static async enumAudioDevice() {
|
||||||
|
let ret=await navigator.mediaDevices.enumerateDevices()
|
||||||
|
return ret.filter(item=>item.kind==="audioinput").map(item=>({
|
||||||
|
id:item.deviceId,
|
||||||
|
name:item.label
|
||||||
|
}))
|
||||||
|
}
|
||||||
static async checkVideoStream(id:string) {
|
static async checkVideoStream(id:string) {
|
||||||
let stream=await navigator.mediaDevices.getUserMedia({
|
let stream=await navigator.mediaDevices.getUserMedia({
|
||||||
video:{
|
video:{
|
||||||
@ -110,7 +118,7 @@ export class MeetingClient {
|
|||||||
getRoomInfo() {
|
getRoomInfo() {
|
||||||
return this.roomInfo
|
return this.roomInfo
|
||||||
}
|
}
|
||||||
async join(roomId:string,extraData:any,isVideo=true,isAudio=true,cameraId?:string):Promise<{
|
async join(roomId:string,extraData:any,isVideo=true,isAudio=true,cameraId?:string,audioId?:string):Promise<{
|
||||||
success:boolean,
|
success:boolean,
|
||||||
msg?:string
|
msg?:string
|
||||||
}> {
|
}> {
|
||||||
@ -123,6 +131,7 @@ export class MeetingClient {
|
|||||||
this.defaultVideo=isVideo
|
this.defaultVideo=isVideo
|
||||||
this.defaultAudio=isAudio
|
this.defaultAudio=isAudio
|
||||||
this.defaultCameraId=cameraId
|
this.defaultCameraId=cameraId
|
||||||
|
this.defaultAudioId=audioId
|
||||||
let ret=await this.socket.emitWithAck("joinRoom",roomId,extraData)
|
let ret=await this.socket.emitWithAck("joinRoom",roomId,extraData)
|
||||||
if(ret) {
|
if(ret) {
|
||||||
this.roomInfo=ret;
|
this.roomInfo=ret;
|
||||||
@ -502,7 +511,10 @@ export class MeetingClient {
|
|||||||
const mediaConstraints:MediaStreamConstraints = {
|
const mediaConstraints:MediaStreamConstraints = {
|
||||||
audio: {
|
audio: {
|
||||||
echoCancellation:true,
|
echoCancellation:true,
|
||||||
noiseSuppression:true
|
noiseSuppression:true,
|
||||||
|
...(this.defaultAudioId && {
|
||||||
|
deviceId:this.defaultAudioId
|
||||||
|
})
|
||||||
},
|
},
|
||||||
video: (isVideo && this.defaultCameraId)?{
|
video: (isVideo && this.defaultCameraId)?{
|
||||||
deviceId:this.defaultCameraId
|
deviceId:this.defaultCameraId
|
||||||
|
@ -17,6 +17,7 @@ export enum EClient_EVENTBUS_TYPE {
|
|||||||
OPEN_PROJECT_BOARD_PROFILE="open_project_sprint_board",
|
OPEN_PROJECT_BOARD_PROFILE="open_project_sprint_board",
|
||||||
OPEN_PROJECT_SPRINT_KANBAN_PROFILE="open_project_sprint_kanban_profile",
|
OPEN_PROJECT_SPRINT_KANBAN_PROFILE="open_project_sprint_kanban_profile",
|
||||||
OPEN_PROJECT_RELEASE_PROFILE="open_project_release_profile",
|
OPEN_PROJECT_RELEASE_PROFILE="open_project_release_profile",
|
||||||
|
OPEN_PROJECT_PLAN_PROFILE="open_project_plan_profile",
|
||||||
OPEN_WIKI_PROFILE="open_wiki_profile",
|
OPEN_WIKI_PROFILE="open_wiki_profile",
|
||||||
OPEN_WIKI_ITEM="open_wiki_item",
|
OPEN_WIKI_ITEM="open_wiki_item",
|
||||||
UPDATE_USER_INFO="update_user_info",
|
UPDATE_USER_INFO="update_user_info",
|
||||||
@ -84,6 +85,7 @@ interface IClient_EventBus_Func {
|
|||||||
[EClient_EVENTBUS_TYPE.USER_LOGIN_EXPIRED]:()=>void
|
[EClient_EVENTBUS_TYPE.USER_LOGIN_EXPIRED]:()=>void
|
||||||
[EClient_EVENTBUS_TYPE.GUIDE]:()=>void
|
[EClient_EVENTBUS_TYPE.GUIDE]:()=>void
|
||||||
[EClient_EVENTBUS_TYPE.ORGANIZATION_REMOVE]:(organizationId:string)=>void
|
[EClient_EVENTBUS_TYPE.ORGANIZATION_REMOVE]:(organizationId:string)=>void
|
||||||
|
[EClient_EVENTBUS_TYPE.OPEN_PROJECT_PLAN_PROFILE]:(projectId:string,planId:string)=>void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IClient_EventBus_Emit_Func {
|
interface IClient_EventBus_Emit_Func {
|
||||||
|
@ -17,6 +17,7 @@ import finder from "../../../../../common/routes/finder"
|
|||||||
import notification from "../../../../../common/routes/notification"
|
import notification from "../../../../../common/routes/notification"
|
||||||
import board from "../../../../../common/routes/board"
|
import board from "../../../../../common/routes/board"
|
||||||
import tool from "../../../../../common/routes/tool"
|
import tool from "../../../../../common/routes/tool"
|
||||||
|
import plan from "../../../../../common/routes/plan"
|
||||||
import {Ref} from "vue";
|
import {Ref} from "vue";
|
||||||
import {SessionStorage} from "../storage/session";
|
import {SessionStorage} from "../storage/session";
|
||||||
import {DCSType} from "../../../../../common/types";
|
import {DCSType} from "../../../../../common/types";
|
||||||
@ -171,3 +172,4 @@ export const apiFinder=generatorApi(finder)
|
|||||||
export const apiNotification=generatorApi(notification)
|
export const apiNotification=generatorApi(notification)
|
||||||
export const apiBoard=generatorApi(board)
|
export const apiBoard=generatorApi(board)
|
||||||
export const apiTool=generatorApi(tool)
|
export const apiTool=generatorApi(tool)
|
||||||
|
export const apiPlan=generatorApi(plan)
|
@ -12,9 +12,10 @@ export interface IClient_SessionStorage_Type {
|
|||||||
}
|
}
|
||||||
organizationUserId:string,
|
organizationUserId:string,
|
||||||
firstShow:string,
|
firstShow:string,
|
||||||
|
wechatOpenId:string
|
||||||
}
|
}
|
||||||
|
|
||||||
let keys:(keyof IClient_SessionStorage_Type)[]=["organizationId","userId","userToken","imRecentList","organizationUserId","firstShow"]
|
let keys:(keyof IClient_SessionStorage_Type)[]=["organizationId","userId","userToken","imRecentList","organizationUserId","firstShow","wechatOpenId"]
|
||||||
|
|
||||||
export class SessionStorage {
|
export class SessionStorage {
|
||||||
static get<T extends keyof IClient_SessionStorage_Type>(name:T):IClient_SessionStorage_Type[T] {
|
static get<T extends keyof IClient_SessionStorage_Type>(name:T):IClient_SessionStorage_Type[T] {
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
<a-form :model="{}" layout="vertical">
|
<a-form :model="{}" layout="vertical">
|
||||||
<a-form-item :label="$t('util.calendar')">
|
<a-form-item :label="$t('util.calendar')">
|
||||||
<a-select size="small" v-model="searchForm.calendarId">
|
<a-select size="small" v-model="searchForm.calendarId">
|
||||||
<a-option value="all">{{$t("util.all")}}</a-option>
|
<a-option :value="''">{{$t("util.all")}}</a-option>
|
||||||
<a-option v-for="item in calendarList" :value="item.id">{{item.name}}</a-option>
|
<a-option v-for="item in calendarList" :value="item.id">{{item.name}}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -115,7 +115,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="$t('controller.app.calendar.calendar.startWeekDay')">
|
<a-form-item :label="$t('controller.app.calendar.calendar.startWeekDay')">
|
||||||
<a-select v-model="settingEdit.start_week_day">
|
<a-select v-model="settingEdit.start_week_day">
|
||||||
<a-option v-for="item in ECommon_Calendar_WeekDay" :value="item">{{calendarWeekDayName[item]}}</a-option>
|
<a-option v-for="item in ECommon_Calendar_WeekDay" :value="item">{{calendarWeekDayName[item]?$t("util."+calendarWeekDayName[item].toLowerCase()):undefined}}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@ -170,6 +170,7 @@ import {DCSType} from "../../../../../../common/types";
|
|||||||
const props=defineProps<{
|
const props=defineProps<{
|
||||||
calendarEventId?:string
|
calendarEventId?:string
|
||||||
}>()
|
}>()
|
||||||
|
const {t}=useI18n()
|
||||||
const appContext=getCurrentInstance().appContext
|
const appContext=getCurrentInstance().appContext
|
||||||
const root=getRootNavigatorRef()
|
const root=getRootNavigatorRef()
|
||||||
const isCalendarEventAddSimple=ref(false)
|
const isCalendarEventAddSimple=ref(false)
|
||||||
@ -193,14 +194,14 @@ const calendarEventList=ref<IClient_Calendar_Info[]>([])
|
|||||||
const organizationUserId=SessionStorage.get("organizationUserId")
|
const organizationUserId=SessionStorage.get("organizationUserId")
|
||||||
const searchForm=reactive({
|
const searchForm=reactive({
|
||||||
keyword:"",
|
keyword:"",
|
||||||
calendarId:"All",
|
calendarId:"",
|
||||||
startDate:null,
|
startDate:null,
|
||||||
endDate:null,
|
endDate:null,
|
||||||
location:""
|
location:""
|
||||||
})
|
})
|
||||||
const searchResultList=ref<DCSType<ICommon_Route_Res_Calendar_ListEvent_Item>[]>([])
|
const searchResultList=ref<DCSType<ICommon_Route_Res_Calendar_ListEvent_Item>[]>([])
|
||||||
let searchDebounce=null
|
let searchDebounce=null
|
||||||
const {t}=useI18n()
|
|
||||||
provide(injectCalendarSetting,setting)
|
provide(injectCalendarSetting,setting)
|
||||||
const startDay=computed(()=>{
|
const startDay=computed(()=>{
|
||||||
let start_week_day=setting.value?.start_week_day
|
let start_week_day=setting.value?.start_week_day
|
||||||
@ -548,7 +549,7 @@ const onSearch=async ()=>{
|
|||||||
let res=await apiCalendar.searchCalendarEvent({
|
let res=await apiCalendar.searchCalendarEvent({
|
||||||
location:searchForm.location,
|
location:searchForm.location,
|
||||||
keyword:searchForm.keyword,
|
keyword:searchForm.keyword,
|
||||||
...(searchForm.calendarId!=="All" && {
|
...(searchForm.calendarId!=="" && {
|
||||||
calendarId:searchForm.calendarId
|
calendarId:searchForm.calendarId
|
||||||
}),
|
}),
|
||||||
...(searchForm.startDate && {
|
...(searchForm.startDate && {
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<template v-if="data.recurring===ECommon_Calendar_Recurring_Type.WEEK">
|
<template v-if="data.recurring===ECommon_Calendar_Recurring_Type.WEEK">
|
||||||
{{$t("controller.app.calendar.calendarEventDateEdit.selectWeekday")}}:
|
{{$t("controller.app.calendar.calendarEventDateEdit.selectWeekday")}}:
|
||||||
<a-select size="small" style="width: 130px" v-model="recurryingWeekDay" @change="onChangeRecurry">
|
<a-select size="small" style="width: 130px" v-model="recurryingWeekDay" @change="onChangeRecurry">
|
||||||
<a-option v-for="item in ECommon_Calendar_WeekDay" :value="item">{{calendarWeekDayName[item]}}</a-option>
|
<a-option v-for="item in ECommon_Calendar_WeekDay" :value="item">{{calendarWeekDayName[item]?$t("util."+calendarWeekDayName[item].toLowerCase()):undefined}}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="data.recurring===ECommon_Calendar_Recurring_Type.MONTH">
|
<template v-else-if="data.recurring===ECommon_Calendar_Recurring_Type.MONTH">
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
<RichEditor v-model="content" v-if="type===ECommon_IM_Message_EntityType.USER" key="user" style="width: 100%;min-height: 50px" @upload-file="onUploadFile" :pop-menu-list="popMenuList" @pop-menu-click="onPopMenuClick" @custom-anchor-click="onCustomAnchorClick" ref="objEditorUser" @meta-enter="onSend"></RichEditor>
|
<RichEditor v-model="content" v-if="type===ECommon_IM_Message_EntityType.USER" key="user" style="width: 100%;min-height: 50px" @upload-file="onUploadFile" :pop-menu-list="popMenuList" @pop-menu-click="onPopMenuClick" @custom-anchor-click="onCustomAnchorClick" ref="objEditorUser" @meta-enter="onSend"></RichEditor>
|
||||||
<RichEditor v-model="content" v-else key="team" style="width: 100%" :pop-menu-list="popMenuList" @pop-menu-click="onPopMenuClick" @custom-anchor-click="onCustomAnchorClick" @quote-list="onQuoteList" ref="objEditorTeam" @meta-enter="onSend"></RichEditor>
|
<RichEditor v-model="content" v-else key="team" style="width: 100%" :pop-menu-list="popMenuList" @pop-menu-click="onPopMenuClick" @custom-anchor-click="onCustomAnchorClick" @quote-list="onQuoteList" ref="objEditorTeam" @meta-enter="onSend"></RichEditor>
|
||||||
</div>
|
</div>
|
||||||
<a-button type="primary" style="margin-top: 2px;height: auto;flex: 0 0 60px;border-radius: 5px"
|
<a-button type="primary" style="margin-top: 2px;height: 60px;flex: 0 0 60px;border-radius: 5px;align-self: end"
|
||||||
tabindex="-1" @click="onSend">
|
tabindex="-1" @click="onSend">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-send style="color: white;font-size: x-large"></icon-send>
|
<icon-send style="color: white;font-size: x-large"></icon-send>
|
||||||
|
@ -10,6 +10,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item field="audioId" :label="$t('util.selectAudio')">
|
||||||
|
<div style="width: 80%">
|
||||||
|
<a-select v-model="form.audioId" :placeholder="$t('placeholder.meetingPreview')">
|
||||||
|
<a-option v-for="item in audioList" :value="item.id">{{item.name}}</a-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item field="enableVideo" :label="$t('util.videoOn')">
|
<a-form-item field="enableVideo" :label="$t('util.videoOn')">
|
||||||
<a-switch v-model="form.enableVideo"></a-switch>
|
<a-switch v-model="form.enableVideo"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -32,12 +39,17 @@ const formEle=ref()
|
|||||||
const form=reactive({
|
const form=reactive({
|
||||||
cameraId:"",
|
cameraId:"",
|
||||||
enableVideo:true,
|
enableVideo:true,
|
||||||
enableAudio:true
|
enableAudio:true,
|
||||||
|
audioId:""
|
||||||
})
|
})
|
||||||
const cameraList=ref<{
|
const cameraList=ref<{
|
||||||
id:string,
|
id:string,
|
||||||
name:string
|
name:string
|
||||||
}[]>([])
|
}[]>([])
|
||||||
|
const audioList=ref<{
|
||||||
|
id:string,
|
||||||
|
name:string
|
||||||
|
}[]>([])
|
||||||
const stream=ref<MediaStream>()
|
const stream=ref<MediaStream>()
|
||||||
watch(()=>form.cameraId,async ()=>{
|
watch(()=>form.cameraId,async ()=>{
|
||||||
if(form.cameraId) {
|
if(form.cameraId) {
|
||||||
@ -47,8 +59,15 @@ watch(()=>form.cameraId,async ()=>{
|
|||||||
const getCameraList=async ()=>{
|
const getCameraList=async ()=>{
|
||||||
cameraList.value=(await MeetingClient.enumVideoDevice()).filter(item=>item.id!=="")
|
cameraList.value=(await MeetingClient.enumVideoDevice()).filter(item=>item.id!=="")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAudioList =async () => {
|
||||||
|
audioList.value=(await MeetingClient.enumAudioDevice()).filter(item=>item.id!=="")
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(()=>{
|
onBeforeMount(()=>{
|
||||||
getCameraList()
|
getCameraList()
|
||||||
|
getAudioList()
|
||||||
|
|
||||||
})
|
})
|
||||||
onBeforeUnmount(()=>{
|
onBeforeUnmount(()=>{
|
||||||
stream.value?.getVideoTracks()[0]?.stop()
|
stream.value?.getVideoTracks()[0]?.stop()
|
||||||
|
@ -250,7 +250,7 @@ const initMeeting=async ()=>{
|
|||||||
}
|
}
|
||||||
let preview:any=await Dialog.open(root.value,appContext,t("controller.app.meeting.meetingProfile.meetingPreview"),markRaw(MeetingPreview))
|
let preview:any=await Dialog.open(root.value,appContext,t("controller.app.meeting.meetingProfile.meetingPreview"),markRaw(MeetingPreview))
|
||||||
if(preview) {
|
if(preview) {
|
||||||
let ret=await meetingClient.join(props.meetingId,password,preview.enableVideo,preview.enableAudio,preview.cameraId)
|
let ret=await meetingClient.join(props.meetingId,password,preview.enableVideo,preview.enableAudio,preview.cameraId,preview.audioId)
|
||||||
if(!ret?.success) {
|
if(!ret?.success) {
|
||||||
Message.error(ret.msg)
|
Message.error(ret.msg)
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
|
@ -13,11 +13,11 @@
|
|||||||
</a-button>
|
</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-row>
|
</a-row>
|
||||||
<div style="width: 100%;margin-top: 20px;overflow: auto;height: calc(100% - 90px)">
|
<div style="width: 100%;margin-top: 20px;overflow: auto;height: calc(100% - 140px)">
|
||||||
<Card v-if="sprintInfo" :data="cardData" type="fixed" :gap="10" :rect="{
|
<Card v-if="sprintInfo" :data="cardData" type="fixed" :gap="10" :rect="{
|
||||||
width:230,
|
width:230,
|
||||||
height:swimLaneList.length*250+30
|
height:swimLaneList.length*250+30
|
||||||
}" readonly>
|
}" readonly style="overflow: visible">
|
||||||
<template #header="props">
|
<template #header="props">
|
||||||
<a-row style="height: 100%;display: flex;align-items: center;justify-content: center;padding: 0 5px;box-sizing: border-box">
|
<a-row style="height: 100%;display: flex;align-items: center;justify-content: center;padding: 0 5px;box-sizing: border-box">
|
||||||
<span style="color: grey">{{props.item.name}}</span>
|
<span style="color: grey">{{props.item.name}}</span>
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<a-dropdown trigger="hover">
|
<a-dropdown trigger="hover">
|
||||||
<a-button size="mini" type="primary" :status="item.status===ECommon_Model_Board_Sprint_Status.COMPLETED?'success':'normal'" @click="$event.stopPropagation(),$event.preventDefault()">
|
<a-button size="mini" type="primary" :status="item.status===ECommon_Model_Board_Sprint_Status.COMPLETED?'success':'normal'" @click="$event.stopPropagation(),$event.preventDefault()">
|
||||||
{{item.status===ECommon_Model_Board_Sprint_Status.NOTSTART?"Not Start":item.status===ECommon_Model_Board_Sprint_Status.STARTING?"Starting":"Completed"}}
|
{{item.status===ECommon_Model_Board_Sprint_Status.NOTSTART?$t("util.notStart"):item.status===ECommon_Model_Board_Sprint_Status.STARTING?$t("util.starting"):$t("util.complete")}}
|
||||||
</a-button>
|
</a-button>
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-doption size="mini" type="primary" status="success" @click="onStartSprint(item,$event)" v-if="item.status!==ECommon_Model_Board_Sprint_Status.STARTING">
|
<a-doption size="mini" type="primary" status="success" @click="onStartSprint(item,$event)" v-if="item.status!==ECommon_Model_Board_Sprint_Status.STARTING">
|
||||||
|
@ -38,6 +38,9 @@
|
|||||||
</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item :label="$t('util.manDay')" :extra="$t('help.manDay')">
|
||||||
|
<a-input-number v-model="form.manDay" :min="1" :max="100" :precision="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider orientation="left" v-if="valueList.length>0">Custom Fields</a-divider>
|
<a-divider orientation="left" v-if="valueList.length>0">Custom Fields</a-divider>
|
||||||
<a-form v-if="valueList.length>0" :model="formCustom" ref="eleFormCustom" layout="vertical">
|
<a-form v-if="valueList.length>0" :model="formCustom" ref="eleFormCustom" layout="vertical">
|
||||||
@ -88,7 +91,8 @@ const form=reactive({
|
|||||||
description:[],
|
description:[],
|
||||||
priority:ECommon_Model_Project_Issue_Priority.MEDIUM,
|
priority:ECommon_Model_Project_Issue_Priority.MEDIUM,
|
||||||
assigner:"",
|
assigner:"",
|
||||||
reporter:""
|
reporter:"",
|
||||||
|
manDay:1
|
||||||
})
|
})
|
||||||
const loading=ref(false)
|
const loading=ref(false)
|
||||||
const objEditor=ref<InstanceType<typeof RichEditor>>()
|
const objEditor=ref<InstanceType<typeof RichEditor>>()
|
||||||
@ -186,7 +190,8 @@ onDialogOk(dialogFuncGenerator({
|
|||||||
priority :form.priority,
|
priority :form.priority,
|
||||||
assignerId:form.assigner ,
|
assignerId:form.assigner ,
|
||||||
reporterId:form.reporter ,
|
reporterId:form.reporter ,
|
||||||
values:valueList.value.map(item=>item.fieldValue)
|
values:valueList.value.map(item=>item.fieldValue),
|
||||||
|
manDay:form.manDay
|
||||||
})
|
})
|
||||||
if(res?.code==0) {
|
if(res?.code==0) {
|
||||||
if(form.description.length>0) {
|
if(form.description.length>0) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="issueProfileDetail">
|
<div>
|
||||||
<a-collapse :default-active-key="['detail']">
|
<a-tabs type="rounded" size="small" class="issueProfileDetail">
|
||||||
<a-collapse-item key="detail" :header="$t('util.detail')">
|
<a-tab-pane key="detail" :title="$t('util.detail')">
|
||||||
<a-form layout="vertical" :model="{}">
|
<a-form layout="vertical" :model="{}">
|
||||||
<a-form-item :label="$t('util.issueType')">
|
<a-form-item :label="$t('util.issueType')">
|
||||||
{{info.issueType.name}}
|
{{info.issueType.name}}
|
||||||
@ -15,6 +15,9 @@
|
|||||||
<a-form-item :label="$t('util.priority')">
|
<a-form-item :label="$t('util.priority')">
|
||||||
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.PRIORITY" :value="info.priority as ECommon_Model_Project_Issue_Priority"></FieldEditBasic>
|
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.PRIORITY" :value="info.priority as ECommon_Model_Project_Issue_Priority"></FieldEditBasic>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item :label="$t('util.manDay')">
|
||||||
|
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.MANDAY" :value="info.man_day"></FieldEditBasic>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item :label="$t('util.module')">
|
<a-form-item :label="$t('util.module')">
|
||||||
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.MODULE" :value="moduleList"></FieldEditBasic>
|
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.MODULE" :value="moduleList"></FieldEditBasic>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -27,16 +30,19 @@
|
|||||||
<a-form-item :label="$t('util.sprint')">
|
<a-form-item :label="$t('util.sprint')">
|
||||||
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.SPRINT" :value="sprintInfo"></FieldEditBasic>
|
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.SPRINT" :value="sprintInfo"></FieldEditBasic>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item :label="$t('util.plan')">
|
||||||
|
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.PLANS" :value="planList"></FieldEditBasic>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-collapse-item>
|
</a-tab-pane>
|
||||||
<a-collapse-item key="more" :header="$t('util.more')" v-if="fieldList?.length>0">
|
<a-tab-pane key="more" :title="$t('util.more')" v-if="fieldList?.length>0">
|
||||||
<a-form layout="vertical" :model="{}" v-if="fieldList.length>0">
|
<a-form layout="vertical" :model="{}" v-if="fieldList.length>0">
|
||||||
<a-form-item v-for="item in fieldList" :label="item.nodeField.field.name" :key="item.issueFieldValue.id">
|
<a-form-item v-for="item in fieldList" :label="item.nodeField.field.name" :key="item.issueFieldValue.id">
|
||||||
<FieldEdit :item="item"></FieldEdit>
|
<FieldEdit :item="item"></FieldEdit>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-collapse-item>
|
</a-tab-pane>
|
||||||
</a-collapse>
|
</a-tabs>
|
||||||
<a-row style="margin-top: 10px;color: grey;font-size: 12px;line-height: 1.3;padding-left: 5px;margin-bottom: 10px">
|
<a-row style="margin-top: 10px;color: grey;font-size: 12px;line-height: 1.3;padding-left: 5px;margin-bottom: 10px">
|
||||||
{{$t("util.created")}} {{moment(info.created_time).format('YYYY-MM-DD HH:mm:ss')}}
|
{{$t("util.created")}} {{moment(info.created_time).format('YYYY-MM-DD HH:mm:ss')}}
|
||||||
</a-row>
|
</a-row>
|
||||||
@ -49,7 +55,7 @@ import {EClient_Field_Basic_Type} from "../../../../common/component/field/field
|
|||||||
import moment from "moment/moment";
|
import moment from "moment/moment";
|
||||||
import FieldEdit from "../../../../common/component/field/fieldEdit.vue";
|
import FieldEdit from "../../../../common/component/field/fieldEdit.vue";
|
||||||
import FieldEditBasic from "../../../../common/component/field/fieldEditBasic.vue";
|
import FieldEditBasic from "../../../../common/component/field/fieldEditBasic.vue";
|
||||||
import {apiBoard, apiIssue} from "../../../../common/request/request";
|
import {apiBoard, apiIssue, apiPlan} from "../../../../common/request/request";
|
||||||
import {
|
import {
|
||||||
ICommon_Route_Res_ProjectIssue_BasicInfo,
|
ICommon_Route_Res_ProjectIssue_BasicInfo,
|
||||||
ICommon_Route_Res_ProjectIssue_fieldsInfo
|
ICommon_Route_Res_ProjectIssue_fieldsInfo
|
||||||
@ -60,6 +66,7 @@ import {ICommon_Model_Project_Module} from "../../../../../../../common/model/pr
|
|||||||
import {ICommon_Model_Project_Release} from "../../../../../../../common/model/project_release";
|
import {ICommon_Model_Project_Release} from "../../../../../../../common/model/project_release";
|
||||||
import {DCSType} from "../../../../../../../common/types";
|
import {DCSType} from "../../../../../../../common/types";
|
||||||
import {ECommon_Model_Project_Issue_Priority} from "../../../../../../../common/model/project_issue";
|
import {ECommon_Model_Project_Issue_Priority} from "../../../../../../../common/model/project_issue";
|
||||||
|
import {ICommon_Model_Plan} from "../../../../../../../common/model/plan";
|
||||||
|
|
||||||
const props=defineProps<{
|
const props=defineProps<{
|
||||||
info:DCSType<ICommon_Route_Res_ProjectIssue_BasicInfo>,
|
info:DCSType<ICommon_Route_Res_ProjectIssue_BasicInfo>,
|
||||||
@ -72,6 +79,7 @@ const sprintInfo=ref<{
|
|||||||
id:string,
|
id:string,
|
||||||
name:string
|
name:string
|
||||||
}>()
|
}>()
|
||||||
|
const planList=ref<DCSType<ICommon_Model_Plan>[]>([])
|
||||||
const getReleaseList=async ()=>{
|
const getReleaseList=async ()=>{
|
||||||
let res=await apiIssue.releaseList({
|
let res=await apiIssue.releaseList({
|
||||||
projectIssueId:props.info.id
|
projectIssueId:props.info.id
|
||||||
@ -95,9 +103,19 @@ const getSprintInfo=async ()=>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPlanList=async ()=>{
|
||||||
|
let res=await apiPlan.issuePlanList({
|
||||||
|
projectIssueId:props.info.id
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
planList.value=res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(()=>{
|
onBeforeMount(()=>{
|
||||||
getReleaseList()
|
getReleaseList()
|
||||||
getSprintInfo()
|
getSprintInfo()
|
||||||
|
getPlanList()
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@ -114,4 +132,8 @@ onBeforeMount(()=>{
|
|||||||
.issueProfileDetail :deep .arco-form-item-label {
|
.issueProfileDetail :deep .arco-form-item-label {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.issueProfileDetail {
|
||||||
|
padding-left: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<a-form :model="form" ref="eleForm">
|
||||||
|
<a-form-item :label="$t('util.name')" field="name" required>
|
||||||
|
<a-input v-model="form.name"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="$t('util.startDate')" field="startTime">
|
||||||
|
<a-date-picker v-model="form.startTime"></a-date-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {apiPlan} from "../../../../common/request/request";
|
||||||
|
import {reactive, ref} from "vue";
|
||||||
|
import {onDialogOk} from "../../../../common/component/dialog/dialog";
|
||||||
|
import {dialogFuncGenerator} from "../../../../common/util/helper";
|
||||||
|
import {DCSType} from "../../../../../../../common/types";
|
||||||
|
import {ICommon_Model_Plan} from "../../../../../../../common/model/plan";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
const props=defineProps<{
|
||||||
|
type:"add"|"edit",
|
||||||
|
projectId?:string,
|
||||||
|
item?:DCSType<ICommon_Model_Plan>
|
||||||
|
}>()
|
||||||
|
const form=reactive({
|
||||||
|
name:props.type=="edit"?props.item.name:"",
|
||||||
|
startTime:props.type==="edit"?moment(props.item.start_time).toDate().getTime():moment().startOf("day").toDate().getTime()
|
||||||
|
})
|
||||||
|
const eleForm=ref(null)
|
||||||
|
|
||||||
|
onDialogOk(dialogFuncGenerator({
|
||||||
|
func:()=>{
|
||||||
|
return props.type=="add"?apiPlan.createPlan({
|
||||||
|
projectId:props.projectId,
|
||||||
|
name:form.name,
|
||||||
|
startTime:moment(form.startTime).startOf("day").toDate().getTime()
|
||||||
|
}):apiPlan.editPlan({
|
||||||
|
planId:props.item.id,
|
||||||
|
name:form.name,
|
||||||
|
startTime:moment(form.startTime).startOf("day").toDate().getTime()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
form:()=>{
|
||||||
|
return eleForm.value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<a-form auto-label-width :model="form" ref="eleForm">
|
||||||
|
<a-form-item field="projectIssueId" :label="$t('util.issue')" required v-if="type==='add'">
|
||||||
|
<a-select v-model="form.projectIssueId" @search="onSearch" allow-search>
|
||||||
|
<a-option v-for="item in issueList" :value="item.id">
|
||||||
|
{{item.project.keyword}}-{{item.unique_id}} {{item.name}}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="manDay" :label="$t('util.manDay')" v-if="type==='edit'">
|
||||||
|
<a-input-number v-model="form.manDay" :min="1" :max="100" :precision="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="dependId" :label="$t('util.depend')">
|
||||||
|
<a-select v-model="form.dependId" allow-clear>
|
||||||
|
<a-option v-for="item in dependList" :label="item.name" :value="item.key"></a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="delay" :label="$t('util.delay')">
|
||||||
|
<a-input-number :precision="0" :min="-100" :max="100" v-model="form.delay"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {GanttDataItem} from "@/business/common/component/gantt/types";
|
||||||
|
import {reactive, ref} from "vue";
|
||||||
|
import {onDialogOk} from "@/business/common/component/dialog/dialog";
|
||||||
|
import {dialogFuncGenerator} from "@/business/common/util/helper";
|
||||||
|
import {apiIssue, apiPlan} from "@/business/common/request/request";
|
||||||
|
import {DCSType} from "../../../../../../../common/types";
|
||||||
|
import {ICommon_Route_Res_Project_Issue_filter_Item} from "../../../../../../../common/routes/response";
|
||||||
|
|
||||||
|
const props=defineProps<{
|
||||||
|
projectId:string,
|
||||||
|
type:"add"|"edit",
|
||||||
|
item?:GanttDataItem,
|
||||||
|
planId:string,
|
||||||
|
dependList:GanttDataItem[],
|
||||||
|
parentId?:string
|
||||||
|
}>()
|
||||||
|
const form=reactive({
|
||||||
|
id:props.type==="edit"?props.item.key:"",
|
||||||
|
delay:props.type==="edit"?props.item.delay:0,
|
||||||
|
dependId:props.type==="edit"?props.item.depend:"",
|
||||||
|
manDay:props.type==="edit"?props.item.manDay:1,
|
||||||
|
projectIssueId:props.type==="edit"?props.item.projectIssueId:"",
|
||||||
|
})
|
||||||
|
const eleForm=ref()
|
||||||
|
const issueList=ref<DCSType<ICommon_Route_Res_Project_Issue_filter_Item>[]>([])
|
||||||
|
|
||||||
|
const onSearch=async (keyword:string)=>{
|
||||||
|
let res=await apiIssue.filter({
|
||||||
|
projectId:props.projectId,
|
||||||
|
name:keyword,
|
||||||
|
page:0,
|
||||||
|
size:20
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
issueList.value=res.data.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDialogOk(dialogFuncGenerator({
|
||||||
|
func:()=>{
|
||||||
|
return props.type=="add"?apiPlan.addIssue({
|
||||||
|
planId:props.planId,
|
||||||
|
projectIssueId:form.projectIssueId,
|
||||||
|
dependId:form.dependId,
|
||||||
|
delay:form.delay,
|
||||||
|
parentId:props.parentId
|
||||||
|
}):apiPlan.editIssue({
|
||||||
|
planItemId:form.id,
|
||||||
|
manDay:form.manDay,
|
||||||
|
delay:form.delay,
|
||||||
|
dependId:form.dependId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
form:()=>{
|
||||||
|
return eleForm.value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-alert closable style="margin-bottom: 10px">
|
||||||
|
{{$t("help.plan")}}
|
||||||
|
</a-alert>
|
||||||
|
<a-row>
|
||||||
|
<a-space wrap>
|
||||||
|
<a-input-search v-model="keyword" :placeholder="$t('placeholder.typePlanName')" style="width: 250px" @search="onSearch" v-if="checkPermission(permission,Permission_Types.Project.READ)"></a-input-search>
|
||||||
|
<a-button type="primary" @click="onCreate" v-if="checkPermission(permission,Permission_Types.Project.CREATE)">{{$t("util.create")}}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-row>
|
||||||
|
<a-table style="margin-top: 10px" :columns="columns" :data="planList" :pagination="pagination" @pageChange="onPageChange">
|
||||||
|
<template #name="{record}">
|
||||||
|
<a-link href="javascript:void(0)" @click="onProfile(record,$event)">{{record.name}}</a-link>
|
||||||
|
</template>
|
||||||
|
<template #startDate="{record}">
|
||||||
|
{{moment(record.start_time).format("YYYY-MM-DD")}}
|
||||||
|
</template>
|
||||||
|
<template #operation="{record}">
|
||||||
|
<a-space v-if="checkPermission(permission,Permission_Types.Project.EDIT)" wrap>
|
||||||
|
<a-button size="small" @click="onEdit(record)">{{$t("util.edit")}}</a-button>
|
||||||
|
<a-button size="small" status="danger" @click="onDelete(record)" v-if="checkPermission(permission,Permission_Types.Project.DELETE) || record?.created_by.id===myOrganizationUserId">{{$t("util.delete")}}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {getCurrentInstance, inject, markRaw, onBeforeMount, reactive, ref} from "vue";
|
||||||
|
import {injectProjectInfo} from "../../../../common/util/symbol";
|
||||||
|
import {apiPlan} from "../../../../common/request/request";
|
||||||
|
import {checkPermission, Permission_Types} from "../../../../../../../common/permission/permission";
|
||||||
|
import {Dialog} from "../../../../common/component/dialog/dialog";
|
||||||
|
import {
|
||||||
|
ETeamOS_Navigator_Action,
|
||||||
|
getCurrentNavigator,
|
||||||
|
getRootNavigatorRef,
|
||||||
|
onNavigatorShow
|
||||||
|
} from "../../../../../teamOS/common/component/navigator/navigator";
|
||||||
|
import {Message} from "@arco-design/web-vue";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
import {SessionStorage} from "@/business/common/storage/session";
|
||||||
|
import {DCSType} from "../../../../../../../common/types";
|
||||||
|
import {ICommon_Model_Plan} from "../../../../../../../common/model/plan";
|
||||||
|
import PlanEdit from "@/business/controller/app/project/plan/planEdit.vue";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
const objInject=inject(injectProjectInfo)
|
||||||
|
const projectId=objInject.id
|
||||||
|
const permission=objInject.permission
|
||||||
|
const key=objInject.key
|
||||||
|
const myOrganizationUserId=SessionStorage.get("organizationUserId")
|
||||||
|
const {t}=useI18n()
|
||||||
|
const columns=[
|
||||||
|
{
|
||||||
|
title:t("util.name"),
|
||||||
|
slotName:"name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:t("util.startDate"),
|
||||||
|
slotName:"startDate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:t("util.operation"),
|
||||||
|
slotName: "operation"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const pagination=reactive({
|
||||||
|
total:0,
|
||||||
|
current:1,
|
||||||
|
pageSize:10
|
||||||
|
})
|
||||||
|
const root=getRootNavigatorRef()
|
||||||
|
const appContext=getCurrentInstance().appContext
|
||||||
|
const keyword=ref("")
|
||||||
|
const planList=ref<DCSType<ICommon_Model_Plan[]>>([])
|
||||||
|
const navigator=getCurrentNavigator()
|
||||||
|
const search=async (page:number)=>{
|
||||||
|
let res=await apiPlan.listPlan({
|
||||||
|
projectId:projectId,
|
||||||
|
page:page-1,
|
||||||
|
size:10,
|
||||||
|
keyword:keyword.value
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
planList.value=res.data.data
|
||||||
|
pagination.total=res.data.count;
|
||||||
|
pagination.current=page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onSearch=()=>{
|
||||||
|
search(1)
|
||||||
|
}
|
||||||
|
const onPageChange=(page:number)=>{
|
||||||
|
search(page)
|
||||||
|
}
|
||||||
|
const onCreate=async ()=>{
|
||||||
|
let ret=await Dialog.open(root.value,appContext,t("util.add"),markRaw(PlanEdit),{
|
||||||
|
type:"add",
|
||||||
|
projectId:projectId
|
||||||
|
})
|
||||||
|
if(ret) {
|
||||||
|
search(pagination.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onEdit=async (item:DCSType<ICommon_Model_Plan>)=>{
|
||||||
|
let ret=await Dialog.open(root.value,appContext,t("util.edit"),markRaw(PlanEdit),{
|
||||||
|
type:"edit",
|
||||||
|
projectId:projectId,
|
||||||
|
item:item
|
||||||
|
})
|
||||||
|
if(ret) {
|
||||||
|
search(pagination.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onDelete=async (item:DCSType<ICommon_Model_Plan>)=>{
|
||||||
|
let ret=await Dialog.confirm(root.value,appContext,t("tip.deletePlan"))
|
||||||
|
if(ret) {
|
||||||
|
let res=await apiPlan.removePlan({
|
||||||
|
planId:item.id
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
Message.success(t("tip.deleteSuccess"))
|
||||||
|
search(pagination.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onProfile=async (item:DCSType<ICommon_Model_Plan>,event:MouseEvent)=>{
|
||||||
|
navigator.push("planProfile",{
|
||||||
|
planId:item.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onNavigatorShow(action => {
|
||||||
|
if(action===ETeamOS_Navigator_Action.POP || action===ETeamOS_Navigator_Action.BACK) {
|
||||||
|
search(pagination.current)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeMount(()=>{
|
||||||
|
search(pagination.current)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<a-form auto-label-width :model="form" ref="eleForm">
|
||||||
|
<a-form-item field="name" :label="$t('util.name')" required>
|
||||||
|
<a-input v-model="form.name"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {GanttDataItem} from "@/business/common/component/gantt/types";
|
||||||
|
import {reactive, ref} from "vue";
|
||||||
|
import {onDialogOk} from "@/business/common/component/dialog/dialog";
|
||||||
|
import {dialogFuncGenerator} from "@/business/common/util/helper";
|
||||||
|
import {apiPlan} from "@/business/common/request/request";
|
||||||
|
|
||||||
|
const props=defineProps<{
|
||||||
|
type:"add"|"edit",
|
||||||
|
item?:GanttDataItem,
|
||||||
|
planId:string,
|
||||||
|
parentId?:string
|
||||||
|
}>()
|
||||||
|
const form=reactive({
|
||||||
|
id:props.type==="edit"?props.item.key:"",
|
||||||
|
name:props.type==="edit"?props.item.name:"",
|
||||||
|
})
|
||||||
|
const eleForm=ref()
|
||||||
|
|
||||||
|
onDialogOk(dialogFuncGenerator({
|
||||||
|
func:()=>{
|
||||||
|
return props.type=="add"?apiPlan.createMileStone({
|
||||||
|
planId:props.planId,
|
||||||
|
name:form.name,
|
||||||
|
parentId:props.parentId
|
||||||
|
}):apiPlan.editMileStone({
|
||||||
|
planItemId:form.id,
|
||||||
|
name:form.name,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
form:()=>{
|
||||||
|
return eleForm.value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,584 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%;height: 100%;">
|
||||||
|
<a-row style="width: 100%;font-size: 30px;align-items: center;justify-content: space-between;padding: 0px 10px;box-sizing: border-box;height: 40px">
|
||||||
|
<a-space>
|
||||||
|
{{info?.name}}
|
||||||
|
<span style="color: grey;font-size: small;height:40px;display: flex;align-items: end;padding-bottom: 5px;box-sizing: border-box">
|
||||||
|
{{$t("util.startDate")}}:{{moment(info?.start_time).format("YYYY-MM-DD")}}
|
||||||
|
</span>
|
||||||
|
</a-space>
|
||||||
|
<a-space>
|
||||||
|
<a-select size="small" v-model="type">
|
||||||
|
<a-option value="day">{{$t("util.day")}}</a-option>
|
||||||
|
<a-option value="month">{{$t("util.month")}}</a-option>
|
||||||
|
</a-select>
|
||||||
|
<a-button size="small" type="primary" @click="onEditProfile">
|
||||||
|
{{$t("util.edit")}}
|
||||||
|
</a-button>
|
||||||
|
<a-dropdown-button type="primary" size="small" status="success">
|
||||||
|
{{$t("util.add")}}
|
||||||
|
<template #icon>
|
||||||
|
<icon-down></icon-down>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<a-doption @click="onAddStage(null)">{{$t("util.stage")}}</a-doption>
|
||||||
|
<a-doption @click="onAddMilestone(null)">{{$t("util.milestone")}}</a-doption>
|
||||||
|
<a-doption @click="onAddIssue(null)">{{$t("util.issue")}}</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dropdown-button>
|
||||||
|
<a-popover trigger="hover" position="right">
|
||||||
|
<icon-question-circle-fill style="color: rgb(35,110,184);font-size: large"></icon-question-circle-fill>
|
||||||
|
<template #content>
|
||||||
|
<span style="color: dodgerblue;white-space: pre-line">
|
||||||
|
{{$t("help.planItemType")}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</a-space>
|
||||||
|
</a-row>
|
||||||
|
<div style="height: 70px;margin-top: 20px;overflow-x: auto" v-if="milestoneList.length>0">
|
||||||
|
<a-steps label-placement="vertical" :current="-1" small>
|
||||||
|
<a-step v-for="item in milestoneList" :description="moment(item.startDate).format('YYYY-MM-DD')">
|
||||||
|
{{item.name}}
|
||||||
|
<template #icon>
|
||||||
|
<icon-check style="color: #03ad03" v-if="item.completed"></icon-check>
|
||||||
|
<icon-close style="color: orange" v-else></icon-close>
|
||||||
|
</template>
|
||||||
|
</a-step>
|
||||||
|
</a-steps>
|
||||||
|
</div>
|
||||||
|
<div style="width: 100%;overflow: auto;margin-top: 20px" :style="{
|
||||||
|
height:milestoneList.length>0?'calc(100% - 150px)':'calc(100% - 60px)'
|
||||||
|
}">
|
||||||
|
<Gantt :data="data" :type="type" :start-date="moment(info.start_time).startOf('day').toDate().getTime()" v-if="info && plan" @change="onChange" @move="onMove">
|
||||||
|
<template #type="{record}">
|
||||||
|
<span :style="{
|
||||||
|
color:record.type===ECommon_Model_Plan_Table.MILESTONE?record.completed?typeMap[record.type].doneColor:typeMap[record.type].undoneColor:typeMap[record.type].color
|
||||||
|
}">{{typeMap[record.type].text}}</span>
|
||||||
|
</template>
|
||||||
|
<template #name="{record}">
|
||||||
|
<a-link style="box-sizing: border-box;height: 22px" @click="onOpenIssue(record.projectIssueId)" v-if="record.type===ECommon_Model_Plan_Table.ISSUE">
|
||||||
|
{{record.name}}
|
||||||
|
</a-link>
|
||||||
|
<template v-else>
|
||||||
|
{{record.name}}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #manDay="{record}">
|
||||||
|
<template v-if="record.type===ECommon_Model_Plan_Table.ISSUE || record.type===ECommon_Model_Plan_Table.STAGE">
|
||||||
|
{{moment(record.endDate).startOf("day").diff(record.startDate,"day")+1}}
|
||||||
|
<span v-if="record.type===ECommon_Model_Plan_Table.ISSUE && record.children?.length>0" style="color: dodgerblue">
|
||||||
|
({{record.manDay}})
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #progress="{record}">
|
||||||
|
<template v-if="record.type===ECommon_Model_Plan_Table.MILESTONE">
|
||||||
|
<icon-check style="color: #03ad03" v-if="record.completed"></icon-check>
|
||||||
|
<icon-close style="color: orange" v-else></icon-close>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{record.showProgress!=null?(record.showProgress.toFixed(0)+'%'):""}}
|
||||||
|
<span v-if="record.type===ECommon_Model_Plan_Table.ISSUE && record.children?.length>0" style="color: dodgerblue">
|
||||||
|
({{record.progress.toFixed(0)+"%"}})
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #depend="{record}">
|
||||||
|
<template v-if="record.type===ECommon_Model_Plan_Table.ISSUE || record.type===ECommon_Model_Plan_Table.STAGE">
|
||||||
|
{{record.depend?getNameWithKey(record.depend):""}}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #delay="{record}">
|
||||||
|
<template v-if="record.type===ECommon_Model_Plan_Table.ISSUE || record.type===ECommon_Model_Plan_Table.STAGE">
|
||||||
|
{{record.delay??""}}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #startDate="{record}">
|
||||||
|
{{moment(record.startDate).format("MM-DD")}}
|
||||||
|
</template>
|
||||||
|
<template #endDate="{record}">
|
||||||
|
{{moment(record.endDate).format("MM-DD")}}
|
||||||
|
</template>
|
||||||
|
<template #operation="{record}">
|
||||||
|
<a-dropdown trigger="hover">
|
||||||
|
<icon-more></icon-more>
|
||||||
|
<template #content>
|
||||||
|
<a-dsubmenu trigger="hover" v-if="record.type===ECommon_Model_Plan_Table.STAGE">
|
||||||
|
{{$t("util.add")}}
|
||||||
|
<template #content>
|
||||||
|
<a-doption @click="onAddStage(record)">{{$t("util.stage")}}</a-doption>
|
||||||
|
<a-doption @click="onAddMilestone(record)">{{$t("util.milestone")}}</a-doption>
|
||||||
|
<a-doption @click="onAddIssue(record)">{{$t("util.issue")}}</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dsubmenu>
|
||||||
|
<a-doption @click="onEdit(record)">{{$t("util.edit")}}</a-doption>
|
||||||
|
<a-doption @click="onEditProgress(record)" v-if="record.type===ECommon_Model_Plan_Table.ISSUE && record.progress!==0 && record.progress!==100">{{$t("util.progress")}}</a-doption>
|
||||||
|
<a-doption @click="onDelete(record)" v-if="findObj(data,record.parentId)?.type!==ECommon_Model_Plan_Table.ISSUE">{{$t("util.delete")}}</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template #shortView="{data}">
|
||||||
|
<a-descriptions :data="shortViewInfo(data)" size="small" :title="data.name" :column="1"></a-descriptions>
|
||||||
|
</template>
|
||||||
|
</Gantt>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {computed, getCurrentInstance, inject, markRaw, onBeforeMount, ref} from "vue";
|
||||||
|
import {DCSType} from "../../../../../../../common/types";
|
||||||
|
import {
|
||||||
|
ICommon_Route_Res_Plan_Info,
|
||||||
|
ICommon_Route_Res_Plan_Info_Item
|
||||||
|
} from "../../../../../../../common/routes/response";
|
||||||
|
import {apiPlan} from "@/business/common/request/request";
|
||||||
|
import Gantt from "@/business/common/component/gantt/gantt.vue";
|
||||||
|
import {GanttDataItem} from "@/business/common/component/gantt/types";
|
||||||
|
import {type} from "os";
|
||||||
|
import moment from "moment";
|
||||||
|
import {ECommon_Model_Plan_Table} from "../../../../../../../common/model/plan_table";
|
||||||
|
import {injectProjectInfo} from "@/business/common/util/symbol";
|
||||||
|
import {ECommon_Model_Workflow_Node_Status} from "../../../../../../../common/model/workflow_node";
|
||||||
|
import plan from "../../../../../../../common/routes/plan";
|
||||||
|
import {Message} from "@arco-design/web-vue";
|
||||||
|
import {Dialog} from "@/business/common/component/dialog/dialog";
|
||||||
|
import {getRootNavigatorRef} from "@/teamOS/common/component/navigator/navigator";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
import PlanStageEdit from "@/business/controller/app/project/plan/planStageEdit.vue";
|
||||||
|
import PlanMilestoneEdit from "@/business/controller/app/project/plan/planMilestoneEdit.vue";
|
||||||
|
import PlanIssueEdit from "@/business/controller/app/project/plan/planIssueEdit.vue";
|
||||||
|
import {EClient_EVENTBUS_TYPE, eventBus} from "@/business/common/event/event";
|
||||||
|
import PlanProgressEdit from "@/business/controller/app/project/plan/planProgressEdit.vue";
|
||||||
|
import PlanEdit from "@/business/controller/app/project/plan/planEdit.vue";
|
||||||
|
|
||||||
|
const props=defineProps<{
|
||||||
|
planId:string
|
||||||
|
}>()
|
||||||
|
const info=ref<DCSType<ICommon_Route_Res_Plan_Info>>()
|
||||||
|
const data=ref<GanttDataItem[]>([])
|
||||||
|
const type=ref<"day"|"month">("month")
|
||||||
|
const projectKey=inject(injectProjectInfo).key
|
||||||
|
const root=getRootNavigatorRef()
|
||||||
|
const appContext=getCurrentInstance().appContext
|
||||||
|
const {t}=useI18n()
|
||||||
|
const typeMap={
|
||||||
|
[ECommon_Model_Plan_Table.ISSUE]:{
|
||||||
|
text:t("util.issue"),
|
||||||
|
color:"rgb(85, 171, 251)"
|
||||||
|
},
|
||||||
|
[ECommon_Model_Plan_Table.STAGE]:{
|
||||||
|
text:t("util.stage"),
|
||||||
|
color:"green"
|
||||||
|
},
|
||||||
|
[ECommon_Model_Plan_Table.MILESTONE]:{
|
||||||
|
text:t("util.milestone"),
|
||||||
|
undoneColor:"orange",
|
||||||
|
doneColor:"#03ad03"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getInfo=async ()=>{
|
||||||
|
let res=await apiPlan.info({
|
||||||
|
planId:props.planId
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
info.value=res.data
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const milestoneList=computed(()=>{
|
||||||
|
return getMilestoneList(data.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getMilestoneList=(data:GanttDataItem[])=>{
|
||||||
|
let ret:GanttDataItem[]=[]
|
||||||
|
for(let obj of data) {
|
||||||
|
if(obj.type===ECommon_Model_Plan_Table.MILESTONE) {
|
||||||
|
ret.push(obj)
|
||||||
|
}
|
||||||
|
if(obj.children?.length>0) {
|
||||||
|
ret=ret.concat(getMilestoneList(obj.children))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshData=()=>{
|
||||||
|
data.value=[]
|
||||||
|
handleInfo(info.value.data,data.value)
|
||||||
|
calcItem(data.value,moment(info.value.start_time).startOf("day").toDate().getTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInfo=(list:DCSType<ICommon_Route_Res_Plan_Info_Item>[],ganttData:GanttDataItem[])=>{
|
||||||
|
for(let obj of list) {
|
||||||
|
let ganttItem:GanttDataItem={
|
||||||
|
type:obj.type,
|
||||||
|
name:obj.type===ECommon_Model_Plan_Table.ISSUE?(projectKey.value+"-"+obj.issue.unique_id+" "+obj.issue.name):obj.name,
|
||||||
|
progress:(()=>{
|
||||||
|
if(obj.type===ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
if(obj.workflow.status===ECommon_Model_Workflow_Node_Status.NOTSTART) {
|
||||||
|
return 0
|
||||||
|
} else if(obj.workflow.status===ECommon_Model_Workflow_Node_Status.DONE) {
|
||||||
|
return 100
|
||||||
|
} else {
|
||||||
|
return obj.progress??50
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
delay:obj.delay??0,
|
||||||
|
key:obj.id,
|
||||||
|
depend:obj.depend_id,
|
||||||
|
manDay:obj.type===ECommon_Model_Plan_Table.ISSUE?obj.issue.man_day:1,
|
||||||
|
...(obj.type===ECommon_Model_Plan_Table.ISSUE && {
|
||||||
|
uniqueId:String(obj.issue.unique_id),
|
||||||
|
projectIssueId:obj.ref_id
|
||||||
|
}),
|
||||||
|
parentId:obj.parent_id
|
||||||
|
}
|
||||||
|
ganttData.push(ganttItem)
|
||||||
|
if(obj.children) {
|
||||||
|
ganttItem.children=[]
|
||||||
|
handleInfo(obj.children,ganttItem.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const findObj=(data:GanttDataItem[],key:string):GanttDataItem=>{
|
||||||
|
for(let obj of data) {
|
||||||
|
if(obj.key===key) {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if(obj.children?.length>0) {
|
||||||
|
let ret=findObj(obj.children,key)
|
||||||
|
if(ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNameWithKey=(key:string)=>{
|
||||||
|
let obj=findObj(data.value,key)
|
||||||
|
if(obj) {
|
||||||
|
if(obj.type===ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
return projectKey.value+"-"+obj.uniqueId
|
||||||
|
} else if(obj.type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
return obj.name
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getChildrenWithKey=(key:string):GanttDataItem[]=>{
|
||||||
|
function _find(data:GanttDataItem[]) {
|
||||||
|
for(let obj of data) {
|
||||||
|
if(obj.key===key) {
|
||||||
|
return obj.children
|
||||||
|
}
|
||||||
|
if(obj.children?.length>0) {
|
||||||
|
let ret=_find(obj.children)
|
||||||
|
if(ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(key===null) {
|
||||||
|
return data.value
|
||||||
|
} else {
|
||||||
|
return _find(data.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const calcItem=(data:GanttDataItem[],startDate:number):{
|
||||||
|
maxEndDate:number,
|
||||||
|
progressTotal:number
|
||||||
|
progressCount:number
|
||||||
|
}=>{
|
||||||
|
let maxEndDate=0,progress=0,progressCount=0
|
||||||
|
for(let i=0;i<data.length;i++) {
|
||||||
|
let obj=data[i]
|
||||||
|
if(obj.type===ECommon_Model_Plan_Table.ISSUE || obj.type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
if(obj.depend) {
|
||||||
|
let objDepend:GanttDataItem
|
||||||
|
for(let j=0;j<data.length;j++) {
|
||||||
|
if(data[j].key===obj.depend) {
|
||||||
|
objDepend=data[j]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(objDepend) {
|
||||||
|
obj.startDate=moment(objDepend.endDate).add(obj.delay??1,"day").toDate().getTime()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj.startDate=moment(startDate).add(obj.delay??0,"day").toDate().getTime()
|
||||||
|
}
|
||||||
|
if(obj.children?.length>0) {
|
||||||
|
let temp=calcItem(obj.children,obj.startDate)
|
||||||
|
let endDate=moment(obj.startDate).add(obj.manDay-1,"day").toDate().getTime()
|
||||||
|
obj.endDate=Math.max(temp.maxEndDate,endDate)
|
||||||
|
if(obj.type===ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
obj.showProgress=(temp.progressTotal+obj.progress)/(temp.progressCount+1)
|
||||||
|
} else {
|
||||||
|
obj.showProgress=temp.progressTotal/temp.progressCount
|
||||||
|
obj.manDay=moment(obj.endDate).diff(obj.startDate,"day")+1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj.showProgress=obj.progress
|
||||||
|
obj.endDate=moment(obj.startDate).add(obj.manDay-1,"day").toDate().getTime()
|
||||||
|
}
|
||||||
|
progressCount++;
|
||||||
|
progress+=obj.showProgress
|
||||||
|
} else {
|
||||||
|
let maxEndDate:number=0,isCompleted=true
|
||||||
|
for(let j=0;j<i;j++) {
|
||||||
|
maxEndDate=Math.max(data[j].endDate,maxEndDate)
|
||||||
|
if(data[j].type===ECommon_Model_Plan_Table.STAGE || data[j].type===ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
if(data[j].showProgress!==100) {
|
||||||
|
isCompleted=false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.completed=isCompleted
|
||||||
|
if(maxEndDate===0) {
|
||||||
|
obj.startDate=startDate
|
||||||
|
obj.endDate=startDate
|
||||||
|
} else {
|
||||||
|
obj.startDate=maxEndDate
|
||||||
|
obj.endDate=maxEndDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maxEndDate=Math.max(obj.endDate,maxEndDate)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
maxEndDate,
|
||||||
|
progressTotal:progress,
|
||||||
|
progressCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange=async (item:GanttDataItem,originalStartDate:number,originalEndDate:number,originalDalay:number) => {
|
||||||
|
if(item.type===ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
let res=await apiPlan.editIssue({
|
||||||
|
planItemId:item.key,
|
||||||
|
dependId:item.depend,
|
||||||
|
delay:item.delay,
|
||||||
|
manDay:item.manDay
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
info.value.data=res.data
|
||||||
|
refreshData()
|
||||||
|
} else {
|
||||||
|
Message.error(res.msg)
|
||||||
|
item.delay=originalDalay
|
||||||
|
item.manDay=moment(originalEndDate).endOf("day").diff(originalStartDate,"day")
|
||||||
|
}
|
||||||
|
} else if(item.type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
let res=await apiPlan.editStage({
|
||||||
|
planItemId:item.key,
|
||||||
|
delay:item.delay,
|
||||||
|
dependId:item.depend,
|
||||||
|
name:item.name
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
info.value.data=res.data
|
||||||
|
refreshData()
|
||||||
|
} else {
|
||||||
|
Message.error(res.msg)
|
||||||
|
item.delay=originalDalay
|
||||||
|
}
|
||||||
|
} else if(item.type===ECommon_Model_Plan_Table.MILESTONE) {
|
||||||
|
let res=await apiPlan.editMileStone({
|
||||||
|
planItemId:item.key,
|
||||||
|
name:item.name
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
info.value.data=res.data
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMove=async (key:string,destKey:string,type:"in"|"top"|"bottom") =>{
|
||||||
|
let res=await apiPlan.moveItem({
|
||||||
|
planItemId:key,
|
||||||
|
targetId:destKey,
|
||||||
|
action:type
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
info.value.data=res.data
|
||||||
|
refreshData()
|
||||||
|
} else {
|
||||||
|
Message.error(res.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAddStage=async (item:GanttDataItem)=>{
|
||||||
|
if(item===null || item.type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
let list=getChildrenWithKey(item?item.key:null)?.filter(obj=>obj.type!==ECommon_Model_Plan_Table.MILESTONE && obj.key!=item?.key)
|
||||||
|
let ret:any=await Dialog.open(root.value,appContext,t("util.add"),markRaw(PlanStageEdit),{
|
||||||
|
type:"add",
|
||||||
|
planId:props.planId,
|
||||||
|
dependList:list??[],
|
||||||
|
parentId:item?item.key:null
|
||||||
|
})
|
||||||
|
if(ret) {
|
||||||
|
info.value.data=ret.data
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAddMilestone=async (item:GanttDataItem)=>{
|
||||||
|
if(item===null || item.type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
let ret:any=await Dialog.open(root.value,appContext,t("util.add"),markRaw(PlanMilestoneEdit),{
|
||||||
|
type:"add",
|
||||||
|
planId:props.planId,
|
||||||
|
parentId:item?item.key:null
|
||||||
|
})
|
||||||
|
if(ret) {
|
||||||
|
info.value.data=ret.data
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAddIssue=async (item:GanttDataItem)=>{
|
||||||
|
if(item===null || item.type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
let list=getChildrenWithKey(item?item.key:null)?.filter(obj=>obj.type!==ECommon_Model_Plan_Table.MILESTONE && obj.key!=item?.key)
|
||||||
|
let ret:any=await Dialog.open(root.value,appContext,t("util.add"),markRaw(PlanIssueEdit),{
|
||||||
|
type:"add",
|
||||||
|
projectId:info.value.project_id,
|
||||||
|
planId:props.planId,
|
||||||
|
dependList:list??[],
|
||||||
|
parentId:item?item.key:null
|
||||||
|
})
|
||||||
|
if(ret) {
|
||||||
|
info.value.data=ret.data
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOpenIssue=(projectIssueId:string)=>{
|
||||||
|
eventBus.emit(EClient_EVENTBUS_TYPE.OPEN_PROJECT_ISSUE_PROFILE,info.value.project_id,projectIssueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEdit=async (item:GanttDataItem)=>{
|
||||||
|
let ret:any
|
||||||
|
let list=getChildrenWithKey(item?item.parentId:null)?.filter(obj=>obj.type!==ECommon_Model_Plan_Table.MILESTONE && obj.key!=item?.key)
|
||||||
|
if(item.type===ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
ret=await Dialog.open(root.value,appContext,t("util.edit"),markRaw(PlanIssueEdit),{
|
||||||
|
type:"edit",
|
||||||
|
projectId:info.value.project_id,
|
||||||
|
planId:props.planId,
|
||||||
|
dependList:list??[],
|
||||||
|
parentId:item?item.key:null,
|
||||||
|
item
|
||||||
|
})
|
||||||
|
} else if(item.type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
ret=await Dialog.open(root.value,appContext,t("util.edit"),markRaw(PlanStageEdit),{
|
||||||
|
type:"edit",
|
||||||
|
planId:props.planId,
|
||||||
|
dependList:list??[],
|
||||||
|
parentId:item?item.key:null,
|
||||||
|
item
|
||||||
|
})
|
||||||
|
|
||||||
|
}else if(item.type===ECommon_Model_Plan_Table.MILESTONE) {
|
||||||
|
ret=await Dialog.open(root.value,appContext,t("util.edit"),markRaw(PlanMilestoneEdit),{
|
||||||
|
type:"edit",
|
||||||
|
planId:props.planId,
|
||||||
|
parentId:item?item.key:null,
|
||||||
|
item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(ret) {
|
||||||
|
info.value.data=ret.data
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEditProgress=async (item:GanttDataItem)=>{
|
||||||
|
let ret:any=await Dialog.open(root.value,appContext,t("util.edit"),markRaw(PlanProgressEdit),{
|
||||||
|
planItemId:item.key,
|
||||||
|
progress:item.progress
|
||||||
|
})
|
||||||
|
if(ret) {
|
||||||
|
info.value.data=ret.data
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDelete=async (item:GanttDataItem)=>{
|
||||||
|
let ret=await Dialog.confirm(root.value,appContext,t("tip.deleteItem"))
|
||||||
|
if(ret) {
|
||||||
|
let res=await apiPlan.removeItem({
|
||||||
|
planItemId:item.key
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
info.value.data=res.data
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shortViewInfo=(item:GanttDataItem):{
|
||||||
|
label:string,
|
||||||
|
value:string
|
||||||
|
}[]=>{
|
||||||
|
if(item.type===ECommon_Model_Plan_Table.ISSUE || item.type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label:t("util.type"),
|
||||||
|
value:typeMap[ECommon_Model_Plan_Table.ISSUE].text
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("util.progress"),
|
||||||
|
value:item.showProgress.toFixed(0)+"%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("util.delay"),
|
||||||
|
value:String(item.delay)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("util.startDate"),
|
||||||
|
value:moment(item.startDate).format("YYYY-MM-DD")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("util.endDate"),
|
||||||
|
value:moment(item.endDate).format("YYYY-MM-DD")
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEditProfile=async ()=>{
|
||||||
|
let ret=await Dialog.open(root.value,appContext,t("util.edit"),markRaw(PlanEdit),{
|
||||||
|
type:"edit",
|
||||||
|
projectId:info.value.project_id,
|
||||||
|
item:info.value
|
||||||
|
})
|
||||||
|
if(ret) {
|
||||||
|
getInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(()=>{
|
||||||
|
getInfo()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep td a {
|
||||||
|
padding: 1px 0px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<a-form :model="form" ref="eleForm">
|
||||||
|
<a-form-item field="progress" :label="$t('util.progress')">
|
||||||
|
<a-slider v-model="form.progress" :min="1" :max="99"></a-slider>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {reactive, ref} from "vue";
|
||||||
|
import {onDialogOk} from "@/business/common/component/dialog/dialog";
|
||||||
|
import {dialogFuncGenerator} from "@/business/common/util/helper";
|
||||||
|
import {apiPlan} from "@/business/common/request/request";
|
||||||
|
|
||||||
|
const props=defineProps<{
|
||||||
|
planItemId:string,
|
||||||
|
progress:number
|
||||||
|
}>()
|
||||||
|
const form=reactive({
|
||||||
|
progress:props.progress
|
||||||
|
})
|
||||||
|
const eleForm=ref(null)
|
||||||
|
onDialogOk(dialogFuncGenerator({
|
||||||
|
func:()=>{
|
||||||
|
return apiPlan.editProgress({
|
||||||
|
progress:form.progress,
|
||||||
|
planItemId:props.planItemId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
form:()=>{
|
||||||
|
return eleForm.value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<a-form auto-label-width :model="form" ref="eleForm">
|
||||||
|
<a-form-item field="name" :label="$t('util.name')" required>
|
||||||
|
<a-input v-model="form.name"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="dependId" :label="$t('util.depend')">
|
||||||
|
<a-select allow-clear allow-search v-model="form.dependId">
|
||||||
|
<a-option v-for="item in dependList" :label="item.name" :value="item.key"></a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="delay" :label="$t('util.delay')">
|
||||||
|
<a-input-number :precision="0" :min="-100" :max="100" v-model="form.delay"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {GanttDataItem} from "@/business/common/component/gantt/types";
|
||||||
|
import {reactive, ref} from "vue";
|
||||||
|
import {onDialogOk} from "@/business/common/component/dialog/dialog";
|
||||||
|
import {dialogFuncGenerator} from "@/business/common/util/helper";
|
||||||
|
import {apiPlan} from "@/business/common/request/request";
|
||||||
|
|
||||||
|
const props=defineProps<{
|
||||||
|
type:"add"|"edit",
|
||||||
|
item?:GanttDataItem,
|
||||||
|
planId:string,
|
||||||
|
dependList:GanttDataItem[],
|
||||||
|
parentId?:string
|
||||||
|
}>()
|
||||||
|
const form=reactive({
|
||||||
|
id:props.type==="edit"?props.item.key:"",
|
||||||
|
name:props.type==="edit"?props.item.name:"",
|
||||||
|
delay:props.type==="edit"?props.item.delay:0,
|
||||||
|
dependId:props.type==="edit"?props.item.depend:""
|
||||||
|
})
|
||||||
|
const eleForm=ref()
|
||||||
|
|
||||||
|
onDialogOk(dialogFuncGenerator({
|
||||||
|
func:()=>{
|
||||||
|
return props.type=="add"?apiPlan.createStage({
|
||||||
|
planId:props.planId,
|
||||||
|
name:form.name,
|
||||||
|
dependId:form.dependId,
|
||||||
|
delay:form.delay,
|
||||||
|
parentId:props.parentId
|
||||||
|
}):apiPlan.editStage({
|
||||||
|
planItemId:form.id,
|
||||||
|
name:form.name,
|
||||||
|
delay:form.delay,
|
||||||
|
dependId:form.dependId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
form:()=>{
|
||||||
|
return eleForm.value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -6,6 +6,7 @@
|
|||||||
<a-menu-item key="issue">{{$t("util.issue")}}</a-menu-item>
|
<a-menu-item key="issue">{{$t("util.issue")}}</a-menu-item>
|
||||||
<a-menu-item key="board">{{$t("util.board")}}</a-menu-item>
|
<a-menu-item key="board">{{$t("util.board")}}</a-menu-item>
|
||||||
<a-menu-item key="release">{{$t("util.release")}}</a-menu-item>
|
<a-menu-item key="release">{{$t("util.release")}}</a-menu-item>
|
||||||
|
<a-menu-item key="plan">{{$t("util.plan")}}</a-menu-item>
|
||||||
<a-menu-item key="setting" v-if="checkPermission(permission,Permission_Types.Project.ADMIN)">{{$t("util.setting")}}</a-menu-item>
|
<a-menu-item key="setting" v-if="checkPermission(permission,Permission_Types.Project.ADMIN)">{{$t("util.setting")}}</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
@ -33,6 +34,8 @@ import {Message} from "@arco-design/web-vue";
|
|||||||
import BoardList from "@/business/controller/app/project/board/boardList.vue";
|
import BoardList from "@/business/controller/app/project/board/boardList.vue";
|
||||||
import BoardProfile from "@/business/controller/app/project/board/boardProfile.vue";
|
import BoardProfile from "@/business/controller/app/project/board/boardProfile.vue";
|
||||||
import ProjectHome from "@/business/controller/app/project/home/projectHome.vue";
|
import ProjectHome from "@/business/controller/app/project/home/projectHome.vue";
|
||||||
|
import PlanList from "@/business/controller/app/project/plan/planList.vue";
|
||||||
|
import PlanProfile from "@/business/controller/app/project/plan/planProfile.vue";
|
||||||
|
|
||||||
const props=defineProps<{
|
const props=defineProps<{
|
||||||
projectId:string
|
projectId:string
|
||||||
@ -50,7 +53,9 @@ let objComponent={
|
|||||||
releaseProfile:markRaw(ProjectReleaseProfile),
|
releaseProfile:markRaw(ProjectReleaseProfile),
|
||||||
board:markRaw(BoardList),
|
board:markRaw(BoardList),
|
||||||
boardProfile:markRaw(BoardProfile),
|
boardProfile:markRaw(BoardProfile),
|
||||||
home:markRaw(ProjectHome)
|
home:markRaw(ProjectHome),
|
||||||
|
plan:markRaw(PlanList),
|
||||||
|
planProfile:markRaw(PlanProfile)
|
||||||
}
|
}
|
||||||
const objProvide={
|
const objProvide={
|
||||||
id:props.projectId,
|
id:props.projectId,
|
||||||
@ -62,7 +67,8 @@ let objMeta=getCurrentNavigatorMeta()?.data as {
|
|||||||
projectIssueId?:string
|
projectIssueId?:string
|
||||||
projectReleaseId?:string,
|
projectReleaseId?:string,
|
||||||
boardSprintId?:string,
|
boardSprintId?:string,
|
||||||
boardId?:string
|
boardId?:string,
|
||||||
|
planId?:string
|
||||||
}
|
}
|
||||||
if(objMeta?.projectReleaseId) {
|
if(objMeta?.projectReleaseId) {
|
||||||
menuKey.value=["release"]
|
menuKey.value=["release"]
|
||||||
@ -70,6 +76,8 @@ if(objMeta?.projectReleaseId) {
|
|||||||
menuKey.value=["board"]
|
menuKey.value=["board"]
|
||||||
} else if(objMeta?.projectIssueId) {
|
} else if(objMeta?.projectIssueId) {
|
||||||
menuKey.value=["issue"]
|
menuKey.value=["issue"]
|
||||||
|
} else if(objMeta?.planId) {
|
||||||
|
menuKey.value=["plan"]
|
||||||
}
|
}
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
if(objMeta?.projectIssueId) {
|
if(objMeta?.projectIssueId) {
|
||||||
@ -94,6 +102,11 @@ onMounted(()=>{
|
|||||||
boardId:objMeta?.boardId,
|
boardId:objMeta?.boardId,
|
||||||
});
|
});
|
||||||
delete objMeta.boardId
|
delete objMeta.boardId
|
||||||
|
} else if(objMeta?.planId) {
|
||||||
|
eleNavigator.value.navigator.replaceRoot("planProfile",{
|
||||||
|
planId:objMeta?.planId,
|
||||||
|
});
|
||||||
|
delete objMeta.planId
|
||||||
} else {
|
} else {
|
||||||
eleNavigator.value.navigator.replaceRoot(type.value,null);
|
eleNavigator.value.navigator.replaceRoot(type.value,null);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<span v-if="record.notstart+record.inprogress+record.done==0">
|
<span v-if="record.notstart+record.inprogress+record.done==0">
|
||||||
{{$t("controller.app.project.release.projectReleaseList.noIssues")}}
|
{{$t("controller.app.project.release.projectReleaseList.noIssues")}}
|
||||||
</span>
|
</span>
|
||||||
<a-progress v-else :percent="record.done/(record.notstart+record.inprogress+record.done)" style="width: 180px" color="green"></a-progress>
|
<a-progress v-else :percent="Number((record.done/(record.notstart+record.inprogress+record.done)).toFixed(2))" style="width: 180px" color="green"></a-progress>
|
||||||
</template>
|
</template>
|
||||||
<template #startDate="{record}">
|
<template #startDate="{record}">
|
||||||
{{record.start_time}}
|
{{record.start_time}}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<span v-if="info?.notstart+info?.inprogress+info?.done==0">
|
<span v-if="info?.notstart+info?.inprogress+info?.done==0">
|
||||||
{{$t("controller.app.project.release.projectReleaseList.noIssues")}}
|
{{$t("controller.app.project.release.projectReleaseList.noIssues")}}
|
||||||
</span>
|
</span>
|
||||||
<a-progress v-else :percent="info?.done/(info?.notstart+info?.inprogress+info?.done)" style="width: 100%" color="green"></a-progress>
|
<a-progress v-else :percent="Number((info?.done/(info?.notstart+info?.inprogress+info?.done)).toFixed(2))" style="width: 100%" color="green"></a-progress>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="8" style="text-align: center">
|
<a-col :span="8" style="text-align: center">
|
||||||
<a-button type="outline" size="mini" @click="onProfile">{{$t("util.profile")}}</a-button>
|
<a-button type="outline" size="mini" @click="onProfile">{{$t("util.profile")}}</a-button>
|
||||||
|
@ -28,7 +28,7 @@ const columns=[
|
|||||||
dataIndex:"description"
|
dataIndex:"description"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title:"keyword",
|
title:t("util.keyword"),
|
||||||
dataIndex:"keyword"
|
dataIndex:"keyword"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -173,7 +173,7 @@ const pagination=reactive({
|
|||||||
const action=(item:ICommon_Route_Res_Organization_User_Item)=>{
|
const action=(item:ICommon_Route_Res_Organization_User_Item)=>{
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name:"role",
|
name:t("util.role"),
|
||||||
func:async ()=>{
|
func:async ()=>{
|
||||||
let ret=await Dialog.open(root.value,appContext,t("util.add"),markRaw(EditTeamMemberRole),{
|
let ret=await Dialog.open(root.value,appContext,t("util.add"),markRaw(EditTeamMemberRole),{
|
||||||
type:"edit",
|
type:"edit",
|
||||||
@ -186,7 +186,7 @@ const action=(item:ICommon_Route_Res_Organization_User_Item)=>{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name:"remove",
|
name:t("util.remove"),
|
||||||
func:async ()=>{
|
func:async ()=>{
|
||||||
let ret=await Dialog.confirm(root.value,appContext,t("tip.removeMember"))
|
let ret=await Dialog.confirm(root.value,appContext,t("tip.removeMember"))
|
||||||
if(ret) {
|
if(ret) {
|
||||||
|
@ -19,14 +19,11 @@
|
|||||||
<UserAvatar :organization-user-id="info.modified_by.organizationUserId" :name="info.modified_by.nickname" :photo="info.modified_by.photo" v-if="info"></UserAvatar>
|
<UserAvatar :organization-user-id="info.modified_by.organizationUserId" :name="info.modified_by.nickname" :photo="info.modified_by.photo" v-if="info"></UserAvatar>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-switch v-model="isWrite" v-if="checkPermission(permission,Permission_Types.Wiki.EDIT)">
|
<a-space>
|
||||||
<template #checked>
|
<span style="color: green">{{$t("util.preview")}}</span>
|
||||||
{{$t("util.edit")}}
|
<a-switch type="line" v-model="isWrite" v-if="checkPermission(permission,Permission_Types.Wiki.EDIT)" unchecked-color="green" checked-color="dodgerblue"></a-switch>
|
||||||
</template>
|
<span style="color: dodgerblue">{{$t("util.edit")}}</span>
|
||||||
<template #unchecked>
|
</a-space>
|
||||||
{{$t("util.preview")}}
|
|
||||||
</template>
|
|
||||||
</a-switch>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row style="width: 100%;height: calc(100% - 45px)">
|
<a-row style="width: 100%;height: calc(100% - 45px)">
|
||||||
<a-spin :loading="loading" style="width: 100%;height: 100%">
|
<a-spin :loading="loading" style="width: 100%;height: 100%">
|
||||||
@ -57,10 +54,11 @@ import {ECommon_Model_Finder_Shortcut_Type} from "../../../../../../common/model
|
|||||||
import {vDrag} from "../../../../teamOS/common/directive/drag";
|
import {vDrag} from "../../../../teamOS/common/directive/drag";
|
||||||
import {getRootNavigatorRef} from "../../../../teamOS/common/component/navigator/navigator";
|
import {getRootNavigatorRef} from "../../../../teamOS/common/component/navigator/navigator";
|
||||||
import {DropParam, vDrop} from "../../../../teamOS/common/directive/drop";
|
import {DropParam, vDrop} from "../../../../teamOS/common/directive/drop";
|
||||||
import moment from "moment";
|
|
||||||
import {RichEditorEventHandle} from "../../../common/component/richEditorEventHandle";
|
import {RichEditorEventHandle} from "../../../common/component/richEditorEventHandle";
|
||||||
import {DCSType} from "../../../../../../common/types";
|
import {DCSType} from "../../../../../../common/types";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
moment;
|
||||||
const props=defineProps<{
|
const props=defineProps<{
|
||||||
wikiItemId:string,
|
wikiItemId:string,
|
||||||
path:string[],
|
path:string[],
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<template #barLeft>
|
<template #barLeft>
|
||||||
<a-dropdown trigger="hover" id="dropdownEle">
|
<a-dropdown trigger="hover" id="dropdownEle">
|
||||||
<a-avatar id="myProfile" :size="32" :image-url="avatar" :trigger-icon-style="{height:'12px',width:'12px',lineHeight:'12px',right:'-2px',bottom:'-2px',...(store.status===ECommon_User_Online_Status.MEETING && {backgroundColor:'transparent'})}">
|
<a-avatar id="myProfile" :size="32" :image-url="avatar" :trigger-icon-style="{height:'12px',width:'12px',lineHeight:'12px',right:'-2px',bottom:'-2px',...(store.status===ECommon_User_Online_Status.MEETING && {backgroundColor:'transparent'})}">
|
||||||
T
|
{{imgName}}
|
||||||
<template #trigger-icon>
|
<template #trigger-icon>
|
||||||
<div style="height: 100%;width: 100%;background-color: #03ad03;border-radius: 6px" v-if="store.status===ECommon_User_Online_Status.ONLINE"></div>
|
<div style="height: 100%;width: 100%;background-color: #03ad03;border-radius: 6px" v-if="store.status===ECommon_User_Online_Status.ONLINE"></div>
|
||||||
<icon-stop :stroke-width="5" style="color: darkred" v-else-if="store.status===ECommon_User_Online_Status.BUSY"></icon-stop>
|
<icon-stop :stroke-width="5" style="color: darkred" v-else-if="store.status===ECommon_User_Online_Status.BUSY"></icon-stop>
|
||||||
@ -131,7 +131,7 @@
|
|||||||
|
|
||||||
import TeamOS from "../../../teamOS/index.vue";
|
import TeamOS from "../../../teamOS/index.vue";
|
||||||
import {getDesktopInstance} from "../../../teamOS/teamOS";
|
import {getDesktopInstance} from "../../../teamOS/teamOS";
|
||||||
import {getCurrentInstance, markRaw, nextTick, onBeforeMount, onBeforeUnmount, ref, watch} from "vue";
|
import {computed, getCurrentInstance, markRaw, nextTick, onBeforeMount, onBeforeUnmount, ref, watch} from "vue";
|
||||||
import {useDesktopStore} from "./store/desktop";
|
import {useDesktopStore} from "./store/desktop";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {Message} from "@arco-design/web-vue";
|
import {Message} from "@arco-design/web-vue";
|
||||||
@ -191,9 +191,20 @@ const router=useRouter();
|
|||||||
let iconManager=getDesktopInstance().iconManager
|
let iconManager=getDesktopInstance().iconManager
|
||||||
let windowManager=getDesktopInstance().windowManager
|
let windowManager=getDesktopInstance().windowManager
|
||||||
const appContext=getCurrentInstance().appContext
|
const appContext=getCurrentInstance().appContext
|
||||||
|
store.appContext=appContext
|
||||||
const loading=ref(true)
|
const loading=ref(true)
|
||||||
let objDriver:Driver
|
let objDriver:Driver
|
||||||
let avatar=ref("")
|
let avatar=computed(()=>{
|
||||||
|
return store.userInfo?.photo
|
||||||
|
})
|
||||||
|
const imgName=computed(()=>{
|
||||||
|
if(store.userInfo?.username?.includes(" ")) {
|
||||||
|
let arr=store.userInfo.username.split(" ")
|
||||||
|
return arr[0][0].toUpperCase()+arr[1][0].toUpperCase()
|
||||||
|
} else {
|
||||||
|
return store.userInfo?.username?store.userInfo?.username[0].toUpperCase():""
|
||||||
|
}
|
||||||
|
})
|
||||||
const objFinderHandle=new FinderHandle(root,appContext,"")
|
const objFinderHandle=new FinderHandle(root,appContext,"")
|
||||||
const noteList=ref<DCSType<ICommon_Route_Res_Sticky_Note_Item & {
|
const noteList=ref<DCSType<ICommon_Route_Res_Sticky_Note_Item & {
|
||||||
isReadOnly:boolean
|
isReadOnly:boolean
|
||||||
@ -232,9 +243,6 @@ watch(()=>store.organizationList,()=>{
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if(store.userInfo?.photo) {
|
|
||||||
avatar.value=store.userInfo?.photo
|
|
||||||
}
|
|
||||||
if(SessionStorage.get("organizationId")) {
|
if(SessionStorage.get("organizationId")) {
|
||||||
setMenu(arr,SessionStorage.get("organizationId"))
|
setMenu(arr,SessionStorage.get("organizationId"))
|
||||||
} else {
|
} else {
|
||||||
@ -514,7 +522,11 @@ onBeforeMount(async ()=>{
|
|||||||
if(SessionStorage.get("organizationId")) {
|
if(SessionStorage.get("organizationId")) {
|
||||||
await store.initOrganization(SessionStorage.get("organizationId"))
|
await store.initOrganization(SessionStorage.get("organizationId"))
|
||||||
}
|
}
|
||||||
loading.value=false
|
loading.value=false;
|
||||||
|
window.onbeforeunload = function(event){
|
||||||
|
event.preventDefault()
|
||||||
|
event.returnValue=t("tip.leavePage");
|
||||||
|
};
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(()=>{
|
onBeforeUnmount(()=>{
|
||||||
@ -524,6 +536,7 @@ onBeforeUnmount(()=>{
|
|||||||
eventBus.off(EClient_EVENTBUS_TYPE.USER_LOGIN_EXPIRED,onLogout.bind(null,true))
|
eventBus.off(EClient_EVENTBUS_TYPE.USER_LOGIN_EXPIRED,onLogout.bind(null,true))
|
||||||
eventBus.off(EClient_EVENTBUS_TYPE.GUIDE,onGuide)
|
eventBus.off(EClient_EVENTBUS_TYPE.GUIDE,onGuide)
|
||||||
eventBus.off(EClient_EVENTBUS_TYPE.ORGANIZATION_REMOVE, onRemoveOrganization)
|
eventBus.off(EClient_EVENTBUS_TYPE.ORGANIZATION_REMOVE, onRemoveOrganization)
|
||||||
|
window.onbeforeunload=null;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,9 +9,24 @@ import Meeting from "../../app/meeting/meeting.vue";
|
|||||||
import MeetingProfile from "../../app/meeting/meetingProfile.vue";
|
import MeetingProfile from "../../app/meeting/meetingProfile.vue";
|
||||||
import {EClient_EVENTBUS_TYPE, eventBus} from "../../../common/event/event";
|
import {EClient_EVENTBUS_TYPE, eventBus} from "../../../common/event/event";
|
||||||
import i18n from "@/business/common/i18n/i18n";
|
import i18n from "@/business/common/i18n/i18n";
|
||||||
|
import {useDesktopStore} from "@/business/controller/desktop/store/desktop";
|
||||||
|
import {ECommon_User_Online_Status} from "../../../../../../common/types";
|
||||||
|
import {Dialog} from "@/business/common/component/dialog/dialog";
|
||||||
|
|
||||||
const {t}=i18n.global
|
const {t}=i18n.global
|
||||||
export const iconMeeting=new Icon(t("util.meeting"),iconGroupMap["meeting"],"meeting")
|
export const iconMeeting=new Icon(t("util.meeting"),iconGroupMap["meeting"],"meeting")
|
||||||
|
const closeFunc=async item1 => {
|
||||||
|
const store=useDesktopStore()
|
||||||
|
if(store.status===ECommon_User_Online_Status.MEETING) {
|
||||||
|
let ret=await Dialog.confirm(document.body,store.appContext,t("tip.leaveMeeting"))
|
||||||
|
if(ret) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
const func=item => {
|
const func=item => {
|
||||||
if(!SessionStorage.get("organizationId")) {
|
if(!SessionStorage.get("organizationId")) {
|
||||||
Message.error(t("tip.switchToSpecificOrganization"))
|
Message.error(t("tip.switchToSpecificOrganization"))
|
||||||
@ -33,6 +48,7 @@ const func=item => {
|
|||||||
}
|
}
|
||||||
],t("util.meeting"));
|
],t("util.meeting"));
|
||||||
windowManager.open(win);
|
windowManager.open(win);
|
||||||
|
win.addEventListener("close",closeFunc)
|
||||||
}
|
}
|
||||||
iconMeeting.addEventListener("dbClick",item => {
|
iconMeeting.addEventListener("dbClick",item => {
|
||||||
if(!document.getElementById("application")) {
|
if(!document.getElementById("application")) {
|
||||||
@ -68,4 +84,5 @@ eventBus.on(EClient_EVENTBUS_TYPE.OPEN_MEETING,(meetingId, password,inviteBusine
|
|||||||
}
|
}
|
||||||
],t("util.meeting"));
|
],t("util.meeting"));
|
||||||
windowManager.open(win);
|
windowManager.open(win);
|
||||||
|
win.addEventListener("close",closeFunc)
|
||||||
})
|
})
|
@ -45,7 +45,7 @@ const func=item => {
|
|||||||
},
|
},
|
||||||
default:{
|
default:{
|
||||||
name:"project",
|
name:"project",
|
||||||
title: "project"
|
title: t("util.project")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -264,3 +264,45 @@ eventBus.on(EClient_EVENTBUS_TYPE.OPEN_PROJECT_BOARD_PROFILE,(projectId, boardId
|
|||||||
})
|
})
|
||||||
windowManager.open(win);
|
windowManager.open(win);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
eventBus.on(EClient_EVENTBUS_TYPE.OPEN_PROJECT_PLAN_PROFILE,(projectId, planId) => {
|
||||||
|
const win=new Window(t("util.project"),ETeamOS_Window_Type.TAB, "project",true,[
|
||||||
|
{
|
||||||
|
id:v4(),
|
||||||
|
meta:{
|
||||||
|
title:"project",
|
||||||
|
data:{
|
||||||
|
planId:planId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components:{
|
||||||
|
project:markRaw(Project),
|
||||||
|
profile:markRaw(ProjectProfile)
|
||||||
|
},
|
||||||
|
default:{
|
||||||
|
name:"profile",
|
||||||
|
title: t("util.profile"),
|
||||||
|
props:{
|
||||||
|
projectId:projectId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],t("util.project"));
|
||||||
|
win.addEventListener("newTab", async item => {
|
||||||
|
return {
|
||||||
|
id:v4(),
|
||||||
|
meta:{
|
||||||
|
title:"project"
|
||||||
|
},
|
||||||
|
components:{
|
||||||
|
project:markRaw(Project),
|
||||||
|
profile:markRaw(ProjectProfile)
|
||||||
|
},
|
||||||
|
default:{
|
||||||
|
name:"project",
|
||||||
|
title: t("util.project")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
windowManager.open(win);
|
||||||
|
})
|
@ -39,7 +39,8 @@ export const useDesktopStore=defineStore("desktop",{
|
|||||||
userInfo:{} as DCSType<Omit<ICommon_Model_User,"password">>,
|
userInfo:{} as DCSType<Omit<ICommon_Model_User,"password">>,
|
||||||
status:ECommon_User_Online_Status.OFFLINE,
|
status:ECommon_User_Online_Status.OFFLINE,
|
||||||
heartbeatInterval:null,
|
heartbeatInterval:null,
|
||||||
copyItemList:[] as string[]
|
copyItemList:[] as string[],
|
||||||
|
appContext:null
|
||||||
}),
|
}),
|
||||||
actions:{
|
actions:{
|
||||||
async initNotificationSocket() {
|
async initNotificationSocket() {
|
||||||
|
66
code/client/src/business/controller/login/bindAccount.vue
Normal file
66
code/client/src/business/controller/login/bindAccount.vue
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<a-row align="center" justify="center" style="height: 100%;position: relative;background: radial-gradient(circle at center, rgb(228,232,249), #ffffff);">
|
||||||
|
<a-space style="position: absolute;left: 20px;top: 20px">
|
||||||
|
<img :src="logo" style="width: 20px;aspect-ratio: 1/1" />
|
||||||
|
<span style="font-size: 24px;font-weight: bolder">
|
||||||
|
<router-link :to="{name:'index'}" style="text-decoration: none;color:rgb(35,110,184)">
|
||||||
|
Teamlinker
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
</a-space>
|
||||||
|
<div style="width:40%;box-shadow: rgba(22, 14, 45, 0.02) 0px 0px 40px, rgba(22, 14, 45, 0.06) 0px 0px 104px;padding: 20px;box-sizing: border-box;border: 1px solid rgb(234, 236, 240);border-radius: 10px;background-color: white">
|
||||||
|
<a-form :model="form" auto-label-width @submit="onSubmit">
|
||||||
|
<h1 style="text-align: center;margin-bottom: 50px;color: rgba(16, 24, 40, 0.8)">{{$t("controller.login.bindAccount.bindExistedAccount")}}</h1>
|
||||||
|
<a-form-item field="username" :label="$t('util.username')" required>
|
||||||
|
<a-input v-model="form.username" :placeholder="$t('placeholder.enterUsername')"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="password" :label="$t('util.password')" required>
|
||||||
|
<a-input v-model="form.password" type="password" :placeholder="$t('placeholder.typePassword')"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-button html-type="submit" type="primary" style="background-color: rgb(147, 115, 238)">{{ $t("util.bind") }}</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {getCurrentInstance, reactive} from "vue";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
import logo from "@/assert/logo.png"
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
import {useDesktopStore} from "@/business/controller/desktop/store/desktop";
|
||||||
|
import {apiUser} from "@/business/common/request/request";
|
||||||
|
import {SessionStorage} from "@/business/common/storage/session";
|
||||||
|
import md5 from "blueimp-md5";
|
||||||
|
import {Message} from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
let form = reactive({
|
||||||
|
username: "",
|
||||||
|
password: ""
|
||||||
|
})
|
||||||
|
const appContext=getCurrentInstance().appContext
|
||||||
|
const store=useDesktopStore()
|
||||||
|
const {t}=useI18n()
|
||||||
|
let router = useRouter();
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
let res=await apiUser.bindWechat({
|
||||||
|
openId:SessionStorage.get("wechatOpenId"),
|
||||||
|
username:form.username,
|
||||||
|
password:md5(form.password)
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
Message.success(t("tip.operationSuccess"))
|
||||||
|
SessionStorage.remove("wechatOpenId")
|
||||||
|
await router.replace("login")
|
||||||
|
} else {
|
||||||
|
Message.error(res.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -10,7 +10,7 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
<div style="width:40%;box-shadow: rgba(22, 14, 45, 0.02) 0px 0px 40px, rgba(22, 14, 45, 0.06) 0px 0px 104px;padding: 20px;box-sizing: border-box;border: 1px solid rgb(234, 236, 240);border-radius: 10px;background-color: white">
|
<div style="width:40%;box-shadow: rgba(22, 14, 45, 0.02) 0px 0px 40px, rgba(22, 14, 45, 0.06) 0px 0px 104px;padding: 20px;box-sizing: border-box;border: 1px solid rgb(234, 236, 240);border-radius: 10px;background-color: white">
|
||||||
<a-form :model="form" auto-label-width @submit="onSubmit" v-if="!organizationList">
|
<a-form :model="form" auto-label-width @submit="onSubmit" v-if="!organizationList">
|
||||||
<h1 style="text-align: center;margin-bottom: 50px">{{$t("util.login")}}</h1>
|
<h1 style="text-align: center;margin-bottom: 50px;color: rgba(16, 24, 40, 0.8)">{{$t("util.login")}}</h1>
|
||||||
<a-form-item field="username" :label="$t('util.username')" required>
|
<a-form-item field="username" :label="$t('util.username')" required>
|
||||||
<a-input v-model="form.username" :placeholder="$t('placeholder.enterUsername')"></a-input>
|
<a-input v-model="form.username" :placeholder="$t('placeholder.enterUsername')"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -19,7 +19,14 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row style="justify-content: space-between;width: 100%">
|
<a-row style="justify-content: space-between;width: 100%">
|
||||||
<a-button html-type="submit" type="primary">{{ $t("util.submit") }}</a-button>
|
<a-space size="large">
|
||||||
|
<a-button html-type="submit" type="primary" style="background-color: rgb(147, 115, 238)">{{ $t("util.submit") }}</a-button>
|
||||||
|
<a-button v-if="$deployMode.value===ECommon_Application_Mode.ONLINE" type="text" @click="onWechat">
|
||||||
|
<template #icon>
|
||||||
|
<icon-wechat style="color: green;font-size: x-large"></icon-wechat>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
<a-space v-if="$deployMode.value===ECommon_Application_Mode.ONLINE">
|
<a-space v-if="$deployMode.value===ECommon_Application_Mode.ONLINE">
|
||||||
<a-button html-type="button" type="primary" status="success" @click="onRegister">{{ $t("util.register") }}
|
<a-button html-type="button" type="primary" status="success" @click="onRegister">{{ $t("util.register") }}
|
||||||
</a-button>
|
</a-button>
|
||||||
@ -59,7 +66,7 @@
|
|||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</template>
|
</template>
|
||||||
<a-button type="primary" status="success" @click="onCreate" size="large" style="width: 200px;" v-else>
|
<a-button type="primary" status="success" @click="onCreate" size="large" style="width: 200px;" v-else-if="$deployMode.value===ECommon_Application_Mode.ONLINE">
|
||||||
{{$t("controller.desktop.desktop.createOrganization")}}
|
{{$t("controller.desktop.desktop.createOrganization")}}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="text" style="color: grey;margin-top: 20px" @click="onDesktop()">{{$t("util.skip")}}</a-button>
|
<a-button type="text" style="color: grey;margin-top: 20px" @click="onDesktop()">{{$t("util.skip")}}</a-button>
|
||||||
@ -71,7 +78,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {getCurrentInstance, markRaw, reactive, ref} from "vue";
|
import {getCurrentInstance, markRaw, reactive, ref} from "vue";
|
||||||
import {apiOrganization, apiUser} from "../../common/request/request";
|
import {apiGateway, apiOrganization, apiUser} from "../../common/request/request";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {Message} from "@arco-design/web-vue";
|
import {Message} from "@arco-design/web-vue";
|
||||||
import {NotificationWrapper} from "../../common/component/notification/notification";
|
import {NotificationWrapper} from "../../common/component/notification/notification";
|
||||||
@ -95,6 +102,14 @@ const appContext=getCurrentInstance().appContext
|
|||||||
const store=useDesktopStore()
|
const store=useDesktopStore()
|
||||||
const {t}=useI18n()
|
const {t}=useI18n()
|
||||||
let router = useRouter();
|
let router = useRouter();
|
||||||
|
const login=(userId:string)=>{
|
||||||
|
SessionStorage.remove("organizationId")
|
||||||
|
SessionStorage.set("userId",userId);
|
||||||
|
getOrganizationList()
|
||||||
|
store.initNotificationSocket()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
let ret = await apiUser.login({
|
let ret = await apiUser.login({
|
||||||
username: form.username,
|
username: form.username,
|
||||||
@ -102,10 +117,7 @@ const onSubmit = async () => {
|
|||||||
lang:localStorage.getItem("lang")??(navigator.language || "en").toLowerCase().split("-")[0]
|
lang:localStorage.getItem("lang")??(navigator.language || "en").toLowerCase().split("-")[0]
|
||||||
})
|
})
|
||||||
if (ret.code == 0) {
|
if (ret.code == 0) {
|
||||||
SessionStorage.remove("organizationId")
|
login(ret.data.id)
|
||||||
SessionStorage.set("userId",ret.data.id);
|
|
||||||
getOrganizationList()
|
|
||||||
store.initNotificationSocket()
|
|
||||||
} else {
|
} else {
|
||||||
Message.error(ret.msg);
|
Message.error(ret.msg);
|
||||||
}
|
}
|
||||||
@ -140,6 +152,34 @@ const onCreate=async ()=>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onWechat=async ()=>{
|
||||||
|
SessionStorage.remove("wechatOpenId")
|
||||||
|
let res=await apiGateway.wechatAppId()
|
||||||
|
if(res?.code==0) {
|
||||||
|
window.open(`https://open.weixin.qq.com/connect/qrconnect?appid=${res.data.appId}&redirect_uri=${encodeURIComponent(location.protocol+"//"+location.host+"/#/wechat")}&response_type=code&scope=snsapi_login#wechat_redirect`,"_blank","popup=yes,width=600,height=600,top=100,left=300")
|
||||||
|
window.onmessage= async ev => {
|
||||||
|
let openId=ev.data
|
||||||
|
let res=await apiUser.wechatLogin({
|
||||||
|
openId,
|
||||||
|
lang:localStorage.getItem("lang")??(navigator.language || "en").toLowerCase().split("-")[0]
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
if(res.data) {
|
||||||
|
login(res.data.id)
|
||||||
|
} else {
|
||||||
|
SessionStorage.set("wechatOpenId",openId)
|
||||||
|
let ret=await Dialog.confirm(document.body,appContext,t("tip.bindExistedAccount"))
|
||||||
|
if(ret) {
|
||||||
|
await router.replace("bindAccount")
|
||||||
|
} else {
|
||||||
|
await router.replace("register")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getOrganizationList=async ()=>{
|
const getOrganizationList=async ()=>{
|
||||||
let ret=await apiOrganization.list()
|
let ret=await apiOrganization.list()
|
||||||
if(ret && ret.code==0) {
|
if(ret && ret.code==0) {
|
||||||
|
@ -34,6 +34,7 @@ import {Message} from "@arco-design/web-vue";
|
|||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {useI18n} from "vue-i18n";
|
import {useI18n} from "vue-i18n";
|
||||||
import logo from "@/assert/logo.png";
|
import logo from "@/assert/logo.png";
|
||||||
|
import {SessionStorage} from "@/business/common/storage/session";
|
||||||
|
|
||||||
const props=defineProps<{
|
const props=defineProps<{
|
||||||
username:string
|
username:string
|
||||||
@ -59,12 +60,15 @@ const onSubmit=async()=>{
|
|||||||
if(!form.code) {
|
if(!form.code) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let openId=SessionStorage.get("wechatOpenId")
|
||||||
let res=await apiUser.confirmRegister({
|
let res=await apiUser.confirmRegister({
|
||||||
username:props.username,
|
username:props.username,
|
||||||
code:form.code
|
code:form.code,
|
||||||
|
openId
|
||||||
})
|
})
|
||||||
if(res?.code==0) {
|
if(res?.code==0) {
|
||||||
Message.info(t("tip.registerSuccess"))
|
Message.info(t("tip.registerSuccess"))
|
||||||
|
SessionStorage.remove("wechatOpenId")
|
||||||
await router.replace("login")
|
await router.replace("login")
|
||||||
} else {
|
} else {
|
||||||
Message.error(res.msg)
|
Message.error(res.msg)
|
||||||
|
30
code/client/src/business/controller/login/wechat.vue
Normal file
30
code/client/src/business/controller/login/wechat.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {apiUser} from "@/business/common/request/request";
|
||||||
|
|
||||||
|
const request=async (code:string)=>{
|
||||||
|
let res=await apiUser.wechatCode({
|
||||||
|
code:code
|
||||||
|
})
|
||||||
|
if(res?.code==0) {
|
||||||
|
window.opener.postMessage(res.data.openId)
|
||||||
|
window.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let query=location.hash.substring(location.hash.indexOf("?")+1)
|
||||||
|
let map:{
|
||||||
|
[key:string]:string
|
||||||
|
}={}
|
||||||
|
for(let obj of query.split("&")) {
|
||||||
|
let arr=obj.split("=")
|
||||||
|
map[arr[0]]=arr[1]
|
||||||
|
}
|
||||||
|
request(map["code"])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -52,7 +52,17 @@ const routes=[
|
|||||||
path:"/resetCode",
|
path:"/resetCode",
|
||||||
component: ()=>import("./business/controller/login/resetCode.vue"),
|
component: ()=>import("./business/controller/login/resetCode.vue"),
|
||||||
props:route=>({username:route.query.username})
|
props:route=>({username:route.query.username})
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name:"wechat",
|
||||||
|
path:"/wechat",
|
||||||
|
component: ()=>import("./business/controller/login/wechat.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name:"bindAccount",
|
||||||
|
path:"/bindAccount",
|
||||||
|
component: ()=>import("./business/controller/login/bindAccount.vue"),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
const router=createRouter({
|
const router=createRouter({
|
||||||
history:createWebHashHistory(),
|
history:createWebHashHistory(),
|
||||||
@ -78,6 +88,18 @@ apiGateway.deployInfo().then(async value => {
|
|||||||
if(!(hash.startsWith("#/login") || hash.startsWith("#/desktop"))) {
|
if(!(hash.startsWith("#/login") || hash.startsWith("#/desktop"))) {
|
||||||
await router.replace("login")
|
await router.replace("login")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
loadStaticScript()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function loadStaticScript() {
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.onload = function() {
|
||||||
|
eval('LA.init({id:"KEwmiZNvHr5leFev",ck:"KEwmiZNvHr5leFev"})')
|
||||||
|
}
|
||||||
|
script.src = 'https://sdk.51.la/js-sdk-pro.min.js';
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
|
@ -39,8 +39,9 @@ export interface ITeamOS_Window_Node {
|
|||||||
export interface ITeamOS_Window_Event {
|
export interface ITeamOS_Window_Event {
|
||||||
"open":(item:Window)=>void,
|
"open":(item:Window)=>void,
|
||||||
"move":(item:Window)=>void,
|
"move":(item:Window)=>void,
|
||||||
"newTab":(item:Window)=>Promise<ITeamOS_Window_Node>
|
"newTab":(item:Window)=>Promise<ITeamOS_Window_Node>,
|
||||||
"removeTab":(item:Window)=>void
|
"removeTab":(item:Window)=>void,
|
||||||
|
"close":(item:Window)=>Promise<boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentWindow():Window {
|
export function getCurrentWindow():Window {
|
||||||
@ -59,6 +60,8 @@ export class Window extends Base{
|
|||||||
width:"80%",
|
width:"80%",
|
||||||
height:"80%"
|
height:"80%"
|
||||||
}
|
}
|
||||||
|
originalRect:ITeamOS_Rect
|
||||||
|
originalStatus:ETeamOS_Window_Status
|
||||||
group:string
|
group:string
|
||||||
nodes:ITeamOS_Window_Node[]
|
nodes:ITeamOS_Window_Node[]
|
||||||
status=ETeamOS_Window_Status.NORMAL
|
status=ETeamOS_Window_Status.NORMAL
|
||||||
@ -68,6 +71,7 @@ export class Window extends Base{
|
|||||||
onOpen:(item:Window)=>void
|
onOpen:(item:Window)=>void
|
||||||
onNewTab:(item:Window)=>Promise<ITeamOS_Window_Node>
|
onNewTab:(item:Window)=>Promise<ITeamOS_Window_Node>
|
||||||
onRemoveTab:(item:Window)=>void
|
onRemoveTab:(item:Window)=>void
|
||||||
|
onClose:(item:Window)=>Promise<boolean>
|
||||||
constructor(name:string,type:ETeamOS_Window_Type,group:string,isControl:boolean,nodes:ITeamOS_Window_Node[],title?:string) {
|
constructor(name:string,type:ETeamOS_Window_Type,group:string,isControl:boolean,nodes:ITeamOS_Window_Node[],title?:string) {
|
||||||
super()
|
super()
|
||||||
this.name=name;
|
this.name=name;
|
||||||
@ -87,13 +91,15 @@ export class Window extends Base{
|
|||||||
}
|
}
|
||||||
addEventListener<T extends keyof ITeamOS_Window_Event>(eventType:T,func:ITeamOS_Window_Event[T]) {
|
addEventListener<T extends keyof ITeamOS_Window_Event>(eventType:T,func:ITeamOS_Window_Event[T]) {
|
||||||
if(eventType=="move") {
|
if(eventType=="move") {
|
||||||
this.onMove=func.bind(null,this);
|
this.onMove=func
|
||||||
} else if(eventType=="open") {
|
} else if(eventType=="open") {
|
||||||
this.onOpen=func
|
this.onOpen=func
|
||||||
} else if(eventType=="newTab") {
|
} else if(eventType=="newTab") {
|
||||||
this.onNewTab=func as ITeamOS_Window_Event["newTab"]
|
this.onNewTab=func as ITeamOS_Window_Event["newTab"]
|
||||||
} else if(eventType=="removeTab") {
|
} else if(eventType=="removeTab") {
|
||||||
this.onRemoveTab=func.bind(null,this);
|
this.onRemoveTab=func
|
||||||
|
} else if(eventType=="close") {
|
||||||
|
this.onClose=func as ITeamOS_Window_Event["close"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,7 +23,7 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col flex="auto" style="height: 100%;text-align: center;line-height: 35px;color: rgb(93,93,93);font-size: medium;cursor: move">
|
<a-col flex="auto" style="height: 100%;text-align: center;line-height: 35px;color: rgb(93,93,93);font-size: medium;cursor: move" @dblclick="onDbClick">
|
||||||
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col flex="80px" style="height: 100%">
|
<a-col flex="80px" style="height: 100%">
|
||||||
@ -102,6 +102,15 @@ const onMin=(id:string)=>{
|
|||||||
const onClose=(id:string)=>{
|
const onClose=(id:string)=>{
|
||||||
windowManager.close(id)
|
windowManager.close(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onDbClick=()=>{
|
||||||
|
if(props.item.status===ETeamOS_Window_Status.NORMAL) {
|
||||||
|
windowManager.max(props.item.id)
|
||||||
|
} else {
|
||||||
|
windowManager.normal(props.item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const navigator=ref<InstanceType<typeof NavigatorContainer>>(null)
|
const navigator=ref<InstanceType<typeof NavigatorContainer>>(null)
|
||||||
const navigatorList=ref<InstanceType<typeof NavigatorContainer>[]>([])
|
const navigatorList=ref<InstanceType<typeof NavigatorContainer>[]>([])
|
||||||
const back=()=>{
|
const back=()=>{
|
||||||
|
@ -47,9 +47,16 @@ export class WindowManager extends Base{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeById(id:string) {
|
async removeById(id:string) {
|
||||||
for(let i=0;i<this.windowList.length;i++) {
|
for(let i=0;i<this.windowList.length;i++) {
|
||||||
if(this.windowList[i].id==id) {
|
if(this.windowList[i].id==id) {
|
||||||
|
let obj=this.windowList[i]
|
||||||
|
if(obj.onClose) {
|
||||||
|
let ret=await obj.onClose(obj)
|
||||||
|
if(ret===false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
this.windowList.splice(i,1)
|
this.windowList.splice(i,1)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -91,6 +98,7 @@ export class WindowManager extends Base{
|
|||||||
max(id:string){
|
max(id:string){
|
||||||
let obj=this.setFocus(id);
|
let obj=this.setFocus(id);
|
||||||
if(obj) {
|
if(obj) {
|
||||||
|
obj.originalRect={...obj.rect}
|
||||||
obj.rect.left="0%"
|
obj.rect.left="0%"
|
||||||
obj.rect.top="0%"
|
obj.rect.top="0%"
|
||||||
obj.rect.width="100%"
|
obj.rect.width="100%"
|
||||||
@ -111,6 +119,8 @@ export class WindowManager extends Base{
|
|||||||
hide(id:string) {
|
hide(id:string) {
|
||||||
let obj=this.getById(id);
|
let obj=this.getById(id);
|
||||||
if(obj) {
|
if(obj) {
|
||||||
|
obj.originalRect={...obj.rect}
|
||||||
|
obj.originalStatus=obj.status
|
||||||
obj.status=ETeamOS_Window_Status.MIN
|
obj.status=ETeamOS_Window_Status.MIN
|
||||||
if(obj.isFocus) {
|
if(obj.isFocus) {
|
||||||
obj.isFocus=false
|
obj.isFocus=false
|
||||||
@ -123,10 +133,15 @@ export class WindowManager extends Base{
|
|||||||
if(isNew) {
|
if(isNew) {
|
||||||
obj.rect.top=`${10+Math.random()*8-4}%`
|
obj.rect.top=`${10+Math.random()*8-4}%`
|
||||||
obj.rect.left=`${10+Math.random()*8-4}%`
|
obj.rect.left=`${10+Math.random()*8-4}%`
|
||||||
|
obj.status=ETeamOS_Window_Status.NORMAL
|
||||||
|
obj.rect.width="80%"
|
||||||
|
obj.rect.height="80%"
|
||||||
|
} else {
|
||||||
|
if(obj.status===ETeamOS_Window_Status.MIN) {
|
||||||
|
Object.assign(obj.rect,obj.originalRect)
|
||||||
|
obj.status=obj.originalStatus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
obj.status=ETeamOS_Window_Status.NORMAL
|
|
||||||
obj.rect.width="80%"
|
|
||||||
obj.rect.height="80%"
|
|
||||||
}
|
}
|
||||||
this.setFocus(id);
|
this.setFocus(id);
|
||||||
}
|
}
|
||||||
|
@ -6,51 +6,51 @@ import viteCompression from 'vite-plugin-compression'
|
|||||||
import {createSvgIconsPlugin} from "vite-plugin-svg-icons";
|
import {createSvgIconsPlugin} from "vite-plugin-svg-icons";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
resolve:{
|
resolve: {
|
||||||
alias:{
|
alias: {
|
||||||
"@":path.join(__dirname,"src")
|
"@": path.join(__dirname, "src")
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
viteCompression({
|
||||||
|
threshold: 102400
|
||||||
|
}),
|
||||||
|
createSvgIconsPlugin({
|
||||||
|
iconDirs: [path.join(__dirname, "./src/assert/custom")],
|
||||||
|
symbolId: '[name]'
|
||||||
|
})
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
host: "0.0.0.0",
|
||||||
|
https: {
|
||||||
|
key: fs.readFileSync(path.join(__dirname, './certs/key.pem')),
|
||||||
|
cert: fs.readFileSync(path.join(__dirname, './certs/cert.pem'))
|
||||||
|
},
|
||||||
|
port: 3000,
|
||||||
|
hmr: true,
|
||||||
|
open: false, //自动打开
|
||||||
|
base: "./ ", //生产环境路径
|
||||||
|
proxy: { // 本地开发环境通过代理实现跨域,生产环境使用 nginx 转发
|
||||||
|
// 正则表达式写法
|
||||||
|
'^/api': {
|
||||||
|
secure: false,
|
||||||
|
target: 'https://localhost:14000/api', // 后端服务实际地址
|
||||||
|
changeOrigin: true, //开启代理
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
},
|
||||||
|
'^/file': {
|
||||||
|
secure: false,
|
||||||
|
target: 'https://localhost:14000/file', // 后端服务实际地址
|
||||||
|
changeOrigin: true, //开启代理
|
||||||
|
rewrite: (path) => path.replace(/^\/file/, '')
|
||||||
|
},
|
||||||
|
"/socket.io": {
|
||||||
|
target: "https://localhost:14000",
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
|
||||||
vue(),
|
|
||||||
viteCompression({
|
|
||||||
threshold:102400
|
|
||||||
}),
|
|
||||||
createSvgIconsPlugin({
|
|
||||||
iconDirs:[path.join(__dirname,"./src/assert/custom")],
|
|
||||||
symbolId:'[name]'
|
|
||||||
})
|
|
||||||
],
|
|
||||||
server:{
|
|
||||||
host:"0.0.0.0",
|
|
||||||
https:{
|
|
||||||
key:fs.readFileSync(path.join(__dirname,'./certs/key.pem')),
|
|
||||||
cert: fs.readFileSync(path.join(__dirname,'./certs/cert.pem'))
|
|
||||||
},
|
|
||||||
port: 3000,
|
|
||||||
hmr:true,
|
|
||||||
open: false, //自动打开
|
|
||||||
base: "./ ", //生产环境路径
|
|
||||||
proxy: { // 本地开发环境通过代理实现跨域,生产环境使用 nginx 转发
|
|
||||||
// 正则表达式写法
|
|
||||||
'^/api': {
|
|
||||||
secure:false,
|
|
||||||
target: 'https://localhost:14000/api', // 后端服务实际地址
|
|
||||||
changeOrigin: true, //开启代理
|
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
|
||||||
},
|
|
||||||
'^/file': {
|
|
||||||
secure:false,
|
|
||||||
target: 'https://localhost:14000/file', // 后端服务实际地址
|
|
||||||
changeOrigin: true, //开启代理
|
|
||||||
rewrite: (path) => path.replace(/^\/file/, '')
|
|
||||||
},
|
|
||||||
"/socket.io":{
|
|
||||||
target:"https://localhost:14000",
|
|
||||||
secure:false,
|
|
||||||
ws:true,
|
|
||||||
changeOrigin: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
@ -42,7 +42,12 @@ export default {
|
|||||||
column:"column is kanban status for issues,you can add,edit or delete the column",
|
column:"column is kanban status for issues,you can add,edit or delete the column",
|
||||||
sprint:"sprint is a short version that includes many issues for agile project",
|
sprint:"sprint is a short version that includes many issues for agile project",
|
||||||
kanban:"kanban can show issues' status clearly in the sprint",
|
kanban:"kanban can show issues' status clearly in the sprint",
|
||||||
swimLane:"swim lane is a issues' pool that needs to complete a specific task"
|
swimLane:"swim lane is a issues' pool that needs to complete a specific task",
|
||||||
|
manDay:"the number of days it takes to complete the issue",
|
||||||
|
plan:"we can plan the issues,arrange the dependence relationship,and make a gantt chart",
|
||||||
|
planItemType:`stage:it's a collection that includes sub stages,issues,and milestones.
|
||||||
|
milestone:it shows the completion of issues and stages above in a level.
|
||||||
|
`
|
||||||
},
|
},
|
||||||
notification:{
|
notification:{
|
||||||
teamUserAdd:{
|
teamUserAdd:{
|
||||||
@ -141,6 +146,7 @@ export default {
|
|||||||
deleteEvent:"Do you want to delete this event?",
|
deleteEvent:"Do you want to delete this event?",
|
||||||
deleteFolder:"do you want to delete this folder?",
|
deleteFolder:"do you want to delete this folder?",
|
||||||
deleteItems:"Do you want to delete these items?",
|
deleteItems:"Do you want to delete these items?",
|
||||||
|
deleteItem:"Do you want to delete this item?",
|
||||||
deleteFavorite:"Do you want delete this favorite?",
|
deleteFavorite:"Do you want delete this favorite?",
|
||||||
deleteMeeting:"Do you want to delete this meeting?",
|
deleteMeeting:"Do you want to delete this meeting?",
|
||||||
leaveMeeting:"Do you leave this meeting?",
|
leaveMeeting:"Do you leave this meeting?",
|
||||||
@ -237,7 +243,10 @@ export default {
|
|||||||
deleteOrganization:"Do you want to delete this organization?",
|
deleteOrganization:"Do you want to delete this organization?",
|
||||||
notMatch:"input not match",
|
notMatch:"input not match",
|
||||||
quitOrganization:"do you want to quit this organization?",
|
quitOrganization:"do you want to quit this organization?",
|
||||||
switchToOrganization:"you should switch to organization "
|
switchToOrganization:"you should switch to organization ",
|
||||||
|
deletePlan:"Do you want to delete this plan?",
|
||||||
|
bindExistedAccount:"Do you want to bind an existed account?",
|
||||||
|
leavePage:"Are you sure you want to leave?"
|
||||||
},
|
},
|
||||||
placeholder:{
|
placeholder:{
|
||||||
pleaseSelect:"Please Select",
|
pleaseSelect:"Please Select",
|
||||||
@ -277,7 +286,8 @@ export default {
|
|||||||
typeOrganizationName:"Type Organization Name",
|
typeOrganizationName:"Type Organization Name",
|
||||||
typeProjectReleaseName:"Type Project Release Name",
|
typeProjectReleaseName:"Type Project Release Name",
|
||||||
typeCalendarEventName:"type calendar event name",
|
typeCalendarEventName:"type calendar event name",
|
||||||
typeMeetingRoomName:"type meeting room name"
|
typeMeetingRoomName:"type meeting room name",
|
||||||
|
typePlanName:"Type Plan Name",
|
||||||
},
|
},
|
||||||
"util":{
|
"util":{
|
||||||
"ok":"Ok",
|
"ok":"Ok",
|
||||||
@ -426,6 +436,7 @@ export default {
|
|||||||
meetingPassword:"Meeting Password",
|
meetingPassword:"Meeting Password",
|
||||||
users:"Users",
|
users:"Users",
|
||||||
selectCamera:"Select Camera",
|
selectCamera:"Select Camera",
|
||||||
|
selectAudio:"Select Audio Input",
|
||||||
videoOn:"Video On",
|
videoOn:"Video On",
|
||||||
audioOn:"Audio On",
|
audioOn:"Audio On",
|
||||||
created:"Created",
|
created:"Created",
|
||||||
@ -502,7 +513,23 @@ export default {
|
|||||||
createdBy:"Created By",
|
createdBy:"Created By",
|
||||||
application:"Application",
|
application:"Application",
|
||||||
todo:"To Do",
|
todo:"To Do",
|
||||||
unNamed:"UnNamed"
|
unNamed:"UnNamed",
|
||||||
|
manDay:"Man Day",
|
||||||
|
plan:"Plan",
|
||||||
|
gantt:"Gantt",
|
||||||
|
stage:"Stage",
|
||||||
|
milestone:"Milestone",
|
||||||
|
depend:"Depend",
|
||||||
|
delay:"Delay(Day)",
|
||||||
|
day:"Day",
|
||||||
|
month:"Month",
|
||||||
|
sunday:"Sunday",
|
||||||
|
monday:"Monday",
|
||||||
|
tuesday:"Tuesday",
|
||||||
|
wednesday:"Wednesday",
|
||||||
|
thursday:"Thursday",
|
||||||
|
friday:"Friday",
|
||||||
|
saturday:"Saturday"
|
||||||
},
|
},
|
||||||
"common":{
|
"common":{
|
||||||
"component":{
|
"component":{
|
||||||
@ -925,6 +952,9 @@ export default {
|
|||||||
"login":{
|
"login":{
|
||||||
registerCode:{
|
registerCode:{
|
||||||
receiveCode:"if you don't receive verification code email,you can"
|
receiveCode:"if you don't receive verification code email,you can"
|
||||||
|
},
|
||||||
|
bindAccount:{
|
||||||
|
bindExistedAccount: "Bind Account"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,12 @@ export default {
|
|||||||
column:"列是代表了工作项的看板状态,你可以编辑,新增,删除列",
|
column:"列是代表了工作项的看板状态,你可以编辑,新增,删除列",
|
||||||
sprint:"对于敏捷项目来说,冲刺就是一个包含工作项的小版本",
|
sprint:"对于敏捷项目来说,冲刺就是一个包含工作项的小版本",
|
||||||
kanban:"看板可以直观地反映出冲刺中工作项的状态",
|
kanban:"看板可以直观地反映出冲刺中工作项的状态",
|
||||||
swimLane:"泳道可以看做是一个为了完成特定任务的需求池"
|
swimLane:"泳道可以看做是一个为了完成特定任务的需求池",
|
||||||
|
manDay: "完成工作项需要花费的天数",
|
||||||
|
plan:"我们可以对工作项进行排期,规划前后依赖关系,形成甘特图",
|
||||||
|
planItemType:`阶段:阶段包含了子阶段,里程碑和工作项,可以看做是一段工作的集合.
|
||||||
|
里程碑:它反应了同层级下在其上面的工作项和阶段的完成情况.
|
||||||
|
`
|
||||||
},
|
},
|
||||||
notification:{
|
notification:{
|
||||||
teamUserAdd:{
|
teamUserAdd:{
|
||||||
@ -92,7 +97,7 @@ export default {
|
|||||||
issueReporterAssign:{
|
issueReporterAssign:{
|
||||||
0:"工作项:",
|
0:"工作项:",
|
||||||
1:"将",
|
1:"将",
|
||||||
2:"的reporter移交给你了"
|
2:"的汇报人移交给你了"
|
||||||
},
|
},
|
||||||
issueWorkflowChange:{
|
issueWorkflowChange:{
|
||||||
0:"工作项:",
|
0:"工作项:",
|
||||||
@ -112,7 +117,7 @@ export default {
|
|||||||
issueAssignerAssign:{
|
issueAssignerAssign:{
|
||||||
0:"工作项:",
|
0:"工作项:",
|
||||||
1:"将",
|
1:"将",
|
||||||
2:"的assigner移交给你了"
|
2:"的执行人移交给你了"
|
||||||
},
|
},
|
||||||
issueAssignRelease:{
|
issueAssignRelease:{
|
||||||
0:"工作项:",
|
0:"工作项:",
|
||||||
@ -141,6 +146,7 @@ export default {
|
|||||||
deleteEvent:"你要删除这个日历事件吗?",
|
deleteEvent:"你要删除这个日历事件吗?",
|
||||||
deleteFolder:"你要删除这个文件夹吗?",
|
deleteFolder:"你要删除这个文件夹吗?",
|
||||||
deleteItems:"你要删除这些项目吗?",
|
deleteItems:"你要删除这些项目吗?",
|
||||||
|
deleteItem:"你要删除这个项目吗?",
|
||||||
deleteFavorite:"你要删除这个会议吗?",
|
deleteFavorite:"你要删除这个会议吗?",
|
||||||
leaveMeeting:"你要离开这个会议吗?",
|
leaveMeeting:"你要离开这个会议吗?",
|
||||||
endMeeting:"你要结束这个会议吗?",
|
endMeeting:"你要结束这个会议吗?",
|
||||||
@ -236,7 +242,10 @@ export default {
|
|||||||
deleteOrganization:"你是否想删除该组织?",
|
deleteOrganization:"你是否想删除该组织?",
|
||||||
notMatch:"输入不匹配",
|
notMatch:"输入不匹配",
|
||||||
quitOrganization:"你确定要退出当前组织吗?",
|
quitOrganization:"你确定要退出当前组织吗?",
|
||||||
switchToOrganization:"你应该切换到组织"
|
switchToOrganization:"你应该切换到组织",
|
||||||
|
deletePlan:"你要删除这个规划吗?",
|
||||||
|
bindExistedAccount:"你是否要绑定到一个已有账号?",
|
||||||
|
leavePage:"你确定要离开当前页面吗?"
|
||||||
},
|
},
|
||||||
placeholder:{
|
placeholder:{
|
||||||
pleaseSelect:"请选择",
|
pleaseSelect:"请选择",
|
||||||
@ -275,7 +284,8 @@ export default {
|
|||||||
typeOrganizationName:"输入组织名称",
|
typeOrganizationName:"输入组织名称",
|
||||||
typeProjectReleaseName:"输入发布名称",
|
typeProjectReleaseName:"输入发布名称",
|
||||||
typeCalendarEventName:"输入日历事件名称",
|
typeCalendarEventName:"输入日历事件名称",
|
||||||
typeMeetingRoomName:"输入会议名称"
|
typeMeetingRoomName:"输入会议名称",
|
||||||
|
typePlanName:"输入规划名称"
|
||||||
},
|
},
|
||||||
"util":{
|
"util":{
|
||||||
"ok":"确认",
|
"ok":"确认",
|
||||||
@ -388,7 +398,7 @@ export default {
|
|||||||
resend:"重发",
|
resend:"重发",
|
||||||
wallpaper:"壁纸",
|
wallpaper:"壁纸",
|
||||||
members:"成员",
|
members:"成员",
|
||||||
detail:"描述",
|
detail:"详情",
|
||||||
more:"更多",
|
more:"更多",
|
||||||
basic:"基本信息",
|
basic:"基本信息",
|
||||||
projectBoard:"项目面板",
|
projectBoard:"项目面板",
|
||||||
@ -424,6 +434,7 @@ export default {
|
|||||||
meetingPassword:"会议密码",
|
meetingPassword:"会议密码",
|
||||||
users:"用户",
|
users:"用户",
|
||||||
selectCamera:"选择相机",
|
selectCamera:"选择相机",
|
||||||
|
selectAudio:"选择音频输入",
|
||||||
videoOn:"视频开启",
|
videoOn:"视频开启",
|
||||||
audioOn:"声音开启",
|
audioOn:"声音开启",
|
||||||
created:"已创建",
|
created:"已创建",
|
||||||
@ -431,7 +442,7 @@ export default {
|
|||||||
issueTypes:"工作项类型",
|
issueTypes:"工作项类型",
|
||||||
dateRange:"日期范围",
|
dateRange:"日期范围",
|
||||||
fixVersions:"修复版本",
|
fixVersions:"修复版本",
|
||||||
keyword:"项目Id",
|
keyword:"项目标识",
|
||||||
issueKey:"工作项Id",
|
issueKey:"工作项Id",
|
||||||
logo:"Logo",
|
logo:"Logo",
|
||||||
fieldType:"字段类型",
|
fieldType:"字段类型",
|
||||||
@ -500,7 +511,23 @@ export default {
|
|||||||
createdBy:"创建人",
|
createdBy:"创建人",
|
||||||
application:"应用",
|
application:"应用",
|
||||||
todo:"待办中",
|
todo:"待办中",
|
||||||
unNamed:"未命名"
|
unNamed:"未命名",
|
||||||
|
manDay:"工天",
|
||||||
|
plan:"规划",
|
||||||
|
gantt:"甘特图",
|
||||||
|
stage:"阶段",
|
||||||
|
milestone:"里程碑",
|
||||||
|
depend:"前置依赖",
|
||||||
|
delay:"延迟(天)",
|
||||||
|
day:"天",
|
||||||
|
month:"月",
|
||||||
|
sunday:"周日",
|
||||||
|
monday:"周一",
|
||||||
|
tuesday:"周二",
|
||||||
|
wednesday:"周三",
|
||||||
|
thursday:"周四",
|
||||||
|
friday:"周五",
|
||||||
|
saturday:"周六"
|
||||||
},
|
},
|
||||||
"common":{
|
"common":{
|
||||||
"component":{
|
"component":{
|
||||||
@ -923,6 +950,9 @@ export default {
|
|||||||
"login":{
|
"login":{
|
||||||
registerCode:{
|
registerCode:{
|
||||||
receiveCode:"如果你没有收到验证码邮件,你可以"
|
receiveCode:"如果你没有收到验证码邮件,你可以"
|
||||||
|
},
|
||||||
|
bindAccount:{
|
||||||
|
bindExistedAccount: "绑定已有账号"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
code/common/model/plan.ts
Normal file
18
code/common/model/plan.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import {BaseModel} from "./base"
|
||||||
|
|
||||||
|
export interface ICommon_Model_Plan {
|
||||||
|
id :string ,
|
||||||
|
name:string,
|
||||||
|
start_time:Date,
|
||||||
|
organization_user_id:string,
|
||||||
|
project_id:string,
|
||||||
|
organization_id:string
|
||||||
|
}
|
||||||
|
export const Table_Plan="plan"
|
||||||
|
|
||||||
|
class PlanModel extends BaseModel {
|
||||||
|
table=Table_Plan
|
||||||
|
model=<ICommon_Model_Plan>{}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let planModel=new PlanModel
|
28
code/common/model/plan_table.ts
Normal file
28
code/common/model/plan_table.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import {BaseModel} from "./base"
|
||||||
|
|
||||||
|
export enum ECommon_Model_Plan_Table {
|
||||||
|
STAGE,
|
||||||
|
MILESTONE,
|
||||||
|
ISSUE
|
||||||
|
}
|
||||||
|
export interface ICommon_Model_Plan_Table {
|
||||||
|
id :string,
|
||||||
|
sort:number,
|
||||||
|
type:ECommon_Model_Plan_Table
|
||||||
|
name:string,
|
||||||
|
ref_id:string,
|
||||||
|
progress:number,
|
||||||
|
depend_id:string,
|
||||||
|
delay:number,
|
||||||
|
parent_id:string,
|
||||||
|
plan_id:string,
|
||||||
|
project_id:string
|
||||||
|
}
|
||||||
|
export const Table_Plan_Table="plan_table"
|
||||||
|
|
||||||
|
class PlanTableModel extends BaseModel {
|
||||||
|
table=Table_Plan_Table
|
||||||
|
model=<ICommon_Model_Plan_Table>{}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let planTableModel=new PlanTableModel
|
@ -19,7 +19,8 @@ export interface ICommon_Model_Project_Issue {
|
|||||||
priority: ECommon_Model_Project_Issue_Priority,
|
priority: ECommon_Model_Project_Issue_Priority,
|
||||||
assigner_id: string,
|
assigner_id: string,
|
||||||
reporter_id: string,
|
reporter_id: string,
|
||||||
workflow_node_id: string
|
workflow_node_id: string,
|
||||||
|
man_day:number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Table_Project_Issue = "project_issue"
|
export const Table_Project_Issue = "project_issue"
|
||||||
|
@ -5,6 +5,12 @@ export enum ECommon_User_Type {
|
|||||||
ADMIN,
|
ADMIN,
|
||||||
DELETED
|
DELETED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ECommon_User_From_Type {
|
||||||
|
LOCAL,
|
||||||
|
WECHAT
|
||||||
|
}
|
||||||
|
|
||||||
export interface ICommon_Model_User {
|
export interface ICommon_Model_User {
|
||||||
id :string,
|
id :string,
|
||||||
username :string,
|
username :string,
|
||||||
@ -15,7 +21,9 @@ export interface ICommon_Model_User {
|
|||||||
sign :string,
|
sign :string,
|
||||||
active:number,
|
active:number,
|
||||||
role:ECommon_User_Type,
|
role:ECommon_User_Type,
|
||||||
count:number
|
count:number,
|
||||||
|
from_type:ECommon_User_From_Type,
|
||||||
|
from_id:string
|
||||||
}
|
}
|
||||||
export const Table_User="user"
|
export const Table_User="user"
|
||||||
|
|
||||||
|
@ -35,6 +35,15 @@ const api={
|
|||||||
type:ECommon_Application_Mode
|
type:ECommon_Application_Mode
|
||||||
}>{},
|
}>{},
|
||||||
ignoreValidate:true
|
ignoreValidate:true
|
||||||
|
},
|
||||||
|
wechatAppId:{
|
||||||
|
method:ECommon_HttpApi_Method.GET,
|
||||||
|
path:"/wechat/appid",
|
||||||
|
req:{},
|
||||||
|
res:<{
|
||||||
|
appId:string
|
||||||
|
}>{},
|
||||||
|
ignoreValidate:true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ const api={
|
|||||||
priority :number,
|
priority :number,
|
||||||
assignerId? :string ,
|
assignerId? :string ,
|
||||||
reporterId? :string ,
|
reporterId? :string ,
|
||||||
|
manDay:number,
|
||||||
values?:ICommon_Route_Req_ProjectIssue_Field[]
|
values?:ICommon_Route_Req_ProjectIssue_Field[]
|
||||||
}>{},
|
}>{},
|
||||||
res:<ICommon_Model_Project_Issue>{},
|
res:<ICommon_Model_Project_Issue>{},
|
||||||
@ -89,7 +90,8 @@ const api={
|
|||||||
name? :string,
|
name? :string,
|
||||||
priority? :number,
|
priority? :number,
|
||||||
assignerId? :string ,
|
assignerId? :string ,
|
||||||
reporterId? :string
|
reporterId? :string ,
|
||||||
|
manDay?:number
|
||||||
}>{},
|
}>{},
|
||||||
res:<ICommon_Model_Project_Issue>{},
|
res:<ICommon_Model_Project_Issue>{},
|
||||||
permission:[Permission_Types.Project.EDIT]
|
permission:[Permission_Types.Project.EDIT]
|
||||||
|
196
code/common/routes/plan.ts
Normal file
196
code/common/routes/plan.ts
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import {ECommon_Services} from "../types";
|
||||||
|
import {ECommon_HttpApi_Method} from "./types";
|
||||||
|
import {Permission_Types} from "../permission/permission";
|
||||||
|
import {ICommon_Model_Plan} from "../model/plan";
|
||||||
|
import {ICommon_Route_Res_Plan_Info, ICommon_Route_Res_Plan_Info_Item, ICommon_Route_Res_Plan_List} from "./response";
|
||||||
|
|
||||||
|
const api= {
|
||||||
|
baseUrl: "/plan",
|
||||||
|
service: ECommon_Services.Cooperation,
|
||||||
|
routes: {
|
||||||
|
listPlan: {
|
||||||
|
method: ECommon_HttpApi_Method.GET,
|
||||||
|
path: "/list",
|
||||||
|
req: <{
|
||||||
|
projectId: string,
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
keyword?: string
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_List>{},
|
||||||
|
permission: [Permission_Types.Project.READ]
|
||||||
|
},
|
||||||
|
plan: {
|
||||||
|
method: ECommon_HttpApi_Method.GET,
|
||||||
|
path: "/item",
|
||||||
|
req: <{
|
||||||
|
planId:string
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Model_Plan>{},
|
||||||
|
permission: [Permission_Types.Project.READ]
|
||||||
|
},
|
||||||
|
createPlan: {
|
||||||
|
method: ECommon_HttpApi_Method.POST,
|
||||||
|
path: "/item",
|
||||||
|
req: <{
|
||||||
|
projectId:string,
|
||||||
|
name:string,
|
||||||
|
startTime:number
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Model_Plan>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
editPlan:{
|
||||||
|
method: ECommon_HttpApi_Method.PUT,
|
||||||
|
path: "/item",
|
||||||
|
req: <{
|
||||||
|
planId:string
|
||||||
|
name?:string,
|
||||||
|
startTime?:number
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Model_Plan>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
removePlan:{
|
||||||
|
method: ECommon_HttpApi_Method.DELETE,
|
||||||
|
path: "/item",
|
||||||
|
req: <{
|
||||||
|
planId:string
|
||||||
|
}>{},
|
||||||
|
res: {},
|
||||||
|
permission: [Permission_Types.Project.DELETE,Permission_Types.Common.SELF],
|
||||||
|
permissionOr:true
|
||||||
|
},
|
||||||
|
info:{
|
||||||
|
method: ECommon_HttpApi_Method.GET,
|
||||||
|
path: "/info",
|
||||||
|
req: <{
|
||||||
|
planId:string
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info>{},
|
||||||
|
permission: [Permission_Types.Project.READ]
|
||||||
|
},
|
||||||
|
createStage:{
|
||||||
|
method: ECommon_HttpApi_Method.POST,
|
||||||
|
path: "/stage",
|
||||||
|
req: <{
|
||||||
|
planId:string,
|
||||||
|
name:string,
|
||||||
|
parentId?:string,
|
||||||
|
dependId?:string,
|
||||||
|
delay?:number
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info_Item[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
editStage:{
|
||||||
|
method: ECommon_HttpApi_Method.PUT,
|
||||||
|
path: "/stage",
|
||||||
|
req: <{
|
||||||
|
planItemId:string,
|
||||||
|
name?:string,
|
||||||
|
dependId?:string,
|
||||||
|
delay?:number
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info_Item[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
createMileStone:{
|
||||||
|
method: ECommon_HttpApi_Method.POST,
|
||||||
|
path: "/milestone",
|
||||||
|
req: <{
|
||||||
|
planId:string,
|
||||||
|
name:string,
|
||||||
|
parentId?:string
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info_Item[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
editMileStone:{
|
||||||
|
method: ECommon_HttpApi_Method.PUT,
|
||||||
|
path: "/milestone",
|
||||||
|
req: <{
|
||||||
|
planItemId:string,
|
||||||
|
name?:string,
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info_Item[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
addIssue:{
|
||||||
|
method: ECommon_HttpApi_Method.POST,
|
||||||
|
path: "/issue",
|
||||||
|
req: <{
|
||||||
|
planId:string,
|
||||||
|
parentId?:string,
|
||||||
|
projectIssueId:string,
|
||||||
|
dependId?:string,
|
||||||
|
delay?:number
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info_Item[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
editIssue:{
|
||||||
|
method: ECommon_HttpApi_Method.PUT,
|
||||||
|
path: "/issue",
|
||||||
|
req: <{
|
||||||
|
planItemId:string,
|
||||||
|
dependId?:string,
|
||||||
|
delay?:number,
|
||||||
|
manDay?:number
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info_Item[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
removeItem:{
|
||||||
|
method: ECommon_HttpApi_Method.DELETE,
|
||||||
|
path: "/list/item",
|
||||||
|
req: <{
|
||||||
|
planItemId:string
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info_Item[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
editProgress:{
|
||||||
|
method: ECommon_HttpApi_Method.PUT,
|
||||||
|
path: "/issue/progress",
|
||||||
|
req: <{
|
||||||
|
planItemId:string,
|
||||||
|
progress:number
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info_Item[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
moveItem:{
|
||||||
|
method: ECommon_HttpApi_Method.PUT,
|
||||||
|
path: "/move",
|
||||||
|
req: <{
|
||||||
|
planItemId:string,
|
||||||
|
targetId:string,
|
||||||
|
action:"in"|"top"|"bottom"
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Route_Res_Plan_Info_Item[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
},
|
||||||
|
issuePlanList:{
|
||||||
|
method: ECommon_HttpApi_Method.GET,
|
||||||
|
path: "/issueplan/list",
|
||||||
|
req: <{
|
||||||
|
projectIssueId:string
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Model_Plan[]>{},
|
||||||
|
permission: [Permission_Types.Project.READ]
|
||||||
|
},
|
||||||
|
issuePlanEdit:{
|
||||||
|
method: ECommon_HttpApi_Method.POST,
|
||||||
|
path: "/issueplan",
|
||||||
|
req: <{
|
||||||
|
projectIssueId:string,
|
||||||
|
planList:string[]
|
||||||
|
}>{},
|
||||||
|
res: <ICommon_Model_Plan[]>{},
|
||||||
|
permission: [Permission_Types.Project.EDIT]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default api
|
@ -35,6 +35,8 @@ import {ICommon_Model_Board_Column} from "../model/board_column";
|
|||||||
import {ICommon_Model_Sticky_Note} from "../model/sticky_note";
|
import {ICommon_Model_Sticky_Note} from "../model/sticky_note";
|
||||||
import {ICommon_Model_Content} from "../model/content";
|
import {ICommon_Model_Content} from "../model/content";
|
||||||
import {ICommon_Model_Photo} from "../model/photo";
|
import {ICommon_Model_Photo} from "../model/photo";
|
||||||
|
import {ICommon_Model_Plan_Table} from "../model/plan_table";
|
||||||
|
import {ICommon_Model_Plan} from "../model/plan";
|
||||||
|
|
||||||
export interface ICommon_Route_Res_Project_CreateModule_Data {
|
export interface ICommon_Route_Res_Project_CreateModule_Data {
|
||||||
id:string,
|
id:string,
|
||||||
@ -560,3 +562,20 @@ export type ICommon_Route_Res_Photo_item =ICommon_Model_Photo & {
|
|||||||
export type ICommon_Route_Res_RecentIssue_Item= ICommon_Model_Project_Issue & {
|
export type ICommon_Route_Res_RecentIssue_Item= ICommon_Model_Project_Issue & {
|
||||||
project:ICommon_Model_Project
|
project:ICommon_Model_Project
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ICommon_Route_Res_Plan_Info_Item=ICommon_Model_Plan_Table & {
|
||||||
|
issue?:ICommon_Model_Project_Issue,
|
||||||
|
workflow?:ICommon_Model_Workflow_Node,
|
||||||
|
children?:ICommon_Route_Res_Plan_Info_Item[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ICommon_Route_Res_Plan_Info = ICommon_Model_Plan & {
|
||||||
|
data:ICommon_Route_Res_Plan_Info_Item[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ICommon_Route_Res_Plan_List={
|
||||||
|
count:number,
|
||||||
|
totalPage:number,
|
||||||
|
page:number,
|
||||||
|
data:ICommon_Model_Plan[]
|
||||||
|
}
|
@ -162,7 +162,8 @@ const api={
|
|||||||
path:"/register/confirm",
|
path:"/register/confirm",
|
||||||
req:<{
|
req:<{
|
||||||
username:string,
|
username:string,
|
||||||
code:string
|
code:string,
|
||||||
|
openId?:string
|
||||||
}>{},
|
}>{},
|
||||||
res:{},
|
res:{},
|
||||||
ignoreValidate:true
|
ignoreValidate:true
|
||||||
@ -219,6 +220,38 @@ const api={
|
|||||||
lang:string
|
lang:string
|
||||||
}>{},
|
}>{},
|
||||||
res:{},
|
res:{},
|
||||||
|
},
|
||||||
|
wechatCode:{
|
||||||
|
method:ECommon_HttpApi_Method.GET,
|
||||||
|
path:"/wechat/code",
|
||||||
|
req:<{
|
||||||
|
code:string
|
||||||
|
}>{},
|
||||||
|
res:<{
|
||||||
|
openId:string
|
||||||
|
}>{},
|
||||||
|
ignoreValidate:true
|
||||||
|
},
|
||||||
|
wechatLogin:{
|
||||||
|
method:ECommon_HttpApi_Method.POST,
|
||||||
|
path:"/wechat/login",
|
||||||
|
req:<{
|
||||||
|
openId:string,
|
||||||
|
lang:string
|
||||||
|
}>{},
|
||||||
|
res:<Omit<ICommon_Model_User,"password">>{},
|
||||||
|
ignoreValidate:true
|
||||||
|
},
|
||||||
|
bindWechat:{
|
||||||
|
method:ECommon_HttpApi_Method.POST,
|
||||||
|
path:"/wechat/bind",
|
||||||
|
req:<{
|
||||||
|
openId:string,
|
||||||
|
username:string,
|
||||||
|
password:string
|
||||||
|
}>{},
|
||||||
|
res:{},
|
||||||
|
ignoreValidate:true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -582,6 +582,43 @@ export namespace Err {
|
|||||||
zh: "面板为包含该工作项类型"
|
zh: "面板为包含该工作项类型"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Plan:{
|
||||||
|
planNotFound:{
|
||||||
|
code:3920,
|
||||||
|
msg:{
|
||||||
|
en:"plan not found",
|
||||||
|
zh:"计划不存在"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
planItemNotFound:{
|
||||||
|
code:3921,
|
||||||
|
msg:{
|
||||||
|
en:"plan item not found",
|
||||||
|
zh:"计划项目不存在"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
planTypeNotMatched:{
|
||||||
|
code:3922,
|
||||||
|
msg:{
|
||||||
|
en:"plan type not matched",
|
||||||
|
zh:"计划的类型不匹配"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dependItemNotMatchedParentItem:{
|
||||||
|
code:3923,
|
||||||
|
msg:{
|
||||||
|
en:"depend item not matched parent item",
|
||||||
|
zh:"依赖项需要和是父项目的子项目"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
operationForbidden:{
|
||||||
|
code:3924,
|
||||||
|
msg:{
|
||||||
|
en:"operation is forbidden",
|
||||||
|
zh:"禁止该操作"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const Team = {
|
export const Team = {
|
||||||
|
Binary file not shown.
@ -9,7 +9,7 @@ import rpcCooperationApi from "../../cooperation/rpc/cooperation"
|
|||||||
@DComponent
|
@DComponent
|
||||||
class PermissionProject extends PermissionBase {
|
class PermissionProject extends PermissionBase {
|
||||||
fieldName="projectId"
|
fieldName="projectId"
|
||||||
async translateToField({commentId,projectIssueId,labelId,moduleId,projectReleaseId,roleId,boardId,boardSprintId,boardSprintSwimLaneId,boardColumnId}: { [param: string]: any; isAdmin?: boolean;commentId:string,projectIssueId:string,labelId:string,moduleId:string,projectReleaseId:string ,roleId:string}): Promise<string> {
|
async translateToField({commentId,projectIssueId,labelId,moduleId,projectReleaseId,roleId,boardId,boardSprintId,boardSprintSwimLaneId,boardColumnId,planId,planItemId}: { [param: string]: any; isAdmin?: boolean;commentId:string,projectIssueId:string,labelId:string,moduleId:string,projectReleaseId:string ,roleId:string}): Promise<string> {
|
||||||
let projectId:string
|
let projectId:string
|
||||||
if(commentId) {
|
if(commentId) {
|
||||||
let obj=new REDIS_AUTH.Permission.Project.ProjectIdFromProjectIssueComment(commentId);
|
let obj=new REDIS_AUTH.Permission.Project.ProjectIdFromProjectIssueComment(commentId);
|
||||||
@ -53,6 +53,14 @@ class PermissionProject extends PermissionBase {
|
|||||||
let obj=new REDIS_AUTH.Permission.Project.ProjectIdFromBoardColumn(boardColumnId);
|
let obj=new REDIS_AUTH.Permission.Project.ProjectIdFromBoardColumn(boardColumnId);
|
||||||
projectId=await obj.getValue();
|
projectId=await obj.getValue();
|
||||||
return projectId;
|
return projectId;
|
||||||
|
} else if(planId) {
|
||||||
|
let obj=new REDIS_AUTH.Permission.Project.ProjectIdFromPlan(planId);
|
||||||
|
projectId=await obj.getValue();
|
||||||
|
return projectId;
|
||||||
|
} else if(planItemId) {
|
||||||
|
let obj=new REDIS_AUTH.Permission.Project.ProjectIdFromPlanItem(planItemId);
|
||||||
|
projectId=await obj.getValue();
|
||||||
|
return projectId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +77,10 @@ export default abstract class Application{
|
|||||||
"port": number,
|
"port": number,
|
||||||
"user":string,
|
"user":string,
|
||||||
"pass":string
|
"pass":string
|
||||||
|
},
|
||||||
|
wechat:{
|
||||||
|
appId:string,
|
||||||
|
appSecret:string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Application(){
|
Application(){
|
||||||
|
85
code/server/common/cache/keys/auth.ts
vendored
85
code/server/common/cache/keys/auth.ts
vendored
@ -30,6 +30,9 @@ import {boardModel} from "../../../../common/model/board";
|
|||||||
import {boardSprintModel} from "../../../../common/model/board_sprint";
|
import {boardSprintModel} from "../../../../common/model/board_sprint";
|
||||||
import {boardSprintSwimLaneModel} from "../../../../common/model/board_sprint_swimlane";
|
import {boardSprintSwimLaneModel} from "../../../../common/model/board_sprint_swimlane";
|
||||||
import {boardColumnModel} from "../../../../common/model/board_column";
|
import {boardColumnModel} from "../../../../common/model/board_column";
|
||||||
|
import {planModel} from "../../../../common/model/plan";
|
||||||
|
import {projectModel} from "../../../../common/model/project";
|
||||||
|
import {planTableModel} from "../../../../common/model/plan_table";
|
||||||
|
|
||||||
export namespace REDIS_AUTH {
|
export namespace REDIS_AUTH {
|
||||||
export namespace Resource {
|
export namespace Resource {
|
||||||
@ -489,6 +492,88 @@ export namespace REDIS_AUTH {
|
|||||||
return projectId;
|
return projectId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class ProjectIdFromPlan extends BaseRedisStringCache<string> {
|
||||||
|
key="permission:projectPlan:{0}"
|
||||||
|
constructor(private planId:string) {
|
||||||
|
super()
|
||||||
|
this.objRedis=new RedisStringKey(StringUtil.format(this.key,planId),cacheRedisType<string>().String,1000*10);
|
||||||
|
}
|
||||||
|
async getValue():Promise<string> {
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
let projectId:string
|
||||||
|
let exists=await this.objRedis.exists()
|
||||||
|
if(exists) {
|
||||||
|
projectId=await this.objRedis.get()
|
||||||
|
} else {
|
||||||
|
let sql=generateLeftJoinSql({
|
||||||
|
model:planModel
|
||||||
|
},{
|
||||||
|
model:projectModel,
|
||||||
|
columns:["id"],
|
||||||
|
expression:{
|
||||||
|
id:{
|
||||||
|
model:planModel,
|
||||||
|
field:"project_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
id:{
|
||||||
|
model:planModel,
|
||||||
|
value:this.planId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let ret=await mysql.executeOne(sql)
|
||||||
|
if(ret) {
|
||||||
|
projectId=ret.id
|
||||||
|
await this.objRedis.set(projectId)
|
||||||
|
} else {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return projectId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ProjectIdFromPlanItem extends BaseRedisStringCache<string> {
|
||||||
|
key="permission:projectPlanItem:{0}"
|
||||||
|
constructor(private planItemId:string) {
|
||||||
|
super()
|
||||||
|
this.objRedis=new RedisStringKey(StringUtil.format(this.key,planItemId),cacheRedisType<string>().String,1000*10);
|
||||||
|
}
|
||||||
|
async getValue():Promise<string> {
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
let projectId:string
|
||||||
|
let exists=await this.objRedis.exists()
|
||||||
|
if(exists) {
|
||||||
|
projectId=await this.objRedis.get()
|
||||||
|
} else {
|
||||||
|
let sql=generateLeftJoinSql({
|
||||||
|
model:planTableModel
|
||||||
|
},{
|
||||||
|
model:projectModel,
|
||||||
|
columns:["id"],
|
||||||
|
expression:{
|
||||||
|
id:{
|
||||||
|
model:planTableModel,
|
||||||
|
field:"project_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
id:{
|
||||||
|
model:planTableModel,
|
||||||
|
value:this.planItemId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let ret=await mysql.executeOne(sql)
|
||||||
|
if(ret) {
|
||||||
|
projectId=ret.id
|
||||||
|
await this.objRedis.set(projectId)
|
||||||
|
} else {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return projectId;
|
||||||
|
}
|
||||||
|
}
|
||||||
export class ProjectOrganizationUsers extends BaseRedisHashCache<string> {
|
export class ProjectOrganizationUsers extends BaseRedisHashCache<string> {
|
||||||
key="permission:project:{0}:organizationUser"
|
key="permission:project:{0}:organizationUser"
|
||||||
constructor(private projectId:string) {
|
constructor(private projectId:string) {
|
||||||
|
8
code/server/common/cache/keys/user.ts
vendored
8
code/server/common/cache/keys/user.ts
vendored
@ -19,6 +19,7 @@ export namespace REDIS_USER {
|
|||||||
let USER_ORGANIZATION_KEY=`${ECommon_Services.User}:user:{0}:organization`
|
let USER_ORGANIZATION_KEY=`${ECommon_Services.User}:user:{0}:organization`
|
||||||
let USER_REGISTER_KEY=`${ECommon_Services.User}:username:{0}:register`
|
let USER_REGISTER_KEY=`${ECommon_Services.User}:username:{0}:register`
|
||||||
let USER_RESET_CODE_KEY=`${ECommon_Services.User}:user:{0}:reset`
|
let USER_RESET_CODE_KEY=`${ECommon_Services.User}:user:{0}:reset`
|
||||||
|
let WECHAT_OPEN_ID_KEY=`${ECommon_Services.User}:wechat:openid`
|
||||||
export function token(userId:string)
|
export function token(userId:string)
|
||||||
{
|
{
|
||||||
let obj=new RedisStringKey(StringUtil.format(USER_TOKEN_KEY,userId),cacheRedisType<string>().String,300)
|
let obj=new RedisStringKey(StringUtil.format(USER_TOKEN_KEY,userId),cacheRedisType<string>().String,300)
|
||||||
@ -51,4 +52,11 @@ export namespace REDIS_USER {
|
|||||||
let obj=new RedisStringKey(StringUtil.format(USER_RESET_CODE_KEY,userId),cacheRedisType<ICommon_Register_Cache_Info>().Object,600)
|
let obj=new RedisStringKey(StringUtil.format(USER_RESET_CODE_KEY,userId),cacheRedisType<ICommon_Register_Cache_Info>().Object,600)
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
export function wechatOpenId(openId:string)
|
||||||
|
{
|
||||||
|
let obj=new RedisStringKey(StringUtil.format(WECHAT_OPEN_ID_KEY,openId),cacheRedisType<{
|
||||||
|
img:string
|
||||||
|
}>().Object,600)
|
||||||
|
return obj
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,10 @@
|
|||||||
import Application from "../app/app";
|
import Application from "../app/app";
|
||||||
import {IServer_Common_Config_Mail, IServer_Common_Config_Mysql, IServer_Common_Config_Redis} from "../types/config";
|
import {
|
||||||
|
IServer_Common_Config_Mail,
|
||||||
|
IServer_Common_Config_Mysql,
|
||||||
|
IServer_Common_Config_Redis,
|
||||||
|
IServer_Common_Config_Wechat
|
||||||
|
} from "../types/config";
|
||||||
|
|
||||||
class Config {
|
class Config {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -27,6 +32,9 @@ class Config {
|
|||||||
get mailInfo():IServer_Common_Config_Mail {
|
get mailInfo():IServer_Common_Config_Mail {
|
||||||
return Application.privateConfig.mail
|
return Application.privateConfig.mail
|
||||||
}
|
}
|
||||||
|
get wechatInfo():IServer_Common_Config_Wechat {
|
||||||
|
return Application.privateConfig.wechat
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var g_config:InstanceType<typeof Config>
|
var g_config:InstanceType<typeof Config>
|
||||||
export function getConfigInstance() {
|
export function getConfigInstance() {
|
||||||
|
64
code/server/common/request/request.ts
Normal file
64
code/server/common/request/request.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import * as https from 'https'
|
||||||
|
import {IncomingHttpHeaders} from "http"
|
||||||
|
import * as fs from "fs-extra";
|
||||||
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
|
export const request=(urlOptions: string | https.RequestOptions | URL, data = ''):Promise<{
|
||||||
|
statusCode:number,
|
||||||
|
headers:IncomingHttpHeaders,
|
||||||
|
body:string
|
||||||
|
}> => new Promise((resolve, reject) => {
|
||||||
|
const req = https.request(urlOptions, res => {
|
||||||
|
const chunks = []
|
||||||
|
|
||||||
|
res.on('data', chunk => chunks.push(chunk))
|
||||||
|
res.on('error', reject)
|
||||||
|
res.on('end', () => {
|
||||||
|
const { statusCode, headers } = res
|
||||||
|
const validResponse = statusCode >= 200 && statusCode <= 299
|
||||||
|
const body = chunks.join('')
|
||||||
|
|
||||||
|
if (validResponse) resolve({ statusCode, headers, body })
|
||||||
|
else reject(new Error(`Request failed. status: ${statusCode}, body: ${body}`))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
req.on('error', reject)
|
||||||
|
req.write(data, 'binary')
|
||||||
|
req.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const download=(uri:string,dest:string):Promise<{
|
||||||
|
md5:string,
|
||||||
|
size:number
|
||||||
|
}>=>{
|
||||||
|
return new Promise(async (resolve, reject)=>{
|
||||||
|
await fs.ensureFile(dest)
|
||||||
|
const file = fs.createWriteStream(dest);
|
||||||
|
https.get(uri, (res)=>{
|
||||||
|
if(res.statusCode !== 200){
|
||||||
|
reject(res.statusCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hash = crypto.createHash("md5");
|
||||||
|
let size=0;
|
||||||
|
res.on("data",chunk => {
|
||||||
|
hash.update(chunk, "utf8");
|
||||||
|
size+=chunk.length
|
||||||
|
});
|
||||||
|
file.on('finish', ()=>{
|
||||||
|
file.close(err => {
|
||||||
|
const md5 = hash.digest("hex");
|
||||||
|
resolve({
|
||||||
|
md5,
|
||||||
|
size
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}).on('error', (err)=>{
|
||||||
|
fs.unlink(dest);
|
||||||
|
reject(err.message);
|
||||||
|
})
|
||||||
|
|
||||||
|
res.pipe(file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -24,6 +24,11 @@ export interface IServer_Common_Config_Mail {
|
|||||||
"pass":string
|
"pass":string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IServer_Common_Config_Wechat {
|
||||||
|
appId:string,
|
||||||
|
appSecret:string
|
||||||
|
}
|
||||||
|
|
||||||
export interface IServer_Common_Config {
|
export interface IServer_Common_Config {
|
||||||
services:{
|
services:{
|
||||||
[name:string]:any
|
[name:string]:any
|
||||||
|
@ -7,4 +7,5 @@ import "../http/release";
|
|||||||
import "../http/label";
|
import "../http/label";
|
||||||
import "../http/workflow";
|
import "../http/workflow";
|
||||||
import "../http/board"
|
import "../http/board"
|
||||||
|
import "../http/plan"
|
||||||
import "../rpc/cooperation";
|
import "../rpc/cooperation";
|
File diff suppressed because it is too large
Load Diff
441
code/server/cooperation/http/plan.ts
Normal file
441
code/server/cooperation/http/plan.ts
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
import {DComponent} from "../../common/decorate/component";
|
||||||
|
import {DHttpApi, DHttpController, DHttpReqParam, DHttpReqParamRequired, DHttpUser} from "../../common/http/http";
|
||||||
|
import planApi from "../../../common/routes/plan";
|
||||||
|
import {PlanService, PlanTableService} from "../service/plan";
|
||||||
|
import {Err} from "../../../common/status/error";
|
||||||
|
import {IUserSession} from "../../user/types/config";
|
||||||
|
import {ECommon_Model_Plan_Table} from "../../../common/model/plan_table";
|
||||||
|
import {ProjectIssueService} from "../service/issue";
|
||||||
|
import {ECommon_Model_Workflow_Node_Status} from "../../../common/model/workflow_node";
|
||||||
|
|
||||||
|
@DComponent
|
||||||
|
@DHttpController(planApi)
|
||||||
|
class PlanController {
|
||||||
|
@DHttpApi(planApi.routes.listPlan)
|
||||||
|
async listPlan(@DHttpReqParamRequired("projectId") projectId: string, @DHttpReqParamRequired("page") page: number, @DHttpReqParamRequired("size") size: number, @DHttpReqParam("keyword") keyword: string): Promise<typeof planApi.routes.listPlan.res> {
|
||||||
|
let list=await PlanService.list(projectId,page,size,keyword)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.plan)
|
||||||
|
async plan(@DHttpReqParamRequired("planId") planId: string): Promise<typeof planApi.routes.plan.res> {
|
||||||
|
let obj=await PlanService.getItemById(planId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
return obj.getItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.createPlan)
|
||||||
|
async createPlan(@DHttpReqParamRequired("projectId") projectId: string,@DHttpReqParamRequired("name") name: string,@DHttpReqParamRequired("startTime") startTime: number,@DHttpUser user:IUserSession): Promise<typeof planApi.routes.createPlan.res> {
|
||||||
|
let obj=new PlanService()
|
||||||
|
obj.assignItem({
|
||||||
|
project_id:projectId,
|
||||||
|
name,
|
||||||
|
start_time:new Date(startTime),
|
||||||
|
organization_id:user.organizationInfo.organizationId,
|
||||||
|
organization_user_id:user.organizationInfo.organizationUserId
|
||||||
|
})
|
||||||
|
let ret=await obj.create()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.editPlan)
|
||||||
|
async editPlan(@DHttpReqParamRequired("planId") planId: string,@DHttpReqParam("name") name: string,@DHttpReqParam("startTime") startTime: number): Promise<typeof planApi.routes.editPlan.res> {
|
||||||
|
let obj=await PlanService.getItemById(planId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
obj.assignItem({
|
||||||
|
name,
|
||||||
|
...(startTime && {
|
||||||
|
start_time:new Date(startTime)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let ret=await obj.update()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.removePlan)
|
||||||
|
async removePlan(@DHttpReqParamRequired("planId") planId: string): Promise<typeof planApi.routes.removePlan.res> {
|
||||||
|
let obj=await PlanService.getItemById(planId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
await obj.delete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.info)
|
||||||
|
async info(@DHttpReqParamRequired("planId") planId: string): Promise<typeof planApi.routes.info.res> {
|
||||||
|
let obj=await PlanService.getItemById(planId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
let ret=await obj.info()
|
||||||
|
return {
|
||||||
|
...obj.getItem(),
|
||||||
|
data:ret
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.createStage)
|
||||||
|
async createStage(@DHttpReqParamRequired("planId") planId: string,@DHttpReqParam("name") name: string,@DHttpReqParam("parentId") parentId: string,@DHttpReqParam("dependId") dependId: string,@DHttpReqParam("delay") delay: number): Promise<typeof planApi.routes.createStage.res> {
|
||||||
|
let [objPlan,objParentItem,objDependItem,newSort]=await Promise.all([
|
||||||
|
(async ()=>{
|
||||||
|
let obj=await PlanService.getItemById(planId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
})(),
|
||||||
|
parentId?(async ()=>{
|
||||||
|
let objParent=await PlanTableService.getItemById(parentId)
|
||||||
|
if(!objParent) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
} else if(objParent.getItem().type!==ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
throw Err.Project.Plan.planTypeNotMatched
|
||||||
|
}
|
||||||
|
return objParent.getItem()
|
||||||
|
})():null,
|
||||||
|
dependId?(async ()=>{
|
||||||
|
let objDepend=await PlanTableService.getItemById(dependId)
|
||||||
|
if(!objDepend) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
} else if(objDepend.getItem().type===ECommon_Model_Plan_Table.MILESTONE) {
|
||||||
|
throw Err.Project.Plan.planTypeNotMatched
|
||||||
|
}
|
||||||
|
return objDepend.getItem()
|
||||||
|
})():null,
|
||||||
|
(async ()=>{
|
||||||
|
let sort=await PlanTableService.getNewSort(parentId??null,planId)
|
||||||
|
return sort
|
||||||
|
})()
|
||||||
|
])
|
||||||
|
if(objDependItem && objDependItem.parent_id!=parentId) {
|
||||||
|
throw Err.Project.Plan.dependItemNotMatchedParentItem
|
||||||
|
}
|
||||||
|
let obj=new PlanTableService()
|
||||||
|
obj.assignItem({
|
||||||
|
plan_id:planId,
|
||||||
|
type:ECommon_Model_Plan_Table.STAGE,
|
||||||
|
sort:newSort,
|
||||||
|
name,
|
||||||
|
parent_id:parentId,
|
||||||
|
delay,
|
||||||
|
depend_id:dependId,
|
||||||
|
project_id:objPlan.getItem().project_id,
|
||||||
|
})
|
||||||
|
await obj.create()
|
||||||
|
let ret=await objPlan.info()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.editStage)
|
||||||
|
async editStage(@DHttpReqParamRequired("planItemId") planItemId: string,@DHttpReqParamRequired("name") name: string,@DHttpReqParam("dependId") dependId: string,@DHttpReqParam("delay") delay: number): Promise<typeof planApi.routes.editStage.res> {
|
||||||
|
let obj=await PlanTableService.getItemById(planItemId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
}
|
||||||
|
if(dependId) {
|
||||||
|
let objDepend=await PlanTableService.getItemById(dependId)
|
||||||
|
if(!objDepend) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
} else if(objDepend.getItem().type===ECommon_Model_Plan_Table.MILESTONE) {
|
||||||
|
throw Err.Project.Plan.planTypeNotMatched
|
||||||
|
} else if(objDepend.getItem().parent_id!=obj.getItem().parent_id) {
|
||||||
|
throw Err.Project.Plan.dependItemNotMatchedParentItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.assignItem({
|
||||||
|
name,
|
||||||
|
depend_id:dependId,
|
||||||
|
delay
|
||||||
|
})
|
||||||
|
await obj.update()
|
||||||
|
let objPlan=await PlanService.getItemById(obj.getItem().plan_id)
|
||||||
|
let ret=await objPlan.info()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.createMileStone)
|
||||||
|
async createMileStone(@DHttpReqParamRequired("planId") planId: string,@DHttpReqParamRequired("name") name: string,@DHttpReqParam("parentId") parentId: string): Promise<typeof planApi.routes.createMileStone.res> {
|
||||||
|
let [objPlan,objParentItem,newSort]=await Promise.all([
|
||||||
|
(async ()=>{
|
||||||
|
let obj=await PlanService.getItemById(planId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
})(),
|
||||||
|
parentId?(async ()=>{
|
||||||
|
let objParent=await PlanTableService.getItemById(parentId)
|
||||||
|
if(!objParent) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
} else if(objParent.getItem().type!==ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
throw Err.Project.Plan.planTypeNotMatched
|
||||||
|
}
|
||||||
|
return objParent.getItem()
|
||||||
|
})():null,
|
||||||
|
(async ()=>{
|
||||||
|
let sort=await PlanTableService.getNewSort(parentId??null,planId)
|
||||||
|
return sort
|
||||||
|
})()
|
||||||
|
])
|
||||||
|
let obj=new PlanTableService()
|
||||||
|
obj.assignItem({
|
||||||
|
plan_id:planId,
|
||||||
|
type:ECommon_Model_Plan_Table.MILESTONE,
|
||||||
|
sort:newSort,
|
||||||
|
name,
|
||||||
|
parent_id:parentId,
|
||||||
|
project_id:objPlan.getItem().project_id,
|
||||||
|
})
|
||||||
|
await obj.create()
|
||||||
|
let ret=await objPlan.info()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.editMileStone)
|
||||||
|
async editMileStone(@DHttpReqParamRequired("planItemId") planItemId: string,@DHttpReqParam("name") name: string): Promise<typeof planApi.routes.editMileStone.res> {
|
||||||
|
let obj=await PlanTableService.getItemById(planItemId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
}
|
||||||
|
obj.assignItem({
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
await obj.update()
|
||||||
|
let objPlan=await PlanService.getItemById(obj.getItem().plan_id)
|
||||||
|
let ret=await objPlan.info()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.addIssue)
|
||||||
|
async addIssue(@DHttpReqParamRequired("planId") planId: string,@DHttpReqParam("parentId") parentId: string,@DHttpReqParamRequired("projectIssueId") projectIssueId: string,@DHttpReqParam("dependId") dependId: string,@DHttpReqParam("delay") delay: number): Promise<typeof planApi.routes.addIssue.res> {
|
||||||
|
let [objPlan,objParentItem,objProjectIssue,objDependItem,newSort]=await Promise.all([
|
||||||
|
(async ()=>{
|
||||||
|
let obj=await PlanService.getItemById(planId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
})(),
|
||||||
|
parentId?(async ()=>{
|
||||||
|
let objParent=await PlanTableService.getItemById(parentId)
|
||||||
|
if(!objParent) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
} else if(objParent.getItem().type!==ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
throw Err.Project.Plan.planTypeNotMatched
|
||||||
|
}
|
||||||
|
return objParent.getItem()
|
||||||
|
})():null,
|
||||||
|
ProjectIssueService.getItemById(projectIssueId),
|
||||||
|
dependId?(async ()=>{
|
||||||
|
let objDepend=await PlanTableService.getItemById(dependId)
|
||||||
|
if(!objDepend) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
} else if(objDepend.getItem().type===ECommon_Model_Plan_Table.MILESTONE) {
|
||||||
|
throw Err.Project.Plan.planTypeNotMatched
|
||||||
|
}
|
||||||
|
return objDepend.getItem()
|
||||||
|
})():null,
|
||||||
|
(async ()=>{
|
||||||
|
let sort=await PlanTableService.getNewSort(parentId??null,planId)
|
||||||
|
return sort
|
||||||
|
})()
|
||||||
|
])
|
||||||
|
if(objDependItem && objDependItem.parent_id!=parentId) {
|
||||||
|
throw Err.Project.Plan.dependItemNotMatchedParentItem
|
||||||
|
}
|
||||||
|
let obj=new PlanTableService()
|
||||||
|
obj.assignItem({
|
||||||
|
plan_id:planId,
|
||||||
|
type:ECommon_Model_Plan_Table.ISSUE,
|
||||||
|
sort:newSort,
|
||||||
|
parent_id:parentId,
|
||||||
|
project_id:objPlan.getItem().project_id,
|
||||||
|
delay,
|
||||||
|
depend_id:dependId,
|
||||||
|
ref_id:projectIssueId
|
||||||
|
})
|
||||||
|
await obj.create()
|
||||||
|
let childIssueList=await objProjectIssue.childIssueList()
|
||||||
|
if(childIssueList?.length>0) {
|
||||||
|
await Promise.all(childIssueList.map((item,index)=>{
|
||||||
|
let obj1=new PlanTableService()
|
||||||
|
obj1.assignItem({
|
||||||
|
plan_id:planId,
|
||||||
|
type:ECommon_Model_Plan_Table.ISSUE,
|
||||||
|
sort:index,
|
||||||
|
parent_id:obj.getId(),
|
||||||
|
project_id:objPlan.getItem().project_id,
|
||||||
|
ref_id:item.id
|
||||||
|
})
|
||||||
|
return obj1.create()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
let ret=await objPlan.info()
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.editIssue)
|
||||||
|
async editIssue(@DHttpReqParamRequired("planItemId") planItemId: string,@DHttpReqParam("dependId") dependId: string,@DHttpReqParam("delay") delay: number,@DHttpReqParam("manDay") manDay: number): Promise<typeof planApi.routes.editIssue.res> {
|
||||||
|
let [obj,objDepend]=await Promise.all([
|
||||||
|
(async ()=>{
|
||||||
|
let obj=await PlanTableService.getItemById(planItemId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
}
|
||||||
|
if(obj.getItem().parent_id) {
|
||||||
|
let objParent=await PlanTableService.getItemById(obj.getItem().parent_id)
|
||||||
|
if(!objParent) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
})(),
|
||||||
|
dependId?(async ()=>{
|
||||||
|
let obj=await PlanTableService.getItemById(dependId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
} else if(obj.getItem().type===ECommon_Model_Plan_Table.MILESTONE) {
|
||||||
|
throw Err.Project.Plan.planTypeNotMatched
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}):null
|
||||||
|
])
|
||||||
|
obj.assignItem({
|
||||||
|
depend_id:dependId,
|
||||||
|
delay
|
||||||
|
})
|
||||||
|
await obj.update()
|
||||||
|
if(manDay) {
|
||||||
|
let objIssue=await ProjectIssueService.getItemById(obj.getItem().ref_id)
|
||||||
|
objIssue.assignItem({
|
||||||
|
man_day:manDay
|
||||||
|
})
|
||||||
|
await objIssue.update()
|
||||||
|
}
|
||||||
|
let objPlan=await PlanService.getItemById(obj.getItem().plan_id)
|
||||||
|
let ret=await objPlan.info()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.removeItem)
|
||||||
|
async removeItem(@DHttpReqParamRequired("planItemId") planItemId: string): Promise<typeof planApi.routes.removeItem.res> {
|
||||||
|
let obj=await PlanTableService.getItemById(planItemId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
}
|
||||||
|
let hasParentIssue=await obj.hasParentIssue()
|
||||||
|
if(hasParentIssue) {
|
||||||
|
throw Err.Project.Plan.operationForbidden
|
||||||
|
}
|
||||||
|
await obj.delete()
|
||||||
|
let objPlan=await PlanService.getItemById(obj.getItem().plan_id)
|
||||||
|
let ret=await objPlan.info()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.editProgress)
|
||||||
|
async editProgress(@DHttpReqParamRequired("planItemId") planItemId: string,@DHttpReqParamRequired("progress") progress: number): Promise<typeof planApi.routes.editProgress.res> {
|
||||||
|
let obj=await PlanTableService.getItemById(planItemId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
} else if(obj.getItem().type!==ECommon_Model_Plan_Table.ISSUE) {
|
||||||
|
throw Err.Project.Plan.planTypeNotMatched
|
||||||
|
}
|
||||||
|
let issueInfo=await obj.issueInfo()
|
||||||
|
if(issueInfo.workflow.status!==ECommon_Model_Workflow_Node_Status.INPROGRESS) {
|
||||||
|
throw Err.Project.Plan.operationForbidden
|
||||||
|
}
|
||||||
|
obj.assignItem({
|
||||||
|
progress
|
||||||
|
})
|
||||||
|
await obj.update()
|
||||||
|
let objPlan=await PlanService.getItemById(obj.getItem().plan_id)
|
||||||
|
let ret=await objPlan.info()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.moveItem)
|
||||||
|
async moveItem(@DHttpReqParamRequired("planItemId") planItemId: string,@DHttpReqParamRequired("targetId") targetId: string,@DHttpReqParamRequired("action") action: "in"|"top"|"bottom"): Promise<typeof planApi.routes.moveItem.res> {
|
||||||
|
let obj=await PlanTableService.getItemById(planItemId)
|
||||||
|
if(!obj) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
}
|
||||||
|
let objTarget:PlanTableService
|
||||||
|
if(targetId) {
|
||||||
|
objTarget=await PlanTableService.getItemById(targetId)
|
||||||
|
if(!objTarget) {
|
||||||
|
throw Err.Project.Plan.planItemNotFound
|
||||||
|
}
|
||||||
|
if(action==="in" && objTarget.getItem().type!==ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
throw Err.Project.Plan.operationForbidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(action==="in") {
|
||||||
|
if(obj.getItem().parent_id!==objTarget.getItem().id) {
|
||||||
|
await obj.clearAllDepend()
|
||||||
|
}
|
||||||
|
let sort=await PlanTableService.getNewSort(targetId??null,obj.getItem().plan_id)
|
||||||
|
obj.assignItem({
|
||||||
|
depend_id:null,
|
||||||
|
parent_id:targetId??null,
|
||||||
|
sort
|
||||||
|
})
|
||||||
|
} else if(action==="top") {
|
||||||
|
if(obj.getItem().parent_id!==objTarget.getItem().parent_id) {
|
||||||
|
await obj.clearAllDepend()
|
||||||
|
}
|
||||||
|
await PlanTableService.moveUp(obj.getItem().parent_id,obj.getItem().sort,obj.getItem().plan_id)
|
||||||
|
await PlanTableService.moveDown(objTarget.getItem().parent_id,objTarget.getItem().sort,obj.getItem().plan_id)
|
||||||
|
obj.assignItem({
|
||||||
|
parent_id:objTarget.getItem().parent_id,
|
||||||
|
sort:objTarget.getItem().sort,
|
||||||
|
...(obj.getItem().parent_id!=objTarget.getItem().parent_id && {
|
||||||
|
depend_id:null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if(action==="bottom") {
|
||||||
|
if(obj.getItem().parent_id!==objTarget.getItem().parent_id) {
|
||||||
|
await obj.clearAllDepend()
|
||||||
|
}
|
||||||
|
await PlanTableService.moveUp(obj.getItem().parent_id,obj.getItem().sort,obj.getItem().plan_id)
|
||||||
|
await objTarget.loadItem()
|
||||||
|
await PlanTableService.moveDown(objTarget.getItem().parent_id,objTarget.getItem().sort+1,obj.getItem().plan_id)
|
||||||
|
obj.assignItem({
|
||||||
|
parent_id:objTarget.getItem().parent_id,
|
||||||
|
sort:objTarget.getItem().sort+1,
|
||||||
|
...(obj.getItem().parent_id!=objTarget.getItem().parent_id && {
|
||||||
|
depend_id:null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await obj.update()
|
||||||
|
let objPlan=await PlanService.getItemById(obj.getItem().plan_id)
|
||||||
|
let ret=await objPlan.info()
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.issuePlanList)
|
||||||
|
async issuePlanList(@DHttpReqParamRequired("projectIssueId") projectIssueId: string): Promise<typeof planApi.routes.issuePlanList.res> {
|
||||||
|
let objProjectIssue=await ProjectIssueService.getItemById(projectIssueId)
|
||||||
|
if(!objProjectIssue) {
|
||||||
|
throw Err.Project.ProjectIssue.projectIssueNotFound
|
||||||
|
}
|
||||||
|
let ret=await PlanService.issuePlanList(projectIssueId)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@DHttpApi(planApi.routes.issuePlanEdit)
|
||||||
|
async issuePlanEdit(@DHttpReqParamRequired("projectIssueId") projectIssueId: string,@DHttpReqParamRequired("planList") planList: string[]): Promise<typeof planApi.routes.issuePlanEdit.res> {
|
||||||
|
await PlanService.issuePlanEdit(projectIssueId,planList);
|
||||||
|
let ret=await PlanService.issuePlanList(projectIssueId)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -709,12 +709,26 @@ class ProjectIssueMapper extends Mapper<typeof projectIssueModel> {
|
|||||||
throw Err.Project.ProjectIssue.issueEqualForbidden
|
throw Err.Project.ProjectIssue.issueEqualForbidden
|
||||||
}
|
}
|
||||||
let mysql=getMysqlInstance()
|
let mysql=getMysqlInstance()
|
||||||
let obj=await mysql.executeOne(generateQuerySql(projectIssueParentModel,["id"],{
|
await Promise.all([
|
||||||
child_id:projectChildIssueId
|
(async ()=>{
|
||||||
}))
|
let obj=await mysql.executeOne(generateQuerySql(projectIssueParentModel,["id"],{
|
||||||
if(obj) {
|
child_id:projectChildIssueId,
|
||||||
throw Err.Project.ProjectIssue.parentChildExists
|
parent_id:projectChildIssueId
|
||||||
}
|
},"or"))
|
||||||
|
if(obj) {
|
||||||
|
throw Err.Project.ProjectIssue.parentChildExists
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
(async ()=>{
|
||||||
|
let obj=await mysql.executeOne(generateQuerySql(projectIssueParentModel,["id"],{
|
||||||
|
child_id:projectParentIssueId,
|
||||||
|
parent_id:projectChildIssueId
|
||||||
|
}))
|
||||||
|
if(obj) {
|
||||||
|
throw Err.Project.ProjectIssue.parentChildExists
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
])
|
||||||
await mysql.execute(generateCreateSql(projectIssueParentModel,{
|
await mysql.execute(generateCreateSql(projectIssueParentModel,{
|
||||||
id:await generateSnowId(),
|
id:await generateSnowId(),
|
||||||
parent_id:projectParentIssueId,
|
parent_id:projectParentIssueId,
|
||||||
@ -798,6 +812,10 @@ class ProjectIssueMapper extends Mapper<typeof projectIssueModel> {
|
|||||||
model:projectIssueParentModel,
|
model:projectIssueParentModel,
|
||||||
value:projectIssueId
|
value:projectIssueId
|
||||||
}
|
}
|
||||||
|
},"and",{
|
||||||
|
type:"asc",
|
||||||
|
model:projectIssueParentModel,
|
||||||
|
field:"child_id"
|
||||||
})
|
})
|
||||||
let ret=await mysql.execute(sql)
|
let ret=await mysql.execute(sql)
|
||||||
return ret;
|
return ret;
|
||||||
|
251
code/server/cooperation/mapper/plan.ts
Normal file
251
code/server/cooperation/mapper/plan.ts
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
import {Mapper} from "../../common/entity/mapper";
|
||||||
|
import {ICommon_Model_Plan, planModel} from "../../../common/model/plan";
|
||||||
|
import {ECommon_Model_Plan_Table, ICommon_Model_Plan_Table, planTableModel} from "../../../common/model/plan_table";
|
||||||
|
import {Err} from "../../../common/status/error";
|
||||||
|
import {
|
||||||
|
generateCommonListData,
|
||||||
|
generateDeleteSql,
|
||||||
|
generateGroupLeftJoinSql,
|
||||||
|
generateLeftJoin2Sql,
|
||||||
|
generateQuerySql,
|
||||||
|
generateUpdateSql
|
||||||
|
} from "../../common/util/sql";
|
||||||
|
import {getMysqlInstance} from "../../common/db/mysql";
|
||||||
|
import {keys} from "../../../common/transform";
|
||||||
|
import {ICommon_Model_Project_Issue, projectIssueModel} from "../../../common/model/project_issue";
|
||||||
|
import {ICommon_Model_Workflow_Node, workflowNodeModel} from "../../../common/model/workflow_node";
|
||||||
|
import {ICommon_Route_Res_Plan_Info_Item} from "../../../common/routes/response";
|
||||||
|
|
||||||
|
class PlanMapper extends Mapper<typeof planModel> {
|
||||||
|
constructor() {
|
||||||
|
super(planModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
async list(projectId:string,page:number,size:number,keyword?:string) {
|
||||||
|
if(!projectId) {
|
||||||
|
throw Err.Project.projectNotFound
|
||||||
|
}
|
||||||
|
let sql=generateQuerySql(planModel,null,{
|
||||||
|
project_id:projectId,
|
||||||
|
...(keyword && {
|
||||||
|
name:{
|
||||||
|
exp:"%like%",
|
||||||
|
value:keyword
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},"and",{
|
||||||
|
type:"asc",
|
||||||
|
field:"name"
|
||||||
|
},page*size,size)
|
||||||
|
let ret=generateCommonListData(sql,page,size)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
async info(planId:string) {
|
||||||
|
if(!planId) {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
let sql=generateLeftJoin2Sql({
|
||||||
|
model:planTableModel,
|
||||||
|
columns:keys<ICommon_Model_Plan_Table>().map(item=>item.name)
|
||||||
|
},{
|
||||||
|
model:projectIssueModel,
|
||||||
|
columns:keys<ICommon_Model_Project_Issue>().map(item=>item.name),
|
||||||
|
expression:{
|
||||||
|
id:{
|
||||||
|
model:planTableModel,
|
||||||
|
field:"ref_id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
aggregation:"issue"
|
||||||
|
},{
|
||||||
|
model:workflowNodeModel,
|
||||||
|
columns:keys<ICommon_Model_Workflow_Node>().map(item=>item.name),
|
||||||
|
expression:{
|
||||||
|
id:{
|
||||||
|
model:projectIssueModel,
|
||||||
|
field: "workflow_node_id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
aggregation:"workflow"
|
||||||
|
},{
|
||||||
|
plan_id:{
|
||||||
|
model:planTableModel,
|
||||||
|
value:planId
|
||||||
|
}
|
||||||
|
},"and",{
|
||||||
|
type:"asc",
|
||||||
|
model:planTableModel,
|
||||||
|
field:"sort"
|
||||||
|
})
|
||||||
|
let arr=await mysql.execute(sql)
|
||||||
|
let ret=this.handle(arr,null)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle(arr:ICommon_Route_Res_Plan_Info_Item[],parentId:string):ICommon_Route_Res_Plan_Info_Item[] {
|
||||||
|
let ret:ICommon_Route_Res_Plan_Info_Item[]=[]
|
||||||
|
for(let obj of arr) {
|
||||||
|
if(obj.parent_id===parentId) {
|
||||||
|
ret.push(obj)
|
||||||
|
if(obj.type===ECommon_Model_Plan_Table.ISSUE || obj.type===ECommon_Model_Plan_Table.STAGE) {
|
||||||
|
let temp=this.handle(arr,obj.id)
|
||||||
|
if(temp?.length>0) {
|
||||||
|
obj.children=temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearByProjects(projectIds:string[]) {
|
||||||
|
if(projectIds.length==0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
await Promise.all([
|
||||||
|
mysql.execute(generateDeleteSql(planModel,{
|
||||||
|
project_id:{
|
||||||
|
exp:"in",
|
||||||
|
value:projectIds
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
mysql.execute(generateDeleteSql(planTableModel,{
|
||||||
|
project_id:{
|
||||||
|
exp:"in",
|
||||||
|
value:projectIds
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async issuePlanList(projectIssueId:string) {
|
||||||
|
if(!projectIssueId) {
|
||||||
|
throw Err.Project.ProjectIssue.projectIssueNotFound
|
||||||
|
}
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
let sql=generateGroupLeftJoinSql({
|
||||||
|
model:planTableModel
|
||||||
|
},{
|
||||||
|
model:planModel,
|
||||||
|
columns:{
|
||||||
|
columns:keys<ICommon_Model_Plan>().map(item=>item.name),
|
||||||
|
calcColumns:[]
|
||||||
|
},
|
||||||
|
expression:{
|
||||||
|
id:{
|
||||||
|
model:planTableModel,
|
||||||
|
field:"plan_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},["plan_id"],{
|
||||||
|
ref_id:{
|
||||||
|
model:planTableModel,
|
||||||
|
value:projectIssueId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let arr=await mysql.execute(sql)
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const planMapper=new PlanMapper
|
||||||
|
|
||||||
|
class PlanTableMapper extends Mapper<typeof planTableModel> {
|
||||||
|
constructor() {
|
||||||
|
super(planTableModel)
|
||||||
|
}
|
||||||
|
async clearByPlanId(planId:string) {
|
||||||
|
if(!planId) {
|
||||||
|
throw Err.Project.Plan.planNotFound
|
||||||
|
}
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
await mysql.execute(generateDeleteSql(planTableModel,{
|
||||||
|
plan_id:planId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNewSort(planItemId:string,planId:string) {
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
let obj=await mysql.executeOne(generateQuerySql(planTableModel,["sort"],{
|
||||||
|
plan_id:planId,
|
||||||
|
parent_id:planItemId
|
||||||
|
},"and",{
|
||||||
|
type:"desc",
|
||||||
|
field:"sort"
|
||||||
|
}))
|
||||||
|
if(obj) {
|
||||||
|
return obj.sort+1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async moveUp(parentId:string,index:number,planId:string) {
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
await mysql.execute(generateUpdateSql(planTableModel,{
|
||||||
|
sort:{
|
||||||
|
exp:"-",
|
||||||
|
value:1
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
sort:{
|
||||||
|
exp:">",
|
||||||
|
value:index
|
||||||
|
},
|
||||||
|
parent_id:parentId,
|
||||||
|
plan_id:planId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async moveDown(parentId:string,index:number,planId:string) {
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
await mysql.execute(generateUpdateSql(planTableModel,{
|
||||||
|
sort:{
|
||||||
|
exp:"+",
|
||||||
|
value:1
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
sort:{
|
||||||
|
exp:">=",
|
||||||
|
value:index
|
||||||
|
},
|
||||||
|
parent_id:parentId,
|
||||||
|
plan_id:planId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeItems(ids:string[]) {
|
||||||
|
if(ids?.length==0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
await mysql.execute(generateDeleteSql(planTableModel,{
|
||||||
|
id:{
|
||||||
|
exp:"in",
|
||||||
|
value:ids
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async hasChild(planItemId:string) {
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
let arr=await mysql.execute(generateQuerySql(planTableModel,["id"],{
|
||||||
|
parent_id:planItemId
|
||||||
|
}))
|
||||||
|
return arr.length>0
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearDepend(planItemId:string) {
|
||||||
|
let mysql=getMysqlInstance()
|
||||||
|
await mysql.execute(generateUpdateSql(planTableModel,{
|
||||||
|
depend_id:null
|
||||||
|
},{
|
||||||
|
depend_id:planItemId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const planTableMapper=new PlanTableMapper
|
File diff suppressed because it is too large
Load Diff
221
code/server/cooperation/service/plan.ts
Normal file
221
code/server/cooperation/service/plan.ts
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
import {Entity} from "../../common/entity/entity";
|
||||||
|
import {planModel} from "../../../common/model/plan";
|
||||||
|
import {planMapper, planTableMapper} from "../mapper/plan";
|
||||||
|
import {IServer_Common_Event_Types} from "../../common/event/types";
|
||||||
|
import {ECommon_Model_Plan_Table, planTableModel} from "../../../common/model/plan_table";
|
||||||
|
import {ICommon_Route_Res_Plan_Info_Item} from "../../../common/routes/response";
|
||||||
|
import {ProjectIssueService} from "./issue";
|
||||||
|
import {Err} from "../../../common/status/error";
|
||||||
|
import {WorkflowNodeService} from "./workflow";
|
||||||
|
|
||||||
|
export class PlanService extends Entity<typeof planModel,typeof planMapper> {
|
||||||
|
constructor() {
|
||||||
|
super(planMapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async list(projectId:string,page:number,size:number,keyword?:string) {
|
||||||
|
let ret=await planMapper.list(projectId,page,size,keyword)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async delete(eventPublish?: keyof IServer_Common_Event_Types, ...param): Promise<void> {
|
||||||
|
await super.delete(eventPublish, ...param);
|
||||||
|
await planTableMapper.clearByPlanId(this.getId())
|
||||||
|
}
|
||||||
|
|
||||||
|
async info() {
|
||||||
|
let ret=await planMapper.info(this.getId())
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async issuePlanList(projectIssueId:string) {
|
||||||
|
let ret=await planMapper.issuePlanList(projectIssueId)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async issuePlanEdit(projectIssueId:string,planList: string[]) {
|
||||||
|
let objProjectIssue=await ProjectIssueService.getItemById(projectIssueId)
|
||||||
|
if(!objProjectIssue) {
|
||||||
|
throw Err.Project.ProjectIssue.projectIssueNotFound
|
||||||
|
}
|
||||||
|
let originalList=await this.issuePlanList(projectIssueId)
|
||||||
|
let needAddList:string[]=[],needDeleteList:string[]=[]
|
||||||
|
for(let id of planList) {
|
||||||
|
let index=originalList.findIndex(item=>item.id===id)
|
||||||
|
if(index>-1) {
|
||||||
|
originalList.splice(index,1)
|
||||||
|
} else {
|
||||||
|
if(!needAddList.includes(id)) {
|
||||||
|
needAddList.push(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
needDeleteList=[...originalList.map(item=>item.id)]
|
||||||
|
await Promise.all(needAddList.map(planId=>{
|
||||||
|
return (async ()=>{
|
||||||
|
let objPlan=await PlanService.getItemById(planId)
|
||||||
|
if(!objPlan) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let sort=await PlanTableService.getNewSort(null,planId)
|
||||||
|
let obj=new PlanTableService()
|
||||||
|
obj.assignItem({
|
||||||
|
plan_id:planId,
|
||||||
|
type:ECommon_Model_Plan_Table.ISSUE,
|
||||||
|
sort:sort,
|
||||||
|
project_id:objPlan.getItem().project_id,
|
||||||
|
ref_id:projectIssueId
|
||||||
|
})
|
||||||
|
await obj.create()
|
||||||
|
let childIssueList=await objProjectIssue.childIssueList()
|
||||||
|
if(childIssueList?.length>0) {
|
||||||
|
await Promise.all(childIssueList.map((item,index)=>{
|
||||||
|
let obj1=new PlanTableService()
|
||||||
|
obj1.assignItem({
|
||||||
|
plan_id:planId,
|
||||||
|
type:ECommon_Model_Plan_Table.ISSUE,
|
||||||
|
sort:index,
|
||||||
|
parent_id:obj.getId(),
|
||||||
|
project_id:objPlan.getItem().project_id,
|
||||||
|
ref_id:item.id
|
||||||
|
})
|
||||||
|
return obj1.create()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}).concat(needDeleteList.map(planId=>{
|
||||||
|
return (async ()=>{
|
||||||
|
let arr=await PlanTableService.getItemsByExp({
|
||||||
|
plan_id:planId,
|
||||||
|
ref_id:projectIssueId
|
||||||
|
})
|
||||||
|
for(let obj of arr) {
|
||||||
|
await obj.delete()
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PlanTableService extends Entity<typeof planTableModel,typeof planTableMapper> {
|
||||||
|
constructor() {
|
||||||
|
super(planTableMapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getNewSort(planItemId:string,planId:string) {
|
||||||
|
let ret=await planTableMapper.getNewSort(planItemId,planId)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async delete(eventPublish?: keyof IServer_Common_Event_Types, ...param): Promise<void> {
|
||||||
|
let objPlan=await PlanService.getItemById(this.item.plan_id)
|
||||||
|
let info=await objPlan.info()
|
||||||
|
await super.delete(eventPublish, ...param);
|
||||||
|
await planTableMapper.moveUp(this.item.parent_id,this.item.sort,this.item.plan_id)
|
||||||
|
let item=this.findItem(this.getId(),info)
|
||||||
|
if(item?.children?.length>0) {
|
||||||
|
let arr=this.getChildrenIds(item.children)
|
||||||
|
await planTableMapper.removeItems(arr)
|
||||||
|
}
|
||||||
|
await planTableMapper.clearDepend(this.getId())
|
||||||
|
}
|
||||||
|
|
||||||
|
findItem(id:string,data:ICommon_Route_Res_Plan_Info_Item[]):ICommon_Route_Res_Plan_Info_Item {
|
||||||
|
for(let obj of data) {
|
||||||
|
if(obj.id===id) {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if(obj.children?.length>0) {
|
||||||
|
let ret=this.findItem(id,obj.children)
|
||||||
|
if(ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildrenIds(data:ICommon_Route_Res_Plan_Info_Item[]) {
|
||||||
|
let ret:string[]=[]
|
||||||
|
for(let obj of data) {
|
||||||
|
ret.push(obj.id)
|
||||||
|
if(obj.children?.length>0) {
|
||||||
|
ret=ret.concat(this.getChildrenIds(obj.children))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
async hasParentIssue() {
|
||||||
|
if(this.getItem().parent_id) {
|
||||||
|
let objParent=await PlanTableService.getItemById(this.getItem().parent_id)
|
||||||
|
if(objParent) {
|
||||||
|
return objParent.getItem().type===ECommon_Model_Plan_Table.ISSUE
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async hasChild() {
|
||||||
|
let ret=await planTableMapper.hasChild(this.getId())
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
async issueInfo() {
|
||||||
|
let objProjectIssue=await ProjectIssueService.getItemById(this.getItem().ref_id)
|
||||||
|
if(!objProjectIssue) {
|
||||||
|
throw Err.Project.ProjectIssue.projectIssueNotFound
|
||||||
|
}
|
||||||
|
let objWorkflow=await WorkflowNodeService.getItemById(objProjectIssue.getItem().workflow_node_id)
|
||||||
|
return {
|
||||||
|
issue:objProjectIssue.getItem(),
|
||||||
|
workflow:objWorkflow.getItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async moveUp(parentId:string,index:number,planId:string) {
|
||||||
|
await planTableMapper.moveUp(parentId,index,planId)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async moveDown(parentId:string,index:number,planId:string) {
|
||||||
|
await planTableMapper.moveDown(parentId, index, planId)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addChildIssue(projectIssueId:string,projectIssueChildId:string) {
|
||||||
|
let arr=await PlanTableService.getItemsByExp({
|
||||||
|
ref_id:projectIssueId
|
||||||
|
})
|
||||||
|
if(arr.length>0) {
|
||||||
|
await Promise.all(arr.map(item=>{
|
||||||
|
return (async ()=>{
|
||||||
|
let sort=await PlanTableService.getNewSort(item.getId(),item.getItem().plan_id)
|
||||||
|
let obj=new PlanTableService()
|
||||||
|
obj.assignItem({
|
||||||
|
plan_id:item.getItem().plan_id,
|
||||||
|
type:ECommon_Model_Plan_Table.ISSUE,
|
||||||
|
sort:sort,
|
||||||
|
parent_id:item.getId(),
|
||||||
|
project_id:item.getItem().project_id,
|
||||||
|
ref_id:projectIssueChildId
|
||||||
|
})
|
||||||
|
await obj.create()
|
||||||
|
})()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async removeIssue(projectIssueId:string) {
|
||||||
|
let arr = await PlanTableService.getItemsByExp({
|
||||||
|
ref_id: projectIssueId
|
||||||
|
})
|
||||||
|
await Promise.all(arr.map(item => {
|
||||||
|
return item.delete()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearAllDepend() {
|
||||||
|
await planTableMapper.clearDepend(this.getId())
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ import {emitServiceEvent} from "../../common/event/event";
|
|||||||
import {BoardService} from "./board";
|
import {BoardService} from "./board";
|
||||||
import {REDIS_PROJECT} from "../../common/cache/keys/project";
|
import {REDIS_PROJECT} from "../../common/cache/keys/project";
|
||||||
import rpcUserApi from "../../user/rpc/user"
|
import rpcUserApi from "../../user/rpc/user"
|
||||||
|
import {planMapper} from "../mapper/plan";
|
||||||
|
|
||||||
export class ProjectService extends Entity<typeof projectModel,typeof projectMapper> {
|
export class ProjectService extends Entity<typeof projectModel,typeof projectMapper> {
|
||||||
constructor(){
|
constructor(){
|
||||||
@ -44,7 +45,8 @@ export class ProjectService extends Entity<typeof projectModel,typeof projectMap
|
|||||||
ProjectIssueService.clearByProjectIds([this.item.id]),
|
ProjectIssueService.clearByProjectIds([this.item.id]),
|
||||||
releaseMapper.clear(this.item.id),
|
releaseMapper.clear(this.item.id),
|
||||||
issueTypeSolutionMapper.clearProjects([this.item.id]),
|
issueTypeSolutionMapper.clearProjects([this.item.id]),
|
||||||
BoardService.clearByProjectId(this.getId())
|
BoardService.clearByProjectId(this.getId()),
|
||||||
|
planMapper.clearByProjects([this.item.id])
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +179,8 @@ export class ProjectService extends Entity<typeof projectModel,typeof projectMap
|
|||||||
ProjectIssueService.clearByProjectIds(projectIds),
|
ProjectIssueService.clearByProjectIds(projectIds),
|
||||||
releaseMapper.clearByProjectIds(projectIds),
|
releaseMapper.clearByProjectIds(projectIds),
|
||||||
issueTypeSolutionMapper.clearProjects(projectIds),
|
issueTypeSolutionMapper.clearProjects(projectIds),
|
||||||
BoardService.clearByProjectIds(projectIds)
|
BoardService.clearByProjectIds(projectIds),
|
||||||
|
planMapper.clearByProjects(projectIds)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import * as fs from "fs-extra";
|
import * as fs from "fs-extra";
|
||||||
import Application from "../../common/app/app";
|
import Application from "../../common/app/app";
|
||||||
import {Entity} from "../../common/entity/entity";
|
import {Entity} from "../../common/entity/entity";
|
||||||
import {fileModel} from './../../../common/model/file';
|
import {ECommon_Model_File_Type, fileModel} from './../../../common/model/file';
|
||||||
import {fileMapper} from './../mapper/file';
|
import {fileMapper} from './../mapper/file';
|
||||||
import {IServer_Common_Http_Req_File} from "../../common/types/http";
|
import {IServer_Common_Http_Req_File} from "../../common/types/http";
|
||||||
import {emitServiceEvent} from "../../common/event/event";
|
import {emitServiceEvent} from "../../common/event/event";
|
||||||
import rpcUserApi from "../../user/rpc/user"
|
import rpcUserApi from "../../user/rpc/user"
|
||||||
|
import {download} from "../../common/request/request";
|
||||||
import path = require("path");
|
import path = require("path");
|
||||||
|
|
||||||
export default class File extends Entity<typeof fileModel,typeof fileMapper> {
|
export default class File extends Entity<typeof fileModel,typeof fileMapper> {
|
||||||
@ -22,15 +23,24 @@ export default class File extends Entity<typeof fileModel,typeof fileMapper> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async upload(file:IServer_Common_Http_Req_File,meta?:string){
|
|
||||||
|
static async ensureDir(md5:string,fileName:string) {
|
||||||
let arr=(new Date).toLocaleDateString().split("/").reverse()
|
let arr=(new Date).toLocaleDateString().split("/").reverse()
|
||||||
if(arr[2].length==1) {
|
if(arr[2].length==1) {
|
||||||
arr[2]="0"+arr[2]
|
arr[2]="0"+arr[2]
|
||||||
}
|
}
|
||||||
let dirPath = path.join(Application.uploadPath,arr[0]+arr[2]+arr[1])
|
let dirPath = path.join(Application.uploadPath,arr[0]+arr[2]+arr[1])
|
||||||
let filePath=path.join(dirPath,file.md5)+file.fileName.substr(file.fileName.lastIndexOf("."));
|
let filePath=path.join(dirPath,md5)+fileName.substring(fileName.lastIndexOf("."));
|
||||||
let dbPath=filePath.substring(Application.uploadPath.length);
|
let dbPath=filePath.substring(Application.uploadPath.length);
|
||||||
await fs.ensureDir(dirPath)
|
await fs.ensureDir(dirPath)
|
||||||
|
return {
|
||||||
|
filePath,
|
||||||
|
dbPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async upload(file:IServer_Common_Http_Req_File,meta?:string){
|
||||||
|
let {dbPath,filePath}=await File.ensureDir(file.md5,file.fileName)
|
||||||
fs.writeFile(filePath,file.data)
|
fs.writeFile(filePath,file.data)
|
||||||
this.item.path=dbPath
|
this.item.path=dbPath
|
||||||
await super.create()
|
await super.create()
|
||||||
@ -40,6 +50,26 @@ export default class File extends Entity<typeof fileModel,typeof fileMapper> {
|
|||||||
return this.item.id;
|
return this.item.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async download(uri:string,userId:string):Promise<File> {
|
||||||
|
let url=new URL(uri)
|
||||||
|
let pathName=url.pathname
|
||||||
|
let fileName=pathName.substring(pathName.lastIndexOf("/")+1)
|
||||||
|
let tempPath=path.join("/tmp",fileName,String(Date.now()))
|
||||||
|
let {md5,size}=await download(uri,tempPath)
|
||||||
|
let {filePath,dbPath}=await this.ensureDir(md5,fileName)
|
||||||
|
await fs.move(tempPath,filePath)
|
||||||
|
let file = new File;
|
||||||
|
file.assignItem({
|
||||||
|
created_by_pure:userId,
|
||||||
|
size,
|
||||||
|
md5,
|
||||||
|
path:dbPath,
|
||||||
|
type:ECommon_Model_File_Type.LOCAL
|
||||||
|
})
|
||||||
|
await file.create()
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
static async getPaths(ids:string[]){
|
static async getPaths(ids:string[]){
|
||||||
let arrId=[...ids]
|
let arrId=[...ids]
|
||||||
let ret=await fileMapper.getPaths(ids);
|
let ret=await fileMapper.getPaths(ids);
|
||||||
|
@ -22,6 +22,7 @@ import finderApi from "../../../common/routes/finder";
|
|||||||
import notificationApi from "../../../common/routes/notification";
|
import notificationApi from "../../../common/routes/notification";
|
||||||
import boardApi from "../../../common/routes/board";
|
import boardApi from "../../../common/routes/board";
|
||||||
import toolApi from "../../../common/routes/tool";
|
import toolApi from "../../../common/routes/tool";
|
||||||
|
import planApi from "../../../common/routes/plan";
|
||||||
import {Err} from "../../../common/status/error";
|
import {Err} from "../../../common/status/error";
|
||||||
import Application from "../../common/app/app";
|
import Application from "../../common/app/app";
|
||||||
import {EServer_Common_Http_Body_Type, IServer_Common_Http_Proxy} from "../../common/types/http";
|
import {EServer_Common_Http_Body_Type, IServer_Common_Http_Proxy} from "../../common/types/http";
|
||||||
@ -44,7 +45,7 @@ import userRpcApi from "../../user/rpc/user"
|
|||||||
import {getRedisInstance} from "../../common/cache/redis";
|
import {getRedisInstance} from "../../common/cache/redis";
|
||||||
import {initNotification} from "../../notification/app/app";
|
import {initNotification} from "../../notification/app/app";
|
||||||
|
|
||||||
var apis:ICommon_HttpApi[]=[userApi,projectApi,teamApi,fileApi,issueTypeApi,workflowApi,fieldApi,issueApi,releaseApi,gatewayApi,organizationApi,wikiApi,calendarApi,meetingApi,finderApi,notificationApi,boardApi,toolApi];
|
var apis:ICommon_HttpApi[]=[userApi,projectApi,teamApi,fileApi,issueTypeApi,workflowApi,fieldApi,issueApi,releaseApi,gatewayApi,organizationApi,wikiApi,calendarApi,meetingApi,finderApi,notificationApi,boardApi,toolApi,planApi];
|
||||||
export default class GateWay extends Application {
|
export default class GateWay extends Application {
|
||||||
override async config(app: Koa<Koa.DefaultState, Koa.DefaultContext>) {
|
override async config(app: Koa<Koa.DefaultState, Koa.DefaultContext>) {
|
||||||
let redis=getRedisInstance();
|
let redis=getRedisInstance();
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import gateApi from "../../../common/routes/gateway";
|
import gateApi from "../../../common/routes/gateway";
|
||||||
import Application from "../../common/app/app";
|
import Application from "../../common/app/app";
|
||||||
import { DComponent } from "../../common/decorate/component";
|
import {DComponent} from "../../common/decorate/component";
|
||||||
import { DHttpApi, DHttpController, DHttpReqParam, DHttpReqParamRequired } from "../../common/http/http";
|
import {DHttpApi, DHttpController, DHttpReqParam, DHttpReqParamRequired} from "../../common/http/http";
|
||||||
import { GateWayService } from './../service/http';
|
import {GateWayService} from './../service/http';
|
||||||
|
|
||||||
@DComponent
|
@DComponent
|
||||||
@DHttpController(gateApi)
|
@DHttpController(gateApi)
|
||||||
class GatewayController {
|
class GatewayController {
|
||||||
@ -23,4 +24,11 @@ class GatewayController {
|
|||||||
type:Application.mode
|
type:Application.mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DHttpApi(gateApi.routes.wechatAppId)
|
||||||
|
async wechatAppId():Promise<typeof gateApi.routes.wechatAppId.res> {
|
||||||
|
return {
|
||||||
|
appId:Application.privateConfig.wechat.appId
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -269,7 +269,7 @@ async function checkIfNeedPatch() {
|
|||||||
}
|
}
|
||||||
console.log("patch end");
|
console.log("patch end");
|
||||||
}
|
}
|
||||||
mysql.execute(generateUpdateSql(versionModel,{
|
await mysql.execute(generateUpdateSql(versionModel,{
|
||||||
version:curVersion
|
version:curVersion
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
-- MySQL dump 10.13 Distrib 8.0.31, for macos12 (x86_64)
|
-- MySQL dump 10.13 Distrib 8.0.34, for macos13 (x86_64)
|
||||||
--
|
--
|
||||||
-- Host: localhost Database: teamlinker_dev
|
-- Host: localhost Database: teamlinker_dev
|
||||||
-- ------------------------------------------------------
|
-- ------------------------------------------------------
|
||||||
@ -653,6 +653,58 @@ CREATE TABLE `photo` (
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `plan`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `plan`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
|
CREATE TABLE `plan` (
|
||||||
|
`id` bigint(20) NOT NULL,
|
||||||
|
`name` varchar(65) COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`start_time` timestamp NOT NULL,
|
||||||
|
`organization_user_id` bigint(20) NOT NULL,
|
||||||
|
`project_id` bigint(20) NOT NULL,
|
||||||
|
`organization_id` bigint(20) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||||
|
KEY `organization_user_id` (`organization_user_id`),
|
||||||
|
KEY `project_id` (`project_id`),
|
||||||
|
KEY `organization_id` (`organization_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `plan_table`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `plan_table`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
|
CREATE TABLE `plan_table` (
|
||||||
|
`id` bigint(20) NOT NULL,
|
||||||
|
`sort` tinyint(3) unsigned NOT NULL,
|
||||||
|
`type` tinyint(4) NOT NULL,
|
||||||
|
`name` varchar(65) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`ref_id` bigint(20) DEFAULT NULL,
|
||||||
|
`progress` tinyint(4) DEFAULT NULL,
|
||||||
|
`depend_id` bigint(20) DEFAULT NULL,
|
||||||
|
`delay` int(11) DEFAULT NULL,
|
||||||
|
`parent_id` bigint(20) DEFAULT NULL,
|
||||||
|
`plan_id` bigint(20) NOT NULL,
|
||||||
|
`project_id` bigint(20) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||||
|
KEY `sort` (`sort`),
|
||||||
|
KEY `ref_id` (`ref_id`),
|
||||||
|
KEY `depend_id` (`depend_id`),
|
||||||
|
KEY `parent_id` (`parent_id`),
|
||||||
|
KEY `plan_id` (`plan_id`),
|
||||||
|
KEY `project_id` (`project_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Table structure for table `project`
|
-- Table structure for table `project`
|
||||||
--
|
--
|
||||||
@ -697,6 +749,7 @@ CREATE TABLE `project_issue` (
|
|||||||
`reporter_id` bigint(20) DEFAULT NULL,
|
`reporter_id` bigint(20) DEFAULT NULL,
|
||||||
`workflow_node_id` bigint(20) NOT NULL,
|
`workflow_node_id` bigint(20) NOT NULL,
|
||||||
`unique_id` int(10) unsigned DEFAULT NULL,
|
`unique_id` int(10) unsigned DEFAULT NULL,
|
||||||
|
`man_day` int(11) DEFAULT '1',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `id_UNIQUE` (`id`),
|
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||||
KEY `project` (`project_id`),
|
KEY `project` (`project_id`),
|
||||||
@ -1092,8 +1145,12 @@ CREATE TABLE `user` (
|
|||||||
`photo` bigint(20) DEFAULT NULL,
|
`photo` bigint(20) DEFAULT NULL,
|
||||||
`role` tinyint(4) NOT NULL DEFAULT '0',
|
`role` tinyint(4) NOT NULL DEFAULT '0',
|
||||||
`count` int(11) DEFAULT '0',
|
`count` int(11) DEFAULT '0',
|
||||||
|
`from_type` tinyint(4) DEFAULT '0',
|
||||||
|
`from_id` varchar(65) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `username_UNIQUE` (`username`)
|
UNIQUE KEY `username_UNIQUE` (`username`),
|
||||||
|
UNIQUE KEY `from_id_UNIQUE` (`from_id`),
|
||||||
|
KEY `from_id` (`from_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
@ -1296,4 +1353,4 @@ CREATE TABLE `workflow_node_field_type_config` (
|
|||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
-- Dump completed on 2023-09-24 21:24:18
|
-- Dump completed on 2023-11-06 16:35:44
|
||||||
|
45
code/server/patch/0.1.1.sql
Normal file
45
code/server/patch/0.1.1.sql
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
CREATE TABLE `teamlinker`.`plan` (
|
||||||
|
`id` bigint NOT NULL,
|
||||||
|
`name` varchar(65) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`start_time` timestamp NOT NULL,
|
||||||
|
`organization_user_id` bigint NOT NULL,
|
||||||
|
`project_id` bigint NOT NULL,
|
||||||
|
`organization_id` bigint NOT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `id_UNIQUE`(`id` ASC) USING BTREE,
|
||||||
|
INDEX `organization_user_id`(`organization_user_id` ASC) USING BTREE,
|
||||||
|
INDEX `project_id`(`project_id` ASC) USING BTREE,
|
||||||
|
INDEX `organization_id`(`organization_id` ASC) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
|
CREATE TABLE `teamlinker`.`plan_table` (
|
||||||
|
`id` bigint NOT NULL,
|
||||||
|
`sort` tinyint UNSIGNED NOT NULL,
|
||||||
|
`type` tinyint NOT NULL,
|
||||||
|
`name` varchar(65) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||||
|
`ref_id` bigint NULL DEFAULT NULL,
|
||||||
|
`progress` tinyint NULL DEFAULT NULL,
|
||||||
|
`depend_id` bigint NULL DEFAULT NULL,
|
||||||
|
`delay` int NULL DEFAULT NULL,
|
||||||
|
`parent_id` bigint NULL DEFAULT NULL,
|
||||||
|
`plan_id` bigint NOT NULL,
|
||||||
|
`project_id` bigint NOT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `id_UNIQUE`(`id` ASC) USING BTREE,
|
||||||
|
INDEX `sort`(`sort` ASC) USING BTREE,
|
||||||
|
INDEX `ref_id`(`ref_id` ASC) USING BTREE,
|
||||||
|
INDEX `depend_id`(`depend_id` ASC) USING BTREE,
|
||||||
|
INDEX `parent_id`(`parent_id` ASC) USING BTREE,
|
||||||
|
INDEX `plan_id`(`plan_id` ASC) USING BTREE,
|
||||||
|
INDEX `project_id`(`project_id` ASC) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
|
ALTER TABLE `teamlinker`.`project_issue` ADD COLUMN `man_day` int NULL DEFAULT 1 AFTER `unique_id`;
|
||||||
|
|
||||||
|
ALTER TABLE `teamlinker`.`user` ADD COLUMN `from_type` tinyint NULL DEFAULT 0 AFTER `count`;
|
||||||
|
|
||||||
|
ALTER TABLE `teamlinker`.`user` ADD COLUMN `from_id` varchar(65) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `from_type`;
|
||||||
|
|
||||||
|
ALTER TABLE `teamlinker`.`user` ADD UNIQUE INDEX `from_id_UNIQUE`(`from_id` ASC) USING BTREE;
|
||||||
|
|
||||||
|
ALTER TABLE `teamlinker`.`user` ADD INDEX `from_id`(`from_id` ASC) USING BTREE;
|
19
code/server/patch/0.1.1.ts
Normal file
19
code/server/patch/0.1.1.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import * as path from "path";
|
||||||
|
import * as Importer from "@pivvenit/mysql-import";
|
||||||
|
import {getConfigInstance} from "../common/config/config";
|
||||||
|
|
||||||
|
export default async function() {
|
||||||
|
let config=getConfigInstance()
|
||||||
|
let sqlPath=path.join(__dirname,"0.1.1.sql")
|
||||||
|
let importer = new Importer({
|
||||||
|
host:config.mysqlInfo.url,
|
||||||
|
user:config.mysqlInfo.username,
|
||||||
|
password:config.mysqlInfo.password,
|
||||||
|
database:config.mysqlInfo.database,
|
||||||
|
port:config.mysqlInfo.port,
|
||||||
|
charsetNumber:224
|
||||||
|
})
|
||||||
|
console.log("0.1.1 start")
|
||||||
|
await importer.import(sqlPath)
|
||||||
|
console.log("0.1.1 end")
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
|
import func_0_1_1 from "./0.1.1"
|
||||||
|
|
||||||
export const patchList:{
|
export const patchList:{
|
||||||
version:string,
|
version:string,
|
||||||
func:any
|
func:any
|
||||||
}[]=[
|
}[]=[
|
||||||
|
{
|
||||||
|
version:"0.1.1",
|
||||||
|
func:func_0_1_1
|
||||||
|
}
|
||||||
]
|
]
|
@ -14,12 +14,16 @@
|
|||||||
},
|
},
|
||||||
"port":14000,
|
"port":14000,
|
||||||
"jwt": "teamlinker",
|
"jwt": "teamlinker",
|
||||||
"version":"0.1.0",
|
"version":"0.1.1",
|
||||||
"mq": "amqp://127.0.0.1",
|
"mq": "amqp://127.0.0.1",
|
||||||
"mail":{
|
"mail":{
|
||||||
"host": "smtp.exmail.qq.com",
|
"host": "smtp.exmail.qq.com",
|
||||||
"port": 465,
|
"port": 465,
|
||||||
"user":"notify@team-linker.com",
|
"user":"notify@team-linker.com",
|
||||||
"pass":"gjH9WwP9JEF42QBu"
|
"pass":"gjH9WwP9JEF42QBu"
|
||||||
|
},
|
||||||
|
"wechat":{
|
||||||
|
"appId":"wxe8213f696c7ac4ea",
|
||||||
|
"appSecret":"7a6f6dab82a82a764016cb2dc5bb4e12"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import {ECommon_User_Type} from "../../../common/model/user";
|
import {ECommon_User_From_Type, ECommon_User_Type} from "../../../common/model/user";
|
||||||
import userApi from "../../../common/routes/user";
|
import userApi from "../../../common/routes/user";
|
||||||
import {Err} from "../../../common/status/error";
|
import {Err} from "../../../common/status/error";
|
||||||
import {DComponent} from '../../common/decorate/component';
|
import {DComponent} from '../../common/decorate/component';
|
||||||
@ -21,6 +21,7 @@ import rpcFileApi from "../../file/rpc/file"
|
|||||||
import {REDIS_USER} from "../../common/cache/keys/user";
|
import {REDIS_USER} from "../../common/cache/keys/user";
|
||||||
import rpcFinderApi from "../../finder/rpc/finder";
|
import rpcFinderApi from "../../finder/rpc/finder";
|
||||||
import * as i18next from "i18next";
|
import * as i18next from "i18next";
|
||||||
|
import File from "../../file/service/file";
|
||||||
|
|
||||||
@DComponent
|
@DComponent
|
||||||
@DHttpController(userApi)
|
@DHttpController(userApi)
|
||||||
@ -205,9 +206,9 @@ class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DHttpApi(userApi.routes.confirmRegister)
|
@DHttpApi(userApi.routes.confirmRegister)
|
||||||
async confirmRegister(@DHttpReqParamRequired("username") username:string,@DHttpReqParamRequired("code") code:string):Promise<typeof userApi.routes.confirmRegister.res> {
|
async confirmRegister(@DHttpReqParamRequired("username") username:string,@DHttpReqParamRequired("code") code:string,@DHttpReqParamRequired("openId") openId:string):Promise<typeof userApi.routes.confirmRegister.res> {
|
||||||
if(Application.mode===ECommon_Application_Mode.ONLINE) {
|
if(Application.mode===ECommon_Application_Mode.ONLINE) {
|
||||||
await UserService.conformRegister(username,code)
|
await UserService.conformRegister(username,code,openId)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
throw Err.Common.interfaceForbidden
|
throw Err.Common.interfaceForbidden
|
||||||
@ -290,4 +291,75 @@ class UserController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DHttpApi(userApi.routes.wechatCode)
|
||||||
|
async wechatCode(@DHttpReqParamRequired("code") code:string,@DHttpUser user:IUserSession):Promise<typeof userApi.routes.wechatCode.res> {
|
||||||
|
let ret=await UserService.handleWechatCode(code)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@DHttpApi(userApi.routes.wechatLogin)
|
||||||
|
async wechatLogin(@DHttpReqParamRequired("openId") openId:string,@DHttpReqParamRequired("lang") lang:string,@DHttpContext ctx:HttpContext):Promise<typeof userApi.routes.wechatLogin.res> {
|
||||||
|
let user=await UserService.getItemByExp({
|
||||||
|
from_id:openId
|
||||||
|
})
|
||||||
|
if(user) {
|
||||||
|
if(!user.getItem().active) {
|
||||||
|
throw Err.User.accessDenied
|
||||||
|
}
|
||||||
|
let token=await user.startSession(lang)
|
||||||
|
if(user.getItem().count===0) {
|
||||||
|
rpcFinderApi.createFolder(user.getId(),i18next.getFixedT(lang)("backend.newFolder"),null)
|
||||||
|
}
|
||||||
|
user.assignItem({
|
||||||
|
count:user.getItem().count+1
|
||||||
|
})
|
||||||
|
let ret=await user.update()
|
||||||
|
delete ret.password
|
||||||
|
ctx.setHeader("token",token)
|
||||||
|
return ret
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DHttpApi(userApi.routes.bindWechat)
|
||||||
|
async bindWechat(@DHttpReqParamRequired("openId") openId:string,@DHttpReqParamRequired("username") username:string,@DHttpReqParamRequired("password") password:string,@DHttpContext ctx:HttpContext):Promise<typeof userApi.routes.bindWechat.res> {
|
||||||
|
let objUser=await UserService.getItemByExp({
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
if(!objUser) {
|
||||||
|
throw Err.User.userPasswordWrong
|
||||||
|
}
|
||||||
|
let img:string
|
||||||
|
if(openId) {
|
||||||
|
let objRedis=REDIS_USER.wechatOpenId(openId)
|
||||||
|
let value=await objRedis.get()
|
||||||
|
if(!value) {
|
||||||
|
throw Err.User.userCacheExpired
|
||||||
|
}
|
||||||
|
let user=await UserService.getItemByExp({
|
||||||
|
from_id:openId
|
||||||
|
})
|
||||||
|
if(user) {
|
||||||
|
throw Err.User.userExists
|
||||||
|
}
|
||||||
|
img=value.img
|
||||||
|
}
|
||||||
|
objUser.assignItem({
|
||||||
|
from_type:objUser.getItem().from_type | ECommon_User_From_Type.WECHAT,
|
||||||
|
from_id:openId
|
||||||
|
})
|
||||||
|
await objUser.update()
|
||||||
|
if(img && !objUser.getItem().photo) {
|
||||||
|
let file=await File.download(img,objUser.getId())
|
||||||
|
objUser.assignItem({
|
||||||
|
photo:file.getId()
|
||||||
|
})
|
||||||
|
await objUser.update()
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ import {getConfigInstance} from '../../common/config/config';
|
|||||||
import {Entity} from "../../common/entity/entity";
|
import {Entity} from "../../common/entity/entity";
|
||||||
import {userMapper, userSettingMapper} from '../mapper/user';
|
import {userMapper, userSettingMapper} from '../mapper/user';
|
||||||
import rpcFileApi from "../../file/rpc/file";
|
import rpcFileApi from "../../file/rpc/file";
|
||||||
import {ECommon_User_Type, ICommon_Model_User, userModel} from './../../../common/model/user';
|
import {ECommon_User_From_Type, ECommon_User_Type, ICommon_Model_User, userModel} from './../../../common/model/user';
|
||||||
import rpcAuthApi from "../../auth/rpc/auth";
|
import rpcAuthApi from "../../auth/rpc/auth";
|
||||||
import {ICommon_Model_Team} from "../../../common/model/team";
|
import {ICommon_Model_Team} from "../../../common/model/team";
|
||||||
import {ECommon_Model_Role_Reserved} from "../../../common/model/role";
|
import {ECommon_Model_Role_Reserved} from "../../../common/model/role";
|
||||||
@ -19,6 +19,10 @@ import rpcFinderApi from "../../finder/rpc/finder"
|
|||||||
import rpcNotificationApi from "../../notification/rpc/notification"
|
import rpcNotificationApi from "../../notification/rpc/notification"
|
||||||
import {PhotoService, StickyNoteService} from "./tool";
|
import {PhotoService, StickyNoteService} from "./tool";
|
||||||
import {REDIS_ORGANIZATION} from "../../common/cache/keys/organization";
|
import {REDIS_ORGANIZATION} from "../../common/cache/keys/organization";
|
||||||
|
import {request} from "../../common/request/request"
|
||||||
|
import Application from "../../common/app/app";
|
||||||
|
import File from "../../file/service/file";
|
||||||
|
|
||||||
|
|
||||||
export class UserService extends Entity<typeof userModel,typeof userMapper> {
|
export class UserService extends Entity<typeof userModel,typeof userMapper> {
|
||||||
constructor(){
|
constructor(){
|
||||||
@ -83,7 +87,7 @@ export class UserService extends Entity<typeof userModel,typeof userMapper> {
|
|||||||
mail.send(username,"Teamlinker Verification Code","code: "+code)
|
mail.send(username,"Teamlinker Verification Code","code: "+code)
|
||||||
}
|
}
|
||||||
|
|
||||||
static async conformRegister(username:string,code:string) {
|
static async conformRegister(username:string,code:string,openId?:string) {
|
||||||
let obj=await userMapper.getUserByName(username)
|
let obj=await userMapper.getUserByName(username)
|
||||||
if(obj) {
|
if(obj) {
|
||||||
throw Err.User.userExists
|
throw Err.User.userExists
|
||||||
@ -100,11 +104,37 @@ export class UserService extends Entity<typeof userModel,typeof userMapper> {
|
|||||||
throw Err.User.codeNotMatch
|
throw Err.User.codeNotMatch
|
||||||
}
|
}
|
||||||
let objUser=new UserService()
|
let objUser=new UserService()
|
||||||
|
let img:string
|
||||||
|
if(openId) {
|
||||||
|
let objRedis=REDIS_USER.wechatOpenId(openId)
|
||||||
|
let value=await objRedis.get()
|
||||||
|
if(!value) {
|
||||||
|
throw Err.User.userCacheExpired
|
||||||
|
}
|
||||||
|
let user=await UserService.getItemByExp({
|
||||||
|
from_id:openId
|
||||||
|
})
|
||||||
|
if(user) {
|
||||||
|
throw Err.User.userExists
|
||||||
|
}
|
||||||
|
img=value.img
|
||||||
|
}
|
||||||
objUser.assignItem({
|
objUser.assignItem({
|
||||||
username,
|
username,
|
||||||
password:content.password
|
password:content.password,
|
||||||
|
...(openId && {
|
||||||
|
from_type:ECommon_User_From_Type.WECHAT,
|
||||||
|
from_id:openId
|
||||||
|
})
|
||||||
})
|
})
|
||||||
await objUser.create()
|
let ret=await objUser.create()
|
||||||
|
if(img) {
|
||||||
|
let file=await File.download(img,ret.id)
|
||||||
|
objUser.assignItem({
|
||||||
|
photo:file.getId()
|
||||||
|
})
|
||||||
|
await objUser.update()
|
||||||
|
}
|
||||||
objRedis.del()
|
objRedis.del()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,6 +398,27 @@ export class UserService extends Entity<typeof userModel,typeof userMapper> {
|
|||||||
let ret=await userMapper.getDeletedUser()
|
let ret=await userMapper.getDeletedUser()
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async handleWechatCode(code:string) {
|
||||||
|
let res=await request(`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${Application.privateConfig.wechat.appId}&secret=${Application.privateConfig.wechat.appSecret}&code=${code}&grant_type=authorization_code`)
|
||||||
|
if(res.statusCode==200) {
|
||||||
|
let obj=JSON.parse(res.body)
|
||||||
|
if(obj.openid && obj.access_token) {
|
||||||
|
let res=await request(`https://api.weixin.qq.com/sns/userinfo?access_token=${obj.access_token}&openid=${obj.openid}`)
|
||||||
|
if(res.statusCode==200) {
|
||||||
|
let obj1=JSON.parse(res.body)
|
||||||
|
let objKey=REDIS_USER.wechatOpenId(obj.openid)
|
||||||
|
await objKey.set({
|
||||||
|
img:obj1.headimgurl
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
openId:obj.openid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user