This commit is contained in:
sx1989827 2023-08-10 16:28:30 +08:00
parent 9155cdf730
commit fbd08418e4
121 changed files with 3933 additions and 5994 deletions

View File

@ -12,23 +12,20 @@
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@logicflow/core": "^1.1.31",
"@socket.io/redis-adapter": "^8.2.1",
"@socket.io/redis-emitter": "^5.1.0",
"@logicflow/core": "^1.2.10",
"blueimp-md5": "^2.19.0",
"eventemitter3": "^5.0.0",
"mediasoup-client": "^3.6.98",
"moment": "^2.29.4",
"moment-timezone": "^0.5.42",
"pinia": "^2.1.4",
"socket.io": "^4.7.1",
"socket.io-client": "^4.6.1",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-router": "^4.2.2"
},
"devDependencies": {
"@arco-design/web-vue": "^2.48.1",
"@arco-design/web-vue": "^2.49.1",
"@rollup/plugin-typescript": "^10.0.1",
"@types/blueimp-md5": "^2.18.0",
"@types/node": "^18.15.11",

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,54 @@
<template>
<a-spin style="width: 100%;min-height: 100px;border: 1px solid lightgray" :loading="loading" v-drop.file.shortcut.disk="onDrop">
<RichEditor v-model="content" style="width: 100%" @upload-file="onUploadFile" :pop-menu-list="popMenuList" @pop-menu-click="onPopMenuClick" @custom-anchor-click="onCustomAnchorClick" ref="objEditorUser"></RichEditor>
</a-spin>
</template>
<script setup lang="ts">
import {getCurrentInstance, inject, Ref, ref} from "vue";
import RichEditor from "@/business/common/component/richEditor/richEditor.vue";
import {
ECommon_Content_Line_Config_Type,
ICommon_Content_Line,
ICommon_Content_Line_Config
} from "../../../../../../common/model/content";
import {RichEditorEventHandle} from "@/business/common/component/richEditorEventHandle";
import {DropParam, vDrop} from "@/teamOS/common/directive/drop";
import {apiFile} from "@/business/common/request/request";
import {onDialogOk} from "@/business/common/component/dialog/dialog";
const loading=ref(false)
const root =inject("dialogRootRef") as Ref<HTMLElement>
const appContext=getCurrentInstance().appContext
const content = ref<ICommon_Content_Line[]>([])
const popMenuList=ref(RichEditorEventHandle.popMenuList)
const objEditorUser=ref<InstanceType<typeof RichEditor>>()
const onCustomAnchorClick=(type:ECommon_Content_Line_Config_Type,value:string,link:string,label:string)=>{
RichEditorEventHandle.onCustomAnchorClick(type,value,link,label)
}
const onDrop=(data?:DropParam)=>{
RichEditorEventHandle.onDrop(objEditorUser,data)
}
const onUploadFile=async (file, handleFunc) => {
let res=await apiFile.upload({
file:file
})
if(res?.code==0) {
handleFunc(res.data.id,res.data.path)
}
}
const onPopMenuClick=(type:ECommon_Content_Line_Config_Type,handleFunc:(item:ICommon_Content_Line_Config)=>void)=>{
RichEditorEventHandle.onPopMenuClick(type,root,appContext,loading,handleFunc)
}
onDialogOk(async ()=>{
return content.value
})
</script>
<style scoped>
</style>

View File

@ -1,6 +1,7 @@
import {renderComponent} from "../../../../teamOS/common/util/component";
import DialogView from "./dialogView.vue";
import {AppContext, Component, inject, ref} from "vue";
import {AppContext, Component, inject, markRaw, ref} from "vue";
import DiaglogRich from "@/business/common/component/dialog/diaglogRich.vue";
export function onDialogOk(func:()=>void){
const events:any=inject("dialogEvents");
@ -104,4 +105,49 @@ export class Dialog {
}
})
}
static inputRich(el:HTMLElement,appContext: AppContext,title:string) {
return new Promise((resolve,reject)=>{
let ele=document.createElement("div")
const events:{
onOk:()=>any,
onClose:()=>void
}={
onOk:null,
onClose:null
}
const loading=ref(false);
let destroyFunc=renderComponent(ele,DialogView,appContext,{
component:markRaw(DiaglogRich),
title,
events,
onOk,
onClose,
loading
});
el.appendChild(ele);
async function onOk(){
if(events.onOk) {
loading.value=true;
let ret=await events.onOk();
loading.value=false
if(ret!==false) {
destroyFunc();
ele.parentNode.removeChild(ele);
resolve(ret)
}
} else {
destroyFunc();
ele.parentNode.removeChild(ele);
resolve(null);
}
}
function onClose(){
events.onClose?.();
destroyFunc();
ele.parentNode.removeChild(ele);
resolve(null)
}
})
}
}

View File

@ -1,5 +1,5 @@
<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: 10000">
<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: 10000" ref="root">
<div style="background-color: white;width: 60%;height:auto;max-height: 80%;border-radius: 5px;display: flex;flex-direction: column">
<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?"Input":"Alert"}}</b>
@ -42,6 +42,8 @@ const props=defineProps<{
}
}>()
const inputValue=props.input?props.input.text:ref("")
const root=ref()
provide("dialogRootRef",root)
if(props.events) {
provide("dialogEvents",props.events);
}

View File

@ -0,0 +1,99 @@
<template>
<div>
<template v-if="!isEdit">
<UserAvatar v-if="showValue" :photo="showValue.photo" :name="showValue.nickname" :organization-user-id="showValue.organizationUserId"></UserAvatar>
<span v-else style="line-height: 30px;width: 100%;color: gray">No User</span>
</template>
<a-row style="padding-right: 10px" v-else>
<a-select allow-search allow-clear v-model="editValue" @search="onSearchAssigner">
<a-option v-for="item in assignerList" :label="item.organizationUser.nickname" :value="item.organizationUser.id">
<a-avatar :size="24" :image-url="item.user.photo"></a-avatar>&nbsp;&nbsp;
{{ item.organizationUser.nickname }}
</a-option>
</a-select>
<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-row>
</div>
</template>
<script setup lang="ts">
import UserAvatar from "../../userAvatar.vue";
import {ref, watch} from "vue";
import {apiIssue, apiOrganization, DCSType} from "../../../request/request";
import {ICommon_Route_Res_Organization_User_Item} from "../../../../../../../common/routes/response";
import {SessionStorage} from "../../../storage/session";
const props=defineProps<{
isEdit:boolean,
showValue?:{
id:string,
organizationUserId?:string,
photo?:string,
nickname?:string
},
projectIssueId:string
}>()
const emit=defineEmits<{
cancel:[],
update:[value:{
id:string,
organizationUserId?:string,
photo?:string,
nickname?:string
}]
}>()
const editValue=ref("")
const assignerList=ref<DCSType<ICommon_Route_Res_Organization_User_Item>[]>([])
const assignValue=()=>{
editValue.value=""
}
watch(()=>props.showValue,()=>{
assignValue()
},{
immediate:true,
deep:true
})
const onClick=async()=>{
let res=await apiIssue.editBasicField({
projectIssueId:props.projectIssueId,
assignerId:editValue.value as string
})
if(res?.code==0) {
emit("update",res.data.assigner_id)
}
}
const onSearchAssigner=async (keyword:string)=>{
let res=await apiOrganization.listUser({
organizationId:SessionStorage.get("organizationId"),
keyword,
page:0,
size:10
})
if(res?.code==0) {
assignerList.value=res.data.data
}
}
const onBlur=()=>{
emit('cancel')
assignValue()
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,121 @@
<template>
<div>
<RichEditor v-if="!isEdit" :model-value="showValue?JSON.parse(showValue):[]" :readonly="true" @custom-anchor-click="onCustomAnchorClick"></RichEditor>
<template v-else>
<div style="width: 100%">
<div>
<a-spin :loading="loading" v-drop.file.shortcut.disk="onDrop" style="width: 100%">
<RichEditor v-model="editValue" @upload-file="onUploadFile" :pop-menu-list="popMenuList" @pop-menu-click="onPopMenuClick" @custom-anchor-click="onCustomAnchorClick" @quote-list="onQuoteList" ref="objEditor"></RichEditor>
</a-spin>
</div>
<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>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import {apiFile, apiIssue} from "../../../request/request";
import {getCurrentInstance, ref, watch} from "vue";
import RichEditor from "../../richEditor/richEditor.vue";
import {
ECommon_Content_Line_Config_Type,
ICommon_Content_Line,
ICommon_Content_Line_Config
} from "../../../../../../../common/model/content";
import {RichEditorEventHandle} from "../../richEditorEventHandle";
import {DropParam, vDrop} from "../../../../../teamOS/common/directive/drop";
import {getRootNavigatorRef} from "../../../../../teamOS/common/component/navigator/navigator";
const props=defineProps<{
isEdit:boolean,
showValue?:string,
projectIssueId:string
}>()
const emit=defineEmits<{
cancel:[],
update:[value:string]
}>()
const root=getRootNavigatorRef()
const appContext=getCurrentInstance().appContext
const editValue=ref<ICommon_Content_Line[]>([])
const objEditor=ref<InstanceType<typeof RichEditor>>()
const loading=ref(false)
const popMenuList=ref(RichEditorEventHandle.popMenuList)
const assignValue=()=>{
editValue.value=props.showValue?JSON.parse(props.showValue):[]
}
watch(()=>props.showValue,()=>{
assignValue()
},{
immediate:true,
deep:true
})
const onClick=async()=>{
let value=JSON.stringify(editValue.value.map(item=>{
return {
arr:item.arr
}
}))
let res=await apiIssue.editDescription({
projectIssueId:props.projectIssueId,
description:JSON.stringify(editValue.value.map(item=>{
return {
arr:item.arr
}
}))
})
if(res?.code==0) {
emit("update",value)
}
}
const onBlur=()=>{
emit('cancel')
assignValue()
}
const onUploadFile=async (file, handleFunc) => {
let res=await apiFile.upload({
file:file
})
if(res?.code==0) {
handleFunc(res.data.id,res.data.path)
}
}
const onPopMenuClick=(type:ECommon_Content_Line_Config_Type,handleFunc:(item:ICommon_Content_Line_Config)=>void)=>{
RichEditorEventHandle.onPopMenuClick(type,root,appContext,loading,handleFunc)
}
const onCustomAnchorClick=(type:ECommon_Content_Line_Config_Type,value:string,link:string,label:string)=>{
RichEditorEventHandle.onCustomAnchorClick(type,value,link,label)
}
const onQuoteList=(keyword:string,handleFunc:(list:{
value:string,
label:string,
photo:string
}[])=>void)=>{
RichEditorEventHandle.onQuoteList(keyword,handleFunc)
}
const onDrop=(data?:DropParam)=>{
RichEditorEventHandle.onDrop(objEditor,data,loading)
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,144 @@
<template>
<div>
<template v-if="!isEdit">
<a-space wrap size="mini" v-if="(showValue as ICommon_Model_Project_Release[]).length>0">
<ProjectReleasePreview v-for="item in (showValue as ICommon_Model_Project_Release[])" :key="item.id" :project-release-id="item.id" :name="item.name"></ProjectReleasePreview>
</a-space>
<span v-else style="line-height: 30px;width: 100%;color: gray">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="onSearchRelease" 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>
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 {ICommon_Model_Project_Release} from "../../../../../../../common/model/project_release";
import ProjectReleasePreview from "../../../../controller/app/project/release/projectReleasePreview.vue";
import {inject, ref, watch} from "vue";
import {injectProjectInfo} from "../../../util/symbol";
import {apiIssue, apiRelease, DCSType} from "../../../request/request";
const props=defineProps<{
isEdit:boolean,
showValue?:DCSType<ICommon_Model_Project_Release>[],
projectIssueId:string
}>()
const emit=defineEmits<{
cancel:[],
update:[value:DCSType<ICommon_Model_Project_Release>[]]
}>()
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 onSearchRelease=async (keyword:string)=>{
let res=await apiRelease.list({
projectId,
name: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 apiIssue.bindReleases({
projectIssueId:props.projectIssueId,
projectReleaseIds:arrId
})
if(res?.code==0) {
emit("update",res.data)
}
}
const onBlur=()=>{
emit('cancel')
assignValue()
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,140 @@
<template>
<div>
<template v-if="!isEdit">
<a-space wrap size="mini" v-if="(showValue as ICommon_Model_Project_Label[]).length>0">
<a-tag v-for="item in (showValue as ICommon_Model_Project_Label[])" color="blue">{{item.name}}</a-tag>
</a-space>
<span v-else style="line-height: 30px;width: 100%;color: gray">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="onSearchLabel" 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>
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 {ICommon_Model_Project_Label} from "../../../../../../../common/model/project_label";
import {inject, ref, watch} from "vue";
import {apiIssue, apiProject} from "../../../request/request";
import {injectProjectInfo} from "../../../util/symbol";
const props=defineProps<{
isEdit:boolean,
showValue?:ICommon_Model_Project_Label[],
projectIssueId:string
}>()
const emit=defineEmits<{
cancel:[],
update:[value:ICommon_Model_Project_Label[]]
}>()
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 onSearchLabel=async (keyword:string)=>{
let res=await apiProject.listLabel({
projectId,
keyword,
page:0,
size:10
})
if(res?.code==0) {
labelList.value=res.data.data
}
}
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 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 apiIssue.bindLabel({
projectIssueId:props.projectIssueId,
labelIds:arrId
})
if(res?.code==0) {
emit("update",res.data)
}
}
const onBlur=()=>{
emit('cancel')
assignValue()
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,101 @@
<template>
<div>
<template v-if="!isEdit">
<a-space wrap size="mini" v-if="showValue.length>0">
<template #split>
/
</template>
<span v-for="item in showValue" style="color: blue">{{item.name}}</span>
</a-space>
<span v-else style="line-height: 30px;width: 100%;color: gray">None</span>
</template>
<a-row style="padding-right: 10px" v-else>
<a-cascader v-model="editValue" :field-names="fields" :options="moduleList" placeholder="please select" :format-label="format" check-strictly allow-clear allow-search></a-cascader>
<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-row>
</div>
</template>
<script setup lang="ts">
import {ICommon_Model_Project_Module} from "../../../../../../../common/model/project_module";
import {inject, ref, watch} from "vue";
import {injectProjectInfo} from "../../../util/symbol";
import {ICommon_Route_Res_Project_CreateModule_Data} from "../../../../../../../common/routes/response";
import {apiIssue, apiProject} from "../../../request/request";
const props=defineProps<{
isEdit:boolean,
showValue?:ICommon_Model_Project_Module[],
projectIssueId:string
}>()
const emit=defineEmits<{
cancel:[],
update:[value:ICommon_Model_Project_Module[]]
}>()
const labelList=ref<{
id:string,
name:string
}[]>([])
const editValue=ref("")
const fields={
label:"name",
value:"id",
children:"data"
}
const projectId=inject(injectProjectInfo).id;
const moduleList=ref<ICommon_Route_Res_Project_CreateModule_Data[]>([])
const assignValue=()=>{
editValue.value=props.showValue.length>0?props.showValue[props.showValue.length-1].id:""
}
watch(()=>props.showValue,()=>{
assignValue()
},{
immediate:true,
deep:true
})
watch(()=>props.isEdit,()=>{
getModuleList()
})
const format = (options) => {
const labels = options.map(option => option.name)
return labels.join('/')
}
const getModuleList=async ()=>{
let res=await apiProject.listModule({
projectId
})
if(res?.code==0) {
moduleList.value=res.data
}
}
const onClick=async ()=>{
let res=await apiIssue.bindModule({
projectIssueId:props.projectIssueId,
moduleId:editValue.value
})
if(res?.code==0) {
emit("update",res.data)
}
}
const onBlur=()=>{
emit('cancel')
assignValue()
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,65 @@
<template>
<div>
<span style="font-size: 24px;font-weight: bold" v-if="!isEdit">{{ showValue }}</span>
<template v-else>
<a-row style="padding-right: 10px">
<a-input v-model="editValue"></a-input>
<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-row>
</template>
</div>
</template>
<script setup lang="ts">
import {ref, watch} from "vue";
import {apiIssue} from "../../../request/request";
const props=defineProps<{
isEdit:boolean,
showValue?:string,
projectIssueId:string
}>()
const emit=defineEmits<{
cancel:[],
update:[value:string]
}>()
const editValue=ref("")
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,
name:editValue.value as string
})
if(res?.code==0) {
emit("update",res.data.name)
}
}
const onBlur=()=>{
emit('cancel')
assignValue()
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,74 @@
<template>
<div>
<FieldPriority :priority="showValue" v-if="!isEdit"></FieldPriority>
<template v-else>
<a-space size="mini">
<a-select v-model="editValue">
<a-option label="low" :value="ECommon_Model_Project_Issue_Priority.LOW"></a-option>
<a-option label="medium" :value="ECommon_Model_Project_Issue_Priority.MEDIUM"></a-option>
<a-option label="high" :value="ECommon_Model_Project_Issue_Priority.HIGH"></a-option>
<a-option label="urgent" :value="ECommon_Model_Project_Issue_Priority.URGENT"></a-option>
</a-select>
<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 {ECommon_Model_Project_Issue_Priority} from "../../../../../../../common/model/project_issue";
import FieldPriority from "../fieldPriority.vue";
import {ref, watch} from "vue";
import {apiIssue} from "../../../request/request";
const props=defineProps<{
isEdit:boolean,
showValue?:ECommon_Model_Project_Issue_Priority,
projectIssueId:string
}>()
const emit=defineEmits<{
cancel:[],
update:[value:ECommon_Model_Project_Issue_Priority]
}>()
const editValue=ref<ECommon_Model_Project_Issue_Priority>()
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,
priority:editValue.value
})
if(res?.code==0) {
emit("update",editValue.value)
}
}
const onBlur=()=>{
emit('cancel')
assignValue()
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,98 @@
<template>
<div>
<template v-if="!isEdit">
<UserAvatar v-if="showValue" :photo="showValue.photo" :name="showValue.nickname" :organization-user-id="showValue.organizationUserId"></UserAvatar>
<span v-else style="line-height: 30px;width: 100%;color: gray">No User</span>
</template>
<a-row style="padding-right: 10px" v-else>
<a-select allow-search allow-clear v-model="editValue" @search="onSearchReporter">
<a-option v-for="item in reporterList" :label="item.organizationUser.nickname" :value="item.organizationUser.id">
<a-avatar :size="24" :image-url="item.user.photo"></a-avatar>&nbsp;&nbsp;
{{ item.organizationUser.nickname }}
</a-option>
</a-select>
<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-row>
</div>
</template>
<script setup lang="ts">
import UserAvatar from "../../userAvatar.vue";
import {ref, watch} from "vue";
import {apiIssue, apiOrganization, DCSType} from "../../../request/request";
import {ICommon_Route_Res_Organization_User_Item} from "../../../../../../../common/routes/response";
import {SessionStorage} from "../../../storage/session";
const props=defineProps<{
isEdit:boolean,
showValue?:{
id:string,
organizationUserId?:string,
photo?:string,
nickname?:string
},
projectIssueId:string
}>()
const emit=defineEmits<{
cancel:[],
update:[value:{
id:string,
organizationUserId?:string,
photo?:string,
nickname?:string
}]
}>()
const editValue=ref("")
const reporterList=ref<DCSType<ICommon_Route_Res_Organization_User_Item>[]>([])
const assignValue=()=>{
editValue.value=""
}
watch(()=>props.showValue,()=>{
assignValue()
},{
immediate:true,
deep:true
})
const onClick=async()=>{
let res=await apiIssue.editBasicField({
projectIssueId:props.projectIssueId,
reporterId:editValue.value as string
})
if(res?.code==0) {
emit("update",res.data.reporter_id)
}
}
const onSearchReporter=async (keyword:string)=>{
let res=await apiOrganization.listUser({
organizationId:SessionStorage.get("organizationId"),
keyword,
page:0,
size:10
})
if(res?.code==0) {
reporterList.value=res.data.data
}
}
const onBlur=()=>{
emit('cancel')
assignValue()
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,291 @@
<template>
<div @focus="onFocus" tabindex="-1" class="hover">
<template v-if="!isEdit">
<template v-if="showValue.id">
<UserAvatar :organization-user-id="showValue.id" v-if="type==='user'"></UserAvatar>
<ProjectReleasePreview v-else-if="type==='release'" :project-release-id="showValue.id" :name="showValue.name"></ProjectReleasePreview>
<ProjectIssuePreview :name="showValue.name" :project-issue-id="showValue.id" v-else-if="type==='issue'"></ProjectIssuePreview>
<a-tag v-else>
{{showValue.name}}
</a-tag>
</template>
<span v-else style="line-height: 30px;width: 100%;color: gray">None</span>
</template>
<template v-else>
<a-space size="mini" wrap>
<a-select v-model="addValue" allow-search allow-clear @search="onSearch" v-model:input-value="label">
<a-option v-for="item1 in searchValueList" :label="item1.name" :value="item1.id"></a-option>
</a-select>
<a-button type="text" @click="onSubmit">
<template #icon>
<icon-check></icon-check>
</template>
</a-button>
<a-button type="text" @click="onBlur">
<template #icon>
<icon-close></icon-close>
</template>
</a-button>
</a-space>
</template>
</div>
</template>
<script setup lang="ts">
import {ref, watch, watchEffect} from "vue";
import {
apiField,
apiIssue,
apiOrganization,
apiProject,
apiRelease,
apiTeam,
apiWorkflow
} from "@/business/common/request/request";
import UserAvatar from "@/business/common/component/userAvatar.vue";
import ProjectIssuePreview from "@/business/controller/app/project/issue/projectIssuePreview.vue";
import ProjectReleasePreview from "@/business/controller/app/project/release/projectReleasePreview.vue";
const emit=defineEmits<{
(e:"update:modelValue",value:string):void
}>()
const props=defineProps<{
modelValue:string,
type:"user"|"team"|"release"|"label"|"tag"|"issue"|"field",
projectId?:string,
workflowNodeId?:string
}>()
const showValue=ref<{
name?:string,
id:string,
}>({} as any);
const isEdit=ref(false)
const searchValueList=ref<{
name:string,
id:string,
}[]>([])
const addValue=ref("")
const label=ref("")
const request=async ()=>{
if(props.modelValue && props.modelValue.length>0) {
let ret:{
id:string,
name:string
}
if(props.type==="team") {
ret=await apiTeam.info({
teamId:props.modelValue
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.name
}
}
})
} else if(props.type==="user") {
ret=await apiOrganization.user({
organizationUserId:props.modelValue
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.nickname
}
}
})
} else if(props.type==="release") {
ret=await apiRelease.info({
projectReleaseId:props.modelValue
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.name
}
}
})
} else if(props.type==="label") {
ret=await apiProject.getLabel({
labelId:props.modelValue
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.name
}
}
})
} else if(props.type==="tag") {
ret=await apiOrganization.getTag({
memberTagId:props.modelValue
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.name
}
}
})
} else if(props.type==="issue") {
ret=await apiIssue.basicInfo({
projectIssueId:props.modelValue
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.project.keyword+"-"+data.data.unique_id
}
}
})
} else if(props.type==="field") {
ret=await apiField.workflowNodeFieldInfo({
workflowNodeFieldTypeId:props.modelValue
}).then(data=>{
if(data.code==0) {
return {
id:data.data.field.id,
name:data.data.field.name
}
}
})
}
showValue.value=ret
label.value=showValue.value.name
} else {
showValue.value={} as any
}
}
watchEffect(()=>{
request()
})
watch(isEdit,async ()=>{
if(isEdit.value) {
if(props.type==="field") {
let res=await apiWorkflow.listApprovalField({
workflowNodeId:props.workflowNodeId
})
if(res?.code==0) {
searchValueList.value=res.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
}
}
})
const onFocus=()=>{
isEdit.value=true
}
const onBlur=()=>{
isEdit.value=false
}
const onSubmit=()=>{
isEdit.value=false
emit("update:modelValue",addValue.value)
}
const onSearch=async (keyword:string)=>{
if(keyword) {
if(props.type==="team") {
let res=await apiTeam.list({
keyword,
page:0,
size:10
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
} else if(props.type==="user") {
let res=await apiOrganization.listUser({
keyword,
page:0,
size:10
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.organizationUser.id,
name:item.organizationUser.nickname
}
})
}
} else if(props.type==="release") {
let res=await apiRelease.list({
page:0,
size:10,
name:keyword,
projectId:props.projectId
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
} else if(props.type==="label") {
let res=await apiProject.listLabel({
page:0,
size:10,
keyword,
projectId:props.projectId
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
} else if(props.type==="tag") {
let res=await apiOrganization.listTag({
keyword
})
if(res?.code==0) {
searchValueList.value=res.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
} else if(props.type==="issue") {
let res=await apiIssue.filter({
page:0,
size:10,
projectId:props.projectId,
name:keyword
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
}
}
}
</script>
<style scoped>
.hover:hover {
background-color: rgb(230,231,237);
}
</style>

View File

@ -0,0 +1,281 @@
<template>
<div @focus="onFocus" tabindex="-1" class="hover">
<template v-if="!isEdit">
<a-space wrap v-if="showValue && showValue.length>0">
<template v-for="(item,index) in editValue" :key="item.id">
<UserAvatar :organization-user-id="item.id" v-if="type==='user'"></UserAvatar>
<ProjectReleasePreview v-else-if="type==='release'" :project-release-id="item.id" :name="item.name"></ProjectReleasePreview>
<ProjectIssuePreview :name="item.name" :project-issue-id="item.id" v-else-if="type==='issue'"></ProjectIssuePreview>
<a-tag v-else>
{{item.name}}
</a-tag>
</template>
</a-space>
<span v-else style="line-height: 30px;width: 100%;color: gray">None</span>
</template>
<template v-else>
<a-space size="mini" wrap>
<a-tag v-for="(value,index) in editValue" :closable="true" @close="onClose(index)" :key="value.id">
{{value.name}}
</a-tag>
<a-select v-model="addValue" allow-search @search="onSearch" v-if="showInput" @change="onChange">
<a-option v-for="item1 in searchValueList" :label="item1.name" :value="item1.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>
Add
</a-tag>
<a-button type="text" @click="onSubmit">
<template #icon>
<icon-check></icon-check>
</template>
</a-button>
<a-button type="text" @click="onBlur">
<template #icon>
<icon-close></icon-close>
</template>
</a-button>
</a-space>
</template>
</div>
</template>
<script setup lang="ts">
import {ref, watchEffect} from "vue";
import {apiIssue, apiOrganization, apiProject, apiRelease, apiTeam} from "@/business/common/request/request";
import UserAvatar from "@/business/common/component/userAvatar.vue";
import ProjectIssuePreview from "@/business/controller/app/project/issue/projectIssuePreview.vue";
import ProjectReleasePreview from "@/business/controller/app/project/release/projectReleasePreview.vue";
const emit=defineEmits<{
(e:"update:modelValue",value:string[]):void
}>()
const props=defineProps<{
modelValue:string[],
type:"user"|"team"|"release"|"label"|"tag"|"issue",
projectId?:string
}>()
const showValue=ref<{
name?:string,
id:string,
}[]>([]);
const editValue=ref<{
name?:string,
id:string
}[]>([]);
const isEdit=ref(false)
const searchValueList=ref<{
name:string,
id:string,
}[]>([])
const addValue=ref("")
const showInput = ref(false);
const request=async ()=>{
if(props.modelValue && props.modelValue.length>0) {
let arr=await Promise.all(props.modelValue.map(item=>{
if(props.type==="team") {
return apiTeam.info({
teamId:item
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.name
}
}
})
} else if(props.type==="user") {
return apiOrganization.user({
organizationUserId:item
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.nickname
}
}
})
} else if(props.type==="release") {
return apiRelease.info({
projectReleaseId:item
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.name
}
}
})
} else if(props.type==="label") {
return apiProject.getLabel({
labelId:item
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.name
}
}
})
} else if(props.type==="tag") {
return apiOrganization.getTag({
memberTagId:item
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.name
}
}
})
} else if(props.type==="issue") {
return apiIssue.basicInfo({
projectIssueId:item
}).then(data=>{
if(data.code==0) {
return {
id:data.data.id,
name:data.data.project.keyword+"-"+data.data.unique_id
}
}
})
}
}))
arr=arr.filter(item=>{
return item!=null
})
showValue.value=arr
editValue.value=[...arr]
} else {
showValue.value=[]
editValue.value=[]
}
}
watchEffect(()=>{
request()
})
const onFocus=()=>{
isEdit.value=true
}
const onBlur=()=>{
isEdit.value=false
}
const onSubmit=()=>{
isEdit.value=false
emit("update:modelValue",editValue.value.map(item=>item.id))
}
const onChange=()=>{
showInput.value=false
editValue.value=[...editValue.value,searchValueList.value.find(item=>{
if(item.id==addValue.value) {
return true
}
})]
addValue.value=""
}
const onClose=(index:number)=>{
editValue.value.splice(index,1)
}
const onSearch=async (keyword:string)=>{
if(keyword) {
if(props.type==="team") {
let res=await apiTeam.list({
keyword,
page:0,
size:10
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
} else if(props.type==="user") {
let res=await apiOrganization.listUser({
keyword,
page:0,
size:10
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.organizationUser.id,
name:item.organizationUser.nickname
}
})
}
} else if(props.type==="release") {
let res=await apiRelease.list({
page:0,
size:10,
name:keyword,
projectId:props.projectId
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
} else if(props.type==="label") {
let res=await apiProject.listLabel({
page:0,
size:10,
keyword,
projectId:props.projectId
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
} else if(props.type==="tag") {
let res=await apiOrganization.listTag({
keyword
})
if(res?.code==0) {
searchValueList.value=res.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
} else if(props.type==="issue") {
let res=await apiIssue.filter({
page:0,
size:10,
projectId:props.projectId,
name:keyword
})
if(res?.code==0) {
searchValueList.value=res.data.data.map(item=>{
return {
id:item.id,
name:item.name
}
})
}
}
}
}
</script>
<style scoped>
.hover:hover {
background-color: rgb(230,231,237);
}
</style>

View File

@ -1,206 +1,13 @@
<template>
<span style="display: inline-block;width: 100%;cursor: text" @mouseenter="onEnter" @mouseleave="onLeave" tabindex="-1" @focusin="onFocus" ref="element">
<template v-if="type==EClient_Field_Basic_Type.NAME">
<span style="font-size: 24px;font-weight: bold" v-if="!isEdit">{{ showValue }}</span>
<template v-else>
<a-row style="padding-right: 10px">
<a-input v-model="editValue"></a-input>
<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-row>
</template>
</template>
<template v-if="type==EClient_Field_Basic_Type.DESCRIPTION">
<span style="line-height: 30px" v-if="!isEdit">{{ showValue }}</span>
<template v-else>
<a-row style="padding-right: 10px">
<a-textarea v-model="editValue" allow-clear></a-textarea>
<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-row>
</template>
</template>
<template v-if="type==EClient_Field_Basic_Type.ASSIGNER">
<template v-if="!isEdit">
<UserAvatar v-if="showValue" :photo="showValue.photo" :name="showValue.nickname" :organization-user-id="showValue.organizationUserId"></UserAvatar>
<span v-else style="line-height: 30px;width: 100%;color: gray">No User</span>
</template>
<a-row style="padding-right: 10px" v-else>
<a-select allow-search allow-clear v-model="editValue" @search="onSearchAssigner">
<a-option v-for="item in assignerList" :label="item.organizationUser.nickname" :value="item.organizationUser.id">
<a-avatar :size="24" :image-url="item.user.photo"></a-avatar>&nbsp;&nbsp;
{{ item.organizationUser.nickname }}
</a-option>
</a-select>
<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-row>
</template>
<template v-if="type==EClient_Field_Basic_Type.REPORTER">
<template v-if="!isEdit">
<UserAvatar v-if="showValue" :photo="showValue.photo" :name="showValue.nickname" :organization-user-id="showValue.organizationUserId"></UserAvatar>
<span v-else style="line-height: 30px;width: 100%;color: gray">No User</span>
</template>
<template v-else>
<a-row style="padding-right: 10px">
<a-select allow-search allow-clear v-model="editValue" @search="onSearchReporter">
<a-option v-for="item in reporterList" :label="item.organizationUser.nickname" :value="item.organizationUser.id">
<a-avatar :size="24" :image-url="item.user.photo"></a-avatar>&nbsp;&nbsp;
{{item.organizationUser.nickname}}
</a-option>
</a-select>
<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-row>
</template>
</template>
<template v-if="type==EClient_Field_Basic_Type.PRIORITY">
<FieldPriority :priority="showValue" v-if="!isEdit"></FieldPriority>
<template v-else>
<a-space size="mini">
<a-select v-model="editValue">
<a-option label="low" :value="ECommon_Model_Project_Issue_Priority.LOW"></a-option>
<a-option label="medium" :value="ECommon_Model_Project_Issue_Priority.MEDIUM"></a-option>
<a-option label="high" :value="ECommon_Model_Project_Issue_Priority.HIGH"></a-option>
<a-option label="urgent" :value="ECommon_Model_Project_Issue_Priority.URGENT"></a-option>
</a-select>
<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>
</template>
<template v-if="type==EClient_Field_Basic_Type.LABEL">
<template v-if="!isEdit">
<a-space wrap size="mini" v-if="showValue.length>0">
<a-tag v-for="item in showValue" color="blue">{{item.name}}</a-tag>
</a-space>
<span v-else style="line-height: 30px;width: 100%;color: gray">None</span>
</template>
<a-row style="padding-right: 10px" v-else>
<a-space size="mini" wrap>
<a-tag v-for="(item,index) in editValue" :closable="true" @close="onCloseLabelTag(index)" :key="item.id">
{{item.name}}
</a-tag>
<a-select v-model="addValue" allow-search @search="onSearchLabel" 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>
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>
</template>
<template v-if="type==EClient_Field_Basic_Type.MODULE">
<template v-if="!isEdit">
<a-space wrap size="mini" v-if="showValue.length>0">
<template #split>
/
</template>
<span v-for="item in showValue" style="color: blue">{{item.name}}</span>
</a-space>
<span v-else style="line-height: 30px;width: 100%;color: gray">None</span>
</template>
<a-row style="padding-right: 10px" v-else>
<a-cascader v-model="editValue" :field-names="fields" :options="moduleList" placeholder="please select" :format-label="format" check-strictly allow-clear allow-search></a-cascader>
<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-row>
</template>
<template v-if="type==EClient_Field_Basic_Type.FIXVERSIONS">
<template v-if="!isEdit">
<a-space wrap size="mini" v-if="showValue.length>0">
<ProjectReleasePreview v-for="item in showValue" :key="item.id" :project-release-id="item.id" :name="item.name"></ProjectReleasePreview>
</a-space>
<span v-else style="line-height: 30px;width: 100%;color: gray">None</span>
</template>
<a-row style="padding-right: 10px" v-else>
<a-space size="mini" wrap>
<a-tag v-for="(item,index) in editValue" :closable="true" @close="onCloseLabelTag(index)" :key="item.id">
{{item.name}}
</a-tag>
<a-select v-model="addValue" allow-search @search="onSearchRelease" 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>
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>
</template>
<span style="display: inline-block;width: 100%;cursor: text" @mouseenter="onEnter" @mouseleave="onLeave" tabindex="-1" @focus="onFocus" ref="element">
<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>
<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>
<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>
</span>
</template>
@ -208,118 +15,61 @@
import {EClient_Field_Basic_Type} from "./fieldBasicType";
import {ICommon_Model_Project_Label} from "../../../../../../common/model/project_label";
import {ICommon_Model_Project_Module} from "../../../../../../common/model/project_module";
import {inject, onMounted, ref, watch} from "vue";
import {apiIssue, apiOrganization, apiProject, apiRelease, DCSType} from "../../request/request";
import UserAvatar from "../userAvatar.vue";
import {
ICommon_Route_Res_Organization_User_Item,
ICommon_Route_Res_Project_CreateModule_Data
} from "../../../../../../common/routes/response";
import FieldPriority from "./fieldPriority.vue";
import {inject, ref, watch} from "vue";
import {DCSType} from "../../request/request";
import {ECommon_Model_Project_Issue_Priority} from "../../../../../../common/model/project_issue";
import {injectProjectInfo} from "../../util/symbol";
import {checkPermission, Permission_Types} from "../../../../../../common/permission/permission";
import {ICommon_Model_Project_Release} from "../../../../../../common/model/project_release";
import ProjectReleasePreview from "../../../controller/app/project/release/projectReleasePreview.vue";
import {SessionStorage} from "../../storage/session";
import FieldEditBasicName from "./basic/fieldEditBasicName.vue";
import FieldEditBasicDescription from "./basic/fieldEditBasicDescription.vue";
import FieldEditBasicAssigner from "./basic/fieldEditBasicAssigner.vue";
import FieldEditBasicPriority from "./basic/fieldEditBasicPriority.vue";
import FieldEditBasicReporter from "./basic/fieldEditBasicReporter.vue";
import FieldEditBasicLabel from "./basic/fieldEditBasicLabel.vue";
import FieldEditBasicModule from "./basic/fieldEditBasicModule.vue";
import FieldEditBasicFixVersion from "./basic/fieldEditBasicFixVersion.vue";
type User={
id:string,
organizationUserId:string,
organizationUserId?:string,
photo?: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[]
const props=defineProps<{
projectIssueId:string,
type:EClient_Field_Basic_Type,
value?:Value
}>()
const fields={
label:"name",
value:"id",
children:"data"
}
const showInput=ref(false)
const addValue=ref("")
type Props={
projectIssueId:string
} & (
{
type:EClient_Field_Basic_Type.NAME | EClient_Field_Basic_Type.DESCRIPTION,
value?:string
} | {
type:EClient_Field_Basic_Type.PRIORITY,
value?:number
} | {
type:EClient_Field_Basic_Type.ASSIGNER | EClient_Field_Basic_Type.REPORTER,
value?:User
} | {
type:EClient_Field_Basic_Type.LABEL,
value?:ICommon_Model_Project_Label[]
} | {
type:EClient_Field_Basic_Type.MODULE,
value?:ICommon_Model_Project_Module[]
} | {
type:EClient_Field_Basic_Type.FIXVERSIONS,
value?:ICommon_Model_Project_Release[]
}
)
const props=defineProps<Props>()
const element=ref<HTMLSpanElement>(null)
const showValue=ref(props.value)
const editValue=ref<string|ECommon_Model_Project_Issue_Priority|string[]|{
id:string,
name:string
}[]>(null)
const assignEditValue=()=>{
switch (props.type) {
case EClient_Field_Basic_Type.DESCRIPTION:
case EClient_Field_Basic_Type.NAME: {
editValue.value=showValue.value as string
break
}
case EClient_Field_Basic_Type.PRIORITY:{
editValue.value=showValue.value as number
break
}
case EClient_Field_Basic_Type.REPORTER:
case EClient_Field_Basic_Type.ASSIGNER:{
editValue.value=""
break
}
case EClient_Field_Basic_Type.LABEL:{
let value=showValue.value as ICommon_Model_Project_Label[]
editValue.value=value.length>0?value.map(item=>{
return {
id:item.id,
name:item.name
}
}):[]
break
}
case EClient_Field_Basic_Type.MODULE:{
let value=showValue.value as ICommon_Model_Project_Module[]
editValue.value=value.length>0?value[value.length-1].id:""
break
}
case EClient_Field_Basic_Type.FIXVERSIONS:{
let value=showValue.value as ICommon_Model_Project_Release[]
editValue.value=value.length>0?value.map(item=>{
return {
id:item.id,
name:item.name
}
}):[]
break
}
default:
break
}
}
const projectId=inject(injectProjectInfo).id;
const permission=inject(injectProjectInfo).permission
const isEdit=ref(false)
const assignerList=ref<DCSType<ICommon_Route_Res_Organization_User_Item[]>>([])
const reporterList=ref<DCSType<ICommon_Route_Res_Organization_User_Item[]>>([])
const labelList=ref<{
id:string,
name:string
}[]>([])
const moduleList=ref<ICommon_Route_Res_Project_CreateModule_Data[]>([])
watch(()=>props.value,(newValue,oldValue)=>{
showValue.value=props.value
if(props.type===EClient_Field_Basic_Type.DESCRIPTION) {
if(!showValue.value) {
isEdit.value=true
} else {
isEdit.value=false
}
}
assignEditValue()
},{
immediate:true
})
watch(isEdit,()=>{
if(isEdit.value && props.type==EClient_Field_Basic_Type.MODULE) {
getModuleList()
}
immediate:true,
deep:true
})
const onEnter=(event:MouseEvent)=>{
if(!isEdit.value) {
@ -339,180 +89,14 @@ const onFocus=async (event:MouseEvent)=>{
}
}
const onBlur=async ()=>{
if(props.type===EClient_Field_Basic_Type.DESCRIPTION && !editValue.value) {
return
}
isEdit.value=false
showInput.value=false
addValue.value=""
assignEditValue()
}
const onClick=async ()=>{
if(props.type==EClient_Field_Basic_Type.NAME) {
let res=await apiIssue.editBasicField({
projectIssueId:props.projectIssueId,
name:editValue.value as string
})
if(res?.code==0) {
showValue.value=editValue.value as string
onBlur()
}
} else if(props.type==EClient_Field_Basic_Type.DESCRIPTION) {
let res=await apiIssue.editDescription({
projectIssueId:props.projectIssueId,
description:editValue.value as string
})
if(res?.code==0) {
showValue.value=editValue.value as string
onBlur()
}
} else if(props.type==EClient_Field_Basic_Type.ASSIGNER) {
let res=await apiIssue.editBasicField({
projectIssueId:props.projectIssueId,
assignerId:editValue.value as string
})
if(res?.code==0) {
showValue.value=res.data.assigner_id as User
onBlur()
}
} else if(props.type==EClient_Field_Basic_Type.REPORTER) {
let res=await apiIssue.editBasicField({
projectIssueId:props.projectIssueId,
reporterId:editValue.value as string
})
if(res?.code==0) {
showValue.value=res.data.reporter_id as User
onBlur()
}
} else if(props.type==EClient_Field_Basic_Type.PRIORITY) {
let res=await apiIssue.editBasicField({
projectIssueId:props.projectIssueId,
priority:editValue.value as ECommon_Model_Project_Issue_Priority
})
if(res?.code==0) {
showValue.value=editValue.value as number
onBlur()
}
} else if(props.type==EClient_Field_Basic_Type.LABEL) {
let arr=editValue.value as {
id:string,
name:string
}[]
let arrId=Array.from(new Set(arr.map(item=>item.id)));
let res=await apiIssue.bindLabel({
projectIssueId:props.projectIssueId,
labelIds:arrId
})
if(res?.code==0) {
showValue.value=res.data
onBlur()
}
} else if(props.type==EClient_Field_Basic_Type.MODULE) {
let res=await apiIssue.bindModule({
projectIssueId:props.projectIssueId,
moduleId:editValue.value as string
})
if(res?.code==0) {
showValue.value=res.data
onBlur()
}
} else if(props.type==EClient_Field_Basic_Type.FIXVERSIONS) {
let arr=editValue.value as {
id:string,
name:string
}[]
let arrId=Array.from(new Set(arr.map(item=>item.id)));
let res=await apiIssue.bindReleases({
projectIssueId:props.projectIssueId,
projectReleaseIds:arrId
})
if(res?.code==0) {
showValue.value=res.data
onBlur()
}
}
}
const onSearchAssigner=async (keyword:string)=>{
let res=await apiOrganization.listUser({
organizationId:SessionStorage.get("organizationId"),
keyword,
page:0,
size:10
})
if(res?.code==0) {
assignerList.value=res.data.data
}
}
const onSearchReporter=async (keyword:string)=>{
let res=await apiOrganization.listUser({
organizationId:SessionStorage.get("organizationId"),
keyword,
page:0,
size:10
})
if(res?.code==0) {
reporterList.value=res.data.data
}
}
const onSearchLabel=async (keyword:string)=>{
let res=await apiProject.listLabel({
projectId,
keyword,
page:0,
size:10
})
if(res?.code==0) {
labelList.value=res.data.data
}
}
const onSearchRelease=async (keyword:string)=>{
let res=await apiRelease.list({
projectId,
name:keyword,
page:0,
size:10
})
if(res?.code==0) {
labelList.value=res.data.data
}
}
const getModuleList=async ()=>{
let res=await apiProject.listModule({
projectId
})
if(res?.code==0) {
moduleList.value=res.data
}
}
const format = (options) => {
const labels = options.map(option => option.name)
return labels.join('/')
}
onMounted(()=>{
if(props.type==EClient_Field_Basic_Type.DESCRIPTION && showValue.value=="") {
element.value.focus()
}
})
const onCloseLabelTag=(index:number)=>{
(editValue.value as any[]).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 onUpdate=(value)=>{
showValue.value=value
isEdit.value=false
}
</script>
<style scoped>

View File

@ -1,12 +1,12 @@
<template>
<a-space v-if="!isEdit">
<a-space v-if="!isEdit" wrap>
<template v-if="showValue && showValue.length>0">
<a-tag v-for="item in showValue">{{item.value}}</a-tag>
</template>
<span v-else style="line-height: 30px;width: 100%;color: gray">None</span>
</a-space>
<template v-else>
<a-space size="mini">
<a-space size="mini" wrap>
<a-select v-model="editValue" allow-clear allow-search multiple>
<a-option v-for="item1 in list" :label="item1.value" :value="item1.id"></a-option>
</a-select>
@ -18,7 +18,7 @@
<script setup lang="ts">
import {ref, watchEffect} from "vue";
import {
ICommon_Model_Workflow_Node_Field_Type_Config
ICommon_Model_Workflow_Node_Field_Type_Config
} from "../../../../../../common/model/workflow_node_field_type_config";
const props=defineProps<{

View File

@ -5,17 +5,17 @@
<a-textarea :default-value="item.field.default_string_value" v-else-if="item.fieldType.type===ECommon_Field_Type.MULTITEXT" v-model="item.fieldValue.value"></a-textarea>
<a-date-picker v-else-if="item.fieldType.type===ECommon_Field_Type.DATE" v-model="item.fieldValue.value"/>
<a-time-picker v-else-if="item.fieldType.type===ECommon_Field_Type.TIME" v-model="item.fieldValue.value"/>
<a-select v-else-if="item.fieldType.type===ECommon_Field_Type.SELECT" :default-value="item.values.filter(item=>item.selected).map(item=>item.id)" v-model="item.fieldValue.value">
<a-select v-else-if="item.fieldType.type===ECommon_Field_Type.SELECT" :default-value="item.values.filter((item1:ICommon_Model_Workflow_Node_Field_Type_Config)=>item1.selected).map((item1:ICommon_Model_Workflow_Node_Field_Type_Config)=>item1.id)" v-model="item.fieldValue.value">
<a-option v-for="item in item.values" :label="item.value" :value="item.id"></a-option>
</a-select>
<a-select multiple v-else-if="item.fieldType.type===ECommon_Field_Type.MULTISELECT" :default-value="item.values.filter(item=>item.selected).map(item=>item.id)" v-model="item.fieldValue.value">
<a-select multiple v-else-if="item.fieldType.type===ECommon_Field_Type.MULTISELECT" :default-value="item.values.filter((item1:ICommon_Model_Workflow_Node_Field_Type_Config)=>item1.selected).map((item1:ICommon_Model_Workflow_Node_Field_Type_Config)=>item1.id)" v-model="item.fieldValue.value">
<a-option v-for="item in item.values" :label="item.value" :value="item.id"></a-option>
</a-select>
<a-select v-else-if="item.fieldType.type===ECommon_Field_Type.MULTILABEL" allow-search @search="onSearchMultiLabel" v-model="item.fieldValue.value" multiple>
<a-option v-for="item in multiLabelList" :value="item.id" :label="item.name"></a-option>
<a-option v-for="item1 in multiLabelList" :value="item1.id" :label="item1.name"></a-option>
</a-select>
<a-select v-else-if="item.fieldType.type===ECommon_Field_Type.LABEL" allow-search @search="onSearchLabel" v-model="item.fieldValue.value">
<a-option v-for="item in labelList" :value="item.id" :label="item.name"></a-option>
<a-option v-for="item1 in labelList" :value="item1.id" :label="item1.name"></a-option>
</a-select>
</span>
</template>
@ -34,6 +34,9 @@ import {
ECommon_Model_Workflow_Node_Field_Type_Label_Type
} from "../../../../../../common/model/workflow_node_field_type";
import {SessionStorage} from "../../storage/session";
import {
ICommon_Model_Workflow_Node_Field_Type_Config
} from "../../../../../../common/model/workflow_node_field_type_config";
const props=defineProps<{
item:DCSType<ICommon_Route_Res_Workflow_Node_Field & {
@ -89,16 +92,16 @@ switch (props.item.fieldType.type) {
default:
break
}
const multiLabelList=ref<DCSType<ICommon_Route_Res_Project_Issue_filter_Item[] | ICommon_Route_Res_Release_Item[] | {
const multiLabelList=ref<DCSType<ICommon_Route_Res_Project_Issue_filter_Item | ICommon_Route_Res_Release_Item | {
name:string,
id:string,
photo:string
}[]>>([])
const labelList=ref<DCSType<ICommon_Route_Res_Project_Issue_filter_Item[] | ICommon_Route_Res_Release_Item[] | {
}>[]>([])
const labelList=ref<DCSType<ICommon_Route_Res_Project_Issue_filter_Item | ICommon_Route_Res_Release_Item | {
name:string,
id:string,
photo:string
}[]>>([])
}>[]>([])
const onSearchMultiLabel=async (value:string)=>{
if(props.item.field.label_type===ECommon_Model_Workflow_Node_Field_Type_Label_Type.ISSUE) {
let res=await apiIssue.filter({

View File

@ -0,0 +1,51 @@
import {DiamondNode, DiamondNodeModel} from "@logicflow/core";
import {NodeTextTheme} from "@logicflow/core/types/constant/DefaultTheme";
import {
ECommon_Model_Workflow_Node_Status,
ICommon_Model_Workflow_Node
} from "../../../../../../common/model/workflow_node";
class approvalModel extends DiamondNodeModel {
override getNodeStyle(): { [p: string]: any; width?: number; height?: number; radius?: number; fill?: string; stroke?: string; strokeWidth?: number } {
const style=super.getNodeStyle();
style.strokeWidth=0
let props=this.getProperties() as ICommon_Model_Workflow_Node
if(props.status==ECommon_Model_Workflow_Node_Status.INPROGRESS) {
style.fill="rgb(4,57,192)"
} else if(props.status==ECommon_Model_Workflow_Node_Status.DONE) {
style.fill="rgb(15,119,72)"
}
return style
}
override getTextStyle(): NodeTextTheme {
const style=super.getTextStyle();
let props=this.getProperties() as ICommon_Model_Workflow_Node
if(props.status==ECommon_Model_Workflow_Node_Status.INPROGRESS) {
style.fill="white"
style.stroke="white"
} else if(props.status==ECommon_Model_Workflow_Node_Status.DONE) {
style.fill="white"
style.stroke="white"
}
style.fontSize=13
style.strokeWidth=0.8
return style;
}
override initNodeData(data: any) {
super.initNodeData(data);
this.rx=40
this.ry=40
}
}
class approvalView extends DiamondNode {
}
export const flowApproval= {
type: "approval",
view: approvalView,
model: approvalModel
};

View File

@ -54,4 +54,4 @@ export const flowNode= {
type: "node",
view: UserTaskView,
model: UserTaskModel
};
};

View File

@ -132,6 +132,24 @@
&nbsp;&nbsp;
to you
</template>
<template v-else-if="obj.type===ECommon_Model_Notification_Type.ISSUE_APPROVAL_RESOLVE">
ISSUE:
&nbsp;&nbsp;
<UserAvatar :organization-user-id="obj.operationOrganizationUser.id" :name="obj.operationOrganizationUser.name" :photo="obj.operationOrganizationUser.photo" :organization-id="obj.organization_id"></UserAvatar>
&nbsp;&nbsp;
resolved the approval of issue
&nbsp;&nbsp;
<a href="javascript:void(0)" @click="onIssue">{{(obj.data as ISSUE)?.project.keyword}}-{{(obj.data as ISSUE)?.issue.unique_id}}</a>
</template>
<template v-else-if="obj.type===ECommon_Model_Notification_Type.ISSUE_APPROVAL_REJECT">
ISSUE:
&nbsp;&nbsp;
<UserAvatar :organization-user-id="obj.operationOrganizationUser.id" :name="obj.operationOrganizationUser.name" :photo="obj.operationOrganizationUser.photo" :organization-id="obj.organization_id"></UserAvatar>
&nbsp;&nbsp;
rejected the approval of issue
&nbsp;&nbsp;
<a href="javascript:void(0)" @click="onIssue">{{(obj.data as ISSUE)?.project.keyword}}-{{(obj.data as ISSUE)?.issue.unique_id}}</a>
</template>
</div>
</template>

View File

@ -264,6 +264,9 @@ export class RichEditorEvent {
onMouseUp(event:MouseEvent){
this.isMouseDown=false
let selection=window.getSelection()
if(selection.rangeCount==0) {
return
}
let range=selection.getRangeAt(0)
if(this.selectElementList.length>0) {
range=range.cloneRange()

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 434 KiB

View File

@ -59,7 +59,7 @@ export class RichEditorHandle {
ele.setAttribute("dragType",EClient_Drag_Type.FILE)
ele.setAttribute("dragValue",obj.value)
let icon=document.createElement("i")
icon.className="fa fa-file"
icon.className="svg svg-file"
icon.style.marginRight="5px"
icon.style.color="gray"
ele.prepend(icon)
@ -86,7 +86,7 @@ export class RichEditorHandle {
ele.setAttribute("shortcutRefId",obj.value)
ele.setAttribute("shortcutName",obj.label)
let icon=document.createElement("i")
icon.className="fa fa-list-alt"
icon.className="svg svg-project"
icon.style.marginRight="5px"
icon.style.color="green"
ele.prepend(icon)
@ -114,7 +114,7 @@ export class RichEditorHandle {
ele.setAttribute("shortcutRefId",obj.value)
ele.setAttribute("shortcutName",obj.label)
let icon=document.createElement("i")
icon.className="fa fa-link"
icon.className="svg svg-project-issue"
icon.style.marginRight="5px"
icon.style.color="green"
ele.prepend(icon)
@ -142,7 +142,7 @@ export class RichEditorHandle {
ele.setAttribute("shortcutRefId",obj.value)
ele.setAttribute("shortcutName",obj.label)
let icon=document.createElement("i")
icon.className="fa fa-tasks"
icon.className="svg svg-project-release"
icon.style.marginRight="5px"
icon.style.color="green"
ele.prepend(icon)
@ -169,7 +169,7 @@ export class RichEditorHandle {
ele.setAttribute("shortcutRefId",obj.value)
ele.setAttribute("shortcutName",obj.label)
let icon=document.createElement("i")
icon.className="fa fa-files-o"
icon.className="svg svg-wiki"
icon.style.marginRight="5px"
icon.style.color="rgb(0,120,212)"
ele.prepend(icon)
@ -197,7 +197,7 @@ export class RichEditorHandle {
ele.setAttribute("shortcutRefId",obj.value)
ele.setAttribute("shortcutName",obj.label)
let icon=document.createElement("i")
icon.className="fa fa-file-text-o"
icon.className="svg svg-wiki-item"
icon.style.marginRight="5px"
icon.style.color="rgb(0,120,212)"
ele.prepend(icon)
@ -224,7 +224,7 @@ export class RichEditorHandle {
ele.setAttribute("shortcutRefId",obj.value)
ele.setAttribute("shortcutName",obj.label)
let icon=document.createElement("i")
icon.className="fa fa-calendar-o"
icon.className="svg svg-calendar-event"
icon.style.marginRight="5px"
icon.style.color="orange"
ele.prepend(icon)
@ -252,7 +252,7 @@ export class RichEditorHandle {
ele.setAttribute("shortcutRefId",obj.value)
ele.setAttribute("shortcutName",obj.label)
let icon=document.createElement("i")
icon.className="fa fa-video-camera"
icon.className="svg svg-meeting-room"
icon.style.marginRight="5px"
icon.style.color="red"
ele.prepend(icon)

View File

@ -21,7 +21,6 @@ import {
ICommon_Content_Line,
ICommon_Content_Line_Config
} from "../../../../../../common/model/content";
import "./font/css/font-awesome.min.css"
import UserShortView from "../userShortView.vue";
import {dragElementList, EClient_Drag_Type} from "../../../../teamOS/common/directive/drag";
import {ECommon_Model_Finder_Shortcut_Type} from "../../../../../../common/model/finder_item";
@ -147,6 +146,9 @@ const onMouseMove=(event:MouseEvent)=>{
const calculateIndex=()=>{
let selection=window.getSelection()
if(selection.rangeCount==0) {
return
}
let range=selection.getRangeAt(0)
let startOffset=range.startOffset
selectStartIndexPath=[]
@ -343,4 +345,38 @@ div {
:deep a[fileId]:hover {
background-color: lightgray;
}
:deep .svg {
display: inline-block;
width: 1em;
vertical-align: middle;
background-position: 50% 50%;
background-repeat: repeat;
background-size: contain;
height: 1em;
transform: translateY(-1px);
}
:deep .svg-file {
background-image: url("@/assert/custom/file.svg");
}
:deep .svg-project {
background-image: url("@/assert/custom/project_item.svg");
}
:deep .svg-project-issue {
background-image: url("@/assert/custom/project_issue_item.svg");
}
:deep .svg-project-release {
background-image: url("@/assert/custom/project_release_item.svg");
}
:deep .svg-wiki {
background-image: url("@/assert/custom/wiki_space_item.svg");
}
:deep .svg-wiki-item {
background-image: url("@/assert/custom/wiki_item.svg");
}
:deep .svg-calendar-event {
background-image: url("@/assert/custom/calendar_event_item.svg");
}
:deep .svg-meeting-room {
background-image: url("@/assert/custom/meeting_room_item.svg");
}
</style>

View File

@ -62,7 +62,7 @@
</template>
<script setup lang="ts">
import {computed, ref} from "vue";
import {computed, onBeforeMount, ref, watch} from "vue";
import {ICommon_Model_Organization_User} from "../../../../../common/model/organization_user";
import {apiOrganization, DCSType} from "../request/request";
import {EClient_EVENTBUS_TYPE, eventBus} from "../event/event";
@ -73,7 +73,7 @@ import {Message} from "@arco-design/web-vue";
const loading=ref(true)
const props=defineProps<{
name:string,
name?:string,
photo?:string,
organizationUserId:string,
closeable?:boolean,
@ -87,13 +87,21 @@ const emit=defineEmits<{
const myOrganizationUserId=SessionStorage.get("organizationUserId")
const showCloseable=ref(false)
const root=ref(null);
const name=ref("")
const photo=ref("")
const status=ref(ECommon_User_Online_Status.OFFLINE)
watch(()=>[props.name,props.photo],()=>{
name.value=props.name
photo.value=props.photo
},{
immediate:true
})
const imgName=computed(()=>{
if(props.name.includes(" ")) {
let arr=props.name.split(" ")
if(name.value?.includes(" ")) {
let arr=name.value.split(" ")
return arr[0][0].toUpperCase()+arr[1][0].toUpperCase()
} else {
return props.name[0].toUpperCase()
return name.value?name.value[0].toUpperCase():""
}
})
const info=ref<DCSType<ICommon_Model_Organization_User>>(null)
@ -141,6 +149,20 @@ const onMessage=()=>{
}
eventBus.emit(EClient_EVENTBUS_TYPE.OPEN_IM_CHAT,props.organizationUserId,ECommon_IM_Message_EntityType.USER)
}
onBeforeMount(async ()=>{
if(props.organizationUserId && !props.name) {
let res=await apiOrganization.user({
organizationUserId:props.organizationUserId,
...(props.organizationId && {
organizationId:props.organizationId
})
})
if(res?.code==0) {
name.value=res.data.nickname
photo.value=res.data.user.photo
}
}
})
</script>
<style scoped>

View File

@ -72,7 +72,7 @@
<template v-if="calendarEventInfo?.meeting">
<a-switch v-model="form.meeting"></a-switch>
<a-popover position="right" v-if="form.meeting" trigger="click">
<a-button type="primary" status="danger" style="margin-left: 20px">
<a-button type="primary" status="success" style="margin-left: 20px">
Start Meeting
</a-button>
<template #content>
@ -91,7 +91,7 @@
<a-switch v-model="form.meeting" v-else></a-switch>
</template>
</template>
<a-button type="primary" status="danger" @click="onMeeting" v-else>
<a-button type="primary" status="success" @click="onMeeting" v-else>
Join Meeting
</a-button>
</a-form-item>

View File

@ -43,7 +43,7 @@
</div>
<div style="flex: 1 1 auto;display: flex;align-items: center">
<a-popover position="right" trigger="click" v-if="isSelf">
<a-button type="primary" status="danger" size="mini">
<a-button type="primary" status="success" size="mini">
Start Meeting
</a-button>
<template #content>
@ -58,7 +58,7 @@
</a-row>
</template>
</a-popover>
<a-button type="primary" status="danger" size="mini" @click="onMeeting" v-else>
<a-button type="primary" status="success" size="mini" @click="onMeeting" v-else>
Join Meeting
</a-button>
</div>

View File

@ -106,7 +106,11 @@ const onPopMenuClick=(type:ECommon_Content_Line_Config_Type,handleFunc:(item:ICo
const onSend = () => {
if(content.value.length>0) {
props.meetingClient.sendMessage(JSON.stringify(content.value))
props.meetingClient.sendMessage(JSON.stringify(content.value.map(item=>{
return {
arr:item.arr
}
})))
}
content.value = []
}

View File

@ -0,0 +1,210 @@
<template>
<div>
<a-comment :avatar="store.userInfo.photo" style="margin-right: 10px">
<template #content>
<a-spin :loading="loading" style="width: 100%;border: 1px solid lightgray" v-drop.file.shortcut.disk="onDropAdd">
<RichEditor v-model="commentAdd" @upload-file="onUploadFile" :pop-menu-list="popMenuList" @pop-menu-click="onPopMenuClick" @custom-anchor-click="onCustomAnchorClick" @quote-list="onQuoteList" ref="objEditor"></RichEditor>
</a-spin>
</template>
<template #actions>
<a-button type="primary" size="small" @click="onCommentAdd" style="margin-top: 10px">Save</a-button>
</template>
</a-comment>
<a-comment v-for="(item,index) in commentList" :author="item.value.created_by.nickname" :datetime="moment(item.value.created_time).format('YYYY-MM-DD HH:mm:ss')">
<template #avatar>
<UserAvatar :organization-user-id="item.value.created_by.organizationUserId" :name="item.value.created_by.nickname" :photo="item.value.created_by.photo" :only-photo="true"></UserAvatar>
</template>
<template #content>
<template v-if="!item.isEdit">
<RichEditor :model-value="JSON.parse(item.value.content)" :readonly="true" @custom-anchor-click="onCustomAnchorClick"></RichEditor>
</template>
<template v-else>
<a-spin :loading="loading" style="width: 100%" v-drop.file.shortcut.disk="onDrop.bind(null,index)">
<RichEditor v-model="item.editContent" @upload-file="onUploadFile" :pop-menu-list="popMenuList" @pop-menu-click="onPopMenuClick" @custom-anchor-click="onCustomAnchorClick" @quote-list="onQuoteList" ref="objEditor"></RichEditor>
</a-spin>
</template>
</template>
<template #actions v-if="checkPermission(permission,Permission_Types.Project.ADMIN) || item.value.created_by.id==store.userInfo.id">
<template v-if="!item.isEdit">
<a-link href="javascript:void(0)" style="color: gray;font-size: 13px;padding: 0px" @click="onEdit(item)">
Edit
</a-link>
<a-link href="javascript:void(0)" style="color: gray;font-size: 13px;padding: 0px" @click="onRemoveComment(index)">
Delete
</a-link>
</template>
<template v-else>
<a-button type="text" @click="onSave(item)">
<template #icon>
<icon-check></icon-check>
</template>
</a-button>
<a-button type="text" @click="item.editContent=[],item.isEdit=false">
<template #icon>
<icon-close style="color: red"></icon-close>
</template>
</a-button>
</template>
</template>
</a-comment>
</div>
</template>
<script setup lang="ts">
import moment from "moment/moment";
import {checkPermission, Permission_Types} from "../../../../../../../common/permission/permission";
import {getCurrentInstance, inject, onBeforeMount, ref} from "vue";
import {apiFile, apiIssue, DCSType} from "../../../../common/request/request";
import {
ECommon_Content_Line_Config_Type,
ICommon_Content_Line,
ICommon_Content_Line_Config,
ICommon_Model_Content
} from "../../../../../../../common/model/content";
import {Message} from "@arco-design/web-vue";
import {Dialog} from "../../../../common/component/dialog/dialog";
import {getRootNavigatorRef} from "../../../../../teamOS/common/component/navigator/navigator";
import {useDesktopStore} from "../../../desktop/store/desktop";
import {injectProjectInfo} from "../../../../common/util/symbol";
import RichEditor from "../../../../common/component/richEditor/richEditor.vue";
import {RichEditorEventHandle} from "../../../../common/component/richEditorEventHandle";
import {DropParam, vDrop} from "../../../../../teamOS/common/directive/drop";
import UserAvatar from "../../../../common/component/userAvatar.vue";
const props=defineProps<{
projectIssueId:string
}>()
const commentList=ref<DCSType<{
value:ICommon_Model_Content,
isEdit:boolean,
editContent:ICommon_Content_Line[]
}>[]>([])
const commentAdd=ref<ICommon_Content_Line[]>([])
const permission=inject(injectProjectInfo).permission
const root=getRootNavigatorRef()
const appContext=getCurrentInstance().appContext
const store=useDesktopStore()
const objEditor=ref<InstanceType<typeof RichEditor>>()
const objEditorAdd=ref<InstanceType<typeof RichEditor>>()
const loading=ref(false)
const popMenuList=ref(RichEditorEventHandle.popMenuList)
const getCommentList=async ()=>{
let res=await apiIssue.commentList({
projectIssueId:props.projectIssueId
})
if(res?.code==0) {
commentList.value=res.data.map(item=>{
return {
value:item,
isEdit:false,
editContent:[]
}
})
}
}
const onCommentAdd=async ()=>{
let value=JSON.stringify(commentAdd.value.map(item=>{
return {
arr:item.arr
}
}))
let res=await apiIssue.commentCreate({
projectIssueId:props.projectIssueId,
content:value
})
if(res?.code==0) {
commentList.value.unshift({
value:res.data,
isEdit:false,
editContent:[]
})
commentAdd.value=[]
}
}
const onSave=async (item:DCSType<{
value:ICommon_Model_Content,
isEdit:boolean,
editContent:ICommon_Content_Line[]
}>)=>{
let value=JSON.stringify(item.editContent.map(item=>{
return {
arr:item.arr
}
}))
if(value.length==0) {
Message.error("content can not be empty")
return
}
let res=await apiIssue.commentEdit({
contentId:item.value.id,
content:value
})
if(res?.code==0) {
item.value=res.data
item.isEdit=false
}
}
const onRemoveComment=async (index:number)=>{
let item=commentList.value[index];
let ret=await Dialog.confirm(root.value,appContext,"Do you want to delete this comment?")
if(ret) {
let res=await apiIssue.commentRemove({
contentId:item.value.id
})
if(res?.code==0) {
commentList.value.splice(index,1)
}
}
}
const onEdit=async (item:DCSType<{
value:ICommon_Model_Content,
isEdit:boolean,
editContent:ICommon_Content_Line[]
}>)=>{
item.isEdit=true
item.editContent=JSON.parse(item.value.content)
}
const onUploadFile=async (file, handleFunc) => {
let res=await apiFile.upload({
file:file
})
if(res?.code==0) {
handleFunc(res.data.id,res.data.path)
}
}
const onPopMenuClick=(type:ECommon_Content_Line_Config_Type,handleFunc:(item:ICommon_Content_Line_Config)=>void)=>{
RichEditorEventHandle.onPopMenuClick(type,root,appContext,loading,handleFunc)
}
const onCustomAnchorClick=(type:ECommon_Content_Line_Config_Type,value:string,link:string,label:string)=>{
RichEditorEventHandle.onCustomAnchorClick(type,value,link,label)
}
const onQuoteList=(keyword:string,handleFunc:(list:{
value:string,
label:string,
photo:string
}[])=>void)=>{
RichEditorEventHandle.onQuoteList(keyword,handleFunc)
}
const onDrop=(index:number,data?:DropParam)=>{
RichEditorEventHandle.onDrop(objEditor.value[index],data,loading)
}
const onDropAdd=(data?:DropParam)=>{
RichEditorEventHandle.onDrop(objEditorAdd.value,data,loading)
}
onBeforeMount(()=>{
getCommentList()
})
</script>
<style scoped>
</style>

View File

@ -0,0 +1,94 @@
<template>
<div class="issueProfileDetail">
<a-collapse :default-active-key="['detail']">
<a-collapse-item key="detail" header="Detail">
<a-form layout="vertical" :model="{}">
<a-form-item label="Issue Type">
{{info.issueType.name}}
</a-form-item>
<a-form-item label="Assigner">
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.ASSIGNER" :value="info.assigner_id"></FieldEditBasic>
</a-form-item>
<a-form-item label="Reporter">
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.REPORTER" :value="info.reporter_id"></FieldEditBasic>
</a-form-item>
<a-form-item label="Priority">
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.PRIORITY" :value="info.priority"></FieldEditBasic>
</a-form-item>
<a-form-item label="Module">
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.MODULE" :value="moduleList"></FieldEditBasic>
</a-form-item>
<a-form-item label="Label">
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.LABEL" :value="labelList"></FieldEditBasic>
</a-form-item>
<a-form-item label="Fix Versions">
<FieldEditBasic :project-issue-id="info.id" :type="EClient_Field_Basic_Type.FIXVERSIONS" :value="releaseList"></FieldEditBasic>
</a-form-item>
</a-form>
</a-collapse-item>
<a-collapse-item key="more" header="More">
<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">
<FieldEdit :item="item"></FieldEdit>
</a-form-item>
</a-form>
</a-collapse-item>
</a-collapse>
<a-row style="margin-top: 10px;color: gray;font-size: 12px;line-height: 1.3;padding-left: 5px;margin-bottom: 10px">
Created {{moment(info.created_time).format('YYYY-MM-DD HH:mm:ss')}}
</a-row>
</div>
</template>
<script setup lang="ts">
import {EClient_Field_Basic_Type} from "../../../../common/component/field/fieldBasicType";
import moment from "moment/moment";
import FieldEdit from "../../../../common/component/field/fieldEdit.vue";
import FieldEditBasic from "../../../../common/component/field/fieldEditBasic.vue";
import {apiIssue, DCSType} from "../../../../common/request/request";
import {
ICommon_Route_Res_ProjectIssue_BasicInfo,
ICommon_Route_Res_ProjectIssue_fieldsInfo
} from "../../../../../../../common/routes/response";
import {onBeforeMount, ref} from "vue";
import {ICommon_Model_Project_Label} from "../../../../../../../common/model/project_label";
import {ICommon_Model_Project_Module} from "../../../../../../../common/model/project_module";
import {ICommon_Model_Project_Release} from "../../../../../../../common/model/project_release";
const props=defineProps<{
info:DCSType<ICommon_Route_Res_ProjectIssue_BasicInfo>,
moduleList:DCSType<ICommon_Model_Project_Module>[],
labelList:DCSType<ICommon_Model_Project_Label>[],
fieldList:ICommon_Route_Res_ProjectIssue_fieldsInfo[]
}>()
const releaseList=ref<DCSType<ICommon_Model_Project_Release[]>>([])
const getReleaseList=async ()=>{
let res=await apiIssue.releaseList({
projectIssueId:props.info.id
})
if(res?.code==0) {
releaseList.value=res.data
}
}
onBeforeMount(()=>{
getReleaseList()
})
</script>
<style scoped>
.issueProfileDetail :deep(div[role="region"]) {
background-color: white !important;
padding-left: 5px;
padding-right: 0px;
}
.issueProfileDetail :deep .arco-form-item {
margin-bottom: 10px;
}
.issueProfileDetail :deep .arco-form-item-label {
font-weight: bold;
}
</style>

View File

@ -0,0 +1,91 @@
<template xmlns="http://www.w3.org/1999/html">
<div>
<a-list hoverable>
<a-list-item v-for="item in historyList">
<div style="width: 100%;">
<template v-if="item.type===ECommon_Model_Project_Issue_History_Type.CREATE_ISSUE">
<div style="width: 100%;align-items: center;display: flex">
<UserAvatar :organization-user-id="item.organization_user_id"></UserAvatar>&nbsp;
created the issue&nbsp;
<span style="color: gray">{{moment(item.created_time).format("YYYY-MM-DD HH:mm:ss")}}</span>
</div>
</template>
<template v-else-if="item.type===ECommon_Model_Project_Issue_History_Type.APPROVAL_RESOLVE">
<div style="width: 100%;align-items: center;display: flex">
<UserAvatar :organization-user-id="item.organization_user_id"></UserAvatar>&nbsp;
resolved your approval&nbsp;
<span style="color: gray">{{moment(item.created_time).format("YYYY-MM-DD HH:mm:ss")}}</span>
</div>
</template>
<template v-else-if="item.type===ECommon_Model_Project_Issue_History_Type.APPROVAL_REJECT">
<div style="width: 100%;align-items: center;display: flex">
<UserAvatar :organization-user-id="item.organization_user_id"></UserAvatar>&nbsp;
rejected your approval&nbsp;
<span style="color: gray">{{moment(item.created_time).format("YYYY-MM-DD HH:mm:ss")}}</span>
</div>
</template>
<template v-else-if="item.type===ECommon_Model_Project_Issue_History_Type.UPDATE_FIELD">
<div style="width: 100%;align-items: center;display: flex">
<UserAvatar :organization-user-id="item.organization_user_id"></UserAvatar>&nbsp;
updated the field&nbsp;
<span style="color: blue">
{{item.name}}
</span>&nbsp;
<span style="color: gray">{{moment(item.created_time).format("YYYY-MM-DD HH:mm:ss")}}</span>
</div>
<div v-if="item.value" style="display: flex;align-items: center">
new value :&nbsp;
<UserAvatar v-if="/\d{19,}/.test(item.value)" :organization-user-id="item.value"></UserAvatar>
<span v-else style="color: gray">{{item.value}}</span>
</div>
</template>
<template v-else-if="item.type===ECommon_Model_Project_Issue_History_Type.UPDATE_NODE">
<div style="width: 100%;align-items: center;display: flex">
<UserAvatar :organization-user-id="item.organization_user_id"></UserAvatar>&nbsp;
updated the workflow&nbsp;
<span style="color: #544646">
{{item.name}}
</span>&nbsp;
<span style="color: gray">{{moment(item.created_time).format("YYYY-MM-DD HH:mm:ss")}}</span>
</div>
<div>
new workflow : <span style="color: gray">{{item.value}}</span>
</div>
</template>
</div>
</a-list-item>
</a-list>
</div>
</template>
<script setup lang="ts">
import {apiIssue, DCSType} from "../../../../common/request/request";
import {onBeforeMount, ref} from "vue";
import {
ECommon_Model_Project_Issue_History_Type,
ICommon_Model_Project_Issue_History
} from "../../../../../../../common/model/project_issue_history";
import UserAvatar from "../../../../common/component/userAvatar.vue";
import moment from "moment";
const props=defineProps<{
projectIssueId:string
}>()
const historyList=ref<DCSType<ICommon_Model_Project_Issue_History>[]>([])
const listHistory=async ()=>{
let res=await apiIssue.listHistory({
projectIssueId:props.projectIssueId
})
if(res?.code==0) {
historyList.value=res.data
}
}
onBeforeMount(()=>{
listHistory()
})
</script>
<style scoped>
</style>

View File

@ -50,6 +50,7 @@
</a-space>
</a-row>
<a-divider></a-divider>
<span style="font-size: small;color: gray">If you wanna open in a new tab,hold command(Mac) or control(Win) Key,click the link below</span>
<a-table style="margin-top: 10px" :columns="columns" :data="issueList" :pagination="pagination" @pageChange="onPageChange">
<template #type="{record}">
{{record.issueType.name}}

View File

@ -1,6 +1,6 @@
<template>
<a-layout style="height: 100%">
<a-layout-content id="issueProfileContent">
<a-layout-content class="issueProfileContent">
<a-row style="color: gray" v-drag.shortcut="()=>({
shortcutType:ECommon_Model_Finder_Shortcut_Type.PROJECT_ISSUE,
shortcutRefId:projectIssueId,
@ -11,15 +11,28 @@
<a-row style="margin-top: 10px">
<FieldEditBasic :project-issue-id="projectIssueId" :type="EClient_Field_Basic_Type.NAME" :value="info.name" v-if="info"></FieldEditBasic>
</a-row>
<a-row style="margin-top: 10px">
<a-space>
<a-row v-if="info?.approval?.type===ECommon_Model_Project_Issue_Approval_Type.REJECTED" style="align-items: center;margin-top: 10px;">
<UserAvatar :organization-user-id="info.approval.approval_organization_user_id"></UserAvatar>
<span style="color: orange;font-weight: bold">&nbsp;rejected your approval</span>
<a-popover v-if="info.approval.reason" position="right">
<icon-info-circle-fill style="color: orange;margin-left: 10px"></icon-info-circle-fill>
<template #content>
<RichEditor style="color: black" :model-value="JSON.parse(info.approval.reason)" :readonly="true" @custom-anchor-click="onCustomAnchorClick"></RichEditor>
</template>
</a-popover>
</a-row>
<a-row style="margin-top: 20px">
<a-space wrap>
<a-dropdown>
<a-button :disabled="actionList.length==0" :type="info?.workflowNode.status===ECommon_Model_Workflow_Node_Status.NOTSTART?'secondary':'primary'" :status="info?.workflowNode.status===ECommon_Model_Workflow_Node_Status.DONE?'success':'normal'">{{info?.workflowNode.name}}&nbsp;&nbsp;<icon-down></icon-down>
<a-button :disabled="actionList.length==0" :type="info?.workflowNode.status===ECommon_Model_Workflow_Node_Status.NOTSTART?'secondary':'primary'" :status="info?.approval?.type===ECommon_Model_Project_Issue_Approval_Type.REJECTED?'danger':info?.workflowNode.status===ECommon_Model_Workflow_Node_Status.DONE?'success':'normal'">{{calculateApprovalName}}&nbsp;&nbsp;<icon-down></icon-down>
</a-button>
<template #content>
<a-doption v-for="item in actionList" :key="item.id" @click="onAction(item)">{{item.name}}</a-doption>
<a-doption v-for="item in actionList as any[]" :key="item.isApproval?item.name:item.id" @click="onAction(item)">{{item.name}}</a-doption>
</template>
</a-dropdown>
<a-button v-if="!parentIssue" @click="onCreateSubIssue">
Create Child Issue
</a-button>
<a-dropdown-button>
Link Issue
<template #icon>
@ -52,6 +65,7 @@
</a-row>
</template>
</a-popover>
<a-doption v-if="checkPermission(permission,Permission_Types.Project.EDIT)" @click="onCopy">Copy</a-doption>
<a-doption v-if="checkPermission(permission,Permission_Types.Project.ADMIN) || info?.created_by.id===store.userInfo.id" @click="onDelete" style="color: red">Delete</a-doption>
</template>
</a-dropdown>
@ -61,151 +75,26 @@
Description
</a-row>
<a-row style="margin-top: 10px">
<FieldEditBasic :project-issue-id="projectIssueId" :type="EClient_Field_Basic_Type.DESCRIPTION" :value="description"></FieldEditBasic>
<FieldEditBasic :project-issue-id="projectIssueId" :type="EClient_Field_Basic_Type.DESCRIPTION" :value="description" style="margin-right: 10px;box-sizing: border-box"></FieldEditBasic>
</a-row>
<template v-if="parentIssue">
<a-row style="margin-top: 20px;font-weight: bold">Parent Issue</a-row>
<a-list style="margin-top: 10px;margin-right: 10px" size="small" hoverable>
<a-list-item>
<a-space size="large">
{{key+"-"+parentIssue.unique_id}}
<a-link href="javascript:void(0)">{{parentIssue.name}}</a-link>
<FieldPriority :priority="parentIssue.priority"></FieldPriority>
</a-space>
<template #actions>
<a-button type="text" size="mini" @click="onRemoveItem('parent')">
<template #icon>
<icon-close style="color: red"></icon-close>
</template>
</a-button>
</template>
</a-list-item>
</a-list>
</template>
<template v-if="childIssueList.length>0">
<a-row style="margin-top: 20px;font-weight: bold">Child Issues</a-row>
<a-list style="margin-top: 10px;margin-right: 10px" size="small" hoverable>
<a-list-item v-for="(item,index) in childIssueList" :key="item.id">
<a-space size="large">
{{key+"-"+item.unique_id}}
<a-link href="javascript:void(0)">{{item.name}}</a-link>
<FieldPriority :priority="item.priority"></FieldPriority>
</a-space>
<template #actions>
<a-button type="text" size="mini" @click="onRemoveItem('child',index)">
<template #icon>
<icon-close style="color: red"></icon-close>
</template>
</a-button>
</template>
</a-list-item>
</a-list>
</template>
<template v-if="relatedIssueList.length>0">
<a-row style="margin-top: 20px;font-weight: bold">Related Issues</a-row>
<a-list style="margin-top: 10px;margin-right: 10px" size="small" hoverable>
<a-list-item v-for="(item,index) in relatedIssueList" :key="item.id">
<a-space size="large">
{{key+"-"+item.unique_id}}
<a-link href="javascript:void(0)">{{item.name}}</a-link>
<FieldPriority :priority="item.priority"></FieldPriority>
</a-space>
<template #actions>
<a-button type="text" size="mini" @click="onRemoveItem('related',index)">
<template #icon>
<icon-close style="color: red"></icon-close>
</template>
</a-button>
</template>
</a-list-item>
</a-list>
</template>
<a-row style="margin-top: 50px;font-weight: bold">Comments</a-row>
<a-comment v-for="(item,index) in commentList" :author="item.value.created_by.nickname" :datetime="moment(item.value.created_time).format('YYYY-MM-DD HH:mm:ss')" :avatar="item.value.created_by.photo" style="margin-right: 10px">
<template #content>
<template v-if="!item.isEdit">
{{item.value.content}}
</template>
<template v-else>
<a-textarea allow-clear v-model="item.editContent"></a-textarea>
</template>
</template>
<template #actions v-if="checkPermission(permission,Permission_Types.Project.ADMIN) || item.value.created_by.id==store.userInfo.id">
<template v-if="!item.isEdit">
<a-link href="javascript:void(0)" style="color: gray;font-size: 13px;padding: 0px" @click="item.isEdit=true">
Edit
</a-link>
<a-link href="javascript:void(0)" style="color: gray;font-size: 13px;padding: 0px" @click="onRemoveComment(index)">
Delete
</a-link>
</template>
<template v-else>
<a-button type="text" @click="onEditComment(item)">
<template #icon>
<icon-check></icon-check>
</template>
</a-button>
<a-button type="text" @click="item.editContent='',item.isEdit=false">
<template #icon>
<icon-close style="color: red"></icon-close>
</template>
</a-button>
</template>
</template>
</a-comment>
<a-comment :avatar="store.userInfo.photo" style="margin-right: 10px">
<template #content>
<a-textarea placeholder="please type your comment" v-model="commentAdd"></a-textarea>
</template>
<template #actions>
<a-button type="primary" size="small" @click="onCommentAdd">Save</a-button>
</template>
</a-comment>
<ProjectIssueRelated :project-issue-id="projectIssueId" :child-issue-list="childIssueList" :parent-issue="parentIssue" :related-issue-list="relatedIssueList" @remove-parent="parentIssue=null"></ProjectIssueRelated>
<a-tabs type="rounded" style="margin-top: 50px;" lazy-load size="small">
<a-tab-pane key="comment" title="Comments">
<ProjectIssueComment style="margin-top: 10px" :project-issue-id="projectIssueId"></ProjectIssueComment>
</a-tab-pane>
<a-tab-pane key="history" title="History">
<ProjectIssueHistory :project-issue-id="projectIssueId"></ProjectIssueHistory>
</a-tab-pane>
</a-tabs>
</a-layout-content>
<a-layout-sider :resize-directions="['left']" :width="200" style="overflow-y: auto">
<a-collapse :default-active-key="['detail']" style="margin: 5px 5px 0px 5px" id="issueProfileDetail">
<a-collapse-item key="detail" header="Detail">
<a-form layout="vertical" :model="{}">
<a-form-item label="Issue Type">
{{info?.issueType.name}}
</a-form-item>
<a-form-item label="Assigner">
<FieldEditBasic :project-issue-id="projectIssueId" :type="EClient_Field_Basic_Type.ASSIGNER" :value="info.assigner_id" v-if="info"></FieldEditBasic>
</a-form-item>
<a-form-item label="Reporter">
<FieldEditBasic :project-issue-id="projectIssueId" :type="EClient_Field_Basic_Type.REPORTER" :value="info.reporter_id" v-if="info"></FieldEditBasic>
</a-form-item>
<a-form-item label="Priority">
<FieldEditBasic :project-issue-id="projectIssueId" :type="EClient_Field_Basic_Type.PRIORITY" :value="info.priority" v-if="info"></FieldEditBasic>
</a-form-item>
<a-form-item label="Module">
<FieldEditBasic :project-issue-id="projectIssueId" :type="EClient_Field_Basic_Type.MODULE" :value="moduleList"></FieldEditBasic>
</a-form-item>
<a-form-item label="Label">
<FieldEditBasic :project-issue-id="projectIssueId" :type="EClient_Field_Basic_Type.LABEL" :value="labelList"></FieldEditBasic>
</a-form-item>
<a-form-item label="Fix Versions">
<FieldEditBasic :project-issue-id="projectIssueId" :type="EClient_Field_Basic_Type.FIXVERSIONS" :value="releaseList"></FieldEditBasic>
</a-form-item>
</a-form>
</a-collapse-item>
<a-collapse-item key="more" header="More">
<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">
<FieldEdit :item="item"></FieldEdit>
</a-form-item>
</a-form>
</a-collapse-item>
</a-collapse>
<a-row style="margin-top: 10px;color: gray;font-size: 12px;line-height: 1.3;padding-left: 5px;margin-bottom: 10px">
Created {{moment(info?.created_time).format('YYYY-MM-DD HH:mm:ss')}}
</a-row>
<ProjectIssueField :label-list="labelList" :module-list="moduleList" :field-list="fieldList" :info="info" v-if="info" style="margin: 5px 5px 0px 5px"></ProjectIssueField>
</a-layout-sider>
</a-layout>
</template>
<script setup lang="ts">
import {getCurrentInstance, inject, markRaw, onBeforeMount, ref, watch} from "vue";
import {computed, getCurrentInstance, inject, markRaw, onBeforeMount, ref, watch} from "vue";
import {injectProjectInfo} from "../../../../common/util/symbol";
import {apiIssue, apiMeeting, DCSType} from "../../../../common/request/request";
import {
@ -216,28 +105,30 @@ import FieldEditBasic from "../../../../common/component/field/fieldEditBasic.vu
import {EClient_Field_Basic_Type} from "../../../../common/component/field/fieldBasicType";
import {ICommon_Model_Project_Label} from "../../../../../../../common/model/project_label";
import {ICommon_Model_Project_Module} from "../../../../../../../common/model/project_module";
import FieldEdit from "../../../../common/component/field/fieldEdit.vue";
import moment from "moment";
import {ICommon_Model_Project_Issue} from "../../../../../../../common/model/project_issue";
import FieldPriority from "../../../../common/component/field/fieldPriority.vue";
import {Dialog} from "../../../../common/component/dialog/dialog";
import {getCurrentNavigator, getRootNavigatorRef} from "../../../../../teamOS/common/component/navigator/navigator";
import ProjectIssueBind from "./projectIssueBind.vue";
import {Message} from "@arco-design/web-vue";
import {ICommon_Model_Content} from "../../../../../../../common/model/content";
import {useDesktopStore} from "../../../desktop/store/desktop";
import {checkPermission, Permission_Types} from "../../../../../../../common/permission/permission";
import {ICommon_Model_Workflow_Action} from "../../../../../../../common/model/workflow_action";
import {ECommon_Model_Workflow_Node_Status} from "../../../../../../../common/model/workflow_node";
import ProjectIssueNext from "./projectIssueNext.vue";
import {ICommon_Model_Project_Release} from "../../../../../../../common/model/project_release";
import {vDrag} from "../../../../../teamOS/common/directive/drag";
import {ECommon_Model_Finder_Shortcut_Type} from "../../../../../../../common/model/finder_item";
import UserAvatar from "../../../../common/component/userAvatar.vue";
import {SessionStorage} from "../../../../common/storage/session";
import {TableRowSelection} from "@arco-design/web-vue/es/table/interface";
import {EClient_EVENTBUS_TYPE, eventBus} from "../../../../common/event/event";
import {ECommon_Model_Organization_Member_Type} from "../../../../../../../common/model/organization";
import ProjectIssueComment from "./projectIssueComment.vue";
import ProjectIssueField from "./projectIssueField.vue";
import ProjectIssueRelated from "./projectIssueRelated.vue";
import ProjectIssueHistory from "./projectIssueHistory.vue";
import {ECommon_Model_Project_Issue_Approval_Type} from "../../../../../../../common/model/project_issue_approval";
import RichEditor from "@/business/common/component/richEditor/richEditor.vue";
import {ECommon_Content_Line_Config_Type} from "../../../../../../../common/model/content";
import {RichEditorEventHandle} from "@/business/common/component/richEditorEventHandle";
const props=defineProps<{
projectIssueId:string
@ -250,20 +141,16 @@ const key=inject(injectProjectInfo).key
const permission=inject(injectProjectInfo).permission
const info=ref<DCSType<ICommon_Route_Res_ProjectIssue_BasicInfo>>()
const description=ref("")
const labelList=ref<DCSType<ICommon_Model_Project_Label[]>>([])
const moduleList=ref<DCSType<ICommon_Model_Project_Module[]>>([])
const fieldList=ref<ICommon_Route_Res_ProjectIssue_fieldsInfo[]>([])
const parentIssue=ref<DCSType<ICommon_Model_Project_Issue>>();
const relatedIssueList=ref<DCSType<ICommon_Model_Project_Issue>[]>([])
const childIssueList=ref<DCSType<ICommon_Model_Project_Issue>[]>([])
const commentList=ref<DCSType<{
value:ICommon_Model_Content,
isEdit:boolean,
editContent:string
}[]>>([])
const actionList=ref<DCSType<ICommon_Model_Workflow_Action>[]>([])
const releaseList=ref<DCSType<ICommon_Model_Project_Release[]>>([])
const commentAdd=ref("")
const moduleList=ref<DCSType<ICommon_Model_Project_Module>[]>([])
const labelList=ref<DCSType<ICommon_Model_Project_Label>[]>([])
const actionList=ref<DCSType<ICommon_Model_Workflow_Action>[] | {
isApproval:true,
name:"Resolve"|"Reject"|"Revoke"|"Commit"
}[]>([])
const fieldList=ref<ICommon_Route_Res_ProjectIssue_fieldsInfo[]>([])
const store=useDesktopStore()
const issueRelatedUserList=ref<{
id:string,
@ -302,7 +189,7 @@ const getDescription=async ()=>{
projectIssueId:props.projectIssueId
})
if(res?.code==0) {
description.value=res.data
description.value=res.data?res.data:"[]"
}
}
const getOtherInfo=async ()=>{
@ -317,50 +204,7 @@ const getOtherInfo=async ()=>{
relatedIssueList.value=res.data.relateds
}
}
const getFieldList=async ()=>{
let res=await apiIssue.fieldsInfo({
projectIssueId:props.projectIssueId
})
if(res?.code==0) {
fieldList.value=res.data
}
}
const onRemoveItem=async (type:"parent"|"child"|"related",index:number)=>{
if(type==="parent") {
let ret=await Dialog.confirm(root.value,appContext,"Do you want to remove parent issue?")
if(ret) {
let res=await apiIssue.removeParentIssue({
projectIssueId:props.projectIssueId,
projectIssueParentId:parentIssue.value.id
})
if(res?.code==0) {
parentIssue.value=null;
}
}
} else if(type=="child") {
let ret=await Dialog.confirm(root.value,appContext,"Do you want to remove child issue?")
if(ret) {
let res=await apiIssue.removeChildIssue({
projectIssueId:props.projectIssueId,
projectIssueChildId:childIssueList.value[index].id
})
if(res?.code==0) {
childIssueList.value.splice(index,1)
}
}
} else if(type=="related") {
let ret=await Dialog.confirm(root.value,appContext,"Do you want to remove related issue?")
if(ret) {
let res=await apiIssue.removeRelatedIssue({
projectIssueId:props.projectIssueId,
projectIssueRelatedId:relatedIssueList.value[index].id
})
if(res?.code==0) {
relatedIssueList.value.splice(index,1)
}
}
}
}
const onAddItem=async (type:"parent"|"child"|"related")=>{
if(type=="parent") {
let ret=await Dialog.open(root.value,appContext,"Parent",markRaw(ProjectIssueBind),{
@ -412,64 +256,7 @@ const onAddItem=async (type:"parent"|"child"|"related")=>{
}
}
}
const getCommentList=async ()=>{
let res=await apiIssue.commentList({
projectIssueId:props.projectIssueId
})
if(res?.code==0) {
commentList.value=res.data.map(item=>{
return {
value:item,
isEdit:false,
editContent:item.content
}
})
}
}
const onCommentAdd=async ()=>{
let res=await apiIssue.commentCreate({
projectIssueId:props.projectIssueId,
content:commentAdd.value
})
if(res?.code==0) {
commentList.value.unshift({
value:res.data,
isEdit:false,
editContent:res.data.content
})
commentAdd.value=""
}
}
const onEditComment=async (item:DCSType<{
value:ICommon_Model_Content,
isEdit:boolean,
editContent:string
}>)=>{
if(!item.editContent) {
Message.error("content can not be empty")
return
}
let res=await apiIssue.commentEdit({
contentId:item.value.id,
content:item.editContent
})
if(res?.code==0) {
item.value.content=item.editContent
item.isEdit=false
}
}
const onRemoveComment=async (index:number)=>{
let item=commentList.value[index];
let ret=await Dialog.confirm(root.value,appContext,"Do you want to delete this comment?")
if(ret) {
let res=await apiIssue.commentRemove({
contentId:item.value.id
})
if(res?.code==0) {
commentList.value.splice(index,1)
}
}
}
const onDelete=async ()=>{
let ret=await Dialog.confirm(root.value,appContext,"Do you want to delete this issue?")
if(ret) {
@ -490,34 +277,80 @@ const getActionList=async ()=>{
actionList.value=res.data
}
}
const onAction=async (item:ICommon_Model_Workflow_Action)=>{
let res=await apiIssue.getNextNodeFields({
projectIssueId:props.projectIssueId,
workflowActionId:item.id
})
if(res?.code==0) {
if(res.data.length>0) {
let ret=await Dialog.open(root.value,appContext,item.name,markRaw(ProjectIssueNext),{
projectId:projectId,
projectIssueId:props.projectIssueId,
workflowActionId:item.id,
items:res.data
})
if(!ret) {
return
}
} else {
await apiIssue.confirmNextNode({
projectIssueId:props.projectIssueId,
workflowActionId:item.id
})
}
getInfo()
getFieldList()
getActionList()
} else {
Message.error(res.msg)
}
const onAction=async (item:ICommon_Model_Workflow_Action | {
isApproval:true,
name:"Resolve"|"Reject"|"Revoke"|"Commit"
})=>{
if("isApproval" in item) {
if(item.name==="Revoke") {
let res=await apiIssue.revokeApproval({
projectIssueId:props.projectIssueId
})
if(res?.code!=0) {
Message.error(res.msg)
return
}
} else if(item.name==="Resolve") {
let res=await apiIssue.resolveApproval({
projectIssueId:props.projectIssueId
})
if(res?.code!=0) {
Message.error(res.msg)
return
}
} else if(item.name==="Reject") {
let ret=await Dialog.inputRich(root.value,appContext,"Reject Reason")
if(ret) {
let res=await apiIssue.rejectApproval({
projectIssueId:props.projectIssueId,
reason:JSON.stringify(ret)
})
if(res?.code!=0) {
Message.error(res.msg)
return
}
} else {
return
}
} else if(item.name==="Commit") {
let res=await apiIssue.commitApproval({
projectIssueId:props.projectIssueId
})
if(res?.code!=0) {
Message.error(res.msg)
return
}
}
} else {
let res=await apiIssue.getNextNodeFields({
projectIssueId:props.projectIssueId,
workflowActionId:item.id
})
if(res?.code==0) {
if(res.data.length>0) {
let ret=await Dialog.open(root.value,appContext,item.name,markRaw(ProjectIssueNext),{
projectId:projectId,
projectIssueId:props.projectIssueId,
workflowActionId:item.id,
items:res.data
})
if(!ret) {
return
}
} else {
await apiIssue.confirmNextNode({
projectIssueId:props.projectIssueId,
workflowActionId:item.id
})
}
} else {
Message.error(res.msg)
return
}
}
getInfo()
getActionList()
getFieldList()
}
const onMeeting=async ()=>{
@ -530,13 +363,30 @@ const onMeeting=async ()=>{
}
}
const getReleaseList=async ()=>{
let res=await apiIssue.releaseList({
projectIssueId:props.projectIssueId
})
if(res?.code==0) {
releaseList.value=res.data
}
const onCopy=async ()=>{
let ret=await Dialog.input(root.value,appContext,"type the issue's name")
if(ret) {
let res=await apiIssue.copy({
projectIssueId:props.projectIssueId,
name:ret
})
if(res?.code==0) {
eventBus.emit(EClient_EVENTBUS_TYPE.OPEN_PROJECT_ISSUE_PROFILE,projectId,res.data.id);
}
}
}
const onCreateSubIssue=async ()=>{
let ret=await Dialog.input(root.value,appContext,"type the issue's name")
if(ret) {
let res=await apiIssue.createChildIssue({
projectIssueId:props.projectIssueId,
name:ret
})
if(res?.code==0) {
getOtherInfo()
}
}
}
const showQuickMeeting=async ()=>{
@ -548,30 +398,48 @@ const showQuickMeeting=async ()=>{
}
}
const getFieldList=async ()=>{
let res=await apiIssue.fieldsInfo({
projectIssueId:props.projectIssueId
})
if(res?.code==0) {
fieldList.value=res.data
}
}
const calculateApprovalName=computed(()=>{
if(info.value) {
if(info.value.approval) {
if(info.value.approval.type===ECommon_Model_Project_Issue_Approval_Type.PENDING) {
return `${info.value.approval.workflowNode.name}(Wait For Approval)`
} else if(info.value.approval.type===ECommon_Model_Project_Issue_Approval_Type.RESOLVED) {
return info.value.workflowNode.name
} else if(info.value.approval.type===ECommon_Model_Project_Issue_Approval_Type.REJECTED) {
return `${info.value.approval.workflowNode.name}(Rejected)`
}
} else {
return info.value.workflowNode.name
}
} else {
return ""
}
})
const onCustomAnchorClick=(type:ECommon_Content_Line_Config_Type,value:string,link:string,label:string)=>{
RichEditorEventHandle.onCustomAnchorClick(type,value,link,label)
}
onBeforeMount(()=>{
getInfo()
getDescription()
getOtherInfo()
getFieldList()
getCommentList()
getActionList()
getReleaseList()
getFieldList()
})
</script>
<style scoped>
#issueProfileDetail :deep(div[role="region"]) {
background-color: white !important;
padding-left: 5px;
padding-right: 0px;
}
#issueProfileDetail :deep .arco-form-item {
margin-bottom: 10px;
}
#issueProfileDetail :deep .arco-form-item-label {
font-weight: bold;
}
#issueProfileContent :deep .arco-comment-actions {
.issueProfileContent :deep .arco-comment-actions {
margin-top: 0px;
}
</style>

View File

@ -0,0 +1,131 @@
<template>
<div>
<template v-if="parentIssue">
<a-row style="margin-top: 20px;font-weight: bold">Parent Issue</a-row>
<a-list style="margin-top: 10px;margin-right: 10px" size="small" hoverable>
<a-list-item>
<a-space size="large">
{{key+"-"+parentIssue.unique_id}}
<a-link href="javascript:void(0)" @click="onOpenIssue(parentIssue.id)">{{parentIssue.name}}</a-link>
<FieldPriority :priority="parentIssue.priority"></FieldPriority>
</a-space>
<template #actions>
<a-button type="text" size="mini" @click="onRemoveItem('parent',null)">
<template #icon>
<icon-close style="color: red"></icon-close>
</template>
</a-button>
</template>
</a-list-item>
</a-list>
</template>
<template v-if="childIssueList.length>0">
<a-row style="margin-top: 20px;font-weight: bold">Child Issues</a-row>
<a-list style="margin-top: 10px;margin-right: 10px" size="small" hoverable>
<a-list-item v-for="(item,index) in childIssueList" :key="item.id">
<a-space size="large">
{{key+"-"+item.unique_id}}
<a-link href="javascript:void(0)" @click="onOpenIssue(item.id)">{{item.name}}</a-link>
<FieldPriority :priority="item.priority"></FieldPriority>
</a-space>
<template #actions>
<a-button type="text" size="mini" @click="onRemoveItem('child',index)">
<template #icon>
<icon-close style="color: red"></icon-close>
</template>
</a-button>
</template>
</a-list-item>
</a-list>
</template>
<template v-if="relatedIssueList.length>0">
<a-row style="margin-top: 20px;font-weight: bold">Related Issues</a-row>
<a-list style="margin-top: 10px;margin-right: 10px" size="small" hoverable>
<a-list-item v-for="(item,index) in relatedIssueList" :key="item.id">
<a-space size="large">
{{key+"-"+item.unique_id}}
<a-link href="javascript:void(0)" @click="onOpenIssue(item.id)">{{item.name}}</a-link>
<FieldPriority :priority="item.priority"></FieldPriority>
</a-space>
<template #actions>
<a-button type="text" size="mini" @click="onRemoveItem('related',index)">
<template #icon>
<icon-close style="color: red"></icon-close>
</template>
</a-button>
</template>
</a-list-item>
</a-list>
</template>
</div>
</template>
<script setup lang="ts">
import FieldPriority from "../../../../common/component/field/fieldPriority.vue";
import {apiIssue, DCSType} from "../../../../common/request/request";
import {ICommon_Model_Project_Issue} from "../../../../../../../common/model/project_issue";
import {getCurrentInstance, inject} from "vue";
import {injectProjectInfo} from "../../../../common/util/symbol";
import {Dialog} from "../../../../common/component/dialog/dialog";
import {getRootNavigatorRef} from "../../../../../teamOS/common/component/navigator/navigator";
import {EClient_EVENTBUS_TYPE, eventBus} from "../../../../common/event/event";
const props=defineProps<{
parentIssue?:DCSType<ICommon_Model_Project_Issue>,
childIssueList?:DCSType<ICommon_Model_Project_Issue>[],
relatedIssueList?:DCSType<ICommon_Model_Project_Issue>[]
projectIssueId:string
}>()
const emit=defineEmits<{
removeParent:[]
}>()
const key=inject(injectProjectInfo).key
const projectId=inject(injectProjectInfo).id
const root=getRootNavigatorRef()
const appContext=getCurrentInstance().appContext
const onRemoveItem=async (type:"parent"|"child"|"related",index:number)=>{
if(type==="parent") {
let ret=await Dialog.confirm(root.value,appContext,"Do you want to remove parent issue?")
if(ret) {
let res=await apiIssue.removeParentIssue({
projectIssueId:props.projectIssueId,
projectIssueParentId:props.parentIssue.id
})
if(res?.code==0) {
emit("removeParent")
}
}
} else if(type=="child") {
let ret=await Dialog.confirm(root.value,appContext,"Do you want to remove child issue?")
if(ret) {
let res=await apiIssue.removeChildIssue({
projectIssueId:props.projectIssueId,
projectIssueChildId:props.childIssueList[index].id
})
if(res?.code==0) {
props.childIssueList.splice(index,1)
}
}
} else if(type=="related") {
let ret=await Dialog.confirm(root.value,appContext,"Do you want to remove related issue?")
if(ret) {
let res=await apiIssue.removeRelatedIssue({
projectIssueId:props.projectIssueId,
projectIssueRelatedId:props.relatedIssueList[index].id
})
if(res?.code==0) {
props.relatedIssueList.splice(index,1)
}
}
}
}
const onOpenIssue=(projectIssueId:string)=>{
eventBus.emit(EClient_EVENTBUS_TYPE.OPEN_PROJECT_ISSUE_PROFILE,projectId,projectIssueId)
}
</script>
<style scoped>
</style>

View File

@ -2,7 +2,6 @@
<a-form style="width: 80%" :model="form" ref="eleForm">
<a-form-item field="status" label="type" required>
<a-select v-model="form.status">
<a-option :value="ECommon_Model_Workflow_Node_Status.NOTSTART">NOTSTART</a-option>
<a-option :value="ECommon_Model_Workflow_Node_Status.INPROGRESS">INPROGRESS</a-option>
<a-option :value="ECommon_Model_Workflow_Node_Status.DONE">DONE</a-option>
</a-select>

View File

@ -107,7 +107,7 @@ import {ICommon_Route_Res_Workflow_Node_Field} from "../../../../../../../common
import {Message} from "@arco-design/web-vue";
import {ECommon_Field_Type, Field_Types} from "../../../../../../../common/field/type";
import {
ECommon_Model_Workflow_Node_Field_Type_Label_Type
ECommon_Model_Workflow_Node_Field_Type_Label_Type
} from "../../../../../../../common/model/workflow_node_field_type";
import AddField from "./addField.vue";
import {Dialog} from "../../../../common/component/dialog/dialog";
@ -146,6 +146,7 @@ const form=reactive({
default_number_value:0,
label_type:0,
values:[] as {
id?:string
value:string,
selected:number
}[]
@ -244,6 +245,7 @@ const onClickRow=async (item:ICommon_Route_Res_Workflow_Node_Field)=>{
form.values=[]
for(let obj of item.values) {
form.values.push({
id:obj.id,
value:obj.value,
selected:obj.selected
})

View File

@ -5,12 +5,12 @@
</a-space>
<a-layout style="flex:1 1 auto;border: 1px solid gainsboro">
<a-layout-content style="overflow:hidden;">
<div ref="eleWorkflow" style="height: 100%;width: 100%" tabindex="1" @keydown.delete="onKeyDelete"></div>
<div ref="eleWorkflow" style="height: 100%;width: 100%" tabindex="-1" @keydown.delete="onKeyDelete"></div>
</a-layout-content>
<a-layout-sider :resize-directions="['left']">
<a-form :model="form" layout="vertical" style="margin:10px 5px 0px 5px;width: 90%" v-if="form.type" @submitSuccess="onSubmit">
<h3>{{form.type}}</h3>
<a-form-item field="status" label="status" v-if="form.type=='node'" required>
<a-form-item field="status" label="status" v-if="form.type=='node' || form.type=='approval'" required>
<a-select v-model="form.status">
<a-option :value="ECommon_Model_Workflow_Node_Status.NOTSTART">NOTSTART</a-option>
<a-option :value="ECommon_Model_Workflow_Node_Status.INPROGRESS">INPROGRESS</a-option>
@ -23,8 +23,27 @@
<a-form-item field="description" label="description">
<a-textarea v-model="form.description"></a-textarea>
</a-form-item>
<template v-if="form.status===ECommon_Model_Workflow_Node_Status.INPROGRESS || form.status===ECommon_Model_Workflow_Node_Status.DONE">
<a-form-item field="approval" label="approval">
<a-switch :checked-value="1" :unchecked-value="0" v-model="form.approval"></a-switch>
</a-form-item>
<a-form-item field="approvalType" label="approval type" v-if="form.approval">
<a-select v-model="form.approvalType" @change="onApprovalTypeChange">
<a-option :value="ECommon_Model_Workflow_Approval_Type.PERSON">Person</a-option>
<a-option :value="ECommon_Model_Workflow_Approval_Type.TEAM">Team</a-option>
<a-option :value="ECommon_Model_Workflow_Approval_Type.FIELD">Field</a-option>
</a-select>
</a-form-item>
<a-form-item field="approvalValue" :label="form.approvalType===ECommon_Model_Workflow_Approval_Type.PERSON?'choose person':form.approvalType===ECommon_Model_Workflow_Approval_Type.TEAM?'choose team':'choose workflow field'" v-if="form.approval">
<FieldCommonMultiLabel style="width: 100%" v-model="form.approvalValue" :type="form.approvalType===ECommon_Model_Workflow_Approval_Type.PERSON?'user':'team'" v-if="form.approvalType===ECommon_Model_Workflow_Approval_Type.PERSON || form.approvalType===ECommon_Model_Workflow_Approval_Type.TEAM"></FieldCommonMultiLabel>
<FieldCommonLabel style="width: 100%" v-model="form.approvalValue" type="field" :workflow-node-id="form.id" v-else-if="form.approvalType===ECommon_Model_Workflow_Approval_Type.FIELD"></FieldCommonLabel>
</a-form-item>
<a-form-item field="approvalExtra" label="choose member tag" v-if="form.approval && form.approvalType===ECommon_Model_Workflow_Approval_Type.TEAM">
<FieldCommonLabel style="width: 100%" v-model="form.approvalExtra" type="tag"></FieldCommonLabel>
</a-form-item>
</template>
<a-form-item>
<a-space v-if="form.type=='node'" size="medium">
<a-space v-if="form.type=='node' || form.type=='approval'" size="medium">
<a-button html-type="submit" type="primary">Save</a-button>
<a-button html-type="button" type="primary" @click="onEditFields">Edit Fields</a-button>
</a-space>
@ -37,7 +56,7 @@
</template>
<script setup lang="ts">
import {getCurrentInstance, markRaw, onBeforeMount, onMounted, reactive, ref} from "vue";
import {getCurrentInstance, markRaw, nextTick, onBeforeMount, onMounted, reactive, ref} from "vue";
import LogicFlow from "@logicflow/core";
import {apiWorkflow} from "../../../../common/request/request";
import {Dialog} from "../../../../common/component/dialog/dialog";
@ -47,11 +66,15 @@ import {ECommon_Model_Workflow_Node_Status} from "../../../../../../../common/mo
import {Message} from "@arco-design/web-vue";
import {getCurrentNavigator} from "../../../../../teamOS/common/component/navigator/navigator";
import FieldList from "./fieldList.vue";
import {flowApproval} from "@/business/common/component/flow/approval";
import {ECommon_Model_Workflow_Approval_Type} from "../../../../../../../common/model/workflow_approval";
import FieldCommonMultiLabel from "@/business/common/component/field/common/fieldCommonMultiLabel.vue";
import FieldCommonLabel from "@/business/common/component/field/common/fieldCommonLabel.vue";
const props=defineProps<{
issueTypeId:string
}>()
const eleWorkflow=ref(null);
const eleWorkflow=ref<HTMLElement>(null);
const root=ref(null)
const appContext=getCurrentInstance().appContext
const navigator=getCurrentNavigator();
@ -60,8 +83,12 @@ const form=reactive({
id:"",
name:"",
description:"",
type:"",
status:ECommon_Model_Workflow_Node_Status.INPROGRESS
type:"" ,
status:ECommon_Model_Workflow_Node_Status.INPROGRESS,
approval:0,
approvalType:ECommon_Model_Workflow_Approval_Type.PERSON,
approvalValue:[] as string[]|string,
approvalExtra:""
})
let lf:LogicFlow
const onKeyDelete=async (event:KeyboardEvent)=>{
@ -75,6 +102,10 @@ const onKeyDelete=async (event:KeyboardEvent)=>{
type="node"
name=(<any>elements.nodes[0].text).value;
id=elements.nodes[0].id
if(elements.nodes[0].properties.status===ECommon_Model_Workflow_Node_Status.NOTSTART) {
Message.error("not start node can't be removed !!!")
return
}
}
if(type) {
let ret=await Dialog.confirm(root.value,appContext,`Do you want to delete ${type} ${name}`)
@ -89,11 +120,17 @@ const onKeyDelete=async (event:KeyboardEvent)=>{
}
const onSubmit=async ()=>{
let res=await (form.type=="node"?apiWorkflow.editNode({
let res=await ((form.type=="node" || form.type=="approval")?apiWorkflow.editNode({
workflowNodeId:form.id,
name:form.name,
description:form.description,
status:form.status
status:form.status,
approval:form.approval,
...(form.approval && {
approvalType:form.approvalType,
approvalValue:Array.isArray(form.approvalValue)?form.approvalValue:form.approvalValue?[form.approvalValue]:[],
approvalExtra:form.approvalExtra
})
}):apiWorkflow.editAction({
workflowActionId:form.id,
name:form.name,
@ -101,12 +138,10 @@ const onSubmit=async ()=>{
}))
if(res?.code==0) {
Message.success("update success")
if(form.type=="node") {
if(form.type=="node" || form.type=="approval") {
let obj=lf.getNodeModelById(form.id)
obj.updateText(form.name)
obj.setProperty("name",form.name)
obj.setProperty("description",form.description)
obj.setProperty("status",form.status)
obj.setProperties(res.data)
} else {
let obj=lf.getEdgeModelById(form.id)
obj.updateText(form.name)
@ -122,6 +157,17 @@ const onEditFields=()=>{
workflowNodeId:form.id
},`${form.name} -> Fields`)
}
const onApprovalTypeChange=()=> {
if(form.approvalType===ECommon_Model_Workflow_Approval_Type.PERSON || form.approvalType===ECommon_Model_Workflow_Approval_Type.TEAM) {
form.approvalValue=[];
form.approvalExtra=''
} else if(form.approvalType===ECommon_Model_Workflow_Approval_Type.FIELD) {
form.approvalValue="";
form.approvalExtra=''
}
}
const requestNodes=async ()=>{
let res=await apiWorkflow.info({
issueTypeId:props.issueTypeId
@ -131,7 +177,7 @@ const requestNodes=async ()=>{
nodes:res.data.nodes.map(item=>{
return {
id:item.id,
type:"node",
type:item.is_approval?"approval":"node",
x:item.x,
y:item.y,
text:item.name,
@ -145,7 +191,13 @@ const requestNodes=async ()=>{
sourceNodeId: item.source_node_id,
targetNodeId: item.dest_node_id,
text: item.name,
properties: item
properties: item,
...(item.source_anchor_point && {
startPoint:JSON.parse(item.source_anchor_point)
}),
...(item.end_anchor_point && {
endPoint:JSON.parse(item.end_anchor_point)
})
}
})
}
@ -156,14 +208,17 @@ const addNode=async ()=>{
issueTypeId:props.issueTypeId
})
if(ret) {
lf.dnd.startDrag({
id:ret.id,
type:"node",
text:ret.name,
properties:ret
})
lf.addNode({
id:ret.data.id,
type:"node",
text:ret.data.name,
x:(-lf.graphModel.transformModel.TRANSLATE_X+lf.graphModel.width/2-100),
y:(-lf.graphModel.transformModel.TRANSLATE_Y+20)*lf.graphModel.transformModel.SCALE_Y,
properties:ret.data
})
}
}
let requestNodesPromise;
onBeforeMount(()=>{
requestNodesPromise=requestNodes();
@ -178,6 +233,7 @@ onMounted(async ()=>{
adjustEdgeStartAndEnd:false,
})
lf.register(flowNode)
lf.register(flowApproval)
lf.setTheme({
baseEdge:{
strokeWidth:1.2,
@ -192,24 +248,46 @@ onMounted(async ()=>{
x:data.x,
y:data.y
})
let outgoingEdges=lf.getNodeOutgoingEdge(data.properties.id)
outgoingEdges.forEach(item=>{
apiWorkflow.editAction({
workflowActionId:item.id,
sourceAnchorPoint:JSON.stringify(item.startPoint)
})
})
let incomingEdges=lf.getNodeIncomingEdge(data.properties.id)
incomingEdges.forEach(item=>{
apiWorkflow.editAction({
workflowActionId:item.id,
endAnchorPoint:JSON.stringify(item.endPoint)
})
})
})
lf.on("edge:add",async ({data})=>{
let res=await apiWorkflow.addAction({
issueTypeId:props.issueTypeId,
name:"empty",
sourceNodeId:data.sourceNodeId,
destNodeId:data.targetNodeId
destNodeId:data.targetNodeId,
sourceAnchorPoint:JSON.stringify(data.startPoint),
endAnchorPoint:JSON.stringify(data.endPoint)
})
if(res?.code==0) {
let obj=lf.getEdgeModelById(data.id)
obj.id=res.data.id
obj.updateText("empty");
obj.setProperties(res.data)
lf.openEdgeAnimation(obj.id)
} else {
Message.error(res.msg)
lf.deleteEdge(data.id)
}
})
lf.on("edge:delete",async ({data})=>{
if(data.properties.id==form.id) {
form.type=""
} else if(!data.properties.id) {
return
}
apiWorkflow.deleteAction({
workflowActionId:data.properties.id
@ -219,23 +297,74 @@ onMounted(async ()=>{
if(data.properties.id==form.id) {
form.type=""
}
apiWorkflow.deleteNode({
workflowNodeId:data.properties.id
})
apiWorkflow.deleteNode({
workflowNodeId:data.properties.id
})
})
lf.on("node:click,edge:click",({data})=>{
form.id=data.properties.id
form.type=data.type=="node"?"node":"transition"
form.type=data.type=="node"?"node":data.type==='approval'?"approval":"transition"
form.name=data.properties.name
form.description=data.properties.description
if(data.type=="node") {
if(data.type=="node" || data.type=="approval") {
form.status=data.properties.status
form.approval=data.properties.is_approval
if(form.approval) {
form.approvalType=data.properties.approval.type
if(form.approvalType===ECommon_Model_Workflow_Approval_Type.TEAM || form.approvalType===ECommon_Model_Workflow_Approval_Type.PERSON) {
form.approvalValue=data.properties.approval.value
if(form.approvalType===ECommon_Model_Workflow_Approval_Type.TEAM) {
form.approvalExtra=data.properties.approval.extra
}
} else if(form.approvalType===ECommon_Model_Workflow_Approval_Type.FIELD) {
form.approvalValue=data.properties.approval.value.length>0?data.properties.approval.value[0]:""
}
} else {
form.approvalType=ECommon_Model_Workflow_Approval_Type.PERSON
form.approvalValue=[]
form.approvalExtra=""
}
}
})
lf.on("blank:click",({date})=>{
form.type=""
nextTick(()=>{
form.type=""
})
})
lf.on("text:update",async (param)=>{
if(form.type=="node" || form.type=="approval") {
let obj=lf.getNodeModelById(form.id)
let res=await apiWorkflow.editNode({
workflowNodeId:form.id,
name:obj.text.value
})
if(res?.code==0) {
obj.setProperty("name",obj.text.value)
form.name=obj.text.value
}
} else {
let obj=lf.getEdgeModelById(form.id)
let res=await apiWorkflow.editAction({
workflowActionId:form.id,
name:obj.text.value
})
if(res?.code==0) {
obj.setProperty("name",obj.text.value)
form.name=obj.text.value
}
}
})
lf.on("edge:adjust",({data})=>{
apiWorkflow.editAction({
workflowActionId:data.properties.id,
sourceAnchorPoint:JSON.stringify(data.startPoint),
endAnchorPoint:JSON.stringify(data.endPoint)
})
})
lf.render(ret)
for(let edge of ret.edges) {
lf.openEdgeAnimation(edge.id)
}
}
})
</script>

View File

@ -80,7 +80,7 @@ const onSearch=async (value:string)=>{
})
}
} else if(form.type==ECommon_Model_Organization_Member_Type.MEMBERTAG) {
let res=await apiOrganization.listTag()
let res=await apiOrganization.listTag({})
if(res?.code==0) {
userList.value=res.data.map(item=>{
return {

View File

@ -43,7 +43,6 @@ import EditProjectProfile from "./editProjectProfile.vue";
import EditProjectAccess from "./editProjectAccess.vue";
import LabelList from "./labelList.vue";
import ModuleList from "./moduleList.vue";
import {SessionStorage} from "../../../../common/storage/session";
const columns=[
{
@ -78,7 +77,6 @@ navigator.register("labelList",markRaw(LabelList));
navigator.register("moduleList",markRaw(ModuleList));
const search=async (page:number)=>{
let res=await apiProject.list({
organizationUserId:SessionStorage.get("organizationId"),
size:pagination.pageSize,
page:page-1,
keyword:keyword.value

View File

@ -25,7 +25,7 @@ const tagList=ref<{
checked:boolean
}[]>([])
onBeforeMount(async ()=>{
let res=await apiOrganization.listTag();
let res=await apiOrganization.listTag({});
if(res?.code==0) {
let userTagIds=props.tags.map(item=>item.id);
tagList.value=res.data.map(item=>{

View File

@ -40,7 +40,7 @@ const data=ref<ICommon_Model_Member_Tag[]>([])
const root=ref(null);
const appContext=getCurrentInstance().appContext
const search=async ()=>{
let ret=await apiOrganization.listTag()
let ret=await apiOrganization.listTag({})
if(ret?.code==0) {
data.value=ret.data
}

View File

@ -80,7 +80,7 @@ const onSearch=async (value:string)=>{
})
}
} else if(form.type==ECommon_Model_Organization_Member_Type.MEMBERTAG) {
let res=await apiOrganization.listTag()
let res=await apiOrganization.listTag({})
if(res?.code==0) {
userList.value=res.data.map(item=>{
return {

View File

@ -39,7 +39,6 @@ import {getCurrentNavigator} from "../../../../../teamOS/common/component/naviga
import EditWikiProfile from "./editWikiProfile.vue";
import EditWikiAccess from "./editWikiAccess.vue";
import {ICommon_Model_Wiki} from "../../../../../../../common/model/wiki";
import {SessionStorage} from "../../../../common/storage/session";
const columns=[
{
@ -72,7 +71,6 @@ const navigator=getCurrentNavigator();
navigator.register("editWikiAccess",markRaw(EditWikiAccess));
const search=async (page:number)=>{
let res=await apiWiki.list({
organizationUserId:SessionStorage.get("organizationId"),
size:pagination.pageSize,
page:page-1,
keyword:keyword.value

View File

@ -32,11 +32,11 @@
import {onBeforeMount, onBeforeUnmount, ref} from "vue";
import {apiNotification, DCSType} from "../../common/request/request";
import {ICommon_Route_Res_Notification_Item} from "../../../../../common/routes/response";
import moment from "moment";
import NotificationItem from "../../common/component/notificationItem.vue";
import {ECommon_Model_Notification_Type} from "../../../../../common/model/notification";
import {EClient_EVENTBUS_TYPE, eventBus} from "../../common/event/event";
import {type} from "os";
import moment from "moment";
let page=0
const bottom=ref(false)
@ -51,6 +51,13 @@ const getList=async ()=>{
types:types.value
})
if(res?.code==0) {
for(let i=0;i<res.data.length;i++) {
let obj=res.data[i]
if(!obj.data) {
res.data.splice(i,1)
i--
}
}
list.value.push(...res.data)
if(res.data.length==0) {
bottom.value=true
@ -101,7 +108,7 @@ const onChange=(value:string)=>{
} else if(value==="organization") {
types.value=[ECommon_Model_Notification_Type.ORGANIZATION_USER_QUIT,ECommon_Model_Notification_Type.ORGANIZATION_INVITATION,ECommon_Model_Notification_Type.ORGANIZATION_USER_REMOVE,ECommon_Model_Notification_Type.ORGANIZATION_USER_ROLE_CHANGE]
} else if(value==="issue") {
types.value=[ECommon_Model_Notification_Type.ISSUE_FIELD_CHANGE,ECommon_Model_Notification_Type.ISSUE_REMOVE,ECommon_Model_Notification_Type.ISSUE_ASSIGNER_ASSIGN,ECommon_Model_Notification_Type.ISSUE_WORKFLOW_CHANGE,ECommon_Model_Notification_Type.ISSUE_REPORTER_ASSIGN,ECommon_Model_Notification_Type.ISSUE_COMMENT_ADD,ECommon_Model_Notification_Type.ISSUE_COMMENT_AT]
types.value=[ECommon_Model_Notification_Type.ISSUE_FIELD_CHANGE,ECommon_Model_Notification_Type.ISSUE_REMOVE,ECommon_Model_Notification_Type.ISSUE_ASSIGNER_ASSIGN,ECommon_Model_Notification_Type.ISSUE_WORKFLOW_CHANGE,ECommon_Model_Notification_Type.ISSUE_REPORTER_ASSIGN,ECommon_Model_Notification_Type.ISSUE_COMMENT_ADD,ECommon_Model_Notification_Type.ISSUE_COMMENT_AT,ECommon_Model_Notification_Type.ISSUE_APPROVAL_REJECT,ECommon_Model_Notification_Type.ISSUE_APPROVAL_RESOLVE]
} else if(value==="team") {
types.value=[ECommon_Model_Notification_Type.TEAM_USER_QUIT,ECommon_Model_Notification_Type.TEAM_USER_ROLE_CHANGE,ECommon_Model_Notification_Type.TEAM_USER_REMOVE,ECommon_Model_Notification_Type.TEAM_DISMISS,ECommon_Model_Notification_Type.TEAM_USER_ADD]
} else if(value==="calendar") {

View File

@ -95,6 +95,11 @@ export const useDesktopStore=defineStore("desktop",{
if(!this.appList.includes(iconSetting)) {
this.appList.push(iconSetting)
}
} else {
let index=this.appList.indexOf(iconSetting)
if(index>-1) {
this.appList.splice(index,1)
}
}
}
})()

View File

@ -24,7 +24,7 @@ import {desktop} from "./desktop";
import {windowManager} from "../window/windowManager";
import {ETeamOS_Window_Status} from "../window/window.js";
import {iconGroupMap} from "../icon/icon";
import SvgIcon from "../../icon/custom/svgIcon.vue";
import SvgIcon from "../../icon/svgIcon.vue";
const list=windowManager.getList();
const color=desktop.getBaseColor();

View File

@ -4,7 +4,7 @@
<template v-for="(_, slot) of $slots" v-slot:[slot]="scope"><slot :name="slot" v-bind="scope"/></template>
<WindowContainer></WindowContainer>
</IconContainer>
<DesktopBar style="height: 40px;background-color: rgb(93,92,140)">
<DesktopBar style="height: 40px;background-color: rgb(93,92,140);">
<template v-for="(_, slot) of $slots" v-slot:[slot]="scope"><slot :name="slot" v-bind="scope"/></template>
</DesktopBar>
</div>

View File

@ -22,7 +22,7 @@
import {Icon} from "./icon"
import {vMenu} from "../common/directive/menu";
import {computed, nextTick, ref, watch} from "vue";
import SvgIcon from "../../icon/custom/svgIcon.vue";
import SvgIcon from "../../icon/svgIcon.vue";
const props=defineProps<{
item:Icon,

View File

@ -72,7 +72,7 @@ import {ETeamOS_Window_Status, ETeamOS_Window_Type, Window} from "./window";
import {vDrag} from "../common/directive/drag";
import {vResize} from "../common/directive/resize";
import {v4} from "uuid"
import SvgIcon from "../../icon/custom/svgIcon.vue";
import SvgIcon from "../../icon/svgIcon.vue";
import {iconGroupMap} from "../icon/icon";
vResize;

View File

@ -18,7 +18,7 @@ export class WindowManager extends Base{
this.show(arr[0].id)
} else {
let focused=this.getFocused()
if(focused.group===window.group) {
if(focused?.group===window.group) {
focused.nodes.push(...window.nodes)
focused.activeKey=window.nodes[0].id
this.show(focused.id)

View File

@ -20,7 +20,10 @@
"noImplicitThis": true,
"plugins": [
{ "transform": "../common/transform/transformer.js" }
]
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]

View File

@ -6,13 +6,18 @@ import viteCompression from 'vite-plugin-compression'
import {createSvgIconsPlugin} from "vite-plugin-svg-icons";
export default defineConfig({
resolve:{
alias:{
"@":path.join(__dirname,"src")
}
},
plugins: [
vue(),
viteCompression({
threshold:102400
}),
createSvgIconsPlugin({
iconDirs:[path.join(__dirname,"./src/icon/custom")],
iconDirs:[path.join(__dirname,"./src/assert/custom")],
symbolId:'[name]'
})
],

View File

@ -43,7 +43,8 @@ export enum ECommon_Model_Content_Type {
PROJECT_ISSUE_DESCRIPTION,
WIKI_ITEM,
MEETING_CHAT,
CALENDAR_EVENT_AGENDA
CALENDAR_EVENT_AGENDA,
PROJECT_ISSUE_REJECT
}
export interface ICommon_Model_Content {

View File

@ -18,7 +18,9 @@ export enum ECommon_Model_Notification_Type {
ISSUE_COMMENT_AT,
ISSUE_REMOVE,
WIKI_ITEM_AT,
CALENDAR_EVENT_INVITATION
CALENDAR_EVENT_INVITATION,
ISSUE_APPROVAL_RESOLVE,
ISSUE_APPROVAL_REJECT
}
export enum ECommon_Model_Notification_Status {

View File

@ -0,0 +1,24 @@
import {BaseModel} from "./base"
export enum ECommon_Model_Project_Issue_Approval_Type {
PENDING,
RESOLVED,
REJECTED
}
export interface ICommon_Model_Project_Issue_Approval {
id: string,
project_issue_id:string
workflow_node_id: string,
type:ECommon_Model_Project_Issue_Approval_Type,
approval_organization_user_id:string
}
export const Table_Project_Issue_Approval = "project_issue_approval"
class ProjectIssueApprovalModel extends BaseModel {
table = Table_Project_Issue_Approval
model = <ICommon_Model_Project_Issue_Approval>{}
}
export let projectIssueApprovalModel = new ProjectIssueApprovalModel

View File

@ -0,0 +1,30 @@
import {BaseModel} from "./base";
export enum ECommon_Model_Project_Issue_History_Type {
CREATE_ISSUE,
UPDATE_FIELD,
UPDATE_NODE,
APPROVAL_RESOLVE,
APPROVAL_REJECT,
ISSUE_TYPE_CONVERT
}
export interface ICommon_Model_Project_Issue_History {
id: string,
created_time: Date,
type:ECommon_Model_Project_Issue_History_Type,
project_issue_id:string,
organization_user_id:string,
project_id:string,
name:string,
value:string
}
export const Table_Project_Issue_History = "project_issue_history"
class ProjectIssueHistoryModel extends BaseModel {
table = Table_Project_Issue_History
model = <ICommon_Model_Project_Issue_History>{}
}
export let projectIssueHistoryModel = new ProjectIssueHistoryModel

View File

@ -7,6 +7,8 @@ export interface ICommon_Model_Workflow_Action {
source_node_id :string ,
dest_node_id :string ,
issue_type_id :string,
source_anchor_point:string,
end_anchor_point:string
}
export const Table_Workflow_Action="workflow_action"

View File

@ -0,0 +1,23 @@
import {BaseModel} from "./base"
export enum ECommon_Model_Workflow_Approval_Type {
PERSON,
TEAM,
FIELD
}
export interface ICommon_Model_Workflow_Approval {
id :string ,
workflow_node_id:string,
type:ECommon_Model_Workflow_Approval_Type,
value:string[],
extra:string
}
export const Table_Workflow_Approval="workflow_approval"
class WorkflowApprovalModel extends BaseModel {
table=Table_Workflow_Approval
model=<ICommon_Model_Workflow_Approval>{}
}
export let workflowApprovalModel=new WorkflowApprovalModel

View File

@ -13,7 +13,8 @@ export interface ICommon_Model_Workflow_Node {
status :ECommon_Model_Workflow_Node_Status,
issue_type_id :string ,
x:number,
y:number
y:number,
is_approval:number
}
export const Table_Workflow_Node="workflow_node"

View File

@ -123,6 +123,7 @@ const api={
req: <{
workflowNodeFieldTypeId :string,
data:{
id?:string
value :string,
selected :number
}[]

View File

@ -16,6 +16,7 @@ import {
import {ECommon_HttpApi_Method} from "./types";
import {ICommon_Model_Project_Issue_Field_Value} from "../model/project_issue_field_value";
import {ICommon_Model_Project_Release} from "../model/project_release";
import {ICommon_Model_Project_Issue_History} from "../model/project_issue_history";
const api={
baseUrl:"/issue",
@ -143,7 +144,10 @@ const api={
req:<{
projectIssueId :string
}>{},
res:<ICommon_Model_Workflow_Action[]>{},
res:<ICommon_Model_Workflow_Action[] | {
isApproval:true,
name:"Resolve"|"Reject"|"Revoke"|"Commit"
}[]>{},
permission:[Permission_Types.Project.READ]
},
commentList:{
@ -188,7 +192,8 @@ const api={
method:ECommon_HttpApi_Method.POST,
path:"/item/copy",
req:<{
projectIssueId :string
projectIssueId :string,
name:string
}>{},
res:<ICommon_Model_Project_Issue>{},
permission:[Permission_Types.Project.EDIT]
@ -339,6 +344,73 @@ const api={
photo:string
}[]>{},
permission:[Permission_Types.Project.READ]
},
createChildIssue:{
method:ECommon_HttpApi_Method.POST,
path:"/item/createchild",
req:<{
projectIssueId :string,
name:string
}>{},
res:<ICommon_Model_Project_Issue>{},
permission:[Permission_Types.Project.EDIT]
},
listHistory:{
method:ECommon_HttpApi_Method.GET,
path:"/history",
req:<{
projectIssueId :string,
}>{},
res:<ICommon_Model_Project_Issue_History[]>{},
permission:[Permission_Types.Project.READ]
},
checkApproval:{
method:ECommon_HttpApi_Method.GET,
path:"/approval/check",
req:<{
projectIssueId :string,
}>{},
res:<{
access:boolean
}>{},
permission:[Permission_Types.Project.READ]
},
revokeApproval:{
method:ECommon_HttpApi_Method.POST,
path:"/approval/revoke",
req:<{
projectIssueId :string,
}>{},
res:{},
permission:[Permission_Types.Project.EDIT]
},
resolveApproval:{
method:ECommon_HttpApi_Method.POST,
path:"/approval/resolve",
req:<{
projectIssueId :string,
}>{},
res:{},
permission:[Permission_Types.Project.EDIT]
},
rejectApproval:{
method:ECommon_HttpApi_Method.POST,
path:"/approval/reject",
req:<{
projectIssueId :string,
reason:string
}>{},
res:{},
permission:[Permission_Types.Project.EDIT]
},
commitApproval:{
method:ECommon_HttpApi_Method.POST,
path:"/approval/commit",
req:<{
projectIssueId :string
}>{},
res:{},
permission:[Permission_Types.Project.EDIT]
}
}
}

View File

@ -49,6 +49,14 @@ const api= {
notificationId:string
}>{},
res: <ICommon_Route_Res_Notification_Item>{}
},
remove:{
method: ECommon_HttpApi_Method.DELETE,
path: "/item",
req: <{
notificationId:string
}>{},
res: <ICommon_Route_Res_Notification_Item>{}
}
}
}

View File

@ -73,7 +73,7 @@ const api={
method:ECommon_HttpApi_Method.GET,
path:"/user/list",
req:<{
organizationId:string,
organizationId?:string,
page:number,
size:number,
keyword?:string
@ -193,7 +193,7 @@ const api={
method:ECommon_HttpApi_Method.GET,
path:"/tag/list",
req:<{
keyword?:string
}>{},
res:<ICommon_Model_Member_Tag[]>{},
permission:[Permission_Types.Organization.READ]
@ -217,6 +217,15 @@ const api={
res:<ICommon_Model_Member_Tag>{},
permission:[Permission_Types.Organization.ADMIN]
},
getTag:{
method:ECommon_HttpApi_Method.GET,
path:"/tag",
req:<{
memberTagId:string
}>{},
res:<ICommon_Model_Member_Tag>{},
permission:[Permission_Types.Organization.READ]
},
editTag:{
method:ECommon_HttpApi_Method.PUT,
path:"/tag",

View File

@ -16,6 +16,7 @@ import {
} from './response';
import {ECommon_HttpApi_Method} from "./types";
import {ICommon_Model_Role} from "../model/role";
import {ICommon_Model_Project_Label} from "../model/project_label";
const api={
baseUrl:"/project",
@ -76,6 +77,15 @@ const api={
res:<ICommon_Route_Res_Project_ListTag>{},
permission:[Permission_Types.Project.READ]
},
getLabel:{
method:ECommon_HttpApi_Method.GET,
path:"/tag/item",
req:<{
labelId:string
}>{},
res:<ICommon_Model_Project_Label>{},
permission:[Permission_Types.Project.READ]
},
createLabel:{//创建tag
method:ECommon_HttpApi_Method.POST,
path:"/tag/item",

View File

@ -26,6 +26,8 @@ import {ICommon_Model_Wiki_Item} from "../model/wiki_item";
import {ICommon_Model_Meeting_Room} from "../model/meeting_room";
import {ICommon_Model_Notification} from "../model/notification";
import {ICommon_Model_Meeting_Miss_Call} from "../model/meeting_miss_call";
import {ICommon_Model_Workflow_Approval} from "../model/workflow_approval";
import {ICommon_Model_Project_Issue_Approval} from "../model/project_issue_approval";
export interface ICommon_Route_Res_Project_CreateModule_Data {
id:string,
@ -141,7 +143,9 @@ export interface ICommon_Route_Res_Workflow_Info_Action {
}
export interface ICommon_Route_Res_Workflow_Info {
nodes:ICommon_Model_Workflow_Node[],
nodes:(ICommon_Model_Workflow_Node & {
approval?:ICommon_Model_Workflow_Approval
})[],
actions:ICommon_Model_Workflow_Action[]
}
@ -166,7 +170,11 @@ export type ICommon_Route_Req_ProjectIssue_Field_Value={
export interface ICommon_Route_Res_ProjectIssue_BasicInfo extends Omit<ICommon_Model_Project_Issue,"workflow_node_id"|"issue_type_id"|"project_id"> {
workflowNode:ICommon_Model_Workflow_Node
issueType:ICommon_Model_Issue_Type,
project:ICommon_Model_Project
project:ICommon_Model_Project,
approval?:ICommon_Model_Project_Issue_Approval & {
reason?:string,
workflowNode:ICommon_Model_Workflow_Node
}
}
export type ICommon_Route_Res_ProjectIssue_fieldsInfo = {

View File

@ -5,6 +5,7 @@ import {ECommon_Services} from './../types';
import {ECommon_HttpApi_Method} from "./types";
import {ICommon_Route_Res_Workflow_Info, ICommon_Route_Res_Workflow_Node_List_Item} from "./response";
import {Permission_Types} from "../permission/permission";
import {ECommon_Model_Workflow_Approval_Type, ICommon_Model_Workflow_Approval} from "../model/workflow_approval";
const api={
baseUrl:"/workflow",
@ -42,9 +43,15 @@ const api={
description? :string,
status? :ECommon_Model_Workflow_Node_Status,
x?:number,
y?:number
y?:number,
approval?:number,
approvalType?:ECommon_Model_Workflow_Approval_Type,
approvalValue?:string[],
approvalExtra?:string
}>{},
res:<ICommon_Model_Workflow_Node & {
approval?:ICommon_Model_Workflow_Approval
}>{},
res:<ICommon_Model_Workflow_Node>{},
permission:[Permission_Types.Organization.ADMIN]
},
deleteNode:{
@ -65,6 +72,8 @@ const api={
description? :string,
sourceNodeId:string,
destNodeId:string,
sourceAnchorPoint:string,
endAnchorPoint:string
}>{},
res:<ICommon_Model_Workflow_Action>{},
permission:[Permission_Types.Organization.ADMIN]
@ -78,6 +87,8 @@ const api={
description? :string,
sourceNodeId?:string,
destNodeId?:string,
sourceAnchorPoint?:string,
endAnchorPoint?:string
}>{},
res:<ICommon_Model_Workflow_Action>{},
permission:[Permission_Types.Organization.ADMIN]
@ -98,6 +109,18 @@ const api={
res:<ICommon_Route_Res_Workflow_Node_List_Item[]>{},
permission:[Permission_Types.Organization.READ]
},
listApprovalField:{
method:ECommon_HttpApi_Method.GET,
path:"/approval/field",
req:<{
workflowNodeId:string
}>{},
res:<{
id:string,
name:string
}[]>{},
permission:[Permission_Types.Organization.READ]
}
}
}
export default api

View File

@ -250,6 +250,18 @@ export namespace Err {
unbindReservedWorkflowSolutionForbidden:{
code:3514,
msg:"unbind reserved workflow solution forbidden"
},
approvalOnlyHaveOneExport:{
code:2515,
msg:"approval only have one export"
},
approvalNotFound:{
code:2516,
msg:"approval not found"
},
workflowNodeIsNotApproval:{
code:2517,
msg:"workflow node is not aproval"
}
},
Field:{

View File

@ -1,9 +1,8 @@
import * as mysql from "mysql2";
import { Pool as PromisePool } from "mysql2/promise";
import {Pool as PromisePool} from "mysql2/promise";
import "reflect-metadata";
import { Err } from '../../../common/status/error';
import { IServer_Common_Config_Mysql } from './../types/config';
import {Err} from '../../../common/status/error';
import {IServer_Common_Config_Mysql} from './../types/config';
var g_mysqlConnection:InstanceType<typeof Mysql>
export function getMysqlInstance(){
@ -84,6 +83,18 @@ export default class Mysql {
ret[key]=item[key]
}
}
for(let key in ret) {
let obj=ret[key]
if(typeof(obj)==="object") {
let set=new Set
for(let k in obj) {
set.add(obj[k])
}
if(set.size==1 && set.has(null)) {
delete ret[key]
}
}
}
return ret as any;
}
} else {
@ -131,6 +142,18 @@ export default class Mysql {
ret[key as any]=item[key]
}
}
for(let key in ret) {
let obj=ret[key]
if(typeof(obj)==="object") {
let set=new Set
for(let k in obj) {
set.add(obj[k])
}
if(set.size==1 && set.has(null)) {
delete ret[key]
}
}
}
return ret as any;
})
return <T[]><unknown>rows

View File

@ -45,7 +45,7 @@ export abstract class Entity<T extends BaseModel,M extends Mapper<T>> {
getItem():T["model"] {
return this.item;
}
async create():Promise<T["model"]> {
async create(...param:any):Promise<T["model"]> {
if(!this.item) {
throw Err.Common.itemNotFound;
} else if(this.item.id) {
@ -60,7 +60,7 @@ export abstract class Entity<T extends BaseModel,M extends Mapper<T>> {
})
return this.item;
}
async update():Promise<T["model"]>{
async update(...param:any):Promise<T["model"]>{
if(!this.item || !this.item.id) {
throw Err.Common.itemNotFound;
}
@ -79,7 +79,7 @@ export abstract class Entity<T extends BaseModel,M extends Mapper<T>> {
await this.loadItem();
return this.item;
}
async delete(eventPublish?:keyof IServer_Common_Event_Types){
async delete(eventPublish?:keyof IServer_Common_Event_Types,...param:any){
await this.mapper.delete(this.item.id);
if(eventPublish) {
emitServiceEvent(eventPublish,this.item.id);

View File

@ -85,6 +85,10 @@ class RpcContentApi {
}
}
async clearByRefIdAndType(refId:string,type:ECommon_Model_Content_Type) {
await ContentService.clearByRefIdAndType(refId,type)
}
async clearByRefId(refId:string) {
await ContentService.clearByRefId(refId)
}

View File

@ -23,17 +23,19 @@ export class ContentService extends Entity<typeof contentModel,typeof contentMap
override async delete(eventPublish?: keyof IServer_Common_Event_Types): Promise<void> {
await super.delete(eventPublish);
for(let objLine of JSON.parse(this.getItem().content) as ICommon_Content_Line[]) {
for(let objConfig of objLine.arr) {
if(objConfig.type===ECommon_Content_Line_Config_Type.FILE || objConfig.type===ECommon_Content_Line_Config_Type.IMAGE) {
emitServiceEvent("fileUnref",objConfig.value)
if(this.getItem().content) {
for(let objLine of JSON.parse(this.getItem().content) as ICommon_Content_Line[]) {
for(let objConfig of objLine.arr) {
if(objConfig.type===ECommon_Content_Line_Config_Type.FILE || objConfig.type===ECommon_Content_Line_Config_Type.IMAGE) {
emitServiceEvent("fileUnref",objConfig.value)
}
}
}
}
}
override async create(): Promise<typeof contentModel["model"]> {
this.item.content=this.item.content.replaceAll("\\\"","\\\\\"")
this.item.content=this.item.content.replaceAll("\\","\\\\")
let ret=await super.create();
let obj=this.generateLineObj(ret.content)
for(let key in obj.file) {
@ -61,7 +63,7 @@ export class ContentService extends Entity<typeof contentModel,typeof contentMap
override async update(): Promise<typeof contentModel["model"]> {
let oldObj=this.generateLineObj(this._item.content)
this.item.content=this.item.content.replaceAll("\\\"","\\\\\"")
this.item.content=this.item.content.replaceAll("\\","\\\\")
let ret=await super.update();
let newObj=this.generateLineObj(ret.content)
for(let key in oldObj.file) {
@ -125,7 +127,7 @@ export class ContentService extends Entity<typeof contentModel,typeof contentMap
[param:string]:number
}={}
if(content) {
let arr=JSON.parse(content) as ICommon_Content_Line[]
let arr=JSON.parse(decodeURIComponent(content)) as ICommon_Content_Line[]
for(let obj of arr) {
for(let obj1 of obj.arr) {
if(obj1.type===ECommon_Content_Line_Config_Type.FILE || obj1.type===ECommon_Content_Line_Config_Type.IMAGE) {
@ -161,6 +163,18 @@ export class ContentService extends Entity<typeof contentModel,typeof contentMap
await Promise.all(arrPromise)
}
static async clearByRefIdAndType(refId:string,type:ECommon_Model_Content_Type) {
let arr=await ContentService.getItemsByExp({
ref_id:refId,
type
})
let arrPromise=[]
for(let obj of arr) {
arrPromise.push(obj.delete())
}
await Promise.all(arrPromise)
}
static async clearByRefIds(refIds:string[]) {
let arrPromise=[]
for(let refId of refIds) {

View File

@ -124,6 +124,7 @@ class FieldController {
@DHttpApi(fieldApi.routes.editWorkflowNodeFieldConfig)
async addWorkflowNodeFieldConfig(@DHttpReqParamRequired("workflowNodeFieldTypeId") workflowNodeFieldTypeId:string,
@DHttpReqParamRequired("data") data:{
id?:string,
value :string,
selected :number
}[]): Promise<typeof fieldApi.routes.editWorkflowNodeFieldConfig.res> {

View File

@ -7,7 +7,7 @@ import {
import {Err} from "../../../common/status/error";
import {DComponent} from "../../common/decorate/component";
import {DHttpApi, DHttpController, DHttpReqParam, DHttpReqParamRequired, DHttpUser} from "../../common/http/http";
import {ProjectIssueService} from "../service/issue";
import {ProjectIssueHistoryService, ProjectIssueService} from "../service/issue";
import {ProjectService} from "../service/project";
import {IUserSession} from "../../user/types/config";
import rpcContentApi from "../../content/rpc/content"
@ -15,6 +15,7 @@ import rpcNotificationApi from "../../notification/rpc/notification"
import {ECommon_Model_Notification_Type} from "../../../common/model/notification";
import rpcCooperationApi from "../rpc/cooperation"
import rpcUserApi from "../../user/rpc/user"
import {ECommon_Model_Project_Issue_History_Type} from "../../../common/model/project_issue_history";
@DComponent
@DHttpController(projectIssueApi)
@ -79,7 +80,7 @@ class IssueController {
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
await projectIssue.confirmNextNode(workflowActionId,values)
await projectIssue.confirmNextNode(workflowActionId,values,user.organizationInfo.organizationUserId)
rpcNotificationApi.createNotification(ECommon_Model_Notification_Type.ISSUE_WORKFLOW_CHANGE,projectIssueId,null,user.organizationInfo.organizationUserId)
return
}
@ -90,7 +91,7 @@ class IssueController {
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
let ret=await projectIssue.updateFieldValue(value)
let ret=await projectIssue.updateFieldValue(value,user.organizationInfo.organizationUserId)
rpcNotificationApi.createNotification(ECommon_Model_Notification_Type.ISSUE_FIELD_CHANGE,projectIssueId,null,user.organizationInfo.organizationUserId)
return ret;
}
@ -120,6 +121,30 @@ class IssueController {
if(name || priority) {
rpcNotificationApi.createNotification(ECommon_Model_Notification_Type.ISSUE_FIELD_CHANGE,projectIssueId,null,user.organizationInfo.organizationUserId)
}
let key:string,value:string
if(name) {
key="Name"
value=name
} else if(priority!==undefined) {
key="Priority"
value=String(priority)
} else if(assignerId) {
key="Assigner"
value=assignerId
} else if(reporterId) {
key="Reporter"
value=reporterId
}
let objHistory=new ProjectIssueHistoryService()
objHistory.assignItem({
project_issue_id:ret.id,
name:key,
type:ECommon_Model_Project_Issue_History_Type.UPDATE_FIELD,
organization_user_id:user.organizationInfo.organizationUserId,
project_id:ret.project_id,
value:value
})
objHistory.create()
return ret;
}
@DHttpApi(projectIssueApi.routes.editDescription)
@ -184,12 +209,12 @@ class IssueController {
return ret
}
@DHttpApi(projectIssueApi.routes.actionsInfo)
async actionsInfo(@DHttpReqParamRequired("projectIssueId") projectIssueId :string):Promise<typeof projectIssueApi.routes.actionsInfo.res> {
async actionsInfo(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.actionsInfo.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
let ret=await projectIssue.actionsInfo()
let ret=await projectIssue.actionsInfo(user.organizationInfo.organizationUserId)
return ret
}
@DHttpApi(projectIssueApi.routes.commentList)
@ -214,12 +239,18 @@ class IssueController {
return
}
@DHttpApi(projectIssueApi.routes.copy)
async copy(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.copy.res> {
async copy(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpReqParam("name") name :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.copy.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
let ret=await projectIssue.copy()
if(name) {
ret.assignItem({
name
})
await ret.update()
}
if(ret.getItem().assigner_id) {
rpcNotificationApi.createNotification(ECommon_Model_Notification_Type.ISSUE_ASSIGNER_ASSIGN,ret.getId(),ret.getItem().assigner_id,user.organizationInfo.organizationUserId)
}
@ -292,21 +323,21 @@ class IssueController {
return ret;
}
@DHttpApi(projectIssueApi.routes.bindLabel)
async bindTag(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpReqParamRequired("labelIds") labelIds :string[]):Promise<typeof projectIssueApi.routes.bindLabel.res> {
async bindTag(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpReqParamRequired("labelIds") labelIds :string[],@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.bindLabel.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
let ret=await projectIssue.bindLabels(labelIds)
let ret=await projectIssue.bindLabels(labelIds,user.organizationInfo.organizationUserId)
return ret;
}
@DHttpApi(projectIssueApi.routes.bindModule)
async bindModule(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpReqParam("moduleId") moduleId :string):Promise<typeof projectIssueApi.routes.bindModule.res> {
async bindModule(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpReqParam("moduleId") moduleId :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.bindModule.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
let ret=await projectIssue.bindModule(moduleId)
let ret=await projectIssue.bindModule(moduleId,user.organizationInfo.organizationUserId)
return ret;
}
@ -338,12 +369,12 @@ class IssueController {
}
@DHttpApi(projectIssueApi.routes.bindReleases)
async bindReleases(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpReqParamRequired("projectReleaseIds") projectReleaseIds :string[]):Promise<typeof projectIssueApi.routes.bindReleases.res> {
async bindReleases(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpReqParamRequired("projectReleaseIds") projectReleaseIds :string[],@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.bindReleases.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
let ret=await projectIssue.bindReleases(projectReleaseIds)
let ret=await projectIssue.bindReleases(projectReleaseIds,user.organizationInfo.organizationUserId)
return ret;
}
@ -360,4 +391,91 @@ class IssueController {
});
}
@DHttpApi(projectIssueApi.routes.createChildIssue)
async createChildIssue(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpReqParamRequired("name") name :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.createChildIssue.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
let ret=await projectIssue.copy()
ret.assignItem({
name
})
await Promise.all([
ret.update(),
projectIssue.addChildIssue(ret.getId())
])
if(ret.getItem().assigner_id) {
rpcNotificationApi.createNotification(ECommon_Model_Notification_Type.ISSUE_ASSIGNER_ASSIGN,ret.getId(),ret.getItem().assigner_id,user.organizationInfo.organizationUserId)
}
if(ret.getItem().reporter_id) {
rpcNotificationApi.createNotification(ECommon_Model_Notification_Type.ISSUE_REPORTER_ASSIGN,ret.getId(),ret.getItem().reporter_id,user.organizationInfo.organizationUserId)
}
return ret.getItem()
}
@DHttpApi(projectIssueApi.routes.listHistory)
async listHistory(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.listHistory.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
let ret=await ProjectIssueHistoryService.list(projectIssueId)
return ret;
}
@DHttpApi(projectIssueApi.routes.checkApproval)
async checkApproval(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.checkApproval.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
let ret=await projectIssue.checkApproval(user.organizationInfo.organizationUserId)
return {
access:ret
}
}
@DHttpApi(projectIssueApi.routes.revokeApproval)
async revokeApproval(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.revokeApproval.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
await projectIssue.revokeApproval()
return
}
@DHttpApi(projectIssueApi.routes.resolveApproval)
async resolveApproval(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.resolveApproval.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
await projectIssue.resolveApproval(user.organizationInfo.organizationUserId)
rpcNotificationApi.createNotification(ECommon_Model_Notification_Type.ISSUE_APPROVAL_RESOLVE,projectIssue.getId(),projectIssue.getItem().assigner_id,user.organizationInfo.organizationUserId)
return
}
@DHttpApi(projectIssueApi.routes.rejectApproval)
async rejectApproval(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpReqParamRequired("reason") reason :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.rejectApproval.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
await projectIssue.rejectApproval(user.organizationInfo.organizationUserId,reason)
rpcNotificationApi.createNotification(ECommon_Model_Notification_Type.ISSUE_APPROVAL_REJECT,projectIssue.getId(),projectIssue.getItem().assigner_id,user.organizationInfo.organizationUserId)
return
}
@DHttpApi(projectIssueApi.routes.commitApproval)
async commitApproval(@DHttpReqParamRequired("projectIssueId") projectIssueId :string,@DHttpUser user:IUserSession):Promise<typeof projectIssueApi.routes.commitApproval.res> {
let projectIssue=await ProjectIssueService.getItemById(projectIssueId)
if(!projectIssue) {
throw Err.Project.ProjectIssue.projectIssueNotFound
}
await projectIssue.commitApproval()
return
}
}

View File

@ -2,10 +2,19 @@ import projectApi from "../../../common/routes/project";
import {DComponent} from "../../common/decorate/component";
import {DHttpApi, DHttpController, DHttpReqParam, DHttpReqParamRequired} from "../../common/http/http";
import {ProjectLabelService} from "../service/label";
import {Err} from "../../../common/status/error";
@DComponent
@DHttpController(projectApi)
class TagController {
@DHttpApi(projectApi.routes.getLabel)
async getLabel(@DHttpReqParamRequired("labelId") labelId:string):Promise<typeof projectApi.routes.getLabel.res> {
let tag=await ProjectLabelService.getItemById(labelId)
if(!tag) {
throw Err.Project.Label.labelNotfound
}
return tag.getItem();
}
@DHttpApi(projectApi.routes.listLabel)
async listTag(@DHttpReqParamRequired("projectId") projectId:string,@DHttpReqParamRequired("page") page:number,@DHttpReqParamRequired("size") size:number,@DHttpReqParam("keyword") keyword:string):Promise<typeof projectApi.routes.listLabel.res> {
let tag=new ProjectLabelService()

Some files were not shown because too many files have changed in this diff Show More