Compare commits
37 Commits
master
...
text-dragg
104 changed files with 5471 additions and 8404 deletions
@ -1,219 +0,0 @@ |
|||
--- |
|||
kind: pipeline |
|||
type: docker |
|||
name: development |
|||
|
|||
# 常量值 |
|||
constants: |
|||
- &DEVELOPMENT_HOST test.tall.wiki |
|||
- &DEVELOPMENT_CMD |
|||
- npm config set registry http://registry.npm.taobao.org |
|||
- npm i |
|||
- npm run build |
|||
- &DEVELOPMENT_SCP_TARGET /home/experiment |
|||
- &DEVELOPMENT_URL https://test.tall.wiki/experiment/ |
|||
- &DEVELOPMENT_PORT 22 |
|||
- &DEVELOPMENT_NODE_VERSION node:16 |
|||
- &DEVELOPMENT_BRANCH develop |
|||
- &DEVELOPMENT_SCP_SOURCE dist/* |
|||
- &SCP_STRIP_DIR_LEVEL 1 |
|||
- &NOTIFY_WECHATROBOT_WEBHOOK https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=428e5c5d-f992-4349-939d-9c99556e50b8 |
|||
|
|||
|
|||
# 挂载的主机卷,可以映射到docker容器中 |
|||
volumes: |
|||
# maven构建缓存(宿主机目录) |
|||
- name: ssh_key |
|||
host: |
|||
path: /root/.ssh/ |
|||
- name: cache |
|||
host: |
|||
path: /var/lib/cache |
|||
- name: data |
|||
host: |
|||
path: /var/lib/data |
|||
|
|||
steps: |
|||
- name: restore-cache |
|||
image: drillster/drone-volume-cache |
|||
volumes: |
|||
- name: cache |
|||
path: /cache |
|||
settings: |
|||
restore: true |
|||
mount: |
|||
- ./node_modules |
|||
|
|||
- name: build |
|||
image: *DEVELOPMENT_NODE_VERSION |
|||
pull: if-not-exists # default always |
|||
# volumes: |
|||
# - name: cache |
|||
# path: /root/.m2 |
|||
commands: *DEVELOPMENT_CMD |
|||
|
|||
- name: rebuild-cache |
|||
image: drillster/drone-volume-cache |
|||
volumes: |
|||
- name: cache |
|||
path: /cache |
|||
settings: |
|||
rebuild: true |
|||
mount: |
|||
- ./node_modules |
|||
|
|||
- name: deploy-scp |
|||
image: appleboy/drone-scp |
|||
pull: if-not-exists |
|||
volumes: |
|||
- name: ssh_key |
|||
path: /root/.ssh/ |
|||
settings: |
|||
host: *DEVELOPMENT_HOST |
|||
port: *DEVELOPMENT_PORT |
|||
username: root |
|||
key_path: /root/.ssh/id_rsa |
|||
rm: true # true则会删除目标目录重建 |
|||
target: *DEVELOPMENT_SCP_TARGET |
|||
source: *DEVELOPMENT_SCP_SOURCE |
|||
strip_components: 1 # 去除的目录层数,如果没有该选项,则拷贝过去是 target/xxx.jar,1代表去除target |
|||
|
|||
- name: notify-wechatwork |
|||
image: fifsky/drone-wechat-work |
|||
pull: if-not-exists |
|||
settings: |
|||
url: *NOTIFY_WECHATROBOT_WEBHOOK |
|||
msgtype: markdown |
|||
content: | |
|||
{{if eq .Status "success" }} |
|||
#### 🎉 ${DRONE_REPO} 测试环境构建成功 |
|||
> Branch: ${DRONE_BRANCH} |
|||
> Commit: [${DRONE_COMMIT_MESSAGE} ](${DRONE_COMMIT_LINK}) |
|||
> Author: ${DRONE_COMMIT_AUTHOR} |
|||
> PATH: https://test.tall.wiki/experiment/ |
|||
> [点击查看](https://test.tall.wiki/experiment/) |
|||
{{else}} |
|||
#### ❌ ${DRONE_REPO} 测试环境构建失败 |
|||
> Branch: ${DRONE_BRANCH} |
|||
> Commit: [${DRONE_COMMIT_MESSAGE} ](${DRONE_COMMIT_LINK}) |
|||
> Author: ${DRONE_COMMIT_AUTHOR} |
|||
> 请立即修复!!! |
|||
> [点击查看](https://test.tall.wiki/experiment/) |
|||
{{end}} |
|||
when: |
|||
status: |
|||
- failure |
|||
- success |
|||
|
|||
trigger: |
|||
branch: |
|||
- *DEVELOPMENT_BRANCH |
|||
|
|||
--- |
|||
kind: pipeline |
|||
type: docker |
|||
name: production |
|||
|
|||
# 常量值 |
|||
constants: |
|||
- &PRODUCTION_HOST www.tall.wiki |
|||
- &PRODUCTION_CMD |
|||
- npm config set registry http://registry.npm.taobao.org |
|||
- npm i |
|||
- npm run build:prod |
|||
- &PRODUCTION_SCP_TARGET /home/experiment |
|||
- &PRODUCTION_BRANCH master |
|||
- &PRODUCTION_PORT 22 |
|||
- &PRODUCTION_NODE_VERSION node:16 |
|||
- &PRODUCTION_SCP_SOURCE dist/* |
|||
- &SCP_STRIP_DIR_LEVEL 1 |
|||
- &NOTIFY_WECHATROBOT_WEBHOOK https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=428e5c5d-f992-4349-939d-9c99556e50b8 |
|||
|
|||
# 挂载的主机卷,可以映射到docker容器中 |
|||
volumes: |
|||
# maven构建缓存(宿主机目录) |
|||
- name: ssh_key |
|||
host: |
|||
path: /root/.ssh/ |
|||
- name: cache |
|||
host: |
|||
path: /var/lib/cache |
|||
- name: data |
|||
host: |
|||
path: /var/lib/data |
|||
|
|||
steps: |
|||
- name: restore-cache |
|||
image: drillster/drone-volume-cache |
|||
volumes: |
|||
- name: cache |
|||
path: /cache |
|||
settings: |
|||
restore: true |
|||
mount: |
|||
- ./node_modules |
|||
|
|||
- name: build |
|||
image: *PRODUCTION_NODE_VERSION |
|||
pull: if-not-exists # default always |
|||
# volumes: |
|||
# - name: cache |
|||
# path: /root/.m2 |
|||
commands: *PRODUCTION_CMD |
|||
|
|||
- name: rebuild-cache |
|||
image: drillster/drone-volume-cache |
|||
volumes: |
|||
- name: cache |
|||
path: /cache |
|||
settings: |
|||
rebuild: true |
|||
mount: |
|||
- ./node_modules |
|||
|
|||
- name: deploy-scp |
|||
image: appleboy/drone-scp |
|||
pull: if-not-exists |
|||
volumes: |
|||
- name: ssh_key |
|||
path: /root/.ssh/ |
|||
settings: |
|||
host: *PRODUCTION_HOST |
|||
port: *PRODUCTION_PORT |
|||
username: root |
|||
key_path: /root/.ssh/id_rsa |
|||
rm: true # true则会删除目标目录重建 |
|||
target: *PRODUCTION_SCP_TARGET |
|||
source: *PRODUCTION_SCP_SOURCE |
|||
strip_components: 1 # 去除的目录层数,如果没有该选项,则拷贝过去是 target/xxx.jar,1代表去除target |
|||
|
|||
- name: notify-wechatwork |
|||
image: fifsky/drone-wechat-work |
|||
pull: if-not-exists |
|||
settings: |
|||
url: *NOTIFY_WECHATROBOT_WEBHOOK |
|||
msgtype: markdown |
|||
content: | |
|||
{{if eq .Status "success" }} |
|||
#### 🎉 ${DRONE_REPO} 生产环境构建成功 |
|||
> Branch: ${DRONE_BRANCH} |
|||
> Commit: [${DRONE_COMMIT_MESSAGE}](${DRONE_COMMIT_LINK}) |
|||
> Author: ${DRONE_COMMIT_AUTHOR} |
|||
> PATH: https://www.tall.wiki/experiment/ |
|||
> [点击查看](https://www.tall.wiki/experiment/) |
|||
{{else}} |
|||
#### ❌ ${DRONE_REPO} 生产环境构建失败 |
|||
> Branch: ${DRONE_BRANCH} |
|||
> Commit: [${DRONE_COMMIT_MESSAGE}](${DRONE_COMMIT_LINK}) |
|||
> Author: ${DRONE_COMMIT_AUTHOR} |
|||
> 请立即修复!!! |
|||
> [点击查看](https://www.tall.wiki/experiment/) |
|||
{{end}} |
|||
when: |
|||
status: |
|||
- failure |
|||
- success |
|||
|
|||
trigger: |
|||
branch: |
|||
- *PRODUCTION_BRANCH |
|||
@ -1 +1,5 @@ |
|||
VITE_API_URL=http://localhost:3000 |
|||
VITE_BASE_URL=http://101.201.226.163 |
|||
VITE_API_URL=http://101.201.226.163/gateway |
|||
VITE_MSG_URL=ws://101.201.226.163:8196/message/v4.0/ws |
|||
VITE_SERVICELIST=['ZERO', 'CONTEST', 'PT'] |
|||
VITE_VERSION=v4.0.0 |
|||
|
|||
@ -1 +1,5 @@ |
|||
VITE_API_URL=https://www.tall.wiki |
|||
VITE_BASE_URL=http://101.201.226.163 |
|||
VITE_API_URL=http://101.201.226.163/gateway |
|||
VITE_MSG_URL=ws://101.201.226.163:8196/message/v4.0/ws |
|||
VITE_SERVICELIST=['ZERO', 'CONTEST', 'PT'] |
|||
VITE_VERSION=v4.0.0 |
|||
|
|||
@ -1 +1,5 @@ |
|||
VITE_API_URL=https://test.tall.wiki |
|||
VITE_BASE_URL=http://101.201.226.163 |
|||
VITE_API_URL=http://101.201.226.163/gateway |
|||
VITE_MSG_URL=ws://101.201.226.163:8196/message/v4.0/ws |
|||
VITE_SERVICELIST=['ZERO', 'CONTEST', 'PT'] |
|||
VITE_VERSION=v4.0.0 |
|||
|
|||
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 52 KiB |
@ -0,0 +1,140 @@ |
|||
<template> |
|||
<div class="render-box shadow-lg block w-full task-card-plugin"> |
|||
<div |
|||
class="render-content block w-full" |
|||
:id="`render-${props.task.id}`" |
|||
:data-did="props.task.detailId" |
|||
:data-param="props.param" |
|||
:data-pdu="props.task.planDuration" |
|||
:data-pid="project.id" |
|||
:data-pstart="props.task.planStart" |
|||
:data-rdu="props.task.realDuration" |
|||
:data-rid="roleId" |
|||
:data-tid="props.task.id" |
|||
:data-tname="props.task.name" |
|||
:data-token="token" |
|||
:data-rstart="props.task.realStart" |
|||
:data-uid="userId" |
|||
:data-url="project.url" |
|||
:data-type="1" |
|||
></div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, onMounted, defineProps } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { getOtherPlugin, getConfigInfo } from 'apis'; |
|||
|
|||
const props = defineProps({ |
|||
task: { default: () => {}, type: Object }, |
|||
pluginId: { default: '1', type: String }, |
|||
styleType: { default: 0, type: Number }, |
|||
pluginTaskId: { default: '', type: String }, |
|||
businessPluginId: { default: '', type: String }, |
|||
param: { type: String, default: '' }, |
|||
}); |
|||
|
|||
const store = useStore(); |
|||
const project = computed(() => store.state.projects.project); // 项目信息 |
|||
const userId = computed(() => store.getters['user/userId']); |
|||
const roleId = computed(() => store.state.role.roleId); // 当前角色id |
|||
const allPlugin = computed(() => store.state.layout.allPlugin); // 所有插件列表 |
|||
const businessPlugin = computed(() => store.state.layout.businessPlugin); // 所有服务插件关联 |
|||
const token = computed(() => store.state.user.token); // token |
|||
|
|||
const pluginInfo = ref(null); |
|||
|
|||
onMounted(async () => { |
|||
await getPlugin(); |
|||
if (props.param && pluginInfo.value) { |
|||
// const configParam = JSON.parse(props.param); |
|||
pluginInfo.value.config = `var p${props.pluginId}_config = ${props.param}`; |
|||
} else { |
|||
await getConfig(); |
|||
} |
|||
|
|||
if (pluginInfo.value.html) { |
|||
// 注意: 截止目前版本2022年1月30日 直接this.pluginTaskId在APP端是不能行的 |
|||
// 已向官方提交bug 后期等待修复后 再做调整 |
|||
const content = window.document.getElementById(`render-${pluginInfo.value.renderId}`); |
|||
content.innerHTML = pluginInfo.value.html; |
|||
} |
|||
|
|||
if (pluginInfo.value.config) { |
|||
const scriptConfig = window.document.createElement('script'); |
|||
scriptConfig.innerHTML = pluginInfo.value.config; |
|||
window.document.body.appendChild(scriptConfig); |
|||
} |
|||
|
|||
if (pluginInfo.value.js) { |
|||
const scriptJs = window.document.createElement('script'); |
|||
scriptJs.innerHTML = pluginInfo.value.js; |
|||
window.document.body.appendChild(scriptJs); |
|||
} |
|||
}); |
|||
|
|||
async function getPlugin() { |
|||
const plugins = allPlugin.value || sessionStorage.getItem('allPlugin'); |
|||
if (plugins && JSON.parse(plugins)) { |
|||
// 有本地数据 |
|||
try { |
|||
const pluginLists = JSON.parse(plugins); |
|||
// pluginLists 可能没有find方法,交给catch处理 |
|||
const pluginTarget = pluginLists.find(item => item.id === props.pluginId); |
|||
pluginTarget.renderId = props.task.id; |
|||
pluginInfo.value = pluginTarget || null; |
|||
} catch (error) { |
|||
console.error('error: ', error); |
|||
pluginInfo.value = null; |
|||
} |
|||
} else { |
|||
// 没有本地数据 API 查询 |
|||
const params = { businessPluginId: props.businessPluginId }; |
|||
|
|||
try { |
|||
const res = await getOtherPlugin(params); |
|||
res.renderId = props.task.id; |
|||
pluginInfo.value = res || null; |
|||
} catch (err) { |
|||
pluginInfo.value = null; |
|||
console.error('err: ', err); |
|||
} |
|||
} |
|||
} |
|||
|
|||
async function getConfig() { |
|||
const businessPlugins = businessPlugin.value || sessionStorage.getItem('businessPlugin'); |
|||
if (businessPlugins && JSON.parse(businessPlugins)) { |
|||
// 有本地数据 |
|||
const businessPluginLists = JSON.parse(businessPlugins); |
|||
businessPluginLists.forEach(item => { |
|||
if (item.pluginConfigs) { |
|||
const pluginConfig = item.pluginConfigs.find(plugin => plugin.businessPluginId === props.businessPluginId); |
|||
if (pluginConfig && pluginConfig.config && pluginInfo.value) { |
|||
pluginInfo.value.config = pluginConfig.config; |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
// 没有本地数据 API 查询 |
|||
const params = { id: props.businessPluginId }; |
|||
|
|||
try { |
|||
const res = await getConfigInfo(params); |
|||
|
|||
if (pluginInfo.value && res.config) { |
|||
pluginInfo.value.config = res.config; |
|||
} |
|||
} catch (err) { |
|||
console.error('err: ', err); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.render-box { |
|||
overflow: hidden; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,61 @@ |
|||
<template> |
|||
<div class="px-2 py-1 border rounded-sm" @click="collapsed = !collapsed"> |
|||
<div class="flex justify-between items-center"> |
|||
<div>审核人</div> |
|||
<div class="flex items-center justify-end flex-1 text-sm"> |
|||
<div class="mx-1" v-for="(item, index) in showCheckers" :key="index">{{ item.name }}</div> |
|||
<div class="mx-1" v-show="checkedCheckers.length > 3">...</div> |
|||
<DownOutlined v-if="!collapsed" /> |
|||
<UpOutlined v-else /> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 隐藏的审核人选项 --> |
|||
<div v-show="collapsed" class="foot mt-2 flex flex-wrap"> |
|||
<a-button |
|||
v-for="(item, index) in checkers" |
|||
:key="index" |
|||
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'" |
|||
size="small" |
|||
class="my-1 mx-2" |
|||
@click.stop="handleSelectChecker(item)" |
|||
> |
|||
{{ item.name }} |
|||
</a-button> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, defineExpose } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
const store = useStore(); |
|||
// 是否显示全部审核人 |
|||
const collapsed = ref(false); |
|||
|
|||
// 审核人员列表 从store获取真实的项目成员列表 |
|||
// 项目加载之初 就已经获取了 |
|||
const checkers = computed(() => store.state.role.members); |
|||
// 所有选中的审核人 |
|||
const checkedCheckers = ref([]); |
|||
// 折叠状态 显示的选中的检查人(超过3个用...显示) |
|||
const showCheckers = computed(() => (checkedCheckers.value.length > 3 ? checkedCheckers.value.slice(0, 3) : checkedCheckers.value)); |
|||
|
|||
defineExpose({ checkedCheckers, collapsed }); |
|||
|
|||
/** |
|||
* 点击成员 切换检查人的选中状态 |
|||
* @param {object} member 成员对象 |
|||
*/ |
|||
function handleSelectChecker(member) { |
|||
const target = checkedCheckers.value.find(item => item.memberId === member.memberId); |
|||
if (target) { |
|||
// 已经选中 将此项移除选中的数组中 |
|||
checkedCheckers.value = checkedCheckers.value.filter(item => item.memberId !== member.memberId); |
|||
} else { |
|||
checkedCheckers.value.push(member); |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,117 @@ |
|||
<template> |
|||
<div class="border border-solid rounded-sm mt-5 p-2 pt-4 pl-1" style="border-color: #d9d9d9" @click="collapsed = !collapsed"> |
|||
<div class="relative flex justify-between"> |
|||
<div class="absolute text-sm bg-white reviewer-title">审核人</div> |
|||
<div class="flex flex-wrap"> |
|||
<div v-for="(item, index) in checkers" :key="index"> |
|||
<a-button |
|||
v-if="index < 4" |
|||
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'" |
|||
size="small" |
|||
class="m-1" |
|||
@click.stop="handleSelectChecker(item)" |
|||
> |
|||
{{ item.name }} |
|||
</a-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<DownOutlined style="line-height: 32px" v-if="!collapsed" /> |
|||
<UpOutlined style="line-height: 32px" v-else /> |
|||
</div> |
|||
|
|||
<!-- 隐藏的审核人选项 --> |
|||
<div v-show="!collapsed" class="mt-2 flex flex-wrap"> |
|||
<div v-for="(item, index) in checkers" :key="index"> |
|||
<a-button |
|||
v-if="index >= 4" |
|||
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'" |
|||
size="small" |
|||
class="m-1" |
|||
@click.stop="handleSelectChecker(item)" |
|||
> |
|||
{{ item.name }} |
|||
</a-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, defineExpose, defineProps } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
const props = defineProps({ |
|||
dataCheckers: { |
|||
type: Array, |
|||
default: () => [], |
|||
}, |
|||
}); |
|||
|
|||
const store = useStore(); |
|||
// 是否显示全部审核人 |
|||
const collapsed = ref(true); |
|||
|
|||
// 审核人员列表 从store获取真实的项目成员列表 |
|||
// 项目加载之初 就已经获取了 |
|||
const checkers = computed(() => store.state.role.members); |
|||
// 所有选中的审核人 |
|||
const checkedCheckers = ref([]); |
|||
|
|||
if (props.dataCheckers && checkers.value.length > 0) { |
|||
props.dataCheckers.forEach(item => { |
|||
item.name = item.checkerName; |
|||
item.memberId = item.checkerId; |
|||
}); |
|||
|
|||
checkedCheckers.value = props.dataCheckers; |
|||
|
|||
const arr = []; |
|||
checkers.value.forEach(item => { |
|||
const data = props.dataCheckers.find(checker => checker.memberId === item.memberId); |
|||
if (data) { |
|||
arr.push(item); |
|||
} |
|||
}); |
|||
|
|||
checkers.value.forEach(item => { |
|||
const data = props.dataCheckers.find(checker => checker.memberId === item.memberId); |
|||
if (!data) { |
|||
arr.push(item); |
|||
} |
|||
}); |
|||
|
|||
store.commit('role/setMembers', arr); |
|||
} |
|||
|
|||
// 折叠状态 显示的选中的检查人(超过3个用...显示) |
|||
// const showCheckers = computed(() => (checkedCheckers.value.length > 3 ? checkedCheckers.value.slice(0, 3) : checkedCheckers.value)); |
|||
|
|||
defineExpose({ checkedCheckers, collapsed }); |
|||
|
|||
/** |
|||
* 点击成员 切换检查人的选中状态 |
|||
* @param {object} member 成员对象 |
|||
*/ |
|||
function handleSelectChecker(member) { |
|||
const target = checkedCheckers.value.find(item => item.memberId === member.memberId); |
|||
if (target) { |
|||
// 已经选中 将此项移除选中的数组中 |
|||
checkedCheckers.value = checkedCheckers.value.filter(item => item.memberId !== member.memberId); |
|||
} else { |
|||
checkedCheckers.value.push(member); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.reviewer-title { |
|||
width: 60px; |
|||
height: 20px; |
|||
line-height: 20px; |
|||
text-align: center; |
|||
top: -25px; |
|||
left: 10px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,59 @@ |
|||
<template> |
|||
<draggable class="dragArea" tag="ul" :list="proList" :group="{ name: 'g1' }" item-key="name" :move="checkMove" @end="onEnd"> |
|||
<template #item="{ element }"> |
|||
<li> |
|||
<p :data-id="element.id">{{ element.name }}</p> |
|||
<nested :lists="element.sonProjectList || []" @changeData="handleChangeData" /> |
|||
</li> |
|||
</template> |
|||
</draggable> |
|||
</template> |
|||
|
|||
<script> |
|||
import { defineComponent, ref } from 'vue'; |
|||
import draggable from 'vuedraggable'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'nested', |
|||
props: { |
|||
lists: { |
|||
type: Array, |
|||
default: () => [], |
|||
}, |
|||
}, |
|||
components: { draggable }, |
|||
emit: ['changeData'], |
|||
setup(props, context) { |
|||
const proList = ref(props.lists); |
|||
const moveProjectId = ref(''); |
|||
const businessCode = ref(''); |
|||
|
|||
const checkMove = e => { |
|||
moveProjectId.value = e.draggedContext.element.id; |
|||
businessCode.value = e.draggedContext.element.businessCode; |
|||
}; |
|||
|
|||
const onEnd = e => { |
|||
console.log('Check', e, proList.value); |
|||
const param = { |
|||
moveProjectId: moveProjectId.value, |
|||
businessCode: businessCode.value, |
|||
}; |
|||
context.emit('changeData', param); |
|||
}; |
|||
|
|||
const handleChangeData = data => { |
|||
context.emit('changeData', data); |
|||
}; |
|||
|
|||
return { proList, checkMove, onEnd, handleChangeData }; |
|||
}, |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.dragArea { |
|||
min-height: 5px; |
|||
/* outline: 1px dashed; */ |
|||
} |
|||
</style> |
|||
@ -0,0 +1,508 @@ |
|||
<template> |
|||
<a-divider /> |
|||
<div class="list-flex"> |
|||
<div class="item-box" v-for="(item, index) in projects" :key="index"> |
|||
<div class="one-level h-70 cursor-pointer flex items-center"> |
|||
<!-- <div class="icon" @click.stop="showActionCard(item)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> --> |
|||
<div class="flex-none"> |
|||
<a-popover placement="bottomLeft"> |
|||
<template #content> |
|||
<p class="cursor-pointer" @click="showImportCard(item)">导入</p> |
|||
<p class="cursor-pointer" @click="exportProject(item.id)">导出</p> |
|||
<p class="m-0 cursor-pointer" @click="showDelCard(item)">删除</p> |
|||
</template> |
|||
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> |
|||
</a-popover> |
|||
</div> |
|||
|
|||
<div class="w-full flex items-center justify-between" @click="toDetail(item)"> |
|||
<div class="detail"> |
|||
<div class="name-box flex items-center" :class="{ 'mb-2': item.businessCode !== 'ZERO' }"> |
|||
<div class="name truncate">{{ item.name }}</div> |
|||
<div class="precent-num"> |
|||
{{ item.status === 1 ? '进行中' : item.status === 2 ? '已结束' : item.status === 0 ? '未开始' : '暂停' }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="time" v-if="item.businessCode !== 'ZERO'"> |
|||
{{ dayjs(Number(item.startTime)).format('MM-DD HH:mm') }} 至 |
|||
{{ dayjs(Number(item.endTime)).format('MM-DD HH:mm') }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="right flex justify-end items-center" @click.stop="openMenu"> |
|||
<a-button v-if="item.businessCode === 'ZERO'" class="mr-2" shape="round" type="primary" @click="toWorkbench(item)"> |
|||
工作台 |
|||
</a-button> |
|||
|
|||
<RightOutlined v-if="!item.show" @click="changeShow(item)" /> |
|||
<DownOutlined v-else @click="changeShow(item)" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="two-box" v-if="item.show"> |
|||
<div class="two-flex" v-for="(sonItem, sonIndex) in item.sonProjectList" :key="sonIndex"> |
|||
<div class="two-level h-70 cursor-pointer flex items-center"> |
|||
<!-- <div class="icon" @click.stop="showActionCard(sonItem)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> --> |
|||
|
|||
<div class="flex-none"> |
|||
<a-popover placement="bottomLeft"> |
|||
<template #content> |
|||
<!-- <p class="cursor-pointer" @click="showImportCard(sonItem)">导入</p> --> |
|||
<p class="cursor-pointer" @click="exportProject(sonItem.id)">导出</p> |
|||
<p class="m-0 cursor-pointer" @click="showDelCard(sonItem)">删除</p> |
|||
</template> |
|||
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> |
|||
</a-popover> |
|||
</div> |
|||
|
|||
<div class="flex items-center justify-between" style="width: calc(100% - 32px)" @click="toDetail(sonItem)"> |
|||
<div class="detail"> |
|||
<div class="name-box mb-2 flex items-center"> |
|||
<div class="name truncate">{{ sonItem.name }}</div> |
|||
<div class="precent-num"> |
|||
{{ item.status === 1 ? '进行中' : item.status === 2 ? '已结束' : item.status === 0 ? '未开始' : '暂停' }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="time"> |
|||
{{ dayjs(Number(sonItem.startTime)).format('MM-DD HH:mm') }} 至 |
|||
{{ dayjs(Number(sonItem.endTime)).format('MM-DD HH:mm') }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="right" @click.stop="openMenu"> |
|||
<RightOutlined v-if="!sonItem.show" @click="changeShow(sonItem)" /> |
|||
<DownOutlined v-else @click="changeShow(sonItem)" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<input class="import-wbs hidden" type="file" @change="toImport" /> |
|||
<a-modal v-model:visible="importVisible" title="导入" @ok="handleImport"> |
|||
<p>确定要导入到{{ importParent }}吗?</p> |
|||
</a-modal> |
|||
|
|||
<a-modal v-model:visible="visible" title="删除" @ok="handleOk"> |
|||
<p>确定要删除吗?</p> |
|||
</a-modal> |
|||
|
|||
<draggable |
|||
class="dragArea p-0 list-none" |
|||
tag="ul" |
|||
:list="projects" |
|||
:group="{ name: 'g1' }" |
|||
item-key="name" |
|||
:move="checkMove" |
|||
@end="onEnd" |
|||
> |
|||
<template #item="{ element }"> |
|||
<li class="item-box"> |
|||
<!-- <p>{{ element.name }}</p> --> |
|||
<div |
|||
class="one-level cursor-pointer h-60 flex items-center" |
|||
:class="{ 'h-70': element.sonProjectList && element.sonProjectList.length > 0 }" |
|||
:style="{ 'padding-top': element.sonProjectList && element.sonProjectList.length > 0 ? 0 : '5px' }" |
|||
> |
|||
<!-- <div class="icon" @click.stop="showActionCard(item)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> --> |
|||
<div class="flex-none"> |
|||
<a-popover placement="bottomLeft"> |
|||
<template #content> |
|||
<p class="cursor-pointer" @click="showImportCard(element)">导入</p> |
|||
<p class="cursor-pointer" @click="exportProject(element.id)">导出</p> |
|||
<p class="m-0 cursor-pointer" @click="showDelCard(element)">删除</p> |
|||
</template> |
|||
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> |
|||
</a-popover> |
|||
</div> |
|||
|
|||
<div class="w-full flex items-center justify-between" @click="toDetail(element)"> |
|||
<div class="detail"> |
|||
<div class="name-box flex items-center" :class="{ 'mb-2': element.businessCode !== 'ZERO' }"> |
|||
<div class="name truncate">{{ element.name }}</div> |
|||
<div class="precent-num"> |
|||
{{ element.status === 1 ? '进行中' : element.status === 2 ? '已结束' : element.status === 0 ? '未开始' : '暂停' }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="time" v-if="element.businessCode !== 'ZERO'"> |
|||
{{ dayjs(Number(element.startTime)).format('MM-DD HH:mm') }} 至 |
|||
{{ dayjs(Number(element.endTime)).format('MM-DD HH:mm') }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="right flex justify-end items-center" @click.stop="openMenu"> |
|||
<a-button v-if="element.businessCode === 'ZERO'" class="mr-2" shape="round" type="primary" @click="toWorkbench(element)"> |
|||
工作台 |
|||
</a-button> |
|||
|
|||
<RightOutlined v-if="!element.show" @click="changeShow(element)" /> |
|||
<DownOutlined v-else @click="changeShow(element)" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<draggable |
|||
class="dragArea p-0 list-none two-box" |
|||
tag="ul" |
|||
:list="element.sonProjectList" |
|||
:group="{ name: 'g1' }" |
|||
item-key="name" |
|||
:move="checkMove" |
|||
@end="onEnd" |
|||
> |
|||
<template #item="{ element }"> |
|||
<li class="two-flex"> |
|||
<!-- <p>{{ element.name }}</p> --> |
|||
<div class="two-level h-70 cursor-pointer flex items-center"> |
|||
<!-- <div class="icon" @click.stop="showActionCard(sonItem)"> |
|||
<img src="https://www.tall.wiki/staticrec/drag.svg" /> |
|||
</div> --> |
|||
|
|||
<div class="flex-none"> |
|||
<a-popover placement="bottomLeft"> |
|||
<template #content> |
|||
<!-- <p class="cursor-pointer" @click="showImportCard(sonItem)">导入</p> --> |
|||
<p class="cursor-pointer" @click="exportProject(element.id)">导出</p> |
|||
<p class="m-0 cursor-pointer" @click="showDelCard(element)">删除</p> |
|||
</template> |
|||
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> |
|||
</a-popover> |
|||
</div> |
|||
|
|||
<div class="flex items-center justify-between" style="width: calc(100% - 32px)" @click="toDetail(element)"> |
|||
<div class="detail"> |
|||
<div class="name-box mb-2 flex items-center"> |
|||
<div class="name truncate">{{ element.name }}</div> |
|||
<div class="precent-num"> |
|||
{{ element.status === 1 ? '进行中' : element.status === 2 ? '已结束' : element.status === 0 ? '未开始' : '暂停' }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="time"> |
|||
{{ dayjs(Number(element.startTime)).format('MM-DD HH:mm') }} 至 |
|||
{{ dayjs(Number(element.endTime)).format('MM-DD HH:mm') }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="right" @click.stop="openMenu"> |
|||
<RightOutlined v-if="!element.show" @click="changeShow(element)" /> |
|||
<DownOutlined v-else @click="changeShow(element)" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</li> |
|||
</template> |
|||
</draggable> |
|||
</li> |
|||
</template> |
|||
</draggable> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, reactive, watch, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { useRouter } from 'vue-router'; |
|||
import dayjs from 'dayjs'; |
|||
import { getProjects, delProject, setProjectSort, exportWbs, importWbs } from 'apis'; |
|||
import { RightOutlined, DownOutlined } from '@ant-design/icons-vue'; |
|||
import { message } from 'ant-design-vue'; |
|||
import draggable from 'vuedraggable'; |
|||
|
|||
const store = useStore(); |
|||
const router = useRouter(); |
|||
const visible = ref(false); // 删除弹框 |
|||
const importVisible = ref(false); // 导入弹框 |
|||
const importParent = ref(null); // 导入的目标项目名称 |
|||
const importParentId = ref(null); // 导入的目标项目ID |
|||
const currUrl = ref(null); // 当前导入的url |
|||
const deleteId = ref(null); // 删除项目id |
|||
const sessionProject = sessionStorage.getItem('project'); // 项目信息缓存 |
|||
const projectInfo = computed(() => store.state.projects.project); // 当前选择项目信息 |
|||
const projects = computed(() => store.state.projects.projects); // 项目列表 |
|||
const startTime = computed(() => store.state.layout.startTime); // 当前选择时间 |
|||
const endTime = computed(() => store.state.layout.endTime); // 当前选择时间 |
|||
const refreshProjects = computed(() => store.state.layout.refreshProjects); // 刷新项目列表 |
|||
|
|||
const moveProjectId = ref(''); // 移动项目id |
|||
const moveBusinessCode = ref(''); // 移动项目所属服务 |
|||
const targetProjectId = ref(''); // 目标项目id |
|||
|
|||
if (sessionProject && !projectInfo.value.id) { |
|||
// 如果有缓存,而且store存储为空,则将缓存信息放到store中,一般页面刷新后store数据清空的清空 |
|||
const info = JSON.parse(sessionProject); |
|||
store.commit('projects/setProject', info); |
|||
} |
|||
|
|||
// 判断时间是否存在,不存在则设置 |
|||
if (!startTime.value) { |
|||
const data = { |
|||
startTime: dayjs().startOf('day').format('x'), |
|||
endTime: dayjs().endOf('day').format('x'), |
|||
}; |
|||
store.commit('layout/setSelectTime', data); |
|||
} |
|||
|
|||
getProjectsList(); |
|||
|
|||
watch([startTime, endTime, refreshProjects], () => { |
|||
getProjectsList(); |
|||
}); |
|||
|
|||
// 获取项目列表 |
|||
async function getProjectsList() { |
|||
try { |
|||
const data = await getProjects(startTime.value, endTime.value); |
|||
data.forEach(item => { |
|||
item.show = false; |
|||
if (item.sonProjectList.length > 0) { |
|||
item.show = true; |
|||
} |
|||
}); |
|||
|
|||
store.commit('projects/setProjects', data); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 点击打开项目详情 |
|||
function toDetail(item) { |
|||
clearRolesData(); |
|||
clearTasksData(); |
|||
store.commit('projects/setProject', item); |
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', ''); |
|||
router.push({ path: '/tall/pc/home/test' }); |
|||
} |
|||
|
|||
// 父项目展开收起 |
|||
function changeShow(item) { |
|||
item.show = !item.show; |
|||
} |
|||
|
|||
// 打开导入确认弹框 |
|||
const showImportCard = item => { |
|||
importVisible.value = true; |
|||
importParent.value = item.name; |
|||
importParentId.value = item.id; |
|||
currUrl.value = item.url; |
|||
}; |
|||
|
|||
// 确认导入 |
|||
async function handleImport() { |
|||
importVisible.value = false; |
|||
await importProject(); |
|||
getProjectsList(); |
|||
} |
|||
|
|||
// 导入 |
|||
async function importProject() { |
|||
document.querySelector('.import-wbs').click(); |
|||
} |
|||
|
|||
async function toImport(e) { |
|||
try { |
|||
const file = e.target.files[0]; |
|||
const param = new FormData(); |
|||
param.append('file', file); |
|||
const config = { headers: { 'Content-Type': 'multipart/form-data' } }; |
|||
|
|||
const res = await importWbs(currUrl.value, param, importParentId.value); |
|||
} catch (error) { |
|||
message.info(error || '导入失败'); |
|||
} |
|||
} |
|||
|
|||
// 点击打开删除确认弹框 |
|||
const showDelCard = item => { |
|||
visible.value = true; |
|||
deleteId.value = item.id; |
|||
}; |
|||
|
|||
// 删除 |
|||
async function handleOk() { |
|||
visible.value = false; |
|||
await deleteProject(deleteId.value); |
|||
getProjectsList(); |
|||
} |
|||
|
|||
// 删除项目 |
|||
async function deleteProject(param) { |
|||
try { |
|||
await delProject(param); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 导出 |
|||
async function exportProject(id) { |
|||
try { |
|||
const params = { param: { projectId: id } }; |
|||
|
|||
const { url } = store.state.projects.project; |
|||
const data = await exportWbs(params, url); |
|||
window.open(data.url, '_blank'); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 清空角色数据 |
|||
function clearRolesData() { |
|||
store.commit('role/setInvisibleRoles', []); |
|||
store.commit('role/setVisibleRoles', []); |
|||
store.commit('role/setRoleId', ''); |
|||
} |
|||
|
|||
// 清空任务数据 |
|||
function clearTasksData() { |
|||
// 清空日常任务的数据 |
|||
store.commit('task/setPermanents', []); |
|||
store.commit('task/setDailyTasks', []); |
|||
// 清空定期任务数据 |
|||
store.commit('task/clearTasks'); |
|||
// 清空真实任务数据 |
|||
store.commit('task/clearRealTasks'); |
|||
store.commit('task/setUpNextPage', 1); |
|||
store.commit('task/setDownNextPage', 1); |
|||
} |
|||
|
|||
// 跳转到工作台 |
|||
function toWorkbench(item) { |
|||
toDetail(item); |
|||
store.commit('task/setTaskDetailShow', 'workbench'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
|
|||
function checkMove(e) { |
|||
moveProjectId.value = e.draggedContext.element.id; |
|||
moveBusinessCode.value = e.draggedContext.element.businessCode; |
|||
targetProjectId.value = ''; |
|||
} |
|||
|
|||
function onEnd(e) { |
|||
projects.value.forEach(item => { |
|||
if (item.sonProjectList) { |
|||
const index = item.sonProjectList.findIndex(sec => sec.id === moveProjectId.value); |
|||
if (index > -1) targetProjectId.value = item.id; |
|||
} |
|||
}); |
|||
|
|||
const params = { |
|||
moveProjectId: moveProjectId.value, |
|||
targetProjectId: targetProjectId.value, |
|||
businessCode: moveBusinessCode.value, |
|||
}; |
|||
|
|||
handleSort(params); |
|||
} |
|||
|
|||
// 排序 |
|||
async function handleSort(param) { |
|||
try { |
|||
const params = { param }; |
|||
await setProjectSort(params); |
|||
message.info('层级关系修改成功'); |
|||
} catch (error) { |
|||
message.info(error.msg || '层级关系修改失败'); |
|||
throw new Error(error); |
|||
} |
|||
|
|||
getProjectsList(); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.list-flex { |
|||
height: calc(100vh - 48px - 272px - 56px - 16px - 2px); |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.list-flex::-webkit-scrollbar { |
|||
width: 0 !important; |
|||
} |
|||
|
|||
.ant-divider-horizontal { |
|||
height: 16px; |
|||
background: #eeeeee; |
|||
margin: 0; |
|||
} |
|||
.project-list { |
|||
padding: 16px 0; |
|||
} |
|||
|
|||
.h-60 { |
|||
height: 60px; |
|||
} |
|||
|
|||
.h-70 { |
|||
height: 70px; |
|||
} |
|||
|
|||
.one-level { |
|||
padding: 0 16px; |
|||
} |
|||
|
|||
.two-level { |
|||
padding: 0 16px 0 32px; |
|||
} |
|||
|
|||
.three-level { |
|||
padding: 0 16px 0 48px; |
|||
} |
|||
|
|||
.icon { |
|||
margin-right: 8px; |
|||
width: 24px; |
|||
height: 24px; |
|||
} |
|||
|
|||
.detail { |
|||
width: calc(100% - 46px); |
|||
} |
|||
|
|||
.name { |
|||
margin-right: 8px; |
|||
font-size: 14px; |
|||
line-height: 1; |
|||
font-weight: 600; |
|||
max-width: calc(100% - 56px); |
|||
color: #333333; |
|||
} |
|||
|
|||
.precent-num { |
|||
width: 48px; |
|||
height: 18px; |
|||
line-height: 18px; |
|||
text-align: center; |
|||
border-radius: 18px; |
|||
background-color: rgba(24, 144, 255, 0.2); |
|||
color: #1890ff; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.time { |
|||
font-size: 12px; |
|||
color: #999999; |
|||
} |
|||
|
|||
.right { |
|||
width: 14px; |
|||
margin-left: 30px; |
|||
} |
|||
|
|||
.dragArea { |
|||
min-height: 5px; |
|||
/* outline: 1px dashed; */ |
|||
} |
|||
</style> |
|||
@ -0,0 +1,74 @@ |
|||
<template> |
|||
<p-task-title :task="task" v-if="pluginId === '1'" /> |
|||
|
|||
<!-- 交付物插件 --> |
|||
<p-deliver v-else-if="pluginId === '15'" /> |
|||
<!-- 交付物插件2 --> |
|||
<p-deliver-second v-else-if="pluginId === '25'" /> |
|||
|
|||
<!-- 资源管理 --> |
|||
<p-source-manage v-else-if="pluginId === '16'" /> |
|||
|
|||
<!-- 财务审批统计 --> |
|||
<p-finance-audit v-else-if="pluginId === '17'" /> |
|||
<!-- 财务条 --> |
|||
<p-finance v-else-if="pluginId === '18'" /> |
|||
|
|||
<!-- 个人和终端按钮--> |
|||
<p-account-management v-else-if="pluginId === '19'" /> |
|||
<!-- 域资源管理 --> |
|||
<p-domain-source-manage v-else-if="pluginId === '20'" /> |
|||
<!-- 项目版本管理 --> |
|||
<p-project-version-management v-else-if="pluginId === '21'" /> |
|||
|
|||
<!-- 任务名和跳转详情页箭头 --> |
|||
<p-task-to-detail :task="task" v-else-if="pluginId === '24'"></p-task-to-detail> |
|||
|
|||
<!-- 流水账插件 --> |
|||
<p-daily-account v-else-if="pluginId === '26'"></p-daily-account> |
|||
|
|||
<Render |
|||
v-else |
|||
:task="task" |
|||
:pluginId="pluginId" |
|||
:styleType="styleType" |
|||
:pluginTaskId="pluginTaskId" |
|||
:businessPluginId="businessPluginId" |
|||
:param="param" |
|||
/> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { provide, defineProps } from 'vue'; |
|||
// import { useStore } from 'vuex'; |
|||
import pTaskTitle from '@/plugins/p-task-title.vue'; |
|||
import pDeliver from '@/plugins/p-deliver/p-deliver.vue'; |
|||
import pDeliverSecond from '@/plugins/p-deliver-second/p-deliver-second.vue'; |
|||
import pDailyAccount from '@/plugins/p-daily-account/p-daily-account.vue'; |
|||
import pSourceManage from '@/plugins/p-source-manage/p-source-manage.vue'; |
|||
import pProjectVersionManagement from '@/plugins/p-project-version-management/p-project-version-management.vue'; |
|||
import pDomainSourceManage from '@/plugins/p-domain-source-manage/p-domain-source-manage.vue'; |
|||
import pAccountManagement from '@/plugins/p-account-management/p-account-management.vue'; |
|||
import pFinance from '@/plugins/p-finance/p-finance.vue'; |
|||
import pFinanceAudit from '@/plugins/p-finance/p-finance-audit.vue'; |
|||
import pTaskToDetail from '@/plugins/p-task-to-detail/p-task-to-detail.vue'; |
|||
|
|||
const props = defineProps({ |
|||
task: { default: () => {}, type: Object }, |
|||
pluginId: { default: '1', type: String }, |
|||
styleType: { default: 0, type: Number }, |
|||
pluginTaskId: { default: '', type: String }, |
|||
businessPluginId: { default: '', type: String }, |
|||
param: { type: String, default: '' }, |
|||
pluginInfo: { default: () => {}, type: Object }, |
|||
}); |
|||
|
|||
provide('task', props.task); |
|||
provide('pluginInfo', props.pluginInfo); |
|||
|
|||
// const store = useStore(); |
|||
// const roleId = computed(() => store.state.role.roleId); |
|||
// const token = computed(() => store.state.user.token); |
|||
// const userId = computed(() => store.getters['user/userId']); |
|||
// const projectId = computed(() => store.getters['project/projectId']); |
|||
</script> |
|||
@ -0,0 +1,11 @@ |
|||
<template> |
|||
<iframe :src="taskDetailUrl" class="w-full h-full"></iframe> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
|
|||
const store = useStore(); |
|||
const taskDetailUrl = computed(() => store.state.task.taskDetailUrl); // iframe详情页链接 |
|||
</script> |
|||
@ -1,210 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form ref="formRef" :model="assignExperFormData"> |
|||
<a-form-item> |
|||
<label class="color-3">实验名称</label> |
|||
<a-input v-model:value="assignExperFormData.name" placeholder="实验名称" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">完成期限</label> |
|||
<a-space direction="vertical" :size="12"> |
|||
<a-range-picker v-model:value="assignExperFormData.date" /> |
|||
</a-space> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">负责人</label> |
|||
<a-select |
|||
v-model:value="assignExperFormData.memberId" |
|||
show-search |
|||
optionFilterProp="label" |
|||
placeholder="负责人" |
|||
:options="options" |
|||
:filter-option="filterOption" |
|||
@search="handleSearch" |
|||
:getPopupContainer=" |
|||
triggerNode => { |
|||
return triggerNode.parentNode || document.body; |
|||
} |
|||
" |
|||
></a-select> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">实验目标</label> |
|||
<a-textarea v-model:value="assignExperFormData.target" placeholder="实验目标" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">确定</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, watch } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { createExperiment, memberQuery, getExperimentation } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const formRef = ref(null); |
|||
|
|||
const projectId = computed(() => store.getters['projects/projectId']); |
|||
const options = ref([]); |
|||
const experimentationId = computed(() => store.state.task.experimentationId); // 子课题ID |
|||
|
|||
// 子课题起止时间 |
|||
// const subStartTime = computed(() => store.state.layout.subStartTime); |
|||
// const subEndTime = computed(() => store.state.layout.subEndTime); |
|||
|
|||
// console.log(subStartTime.value, subEndTime.value); |
|||
|
|||
if (experimentationId.value) { |
|||
getSubProject(experimentationId.value); |
|||
} |
|||
|
|||
watch(experimentationId, async () => { |
|||
await getSubProject(experimentationId.value); |
|||
}); |
|||
|
|||
const assignExperFormData = ref({ |
|||
projectId: projectId.value, |
|||
id: '', |
|||
name: '', |
|||
memberId: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
target: '', |
|||
}); |
|||
|
|||
getList(); // 获取成员列表 |
|||
|
|||
const handleSearch = async value => { |
|||
await getList(value); // 获取成员列表 |
|||
}; |
|||
|
|||
const filterOption = (input, option) => { |
|||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
if (assignExperFormData.value.date.length > 0) { |
|||
assignExperFormData.value.date.forEach((item, index) => { |
|||
if (index === 0) { |
|||
assignExperFormData.value.startTime = dayjs(item).format('x'); |
|||
} else { |
|||
assignExperFormData.value.endTime = dayjs(item).format('x'); |
|||
} |
|||
}); |
|||
|
|||
// if (subStartTime.value && assignExperFormData.value.startTime < subStartTime.value) { |
|||
// message.info('实验开始时间不能小于子课题开始时间'); |
|||
// return false; |
|||
// } |
|||
// if (subEndTime.value && assignExperFormData.value.endTime > subEndTime.value) { |
|||
// message.info('实验结束时间不能小于子课题结束时间'); |
|||
// return false; |
|||
// } |
|||
} |
|||
|
|||
try { |
|||
const params = { param: assignExperFormData.value }; |
|||
await createExperiment(params); |
|||
store.commit('layout/setThirdPlanTime', { startTime: assignExperFormData.value.startTime, endTime: assignExperFormData.value.endTime }); |
|||
await getSubProject(experimentationId.value); |
|||
} catch (error) { |
|||
message.info(error); |
|||
} |
|||
}; |
|||
|
|||
// 获取成员列表 |
|||
async function getList(name) { |
|||
try { |
|||
const params = { param: { projectId: projectId.value, name } }; |
|||
const data = await memberQuery(params); |
|||
store.commit('task/setMembers', data); |
|||
options.value = []; |
|||
|
|||
data.forEach(item => { |
|||
const obj = { |
|||
label: item.memberName, |
|||
value: item.memberId, |
|||
}; |
|||
|
|||
options.value.push(obj); |
|||
}); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
async function getSubProject(id) { |
|||
try { |
|||
const params = { param: { id } }; |
|||
const data = await getExperimentation(params); |
|||
store.commit('layout/setRefreshProjects'); |
|||
|
|||
if (data) { |
|||
data.date = []; |
|||
if (data.startTime) { |
|||
const start = dayjs(Number(data.startTime)); |
|||
const end = dayjs(Number(data.endTime)); |
|||
data.date = [start, end]; |
|||
} |
|||
|
|||
data.projectId = projectId.value; |
|||
assignExperFormData.value = data; |
|||
} else { |
|||
assignExperFormData.value = { |
|||
projectId: projectId.value, |
|||
id: '', |
|||
name: '', |
|||
memberId: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
target: '', |
|||
}; |
|||
} |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,453 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form ref="formRef" :model="topicSubFormData"> |
|||
<a-form-item> |
|||
<label class="color-3">子课题名称</label> |
|||
<a-input v-model:value="topicSubFormData.name" placeholder="子课题名称" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">子课题负责人</label> |
|||
<a-select |
|||
v-model:value="topicSubFormData.memberId" |
|||
show-search |
|||
optionFilterProp="label" |
|||
placeholder="负责人" |
|||
:options="options" |
|||
:filter-option="filterOption" |
|||
@search="handleSearch" |
|||
:getPopupContainer=" |
|||
triggerNode => { |
|||
return triggerNode.parentNode || document.body; |
|||
} |
|||
" |
|||
></a-select> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">完成期限</label> |
|||
<a-space direction="vertical" :size="12"> |
|||
<a-range-picker v-model:value="topicSubFormData.date" /> |
|||
</a-space> |
|||
</a-form-item> |
|||
|
|||
<a-form-item class="form-item-dad"> |
|||
<div class="flex items-center" style="margin-bottom: 5px"> |
|||
<label class="color-3" style="margin-bottom: 0; margin-right: 8px">进度安排</label> |
|||
<PlusCircleOutlined style="color: #1890ff; font-size: 16px" @click="addMilestones" /> |
|||
</div> |
|||
|
|||
<div class="form-item-son" style="padding-left: 16px"> |
|||
<div v-for="(item, index) in stageList" :key="index"> |
|||
<a-form-item> |
|||
<label class="color-3">时间</label> |
|||
<a-space direction="vertical" :size="12"> |
|||
<a-range-picker v-model:value="item.date" /> |
|||
</a-space> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">指标</label> |
|||
|
|||
<a-checkbox-group v-model:value="item.checkContent"> |
|||
<a-row> |
|||
<a-col :span="5" class="deliverables"> |
|||
<a-checkbox value="1" @change="handleChange($event, item)"> |
|||
<span class="color-6">论文</span> |
|||
<a-input v-model:value="item.thesis" @change="handleInput($event, item, '1')" /> |
|||
</a-checkbox> |
|||
</a-col> |
|||
<a-col :span="15" class="deliverables"> |
|||
<a-checkbox value="2" @change="handleChange($event, item)"> |
|||
<span class="color-6">专利</span> |
|||
<a-input v-model:value="item.patent" @change="handleInput($event, item, '2')" /> |
|||
</a-checkbox> |
|||
</a-col> |
|||
<a-col :span="4" class="deliverables"> |
|||
<a-checkbox value="3" @change="handleChange($event, item)"> |
|||
<span class="color-6">软著</span> |
|||
<a-input v-model:value="item.theSoft" @change="handleInput($event, item, '3')" /> |
|||
</a-checkbox> |
|||
</a-col> |
|||
<a-col :span="5" class="deliverables-son" style="padding-left: 14px"> |
|||
<a-checkbox value="4" @change="handleChange($event, item)"> |
|||
<span class="color-6">SCI论文</span> |
|||
<a-input v-model:value="item.sciThesis" @change="handleInput($event, item, '4')" /> |
|||
</a-checkbox> |
|||
</a-col> |
|||
<a-col :span="5" class="deliverables-son" style="padding-left: 14px"> |
|||
<a-checkbox value="5" @change="handleChange($event, item)"> |
|||
<span class="color-6">发明专利</span> |
|||
<a-input v-model:value="item.inventPatent" @change="handleInput($event, item, '5')" /> |
|||
</a-checkbox> |
|||
</a-col> |
|||
<a-col :span="5" class="deliverables-son"> |
|||
<a-checkbox value="6" @change="handleChange($event, item)"> |
|||
<span class="color-6">实用新型</span> |
|||
<a-input v-model:value="item.practicalPatent" @change="handleInput($event, item, '6')" /> |
|||
</a-checkbox> |
|||
</a-col> |
|||
<a-col :span="5" class="deliverables-son"> |
|||
<a-checkbox value="7" @change="handleChange($event, item)"> |
|||
<span class="color-6">外观专利</span> |
|||
<a-input v-model:value="item.facadePatent" @change="handleInput($event, item, '7')" /> |
|||
</a-checkbox> |
|||
</a-col> |
|||
<a-col :span="4" class="deliverables-son"></a-col> |
|||
</a-row> |
|||
</a-checkbox-group> |
|||
</a-form-item> |
|||
</div> |
|||
</div> |
|||
</a-form-item> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">确定</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, watch } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { PlusCircleOutlined } from '@ant-design/icons-vue'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { saveSubExperiment, memberQuery, getSubExperiment } from 'apis'; |
|||
import dayjs from 'dayjs'; |
|||
|
|||
const store = useStore(); |
|||
const formRef = ref(null); |
|||
|
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const options = ref([]); |
|||
const detailId = computed(() => store.state.task.detailId); // 子课题ID |
|||
|
|||
// 项目起止时间 |
|||
const planStartTime = computed(() => store.state.layout.planStartTime); |
|||
const planEndTime = computed(() => store.state.layout.planEndTime); |
|||
|
|||
const topicSubFormData = ref({ |
|||
projectId: projectId.value, |
|||
id: detailId.value, |
|||
name: '', |
|||
memberId: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
stageDtoList: [], |
|||
}); |
|||
|
|||
const stageList = ref([ |
|||
{ |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
checkContent: [], |
|||
thesis: '', |
|||
sciThesis: '', |
|||
patent: '', |
|||
inventPatent: '', |
|||
practicalPatent: '', |
|||
facadePatent: '', |
|||
theSoft: '', |
|||
}, |
|||
]); |
|||
|
|||
if (detailId.value) { |
|||
getSubProject(detailId.value); |
|||
} else { |
|||
renderData(); |
|||
} |
|||
|
|||
watch(detailId, async () => { |
|||
if (detailId.value) { |
|||
await getSubProject(detailId.value); |
|||
} else { |
|||
renderData(); |
|||
} |
|||
}); |
|||
|
|||
getList(); // 获取成员列表 |
|||
|
|||
// 添加实施内容与目标 |
|||
function addMilestones() { |
|||
stageList.value.push({ |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
checkContent: [], |
|||
thesis: '', |
|||
sciThesis: '', |
|||
patent: '', |
|||
inventPatent: '', |
|||
practicalPatent: '', |
|||
facadePatent: '', |
|||
theSoft: '', |
|||
}); |
|||
} |
|||
|
|||
const handleChange = (e, data) => { |
|||
if (e.target.checked) { |
|||
if (e.target.value === '4') { |
|||
if (data.checkContent.indexOf('1') === -1) { |
|||
data.checkContent.push('1'); |
|||
} |
|||
} else if (e.target.value === '5' || e.target.value === '6' || e.target.value === '7') { |
|||
if (data.checkContent.indexOf('2') === -1) { |
|||
data.checkContent.push('2'); |
|||
} |
|||
} |
|||
} else if (e.target.value === '1') { |
|||
data.thesis = 0; |
|||
} else if (e.target.value === '2') { |
|||
data.patent = 0; |
|||
} else if (e.target.value === '3') { |
|||
data.theSoft = 0; |
|||
} else if (e.target.value === '4') { |
|||
data.sciThesis = 0; |
|||
} else if (e.target.value === '5') { |
|||
data.inventPatent = 0; |
|||
} else if (e.target.value === '6') { |
|||
data.practicalPatent = 0; |
|||
} else if (e.target.value === '7') { |
|||
data.facadePatent = 0; |
|||
} |
|||
}; |
|||
|
|||
const handleInput = (e, data, label) => { |
|||
if (e.data > 0) { |
|||
if (data.checkContent.indexOf(label) === -1) { |
|||
data.checkContent.push(label); |
|||
} |
|||
|
|||
if (label === '4') { |
|||
if (data.checkContent.indexOf('1') === -1) data.checkContent.push('1'); |
|||
} |
|||
|
|||
if (label === '5' || label === '6' || label === '7') { |
|||
if (data.checkContent.indexOf('2') === -1) data.checkContent.push('2'); |
|||
} |
|||
} else { |
|||
if (data.checkContent.indexOf(label) > -1) { |
|||
data.checkContent.splice(data.checkContent.indexOf(label), 1); |
|||
} |
|||
|
|||
if (label === '4') { |
|||
if (data.checkContent.indexOf('1') > -1) data.checkContent.splice(data.checkContent.indexOf('1'), 1); |
|||
} |
|||
|
|||
if (label === '5' || label === '6' || label === '7') { |
|||
if (data.checkContent.indexOf('2') > -1) data.checkContent.splice(data.checkContent.indexOf('2'), 1); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const handleSearch = async value => { |
|||
await getList(value); // 获取成员列表 |
|||
}; |
|||
|
|||
const filterOption = (input, option) => { |
|||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0; |
|||
}; |
|||
|
|||
// 获取成员列表 |
|||
async function getList(name) { |
|||
try { |
|||
const params = { param: { projectId: projectId.value, name } }; |
|||
const data = await memberQuery(params); |
|||
store.commit('task/setMembers', data); |
|||
options.value = []; |
|||
|
|||
data.forEach(item => { |
|||
const obj = { |
|||
label: item.memberName, |
|||
value: item.memberId, |
|||
}; |
|||
|
|||
options.value.push(obj); |
|||
}); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
const onSubmit = async () => { |
|||
let msgText = ''; |
|||
|
|||
if (topicSubFormData.value.date) { |
|||
topicSubFormData.value.date.forEach((item, index) => { |
|||
if (index === 0) { |
|||
topicSubFormData.value.startTime = dayjs(item).format('x'); |
|||
} else { |
|||
topicSubFormData.value.endTime = dayjs(item).format('x'); |
|||
} |
|||
}); |
|||
|
|||
if (topicSubFormData.value.startTime < planStartTime.value) { |
|||
msgText = '子课题开始时间不能小于项目起始时间'; |
|||
} |
|||
if (topicSubFormData.value.endTime > planEndTime.value) { |
|||
msgText = '子课题结束时间不能大于项目终止时间'; |
|||
} |
|||
} |
|||
|
|||
stageList.value.forEach(item => { |
|||
if (item.date.length > 0) { |
|||
item.date.forEach((val, key) => { |
|||
if (key === 0) { |
|||
item.startTime = dayjs(val).format('x'); |
|||
} else { |
|||
item.endTime = dayjs(val).format('x'); |
|||
} |
|||
}); |
|||
|
|||
if (item.startTime < topicSubFormData.value.startTime || item.endTime > topicSubFormData.value.endTime) { |
|||
msgText = '子课题进度安排起止必须在子课题起止时间'; |
|||
} |
|||
} |
|||
|
|||
if (item.checkContent.indexOf('1') > -1) { |
|||
if (!item.thesis) msgText = '请填写论文数量'; |
|||
} |
|||
if (item.checkContent.indexOf('2') > -1) { |
|||
if (!item.patent) msgText = '请填写专利数量'; |
|||
} |
|||
if (item.checkContent.indexOf('3') > -1) { |
|||
if (!item.theSoft) msgText = '请填写软著数量'; |
|||
} |
|||
if (item.checkContent.indexOf('4') > -1) { |
|||
if (!item.sciThesis) msgText = '请填写SCI论文数量'; |
|||
} |
|||
if (item.checkContent.indexOf('5') > -1) { |
|||
if (!item.inventPatent) msgText = '请填写发明专利数量'; |
|||
} |
|||
if (item.checkContent.indexOf('6') > -1) { |
|||
if (!item.practicalPatent) msgText = '请填写实用新型专利数量'; |
|||
} |
|||
if (item.checkContent.indexOf('7') > -1) { |
|||
if (!item.facadePatent) msgText = '请填写外观专利数量'; |
|||
} |
|||
if (item.thesis < item.sciThesis) msgText = 'SCI论文数量不能比总论文数量大'; |
|||
const totalPatent = Number(item.inventPatent) + Number(item.practicalPatent) + Number(item.facadePatent); |
|||
if (item.patent < totalPatent) msgText = '发明专利数量、实用新型专利数量、外观专利数量总和不能比总专利数量大'; |
|||
}); |
|||
|
|||
if (msgText) { |
|||
message.info(msgText); |
|||
return false; |
|||
} |
|||
|
|||
topicSubFormData.value.stageDtoList = [...stageList.value]; |
|||
|
|||
const params = { param: topicSubFormData.value }; |
|||
await saveSubExperiment(params); |
|||
store.commit('layout/setRefreshProjects'); |
|||
store.commit('layout/setSecPlanTime', { startTime: topicSubFormData.value.startTime, endTime: topicSubFormData.value.endTime }); |
|||
|
|||
if (detailId.value) { |
|||
getSubProject(detailId.value); |
|||
} else { |
|||
renderData(); |
|||
} |
|||
}; |
|||
|
|||
async function getSubProject(id) { |
|||
try { |
|||
const params = { param: { taskDetailId: id } }; |
|||
const data = await getSubExperiment(params); |
|||
|
|||
if (data) { |
|||
const start = dayjs(Number(data.startTime)); |
|||
const end = dayjs(Number(data.endTime)); |
|||
data.date = [start, end]; |
|||
data.projectId = projectId.value; |
|||
topicSubFormData.value = data; |
|||
data.subExperimentStageDtoList.forEach(item => { |
|||
item.startTime = item.stageStartTime; |
|||
item.endTime = item.stageEndTime; |
|||
console.log(item.startTime !== '0'); |
|||
if (item.startTime !== '0') { |
|||
item.date = [dayjs(Number(item.startTime)), dayjs(Number(item.endTime))]; |
|||
} |
|||
|
|||
item.checkContent = []; |
|||
if (item.thesis) item.checkContent.push('1'); |
|||
if (item.patent) item.checkContent.push('2'); |
|||
if (item.theSoft) item.checkContent.push('3'); |
|||
if (item.sciThesis) item.checkContent.push('4'); |
|||
if (item.inventPatent) item.checkContent.push('5'); |
|||
if (item.practicalPatent) item.checkContent.push('6'); |
|||
if (item.facadePatent) item.checkContent.push('7'); |
|||
}); |
|||
stageList.value = data.subExperimentStageDtoList; |
|||
} else { |
|||
// 如果返回值为空渲染空数据 |
|||
renderData(); |
|||
} |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 如果是添加渲染数据 |
|||
function renderData() { |
|||
topicSubFormData.value = { |
|||
projectId: projectId.value, |
|||
id: detailId.value, |
|||
name: '', |
|||
memberId: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
stageDtoList: [], |
|||
}; |
|||
stageList.value = [ |
|||
{ |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
checkContent: [], |
|||
thesis: '', |
|||
sciThesis: '', |
|||
patent: '', |
|||
inventPatent: '', |
|||
practicalPatent: '', |
|||
facadePatent: '', |
|||
theSoft: '', |
|||
}, |
|||
]; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,192 +0,0 @@ |
|||
<template> |
|||
<div class="task-progress flex flex-wrap justify-between"> |
|||
<div class="wrap overflow-hidden"> |
|||
<a-card title="任务目标"> |
|||
<div class="flex flex-wrap justify-center"> |
|||
<div class="achievements border border-right border-bottom text-center"> |
|||
<p class="num">{{ infoOne.thesis }}/{{ infoOne.totalThesis }}</p> |
|||
<p class="name">论文</p> |
|||
</div> |
|||
<div class="achievements border border-bottom text-center"> |
|||
<p class="num">{{ infoOne.patent }}/{{ infoOne.totalPatent }}</p> |
|||
<p class="name">专利</p> |
|||
</div> |
|||
<div class="achievements border border-right text-center"> |
|||
<p class="num">{{ infoOne.theSoft }}/{{ infoOne.totalTheSoft }}</p> |
|||
<p class="name">软著</p> |
|||
</div> |
|||
<div class="achievements border text-center"> |
|||
<p class="num">{{ infoOne.meeting }}/{{ infoOne.totalMeeting }}</p> |
|||
<p class="name">会议</p> |
|||
</div> |
|||
</div> |
|||
</a-card> |
|||
</div> |
|||
|
|||
<div class="wrap overflow-hidden"> |
|||
<a-card title="概览"> |
|||
<div class="topic"> |
|||
<p>{{ infoOne.name }}</p> |
|||
<a-progress |
|||
:percent="infoOne.masterSchedule" |
|||
:strokeWidth="22" |
|||
:show-info="false" |
|||
:stroke-color="'#1890FF'" |
|||
:trail-color="'rgba(24, 144, 255, 0.2)'" |
|||
/> |
|||
</div> |
|||
|
|||
<div class="sub-topic flex justify-between flex-wrap"> |
|||
<div class="topic" v-for="(item, index) in infoSec" :key="index"> |
|||
<p>{{ item.name }}</p> |
|||
<a-progress |
|||
:percent="item.masterSchedule" |
|||
:strokeWidth="22" |
|||
:show-info="false" |
|||
:stroke-color="colorList[index % 4].color" |
|||
:trail-color="colorList[index % 4].bgColor" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</a-card> |
|||
</div> |
|||
|
|||
<div class="wrap overflow-hidden" v-for="(item, index) in infoSec" :key="index"> |
|||
<a-card :title="item.name"> |
|||
<div class="flex flex-wrap justify-center"> |
|||
<div class="achievements border border-right border-bottom text-center"> |
|||
<p class="num">{{ item.thesis }}/{{ item.totalThesis }}</p> |
|||
<p class="name">论文</p> |
|||
</div> |
|||
<div class="achievements border border-bottom text-center"> |
|||
<p class="num">{{ item.patent }}/{{ item.totalPatent }}</p> |
|||
<p class="name">专利</p> |
|||
</div> |
|||
<div class="achievements border border-right text-center"> |
|||
<p class="num">{{ item.theSoft }}/{{ item.totalTheSoft }}</p> |
|||
<p class="name">软著</p> |
|||
</div> |
|||
<div class="achievements border text-center"> |
|||
<p class="num">{{ item.meeting }}/{{ item.totalMeeting }}</p> |
|||
<p class="name">会议</p> |
|||
</div> |
|||
</div> |
|||
</a-card> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { getExperimentProgress } from 'apis'; |
|||
import { useStore } from 'vuex'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const projectId = computed(() => store.getters['projects/projectId']); |
|||
const infoOne = ref({}); |
|||
const infoSec = ref([]); |
|||
|
|||
const colorList = ref([ |
|||
{ color: '#FF9191', bgColor: 'rgba(255, 145, 145, 0.2)' }, |
|||
{ color: '#FF934B', bgColor: 'rgba(255, 147, 75, 0.2)' }, |
|||
{ color: '#B991FF', bgColor: 'rgba(185, 145, 255, 0.2)' }, |
|||
{ color: '#91C1FF', bgColor: 'rgba(145, 193, 255, 0.2)' }, |
|||
]); |
|||
|
|||
progress(); |
|||
|
|||
async function progress() { |
|||
try { |
|||
const params = { param: { projectId: projectId.value } }; |
|||
const data = await getExperimentProgress(params); |
|||
infoOne.value = data.target; |
|||
infoSec.value = data.subTargetList; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.wrap { |
|||
margin-top: 16px; |
|||
width: calc((100% - 16px) / 2); |
|||
background-color: #fff; |
|||
border-radius: 10px; |
|||
border: 1px solid #cccccc; |
|||
} |
|||
|
|||
.wrap:nth-child(-n + 2) { |
|||
margin-top: 0; |
|||
} |
|||
|
|||
:deep(.ant-card-head) { |
|||
padding: 0 16px; |
|||
min-height: 45px; |
|||
max-height: 45px; |
|||
border-color: #cccccc; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
:deep(.ant-card-head-title) { |
|||
padding: 0; |
|||
line-height: 45px; |
|||
} |
|||
|
|||
.wrap .num { |
|||
font-size: 22px; |
|||
color: #4b8aff; |
|||
} |
|||
|
|||
.wrap .name { |
|||
color: #666666; |
|||
} |
|||
|
|||
.wrap p { |
|||
margin: 0; |
|||
} |
|||
|
|||
.border { |
|||
border-color: #fff; |
|||
} |
|||
.border-right { |
|||
border-right-color: #cccccc; |
|||
} |
|||
.border-left { |
|||
border-left-color: #cccccc; |
|||
} |
|||
.border-top { |
|||
border-top-color: #cccccc; |
|||
} |
|||
.border-bottom { |
|||
border-bottom-color: #cccccc; |
|||
} |
|||
|
|||
.achievements { |
|||
width: 40%; |
|||
padding: 25px 0; |
|||
} |
|||
|
|||
.topic p { |
|||
margin-bottom: 8px; |
|||
color: #666666; |
|||
} |
|||
|
|||
.sub-topic .topic { |
|||
margin-top: 40px; |
|||
width: calc((100% - 32px) / 2); |
|||
} |
|||
|
|||
.ant-card { |
|||
height: 100%; |
|||
} |
|||
|
|||
.ant-card :deep(.ant-card-body) { |
|||
height: calc(100% - 48px); |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
} |
|||
</style> |
|||
@ -1,246 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传项目结题报告</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
// 项目起止时间 |
|||
const planStartTime = computed(() => store.state.layout.planStartTime); |
|||
const planEndTime = computed(() => store.state.layout.planEndTime); |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
const time = dayjs(item.date).format('x'); |
|||
if (time < planStartTime.value || time > planEndTime.value) { |
|||
message.info('中期检查时间必须在项目起止时间之内'); |
|||
return false; |
|||
} |
|||
obj.answerList.push(time); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,235 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传合同扫描件 </a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
obj.answerList.push(dayjs(item.date).format('x')); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,172 +0,0 @@ |
|||
<template> |
|||
<div class="flex flex-wrap"> |
|||
<a-card title="解锁申请" v-for="(item, index) in unlockList" :key="index"> |
|||
<p> |
|||
<span class="color-9">申请人</span><span class="color-3">{{ item.proposer }}</span> |
|||
</p> |
|||
<p> |
|||
<span class="color-9">实验名称</span><span class="color-3">{{ item.experimentName }}</span> |
|||
</p> |
|||
<p> |
|||
<span class="color-9">申请时间</span><span class="color-3">{{ dayjs(Number(item.time)).format('YYYY-MM-DD HH:mm') }}</span> |
|||
</p> |
|||
<p> |
|||
<span class="color-9">申请原因</span><span class="color-3">{{ item.remark }}</span> |
|||
</p> |
|||
<div class="flex justify-end"> |
|||
<a-button class="action-btn edit-btn" type="primary" @click="toExamine(0, item.experimentId)">通过</a-button> |
|||
<a-button class="action-btn del-btn" type="primary" @click="showModal(item.experimentId)">驳回</a-button> |
|||
</div> |
|||
</a-card> |
|||
</div> |
|||
|
|||
<!-- 拒绝模态框 --> |
|||
<a-modal v-model:visible="visible" :closable="false" @ok="handleOk"> |
|||
<div class="modal-title flex items-center"> |
|||
<CloseCircleFilled style="margin-right: 8px; font-size: 18px; color: #ff5353" /> |
|||
<span class="color-3" style="font-size: 18px; font-weight: 600">确定要驳回该条解锁申请吗?</span> |
|||
</div> |
|||
<div class="modal-con color-9" style="padding-left: 24px; margin-top: 16px"> |
|||
<div style="margin-bottom: 5px; font-size: 16px; line-height: 26px">驳回原因</div> |
|||
<a-textarea v-model:value="remark" placeholder="驳回原因" :auto-size="{ minRows: 2, maxRows: 5 }" /> |
|||
</div> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { retrospectUnlock, examineUnlock } from 'apis'; |
|||
import { CloseCircleFilled } from '@ant-design/icons-vue'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const projectId = computed(() => store.getters['projects/projectId']); |
|||
|
|||
const unlockList = ref([]); |
|||
const visible = ref(false); |
|||
const remark = ref(null); |
|||
const experimentId = ref(null); |
|||
|
|||
getUnlockList(); |
|||
|
|||
const showModal = id => { |
|||
visible.value = true; |
|||
experimentId.value = id; |
|||
}; |
|||
|
|||
const handleOk = e => { |
|||
console.log(e); |
|||
visible.value = false; |
|||
toExamine(1, experimentId.value); |
|||
}; |
|||
|
|||
// 数据追溯解锁列表 |
|||
async function getUnlockList() { |
|||
try { |
|||
const params = { param: { projectId: projectId.value } }; |
|||
|
|||
const data = await retrospectUnlock(params); |
|||
unlockList.value = data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 审核 |
|||
async function toExamine(type, id) { |
|||
try { |
|||
const params = { |
|||
param: { |
|||
projectId: projectId.value, |
|||
type, |
|||
experimentId: id, |
|||
remark: remark.value, |
|||
}, |
|||
}; |
|||
|
|||
const data = await examineUnlock(params); |
|||
unlockList.value = data; |
|||
getUnlockList(); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.ant-card { |
|||
border-radius: 10px; |
|||
margin-right: 16px; |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
@media screen and (max-width: 1650px) { |
|||
.ant-card { |
|||
width: calc(50% - 8px); |
|||
} |
|||
|
|||
.ant-card:nth-child(2n) { |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
|
|||
@media screen and (min-width: 1651px) { |
|||
.ant-card { |
|||
width: calc((100% - 32px) / 3); |
|||
} |
|||
|
|||
.ant-card:nth-child(3n) { |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
|
|||
:deep(.ant-card-head) { |
|||
padding: 0 16px; |
|||
height: 45px; |
|||
min-height: 45px; |
|||
} |
|||
|
|||
:deep(.ant-card-head-title) { |
|||
padding: 0; |
|||
line-height: 45px; |
|||
font-size: 14px; |
|||
color: #333; |
|||
} |
|||
|
|||
:deep(.ant-card-body) { |
|||
padding: 24px 16px; |
|||
} |
|||
|
|||
:deep(.ant-card-body) p { |
|||
line-height: 1; |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
:deep(.ant-card-body) p span:first-of-type { |
|||
display: inline-block; |
|||
width: 80px; |
|||
} |
|||
|
|||
.action-btn { |
|||
width: 50px !important; |
|||
height: 28px !important; |
|||
font-size: 14px !important; |
|||
padding: 0; |
|||
letter-spacing: 0 !important; |
|||
} |
|||
|
|||
.edit-btn { |
|||
background: #0dc26c; |
|||
border: 0; |
|||
} |
|||
|
|||
.del-btn { |
|||
margin-left: 16px; |
|||
background: #ff5353; |
|||
border: 0; |
|||
} |
|||
</style> |
|||
@ -1,287 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true"> |
|||
确定 |
|||
</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
|
|||
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" /> |
|||
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
// import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
const isShowSuccess = ref(false); |
|||
const isShowWarning = ref(false); |
|||
const tipsMessage = ref(''); |
|||
|
|||
// 实验起止时间 |
|||
// const expreStartTime = computed(() => store.state.layout.expreStartTime); |
|||
// const expreEndTime = computed(() => store.state.layout.expreEndTime); |
|||
|
|||
// 试验状态 |
|||
const expreStatus = computed(() => store.state.projects.expreStatus); |
|||
const sessionStatus = sessionStorage.getItem('expreStatus'); |
|||
|
|||
if (!expreStatus.value && sessionStatus) { |
|||
store.commit('projects/setExpreimentStatus', sessionStatus); |
|||
} |
|||
|
|||
if (expreStatus.value) { |
|||
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) { |
|||
isShowWarning.value = true; |
|||
tipsMessage.value = '数据已锁定,不可操作'; |
|||
} else { |
|||
isShowSuccess.value = true; |
|||
tipsMessage.value = '数据未锁定,可操作'; |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
isShowWarning.value = false; |
|||
isShowSuccess.value = false; |
|||
}, 3000); |
|||
} |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
const time = dayjs(item.date).format('x'); |
|||
// if (time < expreStartTime.value || time > expreEndTime.value) { |
|||
// message.info('实验日期必须在实验起止时间之内'); |
|||
// return false; |
|||
// } |
|||
obj.answerList.push(time); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-form { |
|||
position: relative; |
|||
} |
|||
|
|||
.task-form :deep(.ant-alert) { |
|||
position: absolute; |
|||
right: 30px; |
|||
} |
|||
|
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,287 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true"> |
|||
确定 |
|||
</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
|
|||
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" /> |
|||
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
// import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
const isShowSuccess = ref(false); |
|||
const isShowWarning = ref(false); |
|||
const tipsMessage = ref(''); |
|||
|
|||
// 实验起止时间 |
|||
// const expreStartTime = computed(() => store.state.layout.expreStartTime); |
|||
// const expreEndTime = computed(() => store.state.layout.expreEndTime); |
|||
|
|||
// 试验状态 |
|||
const expreStatus = computed(() => store.state.projects.expreStatus); |
|||
const sessionStatus = sessionStorage.getItem('expreStatus'); |
|||
|
|||
if (!expreStatus.value && sessionStatus) { |
|||
store.commit('projects/setExpreimentStatus', sessionStatus); |
|||
} |
|||
|
|||
if (expreStatus.value) { |
|||
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) { |
|||
isShowWarning.value = true; |
|||
tipsMessage.value = '数据已锁定,不可操作'; |
|||
} else { |
|||
isShowSuccess.value = true; |
|||
tipsMessage.value = '数据未锁定,可操作'; |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
isShowWarning.value = false; |
|||
isShowSuccess.value = false; |
|||
}, 3000); |
|||
} |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
const time = dayjs(item.date).format('x'); |
|||
// if (time < expreStartTime.value || time > expreEndTime.value) { |
|||
// message.info('实验日期必须在实验起止时间之内'); |
|||
// return false; |
|||
// } |
|||
obj.answerList.push(time); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-form { |
|||
position: relative; |
|||
} |
|||
|
|||
.task-form :deep(.ant-alert) { |
|||
position: absolute; |
|||
right: 30px; |
|||
} |
|||
|
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,277 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true"> |
|||
确定 |
|||
</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
|
|||
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" /> |
|||
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
const isShowSuccess = ref(false); |
|||
const isShowWarning = ref(false); |
|||
const tipsMessage = ref(''); |
|||
|
|||
// 试验状态 |
|||
const expreStatus = computed(() => store.state.projects.expreStatus); |
|||
const sessionStatus = sessionStorage.getItem('expreStatus'); |
|||
|
|||
if (!expreStatus.value && sessionStatus) { |
|||
store.commit('projects/setExpreimentStatus', sessionStatus); |
|||
} |
|||
|
|||
if (expreStatus.value) { |
|||
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) { |
|||
isShowWarning.value = true; |
|||
tipsMessage.value = '数据已锁定,不可操作'; |
|||
} else { |
|||
isShowSuccess.value = true; |
|||
tipsMessage.value = '数据未锁定,可操作'; |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
isShowWarning.value = false; |
|||
isShowSuccess.value = false; |
|||
}, 3000); |
|||
} |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
obj.answerList.push(dayjs(item.date).format('x')); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-form { |
|||
position: relative; |
|||
} |
|||
|
|||
.task-form :deep(.ant-alert) { |
|||
position: absolute; |
|||
right: 30px; |
|||
} |
|||
|
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,246 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传中期检查报告</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
// 项目起止时间 |
|||
const planStartTime = computed(() => store.state.layout.planStartTime); |
|||
const planEndTime = computed(() => store.state.layout.planEndTime); |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
const time = dayjs(item.date).format('x'); |
|||
if (time < planStartTime.value || time > planEndTime.value) { |
|||
message.info('中期检查时间必须在项目起止时间之内'); |
|||
return false; |
|||
} |
|||
obj.answerList.push(time); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,274 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="false" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
@change="handleChange($event, index)" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true"> |
|||
确定 |
|||
</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
|
|||
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" /> |
|||
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
// import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
|
|||
const isShowSuccess = ref(false); |
|||
const isShowWarning = ref(false); |
|||
const tipsMessage = ref(''); |
|||
|
|||
// 实验起止时间 |
|||
// const expreStartTime = computed(() => store.state.layout.expreStartTime); |
|||
// const expreEndTime = computed(() => store.state.layout.expreEndTime); |
|||
|
|||
// 试验状态 |
|||
const expreStatus = computed(() => store.state.projects.expreStatus); |
|||
const sessionStatus = sessionStorage.getItem('expreStatus'); |
|||
|
|||
if (!expreStatus.value && sessionStatus) { |
|||
store.commit('projects/setExpreimentStatus', sessionStatus); |
|||
} |
|||
|
|||
if (expreStatus.value) { |
|||
console.log(expreStatus.value); |
|||
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) { |
|||
isShowWarning.value = true; |
|||
isShowSuccess.value = false; |
|||
tipsMessage.value = '数据已锁定,不可操作'; |
|||
} else { |
|||
isShowWarning.value = false; |
|||
isShowSuccess.value = true; |
|||
tipsMessage.value = '数据未锁定,可操作'; |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
isShowWarning.value = false; |
|||
isShowSuccess.value = false; |
|||
}, 3000); |
|||
} |
|||
|
|||
getDataByCode(); |
|||
|
|||
const handleChange = (info, index) => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 获取最新上传 |
|||
const arr = ref([]); |
|||
resFileList.forEach((item, key) => { |
|||
if (key === resFileList.length - 1) { |
|||
arr.value.push(item); |
|||
} |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[index].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
const time = dayjs(item.date).format('x'); |
|||
// if (time < expreStartTime.value || time > expreEndTime.value) { |
|||
// message.info('实验日期必须在实验起止时间之内'); |
|||
// return false; |
|||
// } |
|||
obj.answerList.push(time); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-form { |
|||
position: relative; |
|||
} |
|||
|
|||
.task-form :deep(.ant-alert) { |
|||
position: absolute; |
|||
right: 30px; |
|||
} |
|||
|
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,292 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form :model="topicMeetFormData"> |
|||
<a-form-item> |
|||
<label class="color-3">会议名称</label> |
|||
<a-input v-model:value="topicMeetFormData.name" placeholder="会议名称" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">会议日期</label> |
|||
<a-space direction="vertical" :size="12"> |
|||
<a-range-picker v-model:value="topicMeetFormData.date" /> |
|||
</a-space> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">会议地点</label> |
|||
<a-input v-model:value="topicMeetFormData.address" placeholder="会议地点" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">会议通知</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="notificationList" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
@change="handleChange($event, 1)" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">会议纪要</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="summaryList" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
@change="handleChange($event, 2)" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">照片附件/其他</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="attachmentList" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.jpg,.jpeg,.rar,.zip,.png'" |
|||
@change="handleChange($event, 3)" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:jpg、jpeg、rar、zip</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传会议资料</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, watch } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg, saveMeeting, getMeetDetail } from 'apis'; |
|||
import dayjs from 'dayjs'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
const notificationList = ref([]); |
|||
const summaryList = ref([]); |
|||
const attachmentList = ref([]); |
|||
const meetId = computed(() => store.state.task.meetId); |
|||
const projectId = computed(() => store.getters['projects/projectId']); |
|||
|
|||
const topicMeetFormData = ref({ |
|||
projectId: projectId.value, |
|||
id: '', |
|||
name: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
address: '', |
|||
notificationList: [], |
|||
summaryList: [], |
|||
attachmentList: [], |
|||
}); |
|||
|
|||
watch(meetId, async () => { |
|||
if (meetId.value) { |
|||
await getMeetingInfo(); |
|||
} else { |
|||
topicMeetFormData.value = { |
|||
projectId: projectId.value, |
|||
id: '', |
|||
name: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
address: '', |
|||
notificationList: [], |
|||
summaryList: [], |
|||
attachmentList: [], |
|||
}; |
|||
|
|||
notificationList.value = []; |
|||
summaryList.value = []; |
|||
attachmentList.value = []; |
|||
} |
|||
}); |
|||
|
|||
const handleChange = (info, index) => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let flag = false; |
|||
|
|||
arr.value.forEach(item => { |
|||
if (file.name === item.name) { |
|||
flag = true; |
|||
} |
|||
}); |
|||
if (!flag) { |
|||
arr.value.push(file); |
|||
} |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
if (index === 1) { |
|||
notificationList.value = arr.value; |
|||
} else if (index === 2) { |
|||
summaryList.value = arr.value; |
|||
} else if (index === 3) { |
|||
attachmentList.value = arr.value; |
|||
} |
|||
}; |
|||
|
|||
// 根据ID查询会议详情 |
|||
async function getMeetingInfo() { |
|||
try { |
|||
const params = { param: { id: meetId.value } }; |
|||
const data = await getMeetDetail(params); |
|||
if (data) { |
|||
data.projectId = projectId.value; |
|||
const start = dayjs(Number(data.startTime)); |
|||
const end = dayjs(Number(data.endTime)); |
|||
data.date = [start, end]; |
|||
topicMeetFormData.value = data; |
|||
|
|||
notificationList.value = []; |
|||
summaryList.value = []; |
|||
attachmentList.value = []; |
|||
data.notificationList.forEach(item => { |
|||
const obj = { |
|||
id: item.fileId, |
|||
name: item.fileName, |
|||
url: item.filePath, |
|||
}; |
|||
|
|||
notificationList.value.push(obj); |
|||
}); |
|||
|
|||
data.summaryList.forEach(item => { |
|||
const obj = { |
|||
id: item.fileId, |
|||
name: item.fileName, |
|||
url: item.filePath, |
|||
}; |
|||
|
|||
summaryList.value.push(obj); |
|||
}); |
|||
|
|||
data.attachmentList.forEach(item => { |
|||
const obj = { |
|||
id: item.fileId, |
|||
name: item.fileName, |
|||
url: item.filePath, |
|||
}; |
|||
|
|||
attachmentList.value.push(obj); |
|||
}); |
|||
} else { |
|||
topicMeetFormData.value = { |
|||
projectId: projectId.value, |
|||
id: '', |
|||
name: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
address: '', |
|||
notificationList: [], |
|||
summaryList: [], |
|||
attachmentList: [], |
|||
}; |
|||
notificationList.value = []; |
|||
summaryList.value = []; |
|||
attachmentList.value = []; |
|||
} |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
const onSubmit = async () => { |
|||
notificationList.value.forEach(item => { |
|||
const obj = { |
|||
fileId: item.id, |
|||
fileName: item.name, |
|||
filePath: item.url, |
|||
}; |
|||
|
|||
topicMeetFormData.value.notificationList.push(obj); |
|||
}); |
|||
|
|||
summaryList.value.forEach(item => { |
|||
const obj = { |
|||
fileId: item.id, |
|||
fileName: item.name, |
|||
filePath: item.url, |
|||
}; |
|||
|
|||
topicMeetFormData.value.summaryList.push(obj); |
|||
}); |
|||
|
|||
attachmentList.value.forEach(item => { |
|||
const obj = { |
|||
fileId: item.id, |
|||
fileName: item.name, |
|||
filePath: item.url, |
|||
}; |
|||
|
|||
topicMeetFormData.value.attachmentList.push(obj); |
|||
}); |
|||
|
|||
topicMeetFormData.value.date.forEach((item, index) => { |
|||
if (index === 0) { |
|||
topicMeetFormData.value.startTime = dayjs(item).format('x'); |
|||
} else { |
|||
topicMeetFormData.value.endTime = dayjs(item).format('x'); |
|||
} |
|||
}); |
|||
|
|||
const params = { param: topicMeetFormData.value }; |
|||
await saveMeeting(params); |
|||
getMeetingInfo(); |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
</style> |
|||
@ -1,258 +0,0 @@ |
|||
<template> |
|||
<div> |
|||
<a-table |
|||
class="member-list" |
|||
:columns="columns" |
|||
:data-source="dataList" |
|||
:row-class-name="(_record, index) => (index % 2 === 1 ? null : 'table-striped')" |
|||
> |
|||
<template #bodyCell="{ column, text, record }"> |
|||
<template v-if="column.key === 'action'"> |
|||
<template v-if="record.isEdit === 0"> |
|||
<a-button class="action-btn edit-btn" type="primary" @click="toEdit(record.key)">编辑</a-button> |
|||
<a-button class="action-btn del-btn" type="primary" @click="toDelete(record.key)">删除</a-button> |
|||
</template> |
|||
|
|||
<template v-if="record.isEdit === 1"> |
|||
<a-button class="action-btn edit-btn" type="primary" @click="save(record.key)">保存</a-button> |
|||
<a-button class="action-btn del-btn" type="primary" @click="cancel(record.key)">取消</a-button> |
|||
</template> |
|||
</template> |
|||
|
|||
<template v-else-if="['memberName', 'phone'].includes(column.dataIndex)"> |
|||
<div> |
|||
<a-input v-if="record.isEdit === 1" v-model:value="record[column.dataIndex]" style="margin: -5px 0" /> |
|||
<template v-else> |
|||
{{ text }} |
|||
</template> |
|||
</div> |
|||
</template> |
|||
</template> |
|||
|
|||
<template #footer><PlusCircleOutlined @click="addMember" style="font-size: 24px; color: #1890ff" /></template> |
|||
</a-table> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { PlusCircleOutlined } from '@ant-design/icons-vue'; |
|||
import { memberQuery, saveMember, updateMember, delMember } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const projectId = computed(() => store.getters['projects/projectId']); |
|||
const isAdd = ref(false); |
|||
|
|||
const columns = ref([ |
|||
{ |
|||
title: '序号', |
|||
dataIndex: 'indexId', |
|||
key: 'indexId', |
|||
}, |
|||
{ |
|||
title: '成员名', |
|||
dataIndex: 'memberName', |
|||
key: 'memberName', |
|||
}, |
|||
{ |
|||
title: '手机号', |
|||
dataIndex: 'phone', |
|||
key: 'phone', |
|||
}, |
|||
{ |
|||
title: '角色', |
|||
dataIndex: 'roles', |
|||
key: 'roles', |
|||
}, |
|||
{ |
|||
title: '操作', |
|||
key: 'action', |
|||
dataIndex: 'action', |
|||
}, |
|||
]); |
|||
|
|||
const dataList = ref([]); |
|||
|
|||
getList(); |
|||
|
|||
async function getList() { |
|||
try { |
|||
const params = { param: { projectId: projectId.value } }; |
|||
const data = await memberQuery(params); |
|||
store.commit('task/setMembers', data); |
|||
dataList.value = []; |
|||
|
|||
data.forEach((item, index) => { |
|||
const obj = { |
|||
key: index + 1, |
|||
indexId: index + 1, |
|||
memberId: item.memberId, |
|||
memberName: item.memberName, |
|||
phone: item.phone, |
|||
roles: item.experimentNameList.toString(), |
|||
isEdit: 0, |
|||
}; |
|||
|
|||
dataList.value.push(obj); |
|||
}); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
const addMember = () => { |
|||
const num = dataList.value.length + 1; |
|||
const obj = { key: num, indexId: num, memberName: '', phone: '', isEdit: 1 }; |
|||
dataList.value.push(obj); |
|||
isAdd.value = true; |
|||
}; |
|||
|
|||
function toEdit(key) { |
|||
isAdd.value = false; |
|||
|
|||
dataList.value.forEach(item => { |
|||
if (item.key === key) { |
|||
item.isEdit = 1; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function cancel(key) { |
|||
let ind = -1; |
|||
dataList.value.filter((item, index) => { |
|||
if (item.key === key) { |
|||
if (isAdd.value) { |
|||
ind = index; |
|||
} else { |
|||
getList(); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
if (ind > -1) { |
|||
dataList.value.splice(ind, 1); |
|||
} |
|||
|
|||
isAdd.value = false; |
|||
} |
|||
|
|||
async function save(key) { |
|||
isAdd.value = false; |
|||
|
|||
const params = { |
|||
param: { |
|||
projectId: projectId.value, |
|||
id: '', |
|||
name: '', |
|||
phone: '', |
|||
}, |
|||
}; |
|||
|
|||
dataList.value.forEach(item => { |
|||
if (key === item.key) { |
|||
params.param.id = item.memberId; |
|||
params.param.name = item.memberName; |
|||
params.param.phone = item.phone; |
|||
} |
|||
}); |
|||
|
|||
const myreg = /^[1][3,4,5,7,8,9][0-9]{9}$/; |
|||
|
|||
if (!myreg.test(params.param.phone)) { |
|||
message.info('请填写正确的手机号'); |
|||
return false; |
|||
} |
|||
|
|||
try { |
|||
if (params.param.id) { |
|||
await updateMember(params); |
|||
} else { |
|||
await saveMember(params); |
|||
} |
|||
|
|||
getList(); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
async function toDelete(key) { |
|||
console.log(key); |
|||
const params = { param: { id: '' } }; |
|||
dataList.value.forEach(item => { |
|||
if (key === item.key) { |
|||
params.param.id = item.memberId; |
|||
} |
|||
}); |
|||
|
|||
try { |
|||
await delMember(params); |
|||
getList(); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.member-list { |
|||
border-radius: 10px 10px 0 0; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
:deep(.table-striped) td { |
|||
background-color: #fafafa; |
|||
} |
|||
|
|||
.action-btn { |
|||
width: 50px !important; |
|||
height: 28px !important; |
|||
font-size: 14px !important; |
|||
padding: 0; |
|||
letter-spacing: 0 !important; |
|||
} |
|||
|
|||
:deep(.ant-table-pagination.ant-pagination) { |
|||
height: 0; |
|||
margin: 0; |
|||
} |
|||
|
|||
.add-btn { |
|||
height: 60px; |
|||
background: #fff; |
|||
padding-left: 36px; |
|||
} |
|||
|
|||
.edit-btn { |
|||
background: #0dc26c; |
|||
border: 0; |
|||
} |
|||
|
|||
.del-btn { |
|||
margin-left: 16px; |
|||
background: #ff5353; |
|||
border: 0; |
|||
} |
|||
|
|||
:deep(.ant-table-container table > thead > tr:first-child th:first-child) { |
|||
width: 80px; |
|||
text-align: center; |
|||
} |
|||
|
|||
:deep(.ant-table-container table > thead > tr:first-child th:nth-child(2)) { |
|||
min-width: 100px; |
|||
} |
|||
|
|||
:deep(.ant-table-container table > tbody > tr > td:first-child) { |
|||
text-align: center; |
|||
} |
|||
|
|||
:deep(.ant-table-container table > thead > tr:first-child th:last-child) { |
|||
min-width: 160px; |
|||
} |
|||
</style> |
|||
@ -1,389 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form ref="formRef" :model="topicMeetFormData"> |
|||
<a-form-item> |
|||
<label class="color-3">课题名称</label> |
|||
<a-input v-model:value="topicMeetFormData.name" placeholder="课题名称" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">起止时间</label> |
|||
<a-space direction="vertical" :size="12"> |
|||
<a-range-picker v-model:value="topicMeetFormData.date" /> |
|||
</a-space> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">负责人</label> |
|||
<a-select |
|||
v-model:value="topicMeetFormData.memberId" |
|||
show-search |
|||
optionFilterProp="label" |
|||
placeholder="负责人" |
|||
:options="options" |
|||
:filter-option="filterOption" |
|||
@search="handleSearch" |
|||
:getPopupContainer=" |
|||
triggerNode => { |
|||
return triggerNode.parentNode || document.body; |
|||
} |
|||
" |
|||
></a-select> |
|||
</a-form-item> |
|||
|
|||
<a-form-item class="form-item-dad"> |
|||
<div class="flex items-center" style="margin-bottom: 5px"> |
|||
<label class="color-3" style="margin-bottom: 0; margin-right: 8px">项目分阶段具体实施目标</label> |
|||
<PlusCircleOutlined style="color: #1890ff; font-size: 16px" @click="addMilestones" /> |
|||
</div> |
|||
|
|||
<div class="form-item-son" style="padding-left: 16px"> |
|||
<div v-for="(item, index) in planTaskStageList" :key="index"> |
|||
<a-form-item> |
|||
<label class="color-3">第{{ index + 1 }}阶段</label> |
|||
<a-space direction="vertical" :size="12"> |
|||
<a-range-picker v-model:value="item.date" /> |
|||
</a-space> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">实施内容与目标</label> |
|||
<a-textarea v-model:value="item.remark" placeholder="实施内容与目标" /> |
|||
</a-form-item> |
|||
</div> |
|||
</div> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">主要技术指标</label> |
|||
<a-textarea v-model:value="topicMeetFormData.technicalIndicator" placeholder="主要技术指标" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">经济指标</label> |
|||
<a-textarea v-model:value="topicMeetFormData.economicIndicators" placeholder="经济指标" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">社会效益</label> |
|||
<a-textarea v-model:value="topicMeetFormData.socialBenefit" placeholder="社会效益" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">验收内容</label> |
|||
<a-checkbox-group v-model:value="topicMeetFormData.checkContentList"> |
|||
<a-row> |
|||
<a-col :span="5"> |
|||
<a-checkbox value="1"><span class="color-6">1、工作报告</span></a-checkbox> |
|||
</a-col> |
|||
<a-col :span="6"> |
|||
<a-checkbox value="2"><span class="color-6">2、技术报告</span></a-checkbox> |
|||
</a-col> |
|||
<a-col :span="6"> |
|||
<a-checkbox value="3"><span class="color-6">3、计算机软件</span></a-checkbox> |
|||
</a-col> |
|||
<a-col :span="7"> |
|||
<a-checkbox value="4"><span class="color-6">4、生物品种</span></a-checkbox> |
|||
</a-col> |
|||
<a-col :span="5"> |
|||
<a-checkbox value="5"><span class="color-6">5、样品或样机</span></a-checkbox> |
|||
</a-col> |
|||
<a-col :span="6"> |
|||
<a-checkbox value="6"><span class="color-6">6、成套技术设备</span></a-checkbox> |
|||
</a-col> |
|||
<a-col :span="6"> |
|||
<a-checkbox value="7"><span class="color-6">7、科技论文或著作</span></a-checkbox> |
|||
</a-col> |
|||
<a-col :span="7"> |
|||
<a-checkbox value="8"><span class="color-6">8、科技报告收录证书(必选)</span></a-checkbox> |
|||
</a-col> |
|||
</a-row> |
|||
</a-checkbox-group> |
|||
</a-form-item> |
|||
|
|||
<!-- 自定义 --> |
|||
<div class="flex items-center cursor-pointer" style="margin-bottom: 20px" @click="addDefined"> |
|||
<PlusCircleOutlined style="color: #1890ff; font-size: 16px" /><span class="color-3" style="margin-left: 8px">自定义</span> |
|||
</div> |
|||
|
|||
<div class="form-item-son" style="padding-left: 16px"> |
|||
<div v-for="(item, index) in planTaskDefinedList" :key="index"> |
|||
<a-form-item> |
|||
<label class="color-3">自定义名称</label> |
|||
<a-input v-model:value="item.key" placeholder="自定义名称" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">自定义内容</label> |
|||
<a-textarea v-model:value="item.value" placeholder="自定义内容" /> |
|||
</a-form-item> |
|||
</div> |
|||
</div> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">计划任务书</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="fileList" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传计划任务书</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { InboxOutlined, PlusCircleOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg, memberQuery, savePlanTask, getPlanTask } from 'apis'; |
|||
import dayjs from 'dayjs'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const formRef = ref(null); |
|||
const projectInfo = computed(() => store.state.projects.project); // 当前选择项目信息 |
|||
|
|||
const projectId = computed(() => store.getters['projects/projectId']); |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
const fileList = ref([]); |
|||
|
|||
const topicMeetFormData = ref({ |
|||
projectId: projectInfo.value.id, |
|||
name: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
memberId: '', |
|||
technicalIndicator: '', |
|||
economicIndicators: '', |
|||
socialBenefit: '', |
|||
checkContentList: [], |
|||
fileList: [], |
|||
planTaskStageList: [], |
|||
planTaskDefinedList: [], |
|||
}); |
|||
|
|||
const options = ref([]); |
|||
|
|||
const planTaskStageList = ref([{ date: [], stageStartTime: '', stageEndTime: '', remark: '' }]); |
|||
const planTaskDefinedList = ref([]); |
|||
|
|||
checkPlanTask(); // 查看任务计划书 |
|||
|
|||
getList(); // 获取成员列表 |
|||
|
|||
const handleSearch = async value => { |
|||
await getList(value); // 获取成员列表 |
|||
}; |
|||
|
|||
const filterOption = (input, option) => { |
|||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
fileList.value = arr.value; |
|||
}; |
|||
|
|||
// 查看任务计划书 |
|||
async function checkPlanTask() { |
|||
try { |
|||
const params = { param: { projectId: projectId.value } }; |
|||
const data = await getPlanTask(params); |
|||
if (data.checkContent) data.checkContentList = data.checkContent.split(','); |
|||
|
|||
store.commit('layout/setFirPlanTime', { startTime: data.startTime, endTime: data.endTime }); |
|||
|
|||
data.date = []; |
|||
if (data.startTime) { |
|||
const start = dayjs(Number(data.startTime)); |
|||
const end = dayjs(Number(data.endTime)); |
|||
data.date = [start, end]; |
|||
} |
|||
|
|||
data.projectId = data.id; |
|||
topicMeetFormData.value = data; |
|||
|
|||
planTaskDefinedList.value = data.planTaskDefinedList; |
|||
|
|||
data.planTaskStageList.forEach(item => { |
|||
item.date = []; |
|||
if (item.stageStartTime) { |
|||
const planStart = dayjs(Number(item.stageStartTime)); |
|||
const planEnd = dayjs(Number(item.stageEndTime)); |
|||
item.date = [planStart, planEnd]; |
|||
} |
|||
}); |
|||
planTaskStageList.value = data.planTaskStageList; |
|||
|
|||
const fileArr = []; |
|||
data.fileList.forEach(item => { |
|||
const fileInfo = { |
|||
id: item.fileId, |
|||
name: item.fileName, |
|||
url: item.filePath, |
|||
status: 'done', |
|||
}; |
|||
|
|||
fileArr.push(fileInfo); |
|||
}); |
|||
fileList.value = fileArr; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 获取成员列表 |
|||
async function getList(name) { |
|||
try { |
|||
const params = { param: { projectId: projectId.value, name } }; |
|||
const data = await memberQuery(params); |
|||
store.commit('task/setMembers', data); |
|||
options.value = []; |
|||
|
|||
data.forEach(item => { |
|||
const obj = { |
|||
label: item.memberName, |
|||
value: item.memberId, |
|||
}; |
|||
|
|||
options.value.push(obj); |
|||
}); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 添加实施内容与目标 |
|||
function addMilestones() { |
|||
planTaskStageList.value.push({ date: [], stageStartTime: '', stageEndTime: '', remark: '' }); |
|||
} |
|||
|
|||
// 添加自定义内容 |
|||
function addDefined() { |
|||
planTaskDefinedList.value.push({ key: '', value: '' }); |
|||
} |
|||
|
|||
const onSubmit = async () => { |
|||
fileList.value.forEach(item => { |
|||
const obj = { |
|||
fileId: item.id, |
|||
fileName: item.name, |
|||
filePath: item.url, |
|||
}; |
|||
|
|||
topicMeetFormData.value.fileList.push(obj); |
|||
}); |
|||
|
|||
if (topicMeetFormData.value.date) { |
|||
topicMeetFormData.value.date.forEach((item, index) => { |
|||
if (index === 0) { |
|||
topicMeetFormData.value.startTime = dayjs(item).format('x'); |
|||
} else { |
|||
topicMeetFormData.value.endTime = dayjs(item).format('x'); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
topicMeetFormData.value.planTaskStageList = []; |
|||
planTaskStageList.value.forEach(item => { |
|||
const obj = { |
|||
stageStartTime: '', |
|||
stageEndTime: '', |
|||
remark: item.remark, |
|||
}; |
|||
|
|||
if (item.date.length > 0) { |
|||
item.date.forEach((val, key) => { |
|||
if (key === 0) { |
|||
obj.stageStartTime = dayjs(val).format('x'); |
|||
} else { |
|||
obj.stageEndTime = dayjs(val).format('x'); |
|||
} |
|||
}); |
|||
|
|||
if (obj.stageStartTime < topicMeetFormData.value.startTime) { |
|||
message.info('阶段任务开始时间不能小于项目起始时间'); |
|||
return false; |
|||
} |
|||
if (obj.stageEndTime > topicMeetFormData.value.endTime) { |
|||
message.info('阶段任务结束时间不能小于项目终止时间'); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
topicMeetFormData.value.planTaskStageList.push(obj); |
|||
}); |
|||
|
|||
topicMeetFormData.value.planTaskDefinedList = planTaskDefinedList.value; |
|||
|
|||
const params = { param: topicMeetFormData.value }; |
|||
|
|||
await savePlanTask(params); |
|||
projectInfo.value.name = topicMeetFormData.value.name; |
|||
store.commit('projects/setProject', projectInfo.value); |
|||
store.commit('layout/setRefreshProjects'); |
|||
checkPlanTask(); |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
</style> |
|||
@ -1,271 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
@change="handleChange($event, index)" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true"> |
|||
确定 |
|||
</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
|
|||
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" /> |
|||
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
// import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
|
|||
const isShowSuccess = ref(false); |
|||
const isShowWarning = ref(false); |
|||
const tipsMessage = ref(''); |
|||
|
|||
// 实验起止时间 |
|||
// const expreStartTime = computed(() => store.state.layout.expreStartTime); |
|||
// const expreEndTime = computed(() => store.state.layout.expreEndTime); |
|||
|
|||
// 试验状态 |
|||
const expreStatus = computed(() => store.state.projects.expreStatus); |
|||
const sessionStatus = sessionStorage.getItem('expreStatus'); |
|||
|
|||
if (!expreStatus.value && sessionStatus) { |
|||
store.commit('projects/setExpreimentStatus', sessionStatus); |
|||
} |
|||
|
|||
if (expreStatus.value) { |
|||
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) { |
|||
isShowWarning.value = true; |
|||
tipsMessage.value = '数据已锁定,不可操作'; |
|||
} else { |
|||
isShowSuccess.value = true; |
|||
tipsMessage.value = '数据未锁定,可操作'; |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
isShowWarning.value = false; |
|||
isShowSuccess.value = false; |
|||
}, 3000); |
|||
} |
|||
|
|||
getDataByCode(); |
|||
|
|||
const handleChange = (info, index) => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 获取最新上传 |
|||
const arr = ref([]); |
|||
resFileList.forEach((item, key) => { |
|||
if (key === resFileList.length - 1) { |
|||
arr.value.push(item); |
|||
} |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[index].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
const time = dayjs(item.date).format('x'); |
|||
// if (time < expreStartTime.value || time > expreEndTime.value) { |
|||
// message.info('实验日期必须在实验起止时间之内'); |
|||
// return false; |
|||
// } |
|||
obj.answerList.push(time); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-form { |
|||
position: relative; |
|||
} |
|||
|
|||
.task-form :deep(.ant-alert) { |
|||
position: absolute; |
|||
right: 30px; |
|||
} |
|||
|
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,308 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-radio-group v-model:value="item.con" name="radioGroup" @change="handleChangeRadio"> |
|||
<a-radio :value="val.submitValue" :id="val.optionId" v-for="(val, key) in item.optionList" :key="key">{{ |
|||
val.showValue |
|||
}}</a-radio> |
|||
</a-radio-group> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-select ref="select" v-model:value="item.con"> |
|||
<a-select-option :value="val.submitValue" v-for="(val, key) in item.optionList" :key="key">{{ |
|||
val.showValue |
|||
}}</a-select-option> |
|||
</a-select> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
@change="handleChange($event, index)" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, watch } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
const intellectualId = computed(() => store.state.task.intellectualId); // 知识产权ID |
|||
|
|||
getDataByCode(); |
|||
|
|||
watch(intellectualId, () => { |
|||
getDataByCode(); |
|||
}); |
|||
|
|||
// 单选框选中 |
|||
const handleChangeRadio = value => { |
|||
const selectedName = value.target.value; |
|||
const selectedId = value.target.id; |
|||
let addQuestion = []; |
|||
const getQuestions = questionList.value; |
|||
let selectedIndex = -1; |
|||
|
|||
questionList.value.forEach((item, index) => { |
|||
if (item.type === 3) { |
|||
item.optionList.forEach(val => { |
|||
if (val.optionId === selectedId && val.questionInfos && val.questionInfos.length > 0) { |
|||
addQuestion = val.questionInfos; |
|||
selectedIndex = index; |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
addQuestion.forEach((item, index) => { |
|||
let flag = false; |
|||
getQuestions.forEach(v => { |
|||
if (v.question === '授权日期') { |
|||
flag = true; |
|||
} |
|||
}); |
|||
|
|||
if (!flag) { |
|||
if (selectedName === '授权') { |
|||
const addIndex = selectedIndex + index + 1; |
|||
getQuestions.splice(addIndex, 0, item); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
if (selectedName === '申请') { |
|||
let flag = false; |
|||
let delIndex = -1; |
|||
getQuestions.forEach((v, k) => { |
|||
if (v.question === '授权日期') { |
|||
flag = true; |
|||
delIndex = k; |
|||
} |
|||
}); |
|||
|
|||
if (flag) { |
|||
getQuestions.splice(delIndex, 1); |
|||
} |
|||
} |
|||
|
|||
questionList.value = getQuestions; |
|||
}; |
|||
|
|||
// 文件上传 |
|||
const handleChange = (info, currIndex) => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
|
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: intellectualId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2 || item.type === 3 || item.type === 5) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
obj.answerList.push(dayjs(item.date).format('x')); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitIntellectual', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: intellectualId.value, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getIntellectual', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2 || item.type === 3 || item.type === 5) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,251 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-select ref="select" v-model:value="item.con"> |
|||
<a-select-option :value="val.submitValue" v-for="(val, key) in item.optionList" :key="key">{{ |
|||
val.showValue |
|||
}}</a-select-option> |
|||
</a-select> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, watch } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
const intellectualId = computed(() => store.state.task.intellectualId); // 知识产权ID |
|||
|
|||
getDataByCode(); |
|||
|
|||
watch(intellectualId, () => { |
|||
getDataByCode(); |
|||
}); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: intellectualId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2 || item.type === 5) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
obj.answerList.push(dayjs(item.date).format('x')); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitIntellectual', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: intellectualId.value, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getIntellectual', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2 || item.type === 5) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,251 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-select ref="select" v-model:value="item.con"> |
|||
<a-select-option :value="val.submitValue" v-for="(val, key) in item.optionList" :key="key">{{ |
|||
val.showValue |
|||
}}</a-select-option> |
|||
</a-select> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, watch } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
const intellectualId = computed(() => store.state.task.intellectualId); // 知识产权ID |
|||
|
|||
getDataByCode(); |
|||
|
|||
watch(intellectualId, () => { |
|||
getDataByCode(); |
|||
}); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: intellectualId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2 || item.type === 5) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
obj.answerList.push(dayjs(item.date).format('x')); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitIntellectual', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: intellectualId.value, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getIntellectual', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2 || item.type === 5) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,236 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传验收证书</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
obj.answerList.push(dayjs(item.date).format('x')); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,284 +0,0 @@ |
|||
<template> |
|||
<div> |
|||
<a-table |
|||
class="member-list" |
|||
:columns="columns" |
|||
:data-source="dataList" |
|||
:row-class-name="(_record, index) => (index % 2 === 1 ? null : 'table-striped')" |
|||
> |
|||
<template #bodyCell="{ column, text, record }"> |
|||
<div class="flex items-center" style="min-width: 160px" v-if="column.key === 'action'"> |
|||
<a-button |
|||
:disabled="record.status === 0 ? false : true" |
|||
class="action-btn edit-btn" |
|||
type="primary" |
|||
@click="showModal(record.id, 'success')" |
|||
> |
|||
通过 |
|||
</a-button> |
|||
<a-button |
|||
:disabled="record.status === 0 ? false : true" |
|||
class="action-btn del-btn" |
|||
type="primary" |
|||
@click="showModal(record.id, 'fail')" |
|||
> |
|||
驳回 |
|||
</a-button> |
|||
<img v-if="record.status === 0" style="width: 28px" src="https://www.tall.wiki/staticrec/experiment/unlock.png" /> |
|||
<img |
|||
v-if="record.status === 1" |
|||
class="cursor-pointer" |
|||
style="width: 28px" |
|||
src="https://www.tall.wiki/staticrec/experiment/locking.png" |
|||
@click="showModal(record.id, 'tips')" |
|||
/> |
|||
<div v-if="record.status === 3" class="status-btn" style="background: #cccccc">审</div> |
|||
<div |
|||
v-if="record.status === 4" |
|||
class="status-btn cursor-pointer" |
|||
style="background: #ff5353" |
|||
@click="showModal(record.id, 'tips')" |
|||
> |
|||
驳 |
|||
</div> |
|||
</div> |
|||
|
|||
<template v-else-if="['information', 'result', 'sourceCode'].includes(column.dataIndex)"> |
|||
<a class="break-all" style="color: #1890ff" :href="text" target="_blank" :title="text">{{ text }}</a> |
|||
</template> |
|||
|
|||
<template v-else-if="['report', 'course'].includes(column.dataIndex)"> |
|||
<a |
|||
class="break-all" |
|||
style="color: #1890ff" |
|||
:href="!text || !text.url ? '' : text.url" |
|||
target="_blank" |
|||
:title="!text || !text.name ? '' : text.name" |
|||
> |
|||
{{ !text || !text.name ? '' : text.name }} |
|||
</a> |
|||
</template> |
|||
</template> |
|||
</a-table> |
|||
</div> |
|||
|
|||
<!-- 确定模态框 --> |
|||
<a-modal v-model:visible="success" :closable="false" @ok="handleOk('success')"> |
|||
<div class="modal-title flex items-center"> |
|||
<CheckCircleFilled style="margin-right: 8px; font-size: 18px; color: #52c41a" /> |
|||
<span class="color-3" style="font-size: 18px; font-weight: 600">确定要审核通过该实验交付物吗?</span> |
|||
</div> |
|||
<div class="modal-con color-9" style="padding-left: 24px; margin-top: 16px; font-size: 16px; line-height: 26px"> |
|||
确定通过审核后改实验交付物将被锁定,如需修改需向课题主持人提交解锁请求,通过后即可修改。 |
|||
</div> |
|||
</a-modal> |
|||
|
|||
<!-- 拒绝模态框 --> |
|||
<a-modal v-model:visible="fail" :closable="false" @ok="handleOk('fail')"> |
|||
<div class="modal-title flex items-center"> |
|||
<CloseCircleFilled style="margin-right: 8px; font-size: 18px; color: #ff5353" /> |
|||
<span class="color-3" style="font-size: 18px; font-weight: 600">确定要驳回该实验交付物吗?</span> |
|||
</div> |
|||
<div class="modal-con color-9" style="padding-left: 24px; margin-top: 16px"> |
|||
<div style="margin-bottom: 5px; font-size: 16px; line-height: 26px">驳回原因</div> |
|||
<a-textarea v-model:value="remark1" placeholder="驳回原因" :auto-size="{ minRows: 2, maxRows: 5 }" /> |
|||
</div> |
|||
</a-modal> |
|||
|
|||
<!-- 提示模态框 --> |
|||
<a-modal v-model:visible="tips" :closable="false" @ok="handleOk('tips')"> |
|||
<div class="modal-title flex items-center"> |
|||
<ExclamationCircleFilled style="margin-right: 8px; font-size: 18px; color: #fa8c16" /> |
|||
<span class="color-3" style="font-size: 18px; font-weight: 600">确定要申请解锁该项实验交付物吗?</span> |
|||
</div> |
|||
<div class="modal-con color-9" style="padding-left: 24px; margin-top: 16px"> |
|||
<div style="margin-bottom: 5px; font-size: 16px; line-height: 26px">申请原因</div> |
|||
<a-textarea v-model:value="remark2" placeholder="申请原因" :auto-size="{ minRows: 2, maxRows: 5 }" /> |
|||
</div> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { queryExperimentation, examineExperimentation, applyUnlock } from 'apis'; |
|||
import { CheckCircleFilled, CloseCircleFilled, ExclamationCircleFilled } from '@ant-design/icons-vue'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const projectId = computed(() => store.getters['projects/projectId']); |
|||
|
|||
const success = ref(false); |
|||
const fail = ref(false); |
|||
const tips = ref(false); |
|||
const currId = ref(null); |
|||
const remark1 = ref(null); |
|||
const remark2 = ref(null); |
|||
|
|||
const columns = ref([ |
|||
{ title: '序号', dataIndex: 'indexId', key: 'indexId' }, |
|||
{ title: '实验名称', dataIndex: 'name', key: 'name' }, |
|||
{ title: '实验报告', dataIndex: 'report', key: 'report' }, |
|||
{ title: '实验过程', dataIndex: 'course', key: 'course' }, |
|||
{ title: '实验数据', dataIndex: 'information', key: 'information' }, |
|||
{ title: '程序代码', dataIndex: 'sourceCode', key: 'sourceCode' }, |
|||
{ title: '实验结果', dataIndex: 'result', key: 'result' }, |
|||
{ title: '审核', key: 'action', dataIndex: 'action' }, |
|||
]); |
|||
|
|||
const dataList = ref([]); |
|||
|
|||
getExperimentations(); |
|||
|
|||
// 显示对话框 |
|||
const showModal = (id, tip) => { |
|||
currId.value = id; |
|||
if (tip === 'success') { |
|||
success.value = true; |
|||
} else if (tip === 'fail') { |
|||
fail.value = true; |
|||
} else if (tip === 'tips') { |
|||
tips.value = true; |
|||
} |
|||
}; |
|||
|
|||
// 对话框确认按钮 |
|||
const handleOk = async tip => { |
|||
if (tip === 'success') { |
|||
success.value = false; |
|||
await examine(0); |
|||
} else if (tip === 'fail') { |
|||
fail.value = false; |
|||
await examine(1); |
|||
} else if (tip === 'tips') { |
|||
tips.value = false; |
|||
await toUnlock(); |
|||
} |
|||
}; |
|||
|
|||
async function getExperimentations() { |
|||
try { |
|||
const params = { param: { projectId: projectId.value } }; |
|||
const data = await queryExperimentation(params); |
|||
|
|||
data.forEach((item, index) => { |
|||
item.indexId = index + 1; |
|||
if (item.report) item.report = JSON.parse(item.report); |
|||
if (item.course) item.course = JSON.parse(item.course); |
|||
}); |
|||
dataList.value = data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 通过/驳回 |
|||
async function examine(type) { |
|||
try { |
|||
const params = { |
|||
param: { |
|||
id: currId.value, |
|||
type, |
|||
remark: remark1.value ? remark1.value : '', |
|||
}, |
|||
}; |
|||
await examineExperimentation(params); |
|||
getExperimentations(); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 解锁申请 |
|||
async function toUnlock() { |
|||
try { |
|||
const params = { |
|||
param: { |
|||
id: currId.value, |
|||
remark: remark2.value ? remark2.value : '', |
|||
}, |
|||
}; |
|||
await applyUnlock(params); |
|||
getExperimentations(); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.member-list { |
|||
border-radius: 10px 10px 0 0; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
:deep(.table-striped) td { |
|||
background-color: #fafafa; |
|||
} |
|||
|
|||
.action-btn { |
|||
flex-shrink: 0; |
|||
width: 50px !important; |
|||
height: 28px !important; |
|||
font-size: 14px !important; |
|||
padding: 0; |
|||
letter-spacing: 0 !important; |
|||
} |
|||
|
|||
:deep(.ant-table-pagination.ant-pagination) { |
|||
height: 0; |
|||
margin: 0; |
|||
} |
|||
|
|||
.add-btn { |
|||
height: 60px; |
|||
background: #fff; |
|||
padding-left: 36px; |
|||
} |
|||
|
|||
.edit-btn { |
|||
background: #0dc26c; |
|||
border: 0; |
|||
} |
|||
|
|||
.del-btn { |
|||
margin-left: 16px; |
|||
margin-right: 16px; |
|||
background: #ff5353; |
|||
border: 0; |
|||
} |
|||
|
|||
.status-btn { |
|||
width: 28px; |
|||
height: 28px; |
|||
color: #fff; |
|||
border-radius: 50%; |
|||
text-align: center; |
|||
line-height: 28px; |
|||
} |
|||
|
|||
:deep(.ant-table-container table > thead > tr th) { |
|||
min-width: 100px; |
|||
} |
|||
|
|||
:deep(.ant-table-container table > thead > tr th:first-child) { |
|||
min-width: 70px; |
|||
text-align: center; |
|||
} |
|||
|
|||
:deep(.ant-table-container table > tbody > tr > td:first-child) { |
|||
text-align: center; |
|||
} |
|||
|
|||
:deep(.ant-table-container table > thead > tr th:last-child) { |
|||
min-width: 192px; |
|||
} |
|||
|
|||
:deep(.ant-btn-primary[disabled]) { |
|||
color: #fff; |
|||
background: #cccccc; |
|||
} |
|||
</style> |
|||
@ -1,246 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传课题结题报告</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
// import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
// 课题起止时间 |
|||
// const subStartTime = computed(() => store.state.layout.subStartTime); |
|||
// const subEndTime = computed(() => store.state.layout.subEndTime); |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
const time = dayjs(item.date).format('x'); |
|||
// if (time < subStartTime.value || time > subEndTime.value) { |
|||
// message.info('中期检查时间必须在子课题起止时间之内'); |
|||
// return false; |
|||
// } |
|||
obj.answerList.push(time); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,246 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传中期检查报告</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
// import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
// 课题起止时间 |
|||
// const subStartTime = computed(() => store.state.layout.subStartTime); |
|||
// const subEndTime = computed(() => store.state.layout.subEndTime); |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
const time = dayjs(item.date).format('x'); |
|||
// if (time < subStartTime.value || time > subEndTime.value) { |
|||
// message.info('中期检查时间必须在子课题起止时间之内'); |
|||
// return false; |
|||
// } |
|||
obj.answerList.push(time); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,244 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form :model="topicMeetFormData"> |
|||
<a-form-item> |
|||
<label class="color-3">会议名称</label> |
|||
<a-input v-model:value="topicMeetFormData.name" placeholder="会议名称" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">会议日期</label> |
|||
<a-space direction="vertical" :size="12"> |
|||
<a-range-picker v-model:value="topicMeetFormData.date" /> |
|||
</a-space> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">会议地点</label> |
|||
<a-input v-model:value="topicMeetFormData.address" placeholder="会议地点" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">会议纪要</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="summaryList" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
@change="handleChange($event, 1)" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<label class="color-3">照片附件/其他</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="attachmentList" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.jpg,.jpeg,.rar,.zip,.png'" |
|||
@change="handleChange($event, 2)" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:jpg、jpeg、rar、zip</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传会议资料</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, watch } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg, saveMeeting, getMeetDetail } from 'apis'; |
|||
import dayjs from 'dayjs'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
const summaryList = ref([]); |
|||
const attachmentList = ref([]); |
|||
const subMeetId = computed(() => store.state.task.subMeetId); |
|||
const projectId = computed(() => store.getters['projects/projectId']); |
|||
const topicMeetFormData = ref({ |
|||
projectId: projectId.value, |
|||
id: '', |
|||
name: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
address: '', |
|||
summaryList: [], |
|||
attachmentList: [], |
|||
}); |
|||
|
|||
getMeetingInfo(); |
|||
watch(subMeetId, async () => { |
|||
if (subMeetId.value) { |
|||
await getMeetingInfo(); |
|||
} else { |
|||
topicMeetFormData.value = { |
|||
projectId: projectId.value, |
|||
id: '', |
|||
name: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
address: '', |
|||
summaryList: [], |
|||
attachmentList: [], |
|||
}; |
|||
summaryList.value = []; |
|||
attachmentList.value = []; |
|||
} |
|||
}); |
|||
|
|||
const handleChange = (info, index) => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let flag = false; |
|||
|
|||
arr.value.forEach(item => { |
|||
if (file.name === item.name) { |
|||
flag = true; |
|||
} |
|||
}); |
|||
if (!flag) { |
|||
arr.value.push(file); |
|||
} |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
if (index === 1) { |
|||
summaryList.value = arr.value; |
|||
} else if (index === 2) { |
|||
attachmentList.value = arr.value; |
|||
} |
|||
}; |
|||
|
|||
// 根据ID查询会议详情 |
|||
async function getMeetingInfo() { |
|||
try { |
|||
const params = { param: { id: subMeetId.value } }; |
|||
const data = await getMeetDetail(params); |
|||
|
|||
if (data) { |
|||
data.projectId = projectId.value; |
|||
const start = dayjs(Number(data.startTime)); |
|||
const end = dayjs(Number(data.endTime)); |
|||
data.date = [start, end]; |
|||
topicMeetFormData.value = data; |
|||
|
|||
summaryList.value = []; |
|||
attachmentList.value = []; |
|||
data.summaryList.forEach(item => { |
|||
const obj = { |
|||
id: item.fileId, |
|||
name: item.fileName, |
|||
url: item.filePath, |
|||
}; |
|||
|
|||
summaryList.value.push(obj); |
|||
}); |
|||
|
|||
data.attachmentList.forEach(item => { |
|||
const obj = { |
|||
id: item.fileId, |
|||
name: item.fileName, |
|||
url: item.filePath, |
|||
}; |
|||
|
|||
attachmentList.value.push(obj); |
|||
}); |
|||
} else { |
|||
topicMeetFormData.value = { |
|||
projectId: projectId.value, |
|||
id: '', |
|||
name: '', |
|||
date: [], |
|||
startTime: '', |
|||
endTime: '', |
|||
address: '', |
|||
summaryList: [], |
|||
attachmentList: [], |
|||
}; |
|||
summaryList.value = []; |
|||
attachmentList.value = []; |
|||
} |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
const onSubmit = async () => { |
|||
summaryList.value.forEach(item => { |
|||
const obj = { |
|||
fileId: item.id, |
|||
fileName: item.name, |
|||
filePath: item.url, |
|||
}; |
|||
|
|||
topicMeetFormData.value.summaryList.push(obj); |
|||
}); |
|||
|
|||
attachmentList.value.forEach(item => { |
|||
const obj = { |
|||
fileId: item.id, |
|||
fileName: item.name, |
|||
filePath: item.url, |
|||
}; |
|||
|
|||
topicMeetFormData.value.attachmentList.push(obj); |
|||
}); |
|||
|
|||
topicMeetFormData.value.date.forEach((item, index) => { |
|||
if (index === 0) { |
|||
topicMeetFormData.value.startTime = dayjs(item).format('x'); |
|||
} else { |
|||
topicMeetFormData.value.endTime = dayjs(item).format('x'); |
|||
} |
|||
}); |
|||
|
|||
const params = { param: topicMeetFormData.value }; |
|||
await saveMeeting(params); |
|||
getMeetingInfo(); |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
</style> |
|||
@ -1,246 +0,0 @@ |
|||
<template> |
|||
<div class="task-form bg-white border-radius-10"> |
|||
<a-form> |
|||
<div v-for="(item, index) in questionList" :key="index"> |
|||
<template v-if="item.type === 1"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-input v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 2"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-textarea v-model:value="item.con" :placeholder="item.question" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 3"> </template> |
|||
|
|||
<template v-if="item.type === 4"> </template> |
|||
|
|||
<template v-if="item.type === 5"> </template> |
|||
|
|||
<template v-if="item.type === 6"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-date-picker v-model:value="item.date" /> |
|||
</a-form-item> |
|||
</template> |
|||
|
|||
<template v-if="item.type === 7"> |
|||
<a-form-item> |
|||
<label class="color-3">{{ item.question }}</label> |
|||
<a-upload-dragger |
|||
v-model:fileList="item.files" |
|||
name="param" |
|||
:multiple="true" |
|||
:action="action" |
|||
:headers="headers" |
|||
:accept="'.pdf'" |
|||
:before-upload="beforeUpload(index)" |
|||
@change="handleChange" |
|||
> |
|||
<p class="ant-upload-drag-icon"> |
|||
<inbox-outlined></inbox-outlined> |
|||
</p> |
|||
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p> |
|||
<p class="ant-upload-hint color-c">格式:pdf</p> |
|||
</a-upload-dragger> |
|||
</a-form-item> |
|||
</template> |
|||
</div> |
|||
|
|||
<a-form-item class="text-right"> |
|||
<a-button type="primary" html-type="submit" @click="onSubmit">上传验收证书</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { InboxOutlined } from '@ant-design/icons-vue'; |
|||
import { uploadImg } from 'apis'; |
|||
// import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 上传文件相关 |
|||
const token = computed(() => store.getters['user/token']); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
const action = uploadImg; |
|||
// 查找、提交相关 |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const code = computed(() => store.state.task.label); // code |
|||
const questionList = ref([]); |
|||
// 当前操作的问题下标 |
|||
const currIndex = ref(null); |
|||
|
|||
// 课题起止时间 |
|||
// const subStartTime = computed(() => store.state.layout.subStartTime); |
|||
// const subEndTime = computed(() => store.state.layout.subEndTime); |
|||
|
|||
getDataByCode(); |
|||
|
|||
const beforeUpload = index => { |
|||
currIndex.value = index; |
|||
}; |
|||
|
|||
const handleChange = info => { |
|||
const resFileList = [...info.fileList]; |
|||
|
|||
// 数组去重 |
|||
const arr = ref([]); |
|||
resFileList.forEach(file => { |
|||
let num = -1; |
|||
|
|||
arr.value.forEach((item, index) => { |
|||
if (file.name === item.name) { |
|||
num = index; |
|||
} |
|||
}); |
|||
if (num > -1) { |
|||
arr.value.splice(num, 1); |
|||
} |
|||
|
|||
arr.value.push(file); |
|||
}); |
|||
|
|||
// 更改上传文件路径 |
|||
arr.value = arr.value.map(file => { |
|||
if (file.response) { |
|||
file.url = file.response.data.filePath; |
|||
file.id = file.response.data.fileId; |
|||
} |
|||
|
|||
return file; |
|||
}); |
|||
|
|||
questionList.value[currIndex.value].files = arr.value; |
|||
}; |
|||
|
|||
const onSubmit = async () => { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
questionAndAnswerList: [], |
|||
}, |
|||
}; |
|||
|
|||
const arr = []; |
|||
questionList.value.forEach(item => { |
|||
const obj = { |
|||
questionId: item.questionId, |
|||
answerList: [], |
|||
}; |
|||
|
|||
if (item.type === 1 || item.type === 2) { |
|||
obj.answerList.push(item.con); |
|||
} |
|||
|
|||
if (item.type === 6 && item.date) { |
|||
const time = dayjs(item.date).format('x'); |
|||
// if (time < subStartTime.value || time > subEndTime.value) { |
|||
// message.info('中期检查时间必须在子课题起止时间之内'); |
|||
// return false; |
|||
// } |
|||
obj.answerList.push(time); |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files.forEach(val => { |
|||
const file = { |
|||
id: val.id, |
|||
name: val.name, |
|||
url: val.url, |
|||
}; |
|||
obj.answerList.push(JSON.stringify(file)); |
|||
}); |
|||
} |
|||
arr.push(obj); |
|||
}); |
|||
|
|||
params.param.questionAndAnswerList = arr; |
|||
await store.dispatch('task/submitAnswer', params); |
|||
getDataByCode(); |
|||
}; |
|||
|
|||
async function getDataByCode() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
intellectualId: null, |
|||
}, |
|||
}; |
|||
const data = await store.dispatch('task/getByCode', params); |
|||
|
|||
data.forEach(item => { |
|||
if (item.type === 1 || item.type === 2) { |
|||
item.con = ''; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.con = item.answerList[0].answer; |
|||
} |
|||
} |
|||
|
|||
if (item.type === 6) { |
|||
item.date = null; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
if (item.answerList[0].answer) { |
|||
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD'); |
|||
item.date = dayjs(item.date, 'YYYY-MM-DD'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (item.type === 7) { |
|||
item.files = []; |
|||
|
|||
if (item.answerList.length > 0) { |
|||
item.answerList.forEach(val => { |
|||
if (val.answer) val.answer = JSON.parse(val.answer); |
|||
item.files.push(val.answer); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
questionList.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.ant-col { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.ant-col:nth-child(-n + 4) { |
|||
margin-top: 2px; |
|||
} |
|||
|
|||
.deliverables .ant-input, |
|||
.deliverables-son .ant-input { |
|||
width: 23px; |
|||
height: 14px; |
|||
border-radius: 0; |
|||
padding: 0; |
|||
font-size: 12px; |
|||
color: #1890ff; |
|||
text-align: center; |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.deliverables-son { |
|||
margin-top: 10px !important; |
|||
} |
|||
</style> |
|||
@ -1,195 +0,0 @@ |
|||
<template> |
|||
<div class="task-progress flex flex-wrap justify-between"> |
|||
<div class="wrap overflow-hidden"> |
|||
<a-card title="任务目标"> |
|||
<div class="flex flex-wrap justify-center"> |
|||
<div class="achievements border border-right border-bottom text-center"> |
|||
<p class="num">{{ infoOne.thesis }}/{{ infoOne.totalThesis }}</p> |
|||
<p class="name">论文</p> |
|||
</div> |
|||
<div class="achievements border border-bottom text-center"> |
|||
<p class="num">{{ infoOne.patent }}/{{ infoOne.totalPatent }}</p> |
|||
<p class="name">专利</p> |
|||
</div> |
|||
<div class="achievements border border-right text-center"> |
|||
<p class="num">{{ infoOne.theSoft }}/{{ infoOne.totalTheSoft }}</p> |
|||
<p class="name">软著</p> |
|||
</div> |
|||
<div class="achievements border text-center"> |
|||
<p class="num">{{ infoOne.meeting }}/{{ infoOne.totalMeeting }}</p> |
|||
<p class="name">会议</p> |
|||
</div> |
|||
</div> |
|||
</a-card> |
|||
</div> |
|||
<div class="wrap overflow-hidden"> |
|||
<a-card title="概览"> |
|||
<div class="topic"> |
|||
<p>{{ infoOne.name }}</p> |
|||
<a-progress |
|||
:percent="infoOne.masterSchedule" |
|||
:strokeWidth="22" |
|||
:show-info="false" |
|||
:stroke-color="'#1890FF'" |
|||
:trail-color="'rgba(24, 144, 255, 0.2)'" |
|||
/> |
|||
</div> |
|||
|
|||
<div class="sub-topic flex justify-between flex-wrap"> |
|||
<div class="topic" v-for="(item, index) in infoSec" :key="index"> |
|||
<p>{{ item.name }}</p> |
|||
<a-progress |
|||
:percent="item.masterSchedule" |
|||
:strokeWidth="22" |
|||
:show-info="false" |
|||
:stroke-color="colorList[index % 4].color" |
|||
:trail-color="colorList[index % 4].bgColor" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</a-card> |
|||
</div> |
|||
<div class="wrap overflow-hidden" v-for="(item, index) in infoSec" :key="index"> |
|||
<a-card :title="item.name"> |
|||
<div class="flex flex-wrap justify-center"> |
|||
<div class="achievements border border-right border-bottom text-center"> |
|||
<p class="num">{{ item.report }}</p> |
|||
<p class="name">实验报告</p> |
|||
</div> |
|||
<div class="achievements border border-bottom text-center"> |
|||
<p class="num">{{ item.course }}</p> |
|||
<p class="name">实验过程</p> |
|||
</div> |
|||
<div class="achievements border border-right text-center"> |
|||
<p class="num">{{ item.information }}</p> |
|||
<p class="name">实验数据</p> |
|||
</div> |
|||
<div class="achievements border text-center"> |
|||
<p class="num">{{ item.result }}</p> |
|||
<p class="name">实验结果</p> |
|||
</div> |
|||
</div> |
|||
</a-card> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { getProgress } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
const projectId = computed(() => store.getters['projects/projectId']); |
|||
const infoOne = ref({}); |
|||
const infoSec = ref([]); |
|||
|
|||
const colorList = ref([ |
|||
{ color: '#FF9191', bgColor: 'rgba(255, 145, 145, 0.2)' }, |
|||
{ color: '#FF934B', bgColor: 'rgba(255, 147, 75, 0.2)' }, |
|||
{ color: '#B991FF', bgColor: 'rgba(185, 145, 255, 0.2)' }, |
|||
{ color: '#91C1FF', bgColor: 'rgba(145, 193, 255, 0.2)' }, |
|||
]); |
|||
|
|||
progress(); |
|||
|
|||
async function progress() { |
|||
try { |
|||
const params = { param: { projectId: projectId.value } }; |
|||
const data = await getProgress(params); |
|||
infoOne.value = data.target; |
|||
infoSec.value = data.subTargetList; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.wrap { |
|||
margin-top: 16px; |
|||
width: calc((100% - 16px) / 2); |
|||
background-color: #fff; |
|||
border-radius: 10px; |
|||
border: 1px solid #cccccc; |
|||
} |
|||
|
|||
.wrap:nth-child(-n + 2) { |
|||
margin-top: 0; |
|||
} |
|||
|
|||
:deep(.ant-card-head) { |
|||
padding: 0 16px; |
|||
min-height: 45px; |
|||
max-height: 45px; |
|||
border-color: #cccccc; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
:deep(.ant-card-head-title) { |
|||
padding: 0; |
|||
line-height: 45px; |
|||
} |
|||
|
|||
.wrap .num { |
|||
font-size: 22px; |
|||
color: #4b8aff; |
|||
} |
|||
|
|||
.wrap .name { |
|||
color: #666666; |
|||
} |
|||
|
|||
.wrap p { |
|||
margin: 0; |
|||
} |
|||
|
|||
.border { |
|||
border-color: #fff; |
|||
} |
|||
|
|||
.border-right { |
|||
border-right-color: #cccccc; |
|||
} |
|||
|
|||
.border-left { |
|||
border-left-color: #cccccc; |
|||
} |
|||
|
|||
.border-top { |
|||
border-top-color: #cccccc; |
|||
} |
|||
|
|||
.border-bottom { |
|||
border-bottom-color: #cccccc; |
|||
} |
|||
|
|||
.achievements { |
|||
width: 40%; |
|||
padding: 25px 0; |
|||
} |
|||
|
|||
.topic p { |
|||
margin-bottom: 8px; |
|||
color: #666666; |
|||
} |
|||
|
|||
.sub-topic .topic { |
|||
margin-top: 40px; |
|||
width: calc((100% - 32px) / 2); |
|||
} |
|||
|
|||
.ant-card { |
|||
height: 100%; |
|||
} |
|||
|
|||
.ant-card :deep(.ant-card-body) { |
|||
height: calc(100% - 48px); |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
} |
|||
</style> |
|||
@ -1,144 +0,0 @@ |
|||
<template> |
|||
<div class="list-box"> |
|||
<div class="task-box" v-for="(item, index) in lists" :key="index"> |
|||
<div class="task-time flex items-center justify-between"> |
|||
<div class="flex items-center"> |
|||
<div class="circular"></div> |
|||
<span>{{ dayjs(Number(item.time)).format('YYYY年MM月DD日 HH:mm') }}</span> |
|||
</div> |
|||
<div class="task-action"></div> |
|||
</div> |
|||
|
|||
<div class="task-info"> |
|||
<div> |
|||
<div class="task-card"> |
|||
<div class="task-name cursor-pointer flex justify-between items-center" @click="toDetail(item)"> |
|||
<span class="leading-none truncate color-3" style="width: calc(100% - 30px)" :title="item.name">{{ item.name }}</span> |
|||
<RightOutlined style="color: #666" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed, ref } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { RightOutlined } from '@ant-design/icons-vue'; |
|||
import { getMeetQuery } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const code = computed(() => store.state.task.label); // code |
|||
const projectId = computed(() => store.getters['projects/projectId']); // 项目ID |
|||
const lists = ref([]); |
|||
|
|||
init(); |
|||
|
|||
async function init() { |
|||
if (code.value === 'ZKT_LW' || code.value === 'ZKT_ZL' || code.value === 'ZKT_RZ') { |
|||
await getAchievementsList(); |
|||
} else if (code.value === 'ZKT_HYGL') { |
|||
await getMeetList(1); |
|||
} else if (code.value === 'KT_KYHY') { |
|||
await getMeetList(0); |
|||
} |
|||
} |
|||
|
|||
// 知识产权列表 (论文、专利、软著) |
|||
async function getAchievementsList() { |
|||
const params = { |
|||
param: { |
|||
code: code.value, |
|||
projectId: projectId.value, |
|||
}, |
|||
}; |
|||
|
|||
const data = await store.dispatch('task/getIntellectualList', params); |
|||
lists.value = [...data]; |
|||
} |
|||
|
|||
// 会议列表 |
|||
async function getMeetList(type) { |
|||
const params = { |
|||
param: { |
|||
type, |
|||
projectId: projectId.value, |
|||
}, |
|||
}; |
|||
|
|||
try { |
|||
const data = await getMeetQuery(params); |
|||
data.forEach(item => { |
|||
item.time = item.startTime; |
|||
}); |
|||
lists.value = [...data]; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
function toDetail(item) { |
|||
if (code.value === 'ZKT_HYGL') { |
|||
store.commit('task/setSubMeetId', item.id); |
|||
} else if (code.value === 'KT_KYHY') { |
|||
store.commit('task/setMeetId', item.id); |
|||
} else if (code.value === 'ZKT_LW' || code.value === 'ZKT_ZL' || code.value === 'ZKT_RZ') { |
|||
store.commit('task/setIntellectualId', item.intellectualId); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.list-box { |
|||
padding: 10px 16px 50px; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.task-time { |
|||
height: 32px; |
|||
} |
|||
|
|||
.task-time .circular { |
|||
margin-right: 8px; |
|||
width: 14px; |
|||
height: 14px; |
|||
border-radius: 50%; |
|||
background-color: #1890ff; |
|||
} |
|||
|
|||
.task-time span { |
|||
font-size: 14px; |
|||
color: #595959; |
|||
} |
|||
|
|||
.task-info { |
|||
margin: 8px 0; |
|||
padding-left: 8px; |
|||
} |
|||
|
|||
.task-info > div { |
|||
padding-left: 15px; |
|||
border-left: 1px solid #d2d2d2; |
|||
} |
|||
|
|||
.task-info .task-card { |
|||
padding: 16px; |
|||
border-radius: 8px; |
|||
-moz-box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.12); |
|||
-webkit-box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.12); |
|||
box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.12); |
|||
} |
|||
|
|||
.task-con { |
|||
margin-top: 16px; |
|||
} |
|||
|
|||
.task-con > div { |
|||
height: 30px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,7 @@ |
|||
<template> |
|||
<div class="text-center"> |
|||
<a-image src="/src/assets/personal.png" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
@ -0,0 +1,7 @@ |
|||
<template> |
|||
<div class="text-center"> |
|||
<a-image src="/src/assets/uidispose.png" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
@ -0,0 +1,26 @@ |
|||
<template> |
|||
<div class="flex justify-around task-card-plugin"> |
|||
<a-button type="primary" style="width: 125px" @click="openAudit">账号管理</a-button> |
|||
<a-button type="primary" style="width: 125px" @click="openStatistical">UI配置</a-button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
// 账号管理详情页 |
|||
function openAudit() { |
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', 'personal'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
|
|||
// UI配置详情页 |
|||
function openStatistical() { |
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', 'uidispose'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
</script> |
|||
@ -0,0 +1,695 @@ |
|||
<template> |
|||
<div class="p-4"> |
|||
<!-- 标题 --> |
|||
<div class="mb-8 flex justify-between items-center"> |
|||
<div class="text-2xl font-semibold">流水账</div> |
|||
<div class="flex items-center"> |
|||
<a-button type="primary" v-if="!morning || morning == 0" @click="punch(0)"> 早打卡 </a-button> |
|||
<a-button type="primary" v-else> |
|||
{{ dayjs(+morning).format('HH:mm') }} |
|||
</a-button> |
|||
<a-button class="mx-5" type="primary" v-if="!night || night == 0" @click="punch(1)"> 晚打卡 </a-button> |
|||
<a-button class="mx-5" type="primary" v-else> {{ dayjs(+night).format('HH:mm') }} </a-button> |
|||
<FullscreenExitOutlined v-if="isFullScreen" class="text-lg" style="color: #777" @click="changeIsFullScreen(false)" /> |
|||
<FullscreenOutlined v-else class="text-lg" style="color: #777" @click="changeIsFullScreen(true)" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 筛选 --> |
|||
<a-form class="flex flex-wrap" :model="formState"> |
|||
<a-form-item name="timeRange" label="时间" style="width: 280px; margin-right: 20px; line-height: 32px; margin-bottom: 12px"> |
|||
<a-range-picker v-model:value="formState.timeRange" /> |
|||
</a-form-item> |
|||
|
|||
<a-form-item name="staffRange" label="员工" style="width: 280px; margin-right: 20px; line-height: 32px; margin-bottom: 12px"> |
|||
<a-select |
|||
class="select-box overflow-hidden" |
|||
v-model:value="formState.staffRange" |
|||
:options="memberList" |
|||
:field-names="{ label: 'empName', value: 'empName' }" |
|||
mode="multiple" |
|||
placeholder="请选择员工" |
|||
></a-select> |
|||
</a-form-item> |
|||
|
|||
<a-form-item name="programName" label="项目" style="width: 280px; margin-right: 20px; line-height: 32px; margin-bottom: 12px"> |
|||
<a-select |
|||
class="select-box overflow-hidden" |
|||
v-model:value="formState.programName" |
|||
:options="proList" |
|||
:field-names="{ label: 'projectName', value: 'projectName' }" |
|||
mode="multiple" |
|||
placeholder="请输入项目名称" |
|||
></a-select> |
|||
</a-form-item> |
|||
|
|||
<div class="block w-full"> |
|||
<a-button type="primary" html-type="submit" @click="handleSubmit">筛选</a-button> |
|||
<a-button class="mx-3" type="primary" @click="handleExport">导出</a-button> |
|||
<a-button @click="resetData">重置</a-button> |
|||
</div> |
|||
</a-form> |
|||
|
|||
<!-- 表格 --> |
|||
<a-table |
|||
sticky |
|||
class="mt-6" |
|||
:columns="columns" |
|||
:data-source="columnDatas" |
|||
bordered |
|||
:pagination="false" |
|||
:rowClassName="rowClassName" |
|||
:scroll="{ x: 1000, y: 660 }" |
|||
> |
|||
<template #bodyCell="{ column, text, record }"> |
|||
<div |
|||
style="height: 50px" |
|||
:class="{ 'text-left': column.dataIndex === 'program' }" |
|||
class="overflow-y-auto task-today" |
|||
@dblclick="showModal(record, column.proId)" |
|||
> |
|||
<template v-if="column.dataIndex === 'program'"> |
|||
<template v-for="item in text"> |
|||
<template v-if="column.proId === item.proId"> |
|||
<div class="mb-3" v-for="(task, key) in item.tasks" :key="key"> |
|||
<PushpinOutlined class="mr-2" v-if="task.cooperation === 1" title="协作任务" /> |
|||
<span> |
|||
我今日计划结果{{ key + 1 }}是:{{ task.taskName }},交付物是:{{ task.deliverName }},截止时间:{{ |
|||
dayjs(+task.deadline).format('MM-DD') |
|||
}},时长:{{ task.duration / 3600000 }},检查人:{{ task.checker }} |
|||
</span> |
|||
</div> |
|||
</template> |
|||
</template> |
|||
</template> |
|||
<template v-else> |
|||
{{ text }} |
|||
</template> |
|||
</div> |
|||
</template> |
|||
</a-table> |
|||
|
|||
<a-pagination |
|||
class="text-right" |
|||
v-model:current="current" |
|||
v-model:pageSize="pageSize" |
|||
:total="dataTotal" |
|||
@change="handlePage" |
|||
show-less-items |
|||
/> |
|||
</div> |
|||
|
|||
<a-modal |
|||
v-model:visible="visible" |
|||
title="今日计划" |
|||
centered |
|||
:footer="null" |
|||
width="800px" |
|||
style="padding: 0; overflow-y: auto" |
|||
:body-style="bodystyle" |
|||
> |
|||
<div style="padding: 0 96px"> |
|||
<div class="mb-4"> |
|||
当前操作项目: |
|||
<span class="text-red-500">{{ currProName }} </span>, 当前操作成员: |
|||
<span class="text-red-500"> {{ currEmpName }} </span> |
|||
</div> |
|||
|
|||
<a-form :model="modalFormState"> |
|||
<div v-for="(item, index) in modalFormState" :key="index"> |
|||
<div class="flex justify-between items-center text-gray-400"> |
|||
<div class="mb-5 text-right" style="width: 120px">计划结果{{ index + 1 }}:</div> |
|||
<DeleteOutlined v-if="modalFormState.length > 1 && !isDisabled" @click="delFormItem(index)" /> |
|||
</div> |
|||
|
|||
<a-form-item name="taskName" style="margin-bottom: 20px"> |
|||
<div class="flex"> |
|||
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px"> |
|||
<span style="color: #ff5353; margin-right: 5px">*</span>今日计划结果{{ index + 1 }}: |
|||
</label> |
|||
<a-textarea v-model:value="item.taskName" :disabled="isDisabled" placeholder="请输入今日计划" /> |
|||
</div> |
|||
</a-form-item> |
|||
|
|||
<a-form-item name="deliverName" style="margin-bottom: 20px"> |
|||
<div class="flex"> |
|||
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px"> |
|||
<span style="color: #ff5353; margin-right: 5px">*</span>交付物: |
|||
</label> |
|||
<a-input v-model:value="item.deliverName" :disabled="isDisabled" placeholder="请输入交付物" /> |
|||
</div> |
|||
</a-form-item> |
|||
|
|||
<a-form-item name="showDeadLine" style="margin-bottom: 20px"> |
|||
<div class="flex"> |
|||
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px"> |
|||
<span style="color: #ff5353; margin-right: 5px">*</span>截止时间: |
|||
</label> |
|||
<a-date-picker class="w-full" v-model:value="item.showDeadLine" :disabled="isDisabled" /> |
|||
</div> |
|||
</a-form-item> |
|||
|
|||
<a-form-item name="showDuration" style="margin-bottom: 20px"> |
|||
<div class="flex"> |
|||
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px"> |
|||
<span style="color: #ff5353; margin-right: 5px">*</span>时长: |
|||
</label> |
|||
|
|||
<a-input-group compact class="items-center" style="width: 100%; display: flex"> |
|||
<a-auto-complete |
|||
style="width: calc(100% - 35px)" |
|||
v-model:value="item.showDuration" |
|||
:options="workDurations" |
|||
placeholder="请输入工作时长" |
|||
:disabled="isDisabled" |
|||
@change="handleDuration($event, index)" |
|||
/> |
|||
|
|||
<div style="width: 35px; border: none" class="text-right">小时</div> |
|||
</a-input-group> |
|||
</div> |
|||
</a-form-item> |
|||
|
|||
<a-form-item name="checker" style="margin-bottom: 20px"> |
|||
<div class="flex"> |
|||
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px"> |
|||
<span style="color: #ff5353; margin-right: 5px">*</span>检查人: |
|||
</label> |
|||
<a-select |
|||
v-model:value="item.checker" |
|||
:options="memberList" |
|||
:field-names="{ label: 'empName', value: 'empName' }" |
|||
show-search |
|||
placeholder="请选择检查人" |
|||
:disabled="isDisabled" |
|||
:filter-option="filterOption" |
|||
@change="handleInspector($event, index)" |
|||
></a-select> |
|||
</div> |
|||
</a-form-item> |
|||
|
|||
<a-form-item name="cooperation" style="margin-bottom: 20px"> |
|||
<div class="flex"> |
|||
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px"> 是否协作: </label> |
|||
<a-radio-group v-model:value="item.cooperation" class="w-full items-center" style="display: flex"> |
|||
<a-radio class="items-center" style="display: flex" :value="1" :disabled="isDisabled">是</a-radio> |
|||
<a-radio class="items-center" style="display: flex" :value="0" :disabled="isDisabled">否</a-radio> |
|||
</a-radio-group> |
|||
</div> |
|||
</a-form-item> |
|||
|
|||
<a-form-item name="deliverLink" style="margin-bottom: 20px"> |
|||
<div class="flex"> |
|||
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px"> 交付物链接: </label> |
|||
<a-textarea v-model:value="item.deliverLink" :disabled="isDisabled" placeholder="请输入交付物链接" /> |
|||
</div> |
|||
</a-form-item> |
|||
</div> |
|||
|
|||
<a-form-item v-if="!isDisabled"> |
|||
<div class="flex"> |
|||
<label class="flex-shrink-0 text-right" style="width: 120px"></label> |
|||
<a-button style="color: #1890ff; border-color: #1890ff" type="dashed" @click="addFormItem">+ 添加</a-button> |
|||
</div> |
|||
</a-form-item> |
|||
|
|||
<a-form-item v-if="!isDisabled"> |
|||
<div class="flex items-center"> |
|||
<label class="flex-shrink-0 text-right" style="width: 120px"></label> |
|||
<a-button class="mr-4" type="primary" @click="submitForm">提交</a-button> |
|||
<a-button @click="cancleModal">取消</a-button> |
|||
</div> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { reactive, ref, onMounted, computed, watch } from 'vue'; |
|||
import dayjs from 'dayjs'; |
|||
import { FullscreenExitOutlined, FullscreenOutlined, DeleteOutlined, PushpinOutlined } from '@ant-design/icons-vue'; |
|||
import { getBasicInfo, queryTasks, submitTask, clockQuery, clockPunch, exportQuery } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const projectId = computed(() => store.getters['project/projectId']); |
|||
const sessionProjectId = sessionStorage.getItem('projectId'); |
|||
const roleId = computed(() => store.state.role.roleId); |
|||
const members = computed(() => store.state.role.members); |
|||
const userId = computed(() => store.getters['user/userId']); // 用户id |
|||
const isFullScreen = computed(() => store.state.layout.isFullScreen); // 是否全屏 |
|||
const visible = ref(false); // 是否显示弹框表单 |
|||
const isDisabled = ref(false); // 是否允许编辑 |
|||
const morning = ref(false); // 早打卡 |
|||
const night = ref(false); // 晚打卡 |
|||
const checkerId = ref(null); // 打卡审核人id |
|||
const recordId = ref(null); // 记录id |
|||
const memberId = ref(null); // 成员id |
|||
|
|||
// 筛选表单 |
|||
const formState = reactive({ |
|||
timeRange: [dayjs(+new Date().getTime()), dayjs(+new Date().getTime()).add(1, 'day')], // 时间范围 |
|||
staffRange: [], // 员工 |
|||
programName: [], // 项目名称 |
|||
}); |
|||
const startTime = dayjs(+new Date().getTime()); |
|||
const endTime = dayjs(+new Date().getTime()).add(1, 'day'); |
|||
|
|||
// 下拉选选项 |
|||
const memberList = ref([]); // 成员列表 |
|||
const proList = ref([]); // 项目列表 |
|||
const emps = ref([]); // 选中的筛选成员 |
|||
const proDatas = ref([]); // 选中的筛选项目 |
|||
|
|||
// 表格元素 |
|||
const columns = ref(); |
|||
const current = ref(1); // 当前页数 |
|||
const pageSize = ref(10); // 每页条数 |
|||
const dataTotal = ref(0); // 数据总量 |
|||
|
|||
// 表格数据 |
|||
const columnDatas = ref([]); |
|||
|
|||
const bodystyle = { |
|||
height: '650px', |
|||
overflow: 'hidden', |
|||
overflowY: 'scroll', |
|||
}; |
|||
// 今日计划表单 |
|||
const modalFormState = ref([]); |
|||
// 工作时长 |
|||
const workDurations = [...Array(8)].map((_, i) => ({ value: `${i + 1}` })); |
|||
const isSubmitDeliver = ref(false); // 是否提交交付物 |
|||
const currEmpId = ref(null); // 当前点击的用户id |
|||
const currEmpName = ref(null); // 当前点击的用户名 |
|||
const currProId = ref(null); // 当前点击的项目id |
|||
const currProName = ref(null); // 当前点击的项目名 |
|||
|
|||
onMounted(async () => { |
|||
getClockQuery(); |
|||
await getInfo(); |
|||
await getQueryTasks(); |
|||
}); |
|||
|
|||
// 改变全屏状态 |
|||
function changeIsFullScreen(data) { |
|||
store.commit('layout/setIsFullScreen', data); |
|||
} |
|||
|
|||
// 获取基本信息(成员列表、项目列表) |
|||
async function getInfo() { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const data = await getBasicInfo(url); |
|||
memberList.value = data.emps; |
|||
pageSize.value = data.emps.length; |
|||
proList.value = data.pros; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 获取任务列表 |
|||
async function getQueryTasks() { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const params = { |
|||
param: { |
|||
startTime: startTime.value || formState.timeRange[0].startOf('day').valueOf(), |
|||
endTime: endTime.value || formState.timeRange[1].startOf('day').valueOf(), |
|||
emps: emps.value || [], |
|||
pros: proDatas.value || [], |
|||
}, |
|||
}; |
|||
|
|||
// 相差天数 |
|||
const days = dayjs(+formState.timeRange[1].startOf('day')).diff(+formState.timeRange[0].startOf('day'), 'day'); |
|||
dataTotal.value = pageSize.value * days; // 分页数据总量 |
|||
|
|||
const data = await queryTasks(params, url); |
|||
|
|||
columns.value = [ |
|||
{ |
|||
title: '时间', |
|||
width: 80, |
|||
dataIndex: 'time', |
|||
key: 'time', |
|||
fixed: 'left', |
|||
align: 'center', |
|||
}, |
|||
{ |
|||
title: '员工', |
|||
width: 90, |
|||
dataIndex: 'staff', |
|||
key: 'staff', |
|||
fixed: 'left', |
|||
align: 'center', |
|||
}, |
|||
]; |
|||
|
|||
columnDatas.value = []; |
|||
|
|||
data.pros.forEach((proId, index) => { |
|||
const proInfo = proList.value.find(item => item.id === proId); |
|||
columns.value.push({ |
|||
title: proInfo.projectShortName, |
|||
dataIndex: 'program', |
|||
key: `${index}`, |
|||
proId, |
|||
width: 200, |
|||
align: 'center', |
|||
}); |
|||
}); |
|||
|
|||
memberList.value.forEach((member, key) => { |
|||
const obj = { |
|||
empId: member.id, |
|||
key, |
|||
time: dayjs(+params.param.startTime).format('MM-DD'), |
|||
staff: `${member.empName}`, |
|||
program: [], |
|||
bgColor: key % 2 === 1 ? 1 : 0, |
|||
}; |
|||
|
|||
const currMember = data.recs.filter(item => member.id === item.empId); |
|||
|
|||
if (currMember.length > 0) { |
|||
const { pros } = currMember[0]; |
|||
obj.program = pros; |
|||
} |
|||
|
|||
columnDatas.value.push(obj); |
|||
}); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 筛选 |
|||
function handleSubmit() { |
|||
emps.value = []; |
|||
proDatas.value = []; |
|||
memberList.value.forEach(item => { |
|||
const index = formState.staffRange.findIndex(emp => emp === item.empName); |
|||
if (index > -1) emps.value.push(item.id); |
|||
}); |
|||
|
|||
proList.value.forEach(item => { |
|||
const index = formState.programName.findIndex(pro => pro === item.projectName); |
|||
if (index > -1) proDatas.value.push(item.id); |
|||
}); |
|||
|
|||
startTime.value = dayjs(+formState.timeRange[0].startOf('day')).valueOf(); |
|||
const end = dayjs(+startTime.value).add(1, 'day'); |
|||
endTime.value = dayjs(+end).valueOf(); |
|||
getQueryTasks(); |
|||
current.value = 1; |
|||
} |
|||
|
|||
// 重置 |
|||
function resetData() { |
|||
formState.timeRange = [dayjs(+new Date().getTime()), dayjs(+new Date().getTime()).add(1, 'day')]; |
|||
formState.staffRange = []; |
|||
formState.programName = []; |
|||
startTime.value = dayjs(+formState.timeRange[0].startOf('day')).valueOf(); |
|||
endTime.value = dayjs(+formState.timeRange[1].startOf('day')).valueOf(); |
|||
emps.value = []; |
|||
proDatas.value = []; |
|||
getQueryTasks(); |
|||
} |
|||
|
|||
// 导出 |
|||
async function handleExport() { |
|||
try { |
|||
const start = formState.timeRange[0].startOf('day').valueOf(); |
|||
const end = formState.timeRange[1].startOf('day').valueOf(); |
|||
const params = { |
|||
param: { |
|||
projectId: projectId.value || sessionProjectId, |
|||
roleId: roleId.value, |
|||
memberIdList: [], |
|||
startTime: start, |
|||
endTime: end, |
|||
}, |
|||
}; |
|||
|
|||
const { url } = store.state.projects.project; |
|||
const data = await exportQuery(params, url); |
|||
window.open(data, '_blank'); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 改变单元格颜色 |
|||
function rowClassName(record, index) { |
|||
const bgColor = record.bgColor ? 'bg-style-color' : ''; |
|||
return bgColor; |
|||
} |
|||
|
|||
// 分页改变 |
|||
function handlePage(e) { |
|||
const start = dayjs(+formState.timeRange[0].startOf('day')).add(e - 1, 'day'); |
|||
const end = dayjs(+start).add(1, 'day'); |
|||
startTime.value = dayjs(+start).valueOf(); |
|||
endTime.value = dayjs(+end).valueOf(); |
|||
getQueryTasks(); |
|||
} |
|||
|
|||
// 打开今日计划编辑框 |
|||
function showModal(data, proId) { |
|||
currEmpId.value = data.empId; |
|||
currProId.value = proId; |
|||
const currEmpInfo = memberList.value.find(item => item.id === data.empId); |
|||
currEmpName.value = currEmpInfo.empName; |
|||
const currProInfo = proList.value.find(item => item.id === proId); |
|||
currProName.value = currProInfo.projectName; |
|||
|
|||
isDisabled.value = dayjs(+new Date().getTime()).format('MM-DD') !== data.time; |
|||
visible.value = true; |
|||
|
|||
modalFormState.value = [ |
|||
{ |
|||
taskName: '', |
|||
deliverName: '', |
|||
showDeadLine: dayjs(+new Date().getTime()), |
|||
deadline: '', |
|||
showDuration: '2', |
|||
duration: '', |
|||
checker: '', |
|||
cooperation: 0, |
|||
deliverLink: '', |
|||
sequence: 0, |
|||
}, |
|||
]; |
|||
data.program.forEach(item => { |
|||
if (proId === item.proId) { |
|||
modalFormState.value = [...item.tasks]; |
|||
} |
|||
}); |
|||
|
|||
modalFormState.value.forEach((item, index) => { |
|||
item.showDeadLine = item.deadline ? dayjs(+item.deadline) : dayjs(+new Date().getTime()); |
|||
item.showDuration = item.duration ? `${item.duration / 3600000}` : '2'; |
|||
item.sequence = index; |
|||
}); |
|||
} |
|||
|
|||
// 检查人筛选 |
|||
function filterOption(input, option) { |
|||
return option.empName.indexOf(input) >= 0; |
|||
} |
|||
|
|||
// 选择工作时长 |
|||
function handleDuration(e, index) { |
|||
modalFormState.value[index].duration = e; |
|||
} |
|||
|
|||
// 选择检查人 |
|||
function handleInspector(e, index) { |
|||
modalFormState.value[index].checker = e; |
|||
} |
|||
|
|||
// 添加计划 |
|||
function addFormItem() { |
|||
const len = modalFormState.value.length; |
|||
modalFormState.value.push({ |
|||
taskName: '', |
|||
deliverName: '', |
|||
showDeadLine: dayjs(+new Date().getTime()), |
|||
deadline: '', |
|||
showDuration: '2', |
|||
duration: '', |
|||
checker: '', |
|||
cooperation: 0, |
|||
deliverLink: '', |
|||
sequence: len, |
|||
}); |
|||
} |
|||
|
|||
// 删除计划 |
|||
const delFormItem = index => { |
|||
modalFormState.value.splice(index, 1); |
|||
}; |
|||
|
|||
// 取消任务弹框 |
|||
function cancleModal() { |
|||
visible.value = false; |
|||
} |
|||
|
|||
// 提交表单 |
|||
async function submitForm() { |
|||
let flag = false; |
|||
for (let i = 0; i < modalFormState.value.length; i++) { |
|||
const data = modalFormState.value[i]; |
|||
data.deadline = dayjs(+data.showDeadLine).valueOf(); |
|||
data.duration = data.showDuration * 3600000; |
|||
|
|||
if (!data.taskName) { |
|||
message.info(`请输入今日计划结果${i + 1}`); |
|||
return false; |
|||
} |
|||
|
|||
if (!data.deliverName) { |
|||
message.info(`请输入交付物`); |
|||
return false; |
|||
} |
|||
|
|||
if (!data.duration) { |
|||
message.info(`请输入时长`); |
|||
return false; |
|||
} |
|||
|
|||
if (!data.checker) { |
|||
message.info(`请选择检查人`); |
|||
return false; |
|||
} |
|||
|
|||
const reg = /[a-zA-z]+:\/\/[^\s]*/; |
|||
if (data.deliverLink && !reg.test(data.deliverLink)) { |
|||
message.info(`请输入正确的链接`); |
|||
return false; |
|||
} |
|||
flag = true; |
|||
} |
|||
|
|||
if (flag) { |
|||
isSubmitDeliver.value = true; |
|||
} |
|||
|
|||
const params = { |
|||
param: { |
|||
time: new Date().getTime(), |
|||
empId: currEmpId.value, |
|||
proId: currProId.value, |
|||
tasks: modalFormState.value, |
|||
}, |
|||
}; |
|||
|
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const data = await submitTask(params, url); |
|||
visible.value = false; |
|||
// punch(); |
|||
getQueryTasks(); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 获取当前打卡信息 |
|||
async function getClockQuery() { |
|||
const start = dayjs(+new Date().getTime()) |
|||
.startOf('day') |
|||
.valueOf(); |
|||
const end = dayjs(+new Date().getTime()) |
|||
.endOf('day') |
|||
.valueOf(); |
|||
|
|||
const params = { |
|||
param: { |
|||
projectId: projectId.value || sessionProjectId, |
|||
roleId: roleId.value, |
|||
memberIdList: [], |
|||
startTime: start, |
|||
endTime: end, |
|||
}, |
|||
}; |
|||
|
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const data = await clockQuery(params, url); |
|||
|
|||
// 审核人 |
|||
if (data[0].recordList[0].lastCheckerId) { |
|||
checkerId.value = data[0].recordList[0].lastCheckerId; |
|||
} else if (data[0].recordList[0].checkerId) { |
|||
checkerId.value = data[0].recordList[0].checkerId; |
|||
} else { |
|||
checkerId.value = members.value.length ? members.value[0].memberId : ''; |
|||
} |
|||
|
|||
// 成员id |
|||
if (data[0].recordList[0].isMine === 1) { |
|||
memberId.value = data[0].recordList[0].memberId; |
|||
} |
|||
|
|||
recordId.value = data[0].recordList[0].id; |
|||
|
|||
morning.value = data[0].recordList[0].morning; // 早打卡状态 |
|||
night.value = data[0].recordList[0].night; // 晚打卡状态 |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
async function punch(clockType) { |
|||
if ((clockType === 0 && morning.value > 0) || (clockType === 1 && night.value > 0)) return; |
|||
|
|||
const dateTime = dayjs(+new Date().getTime()).valueOf(); // 打卡时间 |
|||
|
|||
const params = { param: { checkerId: checkerId.value, memberId: memberId.value, id: recordId.value, clockType, dateTime } }; |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const data = await clockPunch(params, url); |
|||
|
|||
getClockQuery(); |
|||
} catch (error) { |
|||
message.info(`打卡失败,${error}`); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-today::-webkit-scrollbar { |
|||
width: 0 !important; |
|||
} |
|||
|
|||
.select-box { |
|||
position: relative; |
|||
height: 32px; |
|||
} |
|||
|
|||
.select-box :deep(.ant-select-selector) { |
|||
position: absolute; |
|||
left: 0; |
|||
} |
|||
|
|||
.select-box :deep(.ant-select-selection-overflow) { |
|||
width: 225px; |
|||
flex-wrap: nowrap; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
:deep(.bg-style-color) td { |
|||
background-color: #fafafa; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,17 @@ |
|||
<template> |
|||
<div class="w-full block text-center task-card-plugin"> |
|||
<a-button type="primary" style="width: 250px" @click="openDailyAccount">流水账</a-button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
function openDailyAccount() { |
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', ''); |
|||
store.commit('task/setTaskDetailShow', 'dailyAccount'); |
|||
} |
|||
</script> |
|||
@ -0,0 +1,362 @@ |
|||
<template> |
|||
<div> |
|||
<!-- 审核标题 --> |
|||
<div class="flex justify-between items-center"> |
|||
<div>{{ deliverData ? deliverData.deliverName : '' }}审核状态</div> |
|||
</div> |
|||
|
|||
<!-- 审核结果 --> |
|||
<div> |
|||
<!-- 提交人和时间 --> |
|||
<div class="my-2 flex justify-between items-center"> |
|||
<div class="text-gray-400 text-xs"> |
|||
<span class="mr-4" v-if="deliverData.submitMemberName">{{ deliverData.submitMemberName }}</span> |
|||
<span v-if="deliverData.submitTime"> {{ dayjs(+deliverData.submitTime).format('MM-DD HH:mm') }}</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div |
|||
@click="openLink" |
|||
class="break-all text-blue-400 text-xs my-1 cursor-pointer" |
|||
v-if="deliverData.details && deliverData.details[0]" |
|||
> |
|||
{{ deliverData.details[0] }} |
|||
</div> |
|||
|
|||
<!-- 审核人 标题 --> |
|||
<div class="text-gray-400 flex justify-between mt-3"> |
|||
<span>审核</span> |
|||
</div> |
|||
|
|||
<!-- 审核人 列表 --> |
|||
<div v-if="deliverData.checkerList"> |
|||
<div v-for="(item, index) in deliverData.checkerList" :key="index"> |
|||
<template v-if="item.isMine === 1"> |
|||
<div class="mt-2 text-sm flex justify-between" v-show="item.status > 0 && isRepeatCheck === 0"> |
|||
<div> |
|||
<div class="font-semibold">{{ item.checkerName }}</div> |
|||
<div class="text-xs text-gray-400">{{ item.remark }}</div> |
|||
<div class="text-xs text-gray-400" v-if="+item.checkTime > 0">{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }}</div> |
|||
</div> |
|||
|
|||
<div class="time-box" v-if="item.checkDuration"> |
|||
<div class="relative"> |
|||
<div class="initial-duration bg-yellow-400" :style="{ width: item.initialPercent + '%' }"></div> |
|||
<div class="absolute duration-value">默认值:{{ deliverData.initialDuration / 3600000 }}小时</div> |
|||
</div> |
|||
<div class="relative"> |
|||
<div class="duration bg-blue-400" :style="{ width: item.currPercent + '%' }"></div> |
|||
<div class="absolute duration-value">工作量时长:{{ deliverData.duration / 3600000 }}小时</div> |
|||
</div> |
|||
<div class="relative"> |
|||
<div class="check-duration bg-green-400" :style="{ width: item.checkPercent + '%' }"></div> |
|||
<div class="absolute duration-value">确认工作:{{ item.checkDuration / 3600000 }}小时</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 自己是审核人 且审核过 当前审核人的审核状态并展示得分情况 --> |
|||
<div class="text-xs"> |
|||
<div class="mb-1"> |
|||
<div v-if="item.status === 1" class="text-green-600">已通过</div> |
|||
<div v-else-if="item.status === 2" class="text-red-600">已驳回</div> |
|||
</div> |
|||
|
|||
<div class="text-yellow-500 font-medium text-base">{{ item.score }}</div> |
|||
|
|||
<!-- <view class="text-blue-500" @click="repeatCheck(1)">重新审核</view> --> |
|||
</div> |
|||
</div> |
|||
|
|||
<div v-if="item.status === null || item.status === 0 || isRepeatCheck === 1"> |
|||
<!-- <view v-if="isRepeatCheck === 1" class="text-blue-500 text-right" @click="repeatCheck(0)">取消重新审核</view> --> |
|||
|
|||
<div class="mt-3 pb-2" style="border-bottom: 1px solid #d1d5db"> |
|||
<div class="flex justify-between"> |
|||
<div class="mr-1 text-sm flex items-center"> |
|||
<div class="mr-2">确认工作</div> |
|||
|
|||
<div class="flex flex-wrap"> |
|||
<a-button |
|||
:type="checkedIndex === 0 ? 'primary' : 'default'" |
|||
size="small" |
|||
class="my-1 ml-0 mr-2" |
|||
@click="handleSelectTime(0)" |
|||
> |
|||
半小时 |
|||
</a-button> |
|||
<a-button |
|||
:type="checkedIndex === 1 ? 'primary' : 'default'" |
|||
size="small" |
|||
class="my-1 ml-0 mr-2" |
|||
@click="handleSelectTime(1)" |
|||
> |
|||
1小时 |
|||
</a-button> |
|||
<a-button |
|||
:type="checkedIndex === 2 ? 'primary' : 'default'" |
|||
size="small" |
|||
class="my-1 ml-0 mr-2" |
|||
@click="handleSelectTime(2)" |
|||
> |
|||
2小时 |
|||
</a-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 时长 --> |
|||
<div class="flex items-center justify-end flex-1 text-sm"> |
|||
<a-input |
|||
v-model="checkDuration" |
|||
type="text" |
|||
placeholder="工作量时长" |
|||
class="input" |
|||
style="text-align: right; width: 120px" |
|||
></a-input> |
|||
小时 |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="mt-3 flex justify-between items-center"> |
|||
<div>交付物质量</div> |
|||
|
|||
<div class="flex justify-end items-center"> |
|||
<a-input-number v-model:value="score" :max="100" :min="0" :step="1"> </a-input-number> |
|||
<div class="w-64 ml-3"> |
|||
<a-progress :percent="score" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="mt-3 relative"> |
|||
<a-textarea style="height: 80px" v-model:value="commit" placeholder="鼓励一下小伙伴" /> |
|||
|
|||
<div |
|||
class="absolute border border-solid border-gray-300 rounded-md text-center text-base cursor-pointer word-btn" |
|||
@click="showWords = !showWords" |
|||
> |
|||
常 |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="common-list" v-if="showWords"> |
|||
<div v-for="(item, index) in words" :key="index" class="px-2 leading-12 word-item" @click="commit = item"> |
|||
{{ item }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="mt-4 flex justify-center items-center"> |
|||
<a-button class="mx-4" type="primary" @click="handleSubmit(1)"> 通过 </a-button> |
|||
<a-button class="mx-4" type="primary" danger @click="handleSubmit(2)"> 驳回 </a-button> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
|
|||
<div v-for="(item, index) in deliverData.checkerList" :key="index"> |
|||
<!-- 不是我 --> |
|||
<template v-if="item.isMine !== 1"> |
|||
<div class="mt-2 text-sm flex justify-between"> |
|||
<div> |
|||
<div class="font-semibold">{{ item.checkerName }}</div> |
|||
<div class="text-xs text-gray-400">{{ item.remark }}</div> |
|||
<div class="text-xs text-gray-400" v-if="+item.checkTime > 0">{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }}</div> |
|||
</div> |
|||
|
|||
<div class="time-box" v-if="item.checkDuration"> |
|||
<div class="relative"> |
|||
<div class="initial-duration bg-yellow-400" :style="{ width: item.initialPercent + '%' }"></div> |
|||
<div class="absolute duration-value">默认值:{{ deliverData.initialDuration / 3600000 }}小时</div> |
|||
</div> |
|||
<div class="relative"> |
|||
<div class="duration bg-blue-400" :style="{ width: item.currPercent + '%' }"></div> |
|||
<span class="absolute duration-value">工作量时长:{{ deliverData.duration / 3600000 }}小时</span> |
|||
</div> |
|||
<div class="relative"> |
|||
<div class="check-duration bg-green-400" :style="{ width: item.checkPercent + '%' }"></div> |
|||
<span class="absolute duration-value">确认工作:{{ item.checkDuration / 3600000 }}小时</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 不是自己 显示审核状态 --> |
|||
<div class="text-xs"> |
|||
<span v-if="item.status === 1" class="text-green-600"> 已通过 </span> |
|||
<span v-else-if="item.status === 2" class="text-red-600"> 已驳回 </span> |
|||
<span v-else class="text-gray-400"> 待审核 </span> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, watch, defineProps, defineEmits } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { checkDeliver } from 'apis'; |
|||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'; |
|||
import { quickWords } from '@/utils/deliver'; |
|||
|
|||
const props = defineProps({ |
|||
deliverData: { type: Object, default: () => {} }, |
|||
task: { type: Object, default: () => {} }, |
|||
}); |
|||
|
|||
const store = useStore(); |
|||
const deliverData = computed(() => props.deliverData); |
|||
const projectId = computed(() => store.getters['project/projectId']); |
|||
const sessionProjectId = sessionStorage.getItem('projectId'); |
|||
const roleId = computed(() => store.state.role.roleId); |
|||
|
|||
const isRepeatCheck = ref(0); // 是否重新审核 |
|||
const words = computed(() => quickWords.RESOLVE); // 快捷用语 |
|||
|
|||
const checkDuration = ref(2); // 工作量时长 |
|||
const checkedIndex = ref(2); // 默认选中 |
|||
const score = ref(100); // 评分 |
|||
const commit = ref(''); // 评价信息 |
|||
const showWords = ref(false); // 是否显示常用语 |
|||
|
|||
const maxDuration = ref(null); |
|||
|
|||
const emits = defineEmits(['check-success']); |
|||
|
|||
if (Object.keys(props.deliverData).length) { |
|||
checkDuration.value = Number(props.deliverData.duration) / 3600000; // 工作量时长 |
|||
checkedIndex.value = checkDuration.value === 0.5 ? 0 : checkDuration.value === 1 ? 1 : checkDuration.value === 2 ? 2 : -1; |
|||
|
|||
handleDataRender(props.deliverData); |
|||
} |
|||
|
|||
watch(deliverData, () => { |
|||
checkDuration.value = Number(deliverData.value.duration) / 3600000; // 工作量时长 |
|||
checkedIndex.value = checkDuration.value === 0.5 ? 0 : checkDuration.value === 1 ? 1 : checkDuration.value === 2 ? 2 : -1; |
|||
|
|||
handleDataRender(deliverData.value); |
|||
}); |
|||
|
|||
// 处理审核记录数据 |
|||
async function handleDataRender(data) { |
|||
maxDuration.value = |
|||
deliverData.value.initialDuration > deliverData.value.duration ? deliverData.value.initialDuration : deliverData.value.duration; |
|||
|
|||
data.checkerList.forEach(item => { |
|||
if (item.checkDuration) { |
|||
maxDuration.value = maxDuration.value > item.checkDuration ? maxDuration.value : item.checkDuration; |
|||
|
|||
if (maxDuration.value === deliverData.value.initialDuration) { |
|||
item.initialPercent = 100; |
|||
item.currPercent = Math.floor((deliverData.value.duration / deliverData.value.initialDuration) * 100); |
|||
item.checkPercent = Math.floor((item.checkDuration / deliverData.value.initialDuration) * 100); |
|||
} else if (maxDuration.value === deliverData.value.duration) { |
|||
item.currPercent = 100; |
|||
item.initialPercent = Math.floor((deliverData.value.initialDuration / deliverData.value.duration) * 100); |
|||
item.checkPercent = Math.floor((item.checkDuration / deliverData.value.duration) * 100); |
|||
} else if (maxDuration.value === item.checkDuration) { |
|||
item.checkPercent = 100; |
|||
item.initialPercent = Math.floor((deliverData.value.initialDuration / item.checkDuration) * 100); |
|||
item.currPercent = Math.floor((deliverData.value.duration / item.checkDuration) * 100); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
return data; |
|||
} |
|||
|
|||
// 交付物链接 |
|||
function openLink() { |
|||
window.open(props.deliverData.details[0], '_blank'); |
|||
} |
|||
|
|||
// 选择工作量时长 |
|||
function handleSelectTime(data) { |
|||
checkedIndex.value = data; |
|||
checkDuration.value = data === 0 ? 0.5 : data === 1 ? 1 : 2; |
|||
} |
|||
|
|||
/** |
|||
* 提交评审信息 |
|||
* 提交成功后隐藏modal 重置表单控件 |
|||
* 给父组件信息 更新值 |
|||
* @param {string} mode 'RESOLVE'|'REJECT' |
|||
*/ |
|||
async function handleSubmit(mode) { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const deliverRecordId = props.deliverData ? props.deliverData.deliverRecordId : ''; |
|||
const param = { |
|||
param: { |
|||
projectId: projectId.value || sessionProjectId, |
|||
roleId: roleId.value, |
|||
deliverRecordId, |
|||
type: mode === 'RESOLVE' ? 1 : 2, |
|||
remark: commit.value, |
|||
score: score.value, |
|||
checkDuration: checkDuration.value * 3600000, |
|||
msgId: props.task.msgId, |
|||
}, |
|||
}; |
|||
|
|||
await checkDeliver(param, url); |
|||
handleHide(); // 隐藏 + 重置 |
|||
message.info('审核信息提交成功'); |
|||
// 通知父组件评审成功 更新信息 |
|||
emits('submit-end', param); |
|||
} catch (error) { |
|||
console.error('error: ', error); |
|||
message.info('审核信息提交失败, 请稍后重试'); |
|||
} |
|||
} |
|||
|
|||
// 重置 |
|||
function handleHide() { |
|||
score.value = 100; |
|||
commit.value = ''; |
|||
} |
|||
|
|||
// 重新审核 |
|||
function repeatCheck(data) { |
|||
isRepeatCheck.value = data; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.ant-progress .ant-progress-inner { |
|||
width: 40px !important; |
|||
height: 40px !important; |
|||
} |
|||
|
|||
.word-btn { |
|||
right: 10px; |
|||
bottom: 10px; |
|||
width: 30px; |
|||
height: 30px; |
|||
line-height: 28px; |
|||
} |
|||
|
|||
.word-item { |
|||
border-bottom: 1px solid #e5e7eb; |
|||
} |
|||
|
|||
.time-box { |
|||
width: 120px; |
|||
} |
|||
|
|||
.time-box view { |
|||
height: 15px; |
|||
border-radius: 2px; |
|||
margin: 2px 0; |
|||
} |
|||
|
|||
.time-box .duration-value { |
|||
height: 15px; |
|||
line-height: 15px; |
|||
font-size: 12px; |
|||
top: 0; |
|||
left: 0; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,290 @@ |
|||
<template> |
|||
<div class="relative"> |
|||
<div class="text-base text-center font-semibold" style="height: 44px; line-height: 44px; background: rgb(248, 248, 248)"> |
|||
交付物上传记录 |
|||
</div> |
|||
|
|||
<div class="tab-box absolute w-full flex justify-between items-center bg-white"> |
|||
<div |
|||
class="tab-item text-center cursor-pointer" |
|||
v-for="(item, index) in clickList" |
|||
:key="index" |
|||
:class="{ 'tab-curr': item.index === current }" |
|||
@click="changeTabs(item.index)" |
|||
> |
|||
<div class="tab-title px-1 inline-block">{{ item.name }}</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 占位 --> |
|||
<div style="height: 44px"></div> |
|||
|
|||
<div id="deliverCon" class="scroll-box"> |
|||
<!-- 提交 --> |
|||
<div class="p-3 text-base scroll-0">当前提交</div> |
|||
|
|||
<div class="px-3"> |
|||
<p-deliver-upload-second |
|||
class="p-3 bg-white rounded-md" |
|||
v-if="deliverData" |
|||
:deliverData="deliverData" |
|||
:task="task" |
|||
@upload-success="getDeliverData" |
|||
></p-deliver-upload-second> |
|||
</div> |
|||
|
|||
<!-- 审核 --> |
|||
<div class="p-3 text-base scroll-1">审核状态</div> |
|||
|
|||
<div class="px-3"> |
|||
<p-deliver-check-second |
|||
class="p-3 bg-white rounded-md" |
|||
v-if="deliverData" |
|||
:deliverData="deliverData" |
|||
:task="task" |
|||
@submit-end="getDeliverData" |
|||
></p-deliver-check-second> |
|||
</div> |
|||
|
|||
<!-- 历史记录 --> |
|||
<div class="p-3 text-base scroll-2">历史记录</div> |
|||
|
|||
<div class="px-3" v-if="listRef && listRef.length"> |
|||
<div class="bg-white mb-3 rounded-md p-3 text-gray-400" v-for="(item, index) in listRef" :key="index"> |
|||
<!-- 插件名称和提交时间显示 --> |
|||
<div class="flex justify-between mb-2"> |
|||
<div class="text-gray-800">{{ deliverName }}</div> |
|||
<div class="ml-1 text-xs w-24 text-right">{{ dayjs(+item.submitTime).format('MM-DD HH:mm') }}</div> |
|||
</div> |
|||
|
|||
<!-- 提交的链接 --> |
|||
<div @click="openLink" class="break-all text-blue-400 text-xs my-1 cursor-pointer" v-if="item.details && item.details[0]"> |
|||
{{ item.details[0] }} |
|||
</div> |
|||
|
|||
<!-- 该插件物的审核人 --> |
|||
<div class="mb-1 mt-3">审核人</div> |
|||
<div class="flex justify-between mb-2" v-for="(checkItem, checkIndex) in item.checkerList" :key="checkIndex"> |
|||
<div> |
|||
<div class="mb-1 text-gray-800 font-semibold"> |
|||
{{ checkItem.checkerName }} |
|||
</div> |
|||
<div class="mb-1 text-xs"> |
|||
{{ checkItem.remark }} |
|||
</div> |
|||
<div class="mb-1 text-xs" v-if="+checkItem.checkTime > 0"> |
|||
{{ dayjs(+checkItem.checkTime).format('MM-DD HH:mm') }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="time-box" v-if="checkItem.checkDuration"> |
|||
<div class="relative"> |
|||
<div class="initial-duration bg-yellow-400" :style="{ width: checkItem.initialPercent + '%' }"></div> |
|||
<span class="absolute duration-value">默认值:{{ initialDuration / 3600000 }}小时</span> |
|||
</div> |
|||
<div class="relative"> |
|||
<div class="duration bg-blue-400" :style="{ width: checkItem.currPercent + '%' }"></div> |
|||
<span class="absolute duration-value">工作量时长:{{ item.duration / 3600000 }}小时</span> |
|||
</div> |
|||
<div class="relative"> |
|||
<div class="check-duration bg-green-400" :style="{ width: checkItem.checkPercent + '%' }"></div> |
|||
<span class="absolute duration-value">确认工作:{{ checkItem.checkDuration / 3600000 }}小时</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="text-center text-xs"> |
|||
<div v-if="checkItem.status == null" class="text-gray-400">待审核</div> |
|||
<div v-else-if="checkItem.status === 1"> |
|||
<div class="text-green-600 mb-1">已通过</div> |
|||
<div class="text-yellow-500 font-medium text-base">{{ checkItem.score }}</div> |
|||
</div> |
|||
<div class="text-red-600" v-else>已驳回</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { ref, onMounted, computed, watch, provide } from 'vue'; |
|||
import { getDeliverByTaskId, getDeliverHistory } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
import pDeliverUploadSecond from '@/plugins/p-deliver-second/p-deliver-upload-second.vue'; |
|||
import pDeliverCheckSecond from '@/plugins/p-deliver-second/p-deliver-check-second.vue'; |
|||
|
|||
const store = useStore(); |
|||
const detailParams = computed(() => store.state.task.detailParams); // 参数 |
|||
const sessionDetailParams = sessionStorage.getItem('detailParams'); // 参数 |
|||
const clickList = ref([ |
|||
{ |
|||
index: 0, |
|||
name: '提交', |
|||
}, |
|||
{ |
|||
index: 1, |
|||
name: '审核', |
|||
}, |
|||
{ |
|||
index: 2, |
|||
name: '历史记录', |
|||
}, |
|||
]); |
|||
const current = ref(0); // 当前点击的按钮 |
|||
|
|||
const deliverData = ref(null); // 交付物信息 |
|||
const task = ref(null); // 任务信息 |
|||
const deliverName = ref(null); |
|||
const listRef = ref([]); // 历史记录列表 |
|||
const initialDuration = ref(null); // 历史交付物默认值 |
|||
const maxDuration = ref(null); |
|||
|
|||
// 设置详情页参数 |
|||
if (sessionDetailParams && !detailParams.value) { |
|||
const params = JSON.parse(sessionDetailParams); |
|||
store.commit('task/setTaskDetailParams', params); |
|||
provide('deliver', params.deliver); |
|||
provide('task', params.task); |
|||
} |
|||
|
|||
if (detailParams.value) { |
|||
deliverData.value = detailParams.value.deliver; |
|||
task.value = detailParams.value.task; |
|||
provide('deliver', deliverData); |
|||
provide('task', task.value); |
|||
} |
|||
|
|||
// 监听参数变化 |
|||
watch(detailParams, () => { |
|||
deliverData.value = detailParams.value.deliver; |
|||
task.value = detailParams.value.task; |
|||
}); |
|||
|
|||
onMounted(() => { |
|||
document.getElementById('deliverCon').addEventListener('scroll', handleScroll, true); |
|||
getDeliverData(); |
|||
}); |
|||
|
|||
// 点击改变当前显示内容 |
|||
function changeTabs(index) { |
|||
current.value = index; |
|||
} |
|||
|
|||
// 向上向下滚动 |
|||
function handleScroll(e) { |
|||
// 变量scrollTop是滚动条滚动时,距离顶部的距离 |
|||
const { scrollTop } = e.target; |
|||
// 变量windowHeight是可视区的高度 |
|||
const windowHeight = e.target.clientHeight; |
|||
// 变量scrollHeight是滚动条的总高度 |
|||
const { scrollHeight } = e.target; |
|||
|
|||
console.log('scrollTop', scrollTop); |
|||
} |
|||
|
|||
// 根据交付物id获取上传记录 |
|||
async function getHistory() { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const param = { param: { deliverId: detailParams.value.deliver.deliverId } }; |
|||
|
|||
const data = await getDeliverHistory(param, url); |
|||
deliverName.value = data.deliverName; |
|||
initialDuration.value = data.initialDuration; |
|||
listRef.value = data.deliverRecordList; |
|||
|
|||
listRef.value.forEach(item => { |
|||
handleDataRender(item); |
|||
}); |
|||
} catch (error) { |
|||
console.log('error: ', error); |
|||
message.info('获取交付物历史失败'); |
|||
} |
|||
} |
|||
|
|||
// 根据任务id获取交付物信息 |
|||
async function getDeliverData() { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const { id: taskId } = task.value; |
|||
if (!taskId) return; |
|||
const param = { param: { taskId } }; |
|||
const data = await getDeliverByTaskId(param, url); |
|||
deliverData.value = data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
} |
|||
} |
|||
|
|||
// 处理审核记录数据 |
|||
async function handleDataRender(data) { |
|||
maxDuration.value = |
|||
deliverData.value.initialDuration > deliverData.value.duration ? deliverData.value.initialDuration : deliverData.value.duration; |
|||
|
|||
data.checkerList.forEach(item => { |
|||
if (item.checkDuration) { |
|||
maxDuration.value = maxDuration.value > item.checkDuration ? maxDuration.value : item.checkDuration; |
|||
|
|||
if (maxDuration.value === deliverData.value.initialDuration) { |
|||
item.initialPercent = 100; |
|||
item.currPercent = Math.floor((deliverData.value.duration / deliverData.value.initialDuration) * 100); |
|||
item.checkPercent = Math.floor((item.checkDuration / deliverData.value.initialDuration) * 100); |
|||
} else if (maxDuration.value === deliverData.value.duration) { |
|||
item.currPercent = 100; |
|||
item.initialPercent = Math.floor((deliverData.value.initialDuration / deliverData.value.duration) * 100); |
|||
item.checkPercent = Math.floor((item.checkDuration / deliverData.value.duration) * 100); |
|||
} else if (maxDuration.value === item.checkDuration) { |
|||
item.checkPercent = 100; |
|||
item.initialPercent = Math.floor((deliverData.value.initialDuration / item.checkDuration) * 100); |
|||
item.currPercent = Math.floor((deliverData.value.duration / item.checkDuration) * 100); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
return data; |
|||
} |
|||
|
|||
// 交付物链接 |
|||
function openLink() { |
|||
window.open(deliverData.value.details[0], '_blank'); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.tab-box { |
|||
height: 44px; |
|||
z-index: 8; |
|||
} |
|||
|
|||
.tab-box .tab-item { |
|||
width: calc(100% / 3); |
|||
height: 44px; |
|||
line-height: 40px; |
|||
} |
|||
|
|||
.tab-box .tab-curr .tab-title { |
|||
color: #2979ff; |
|||
border-bottom: 3px solid #2979ff; |
|||
} |
|||
|
|||
.time-box { |
|||
width: 120px; |
|||
} |
|||
|
|||
.time-box view { |
|||
height: 15px; |
|||
border-radius: 2px; |
|||
margin: 2px 0; |
|||
} |
|||
|
|||
.time-box .duration-value { |
|||
height: 15px; |
|||
line-height: 15px; |
|||
font-size: 12px; |
|||
top: 0; |
|||
left: 0; |
|||
color: #333; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,63 @@ |
|||
<template> |
|||
<!-- 任务名插件 --> |
|||
<div v-if="deliver" class="task-card-plugin"> |
|||
<p-deliver-upload-second v-if="deliver" @upload-success="getDeliverData"></p-deliver-upload-second> |
|||
|
|||
<!-- <p-deliver-check-second |
|||
class="mt-3" |
|||
v-if="deliver && deliver.details && deliver.details.length" |
|||
@check-success="getDeliverData" |
|||
></p-deliver-check-second> --> |
|||
|
|||
<div class="mt-3" v-if="deliver && deliver.details && deliver.details.length" @check-success="getDeliverData"> |
|||
<!-- 审核标题 --> |
|||
<div class="flex justify-between items-center" @click="openDeliverHistory"> |
|||
<div>{{ deliver ? deliver.deliverName : '' }}审核状态</div> |
|||
<right-outlined /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { ref, inject, provide } from 'vue'; |
|||
import { getDeliverByTaskId } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { RightOutlined } from '@ant-design/icons-vue'; |
|||
import pDeliverUploadSecond from '@/plugins/p-deliver-second/p-deliver-upload-second.vue'; |
|||
// import pDeliverCheckSecond from '@/plugins/p-deliver-second/p-deliver-check-second.vue'; |
|||
|
|||
const store = useStore(); |
|||
const task = inject('task'); |
|||
const pluginInfo = inject('pluginInfo'); |
|||
const deliver = ref(null); // 服务端返回的交付物信息 |
|||
|
|||
deliver.value = pluginInfo && pluginInfo.data ? JSON.parse(pluginInfo.data) : null; |
|||
provide('deliver', deliver); |
|||
|
|||
// 点击查看交付物详情页 |
|||
function openDeliverHistory() { |
|||
const detailParams = { |
|||
deliver: deliver.value, |
|||
task, |
|||
}; |
|||
store.commit('task/setTaskDetailParams', detailParams); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', 'deliverSecDetail'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
|
|||
// 根据任务id获取交付物信息 |
|||
async function getDeliverData() { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const { id: taskId } = task; |
|||
if (!taskId) return; |
|||
const param = { param: { taskId } }; |
|||
const data = await getDeliverByTaskId(param, url); |
|||
deliver.value = data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,224 @@ |
|||
<template> |
|||
<div @longpress.prevent="showMask = true"> |
|||
<!-- 插件名称和提交按钮 --> |
|||
<div class="flex item-center justify-between"> |
|||
<div class="flex-1"> |
|||
<div v-if="deliver.deliverName" class="relative inline-block"> |
|||
{{ deliver.deliverName }} |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 提交 --> |
|||
<a-button type="primary" size="small" @click="submit" :disabled="submitState" :loading="submitBtnLoading"> 提交 </a-button> |
|||
</div> |
|||
|
|||
<!-- 插件上传方式 --> |
|||
<div class="mt-3"> |
|||
<div class="link-box"> |
|||
<a-input v-model:value="linkValue" placeholder="请输入交付物地址/链接" /> |
|||
</div> |
|||
<div class="mt-3"> |
|||
<!-- <a-button type="primary" size="small" class="mr-3" @click="paste">粘贴</a-button> --> |
|||
|
|||
<div class="inline-block"> |
|||
<a-upload name="param" :action="action" :headers="headers" :showUploadList="false" :max-count="1" @change="handleChange"> |
|||
<a-button type="primary" size="small" class="mr-3">文件</a-button> |
|||
</a-upload> |
|||
</div> |
|||
|
|||
<!-- <a-button type="primary" size="small" class="mr-3" @click="uploadPhoto">拍照</a-button> --> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="border border-solid rounded-sm mt-5 p-2 pt-4 pl-1" style="border-color: #d9d9d9"> |
|||
<div class="relative flex justify-between"> |
|||
<div class="absolute bg-white text-sm duration-title">工作量时长</div> |
|||
|
|||
<div class="mr-3 flex flex-wrap items-center"> |
|||
<a-button |
|||
:type="checkedIndex === 0 ? 'primary' : 'default'" |
|||
size="small" |
|||
class="m-1" |
|||
style="padding: 0 5px" |
|||
@click="handleSelectTime(0)" |
|||
> |
|||
半小时 |
|||
</a-button> |
|||
<a-button |
|||
:type="checkedIndex === 1 ? 'primary' : 'default'" |
|||
size="small" |
|||
class="m-1" |
|||
style="padding: 0 5px" |
|||
@click="handleSelectTime(1)" |
|||
> |
|||
1小时 |
|||
</a-button> |
|||
<a-button |
|||
:type="checkedIndex === 2 ? 'primary' : 'default'" |
|||
size="small" |
|||
class="m-1" |
|||
style="padding: 0 5px" |
|||
@click="handleSelectTime(2)" |
|||
> |
|||
2小时 |
|||
</a-button> |
|||
</div> |
|||
|
|||
<!-- 时长 --> |
|||
<div class="flex items-center justify-end flex-1 text-sm"> |
|||
<a-input-number id="inputNumber" v-model:value="duration" :min="0" placeholder="工作量时长" /> |
|||
<span class="flex-shrink-0">小时</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 插件审核人员选择 --> |
|||
<ReviewerSecond class="mt-3" ref="reviewerRef" :dataCheckers="deliver.checkerList ? deliver.checkerList : []" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { ref, inject, computed, watch, defineEmits, defineProps } from 'vue'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { submitDeliverInfo, uploadImg } from 'apis'; |
|||
import ReviewerSecond from '@/components/tall/Reviewer/ReviewerSecond.vue'; |
|||
|
|||
const props = defineProps({ |
|||
deliverData: { type: Object, default: () => {} }, |
|||
task: { type: Object, default: () => {} }, |
|||
}); |
|||
|
|||
const store = useStore(); |
|||
const projectId = computed(() => store.getters['project/projectId']); |
|||
const sessionProjectId = sessionStorage.getItem('projectId'); |
|||
const roleId = computed(() => store.state.role.roleId); |
|||
|
|||
const deliverInject = inject('deliver'); // 交付物初始值 |
|||
const taskInject = inject('task'); // 任务信息 |
|||
const deliver = computed(() => (props.deliverData && Object.keys(props.deliverData).length ? props.deliverData : deliverInject.value)); |
|||
const task = computed(() => (props.task && Object.keys(props.task).length ? props.task : taskInject)); |
|||
|
|||
const linkValue = ref(''); // 链接的值 |
|||
const duration = ref(2); // 工作量时长 |
|||
const checkedIndex = ref(2); // 工作量时长默认选中 |
|||
|
|||
// 上传文件 |
|||
const action = uploadImg; |
|||
const token = computed(() => store.state.user.token); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
|
|||
// 判断提交按钮的状态 |
|||
const submitState = computed(() => !linkValue.value); // 按钮状态 |
|||
const submitBtnLoading = ref(false); // 是否加载中 |
|||
|
|||
const reviewerRef = ref(null); // 审核人组件 |
|||
|
|||
const showMask = ref(false); // 编辑和删除页面是否显示 |
|||
|
|||
const emits = defineEmits(['upload-success']); |
|||
|
|||
if (Object.keys(deliver.value).length) { |
|||
linkValue.value = deliver.value.details[0]; // 交付物内容 |
|||
// 工作量时长 |
|||
duration.value = deliver.value.duration ? Number(deliver.value.duration) / 3600000 : Number(deliver.value.initialDuration) / 3600000; |
|||
checkedIndex.value = duration.value === 0.5 ? 0 : duration.value === 1 ? 1 : duration.value === 2 ? 2 : -1; |
|||
} |
|||
|
|||
watch(deliver, () => { |
|||
console.log('deliver', deliver); |
|||
linkValue.value = deliver.value.details[0]; // 交付物内容 |
|||
// 工作量时长 |
|||
duration.value = deliver.value.duration ? Number(deliver.value.duration) / 3600000 : Number(deliver.value.initialDuration) / 3600000; |
|||
checkedIndex.value = duration.value === 0.5 ? 0 : duration.value === 1 ? 1 : duration.value === 2 ? 2 : -1; |
|||
}); |
|||
|
|||
function handleChange(info) { |
|||
console.log('info', info); |
|||
} |
|||
|
|||
// 点击按钮改变工作量时长 |
|||
function handleSelectTime(data) { |
|||
checkedIndex.value = data; |
|||
duration.value = data === 0 ? 0.5 : data === 1 ? 1 : 2; |
|||
} |
|||
|
|||
// 提交交付物 |
|||
async function submit() { |
|||
console.log(reviewerRef.value); |
|||
const { checkedCheckers } = reviewerRef.value; // 拿到选择检查人组件中选中的检查人 |
|||
// 提交前的验证 |
|||
if (!validateDeliverForm(checkedCheckers)) return; |
|||
|
|||
submitBtnLoading.value = true; // 按钮loading |
|||
|
|||
try { |
|||
const checkerList = []; |
|||
checkedCheckers.forEach(item => { |
|||
checkerList.push(item.memberId); |
|||
}); |
|||
|
|||
const { url } = store.state.projects.project; |
|||
|
|||
const params = { |
|||
param: { |
|||
projectId: projectId.value || sessionProjectId, |
|||
roleId: roleId.value, |
|||
deliverId: deliver.value.deliverId, |
|||
fileList: [linkValue.value], |
|||
checkerList, |
|||
duration: Number(duration.value) * 60 * 60 * 1000, |
|||
msgId: task.msgId, |
|||
}, |
|||
}; |
|||
|
|||
const data = await submitDeliverInfo(params, url); |
|||
message.info('提交交付物信息成功'); |
|||
resetControlState(); // 重置控件的初始状态 |
|||
emits('upload-success'); |
|||
} catch (error) { |
|||
message.info('提交交付物信息失败'); |
|||
submitBtnLoading.value = false; // 按钮loading |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 粘贴 |
|||
// function paste() {} |
|||
|
|||
// 拍照 |
|||
// function uploadPhoto() {} |
|||
|
|||
// 验证提交的交付物信息格式 |
|||
function validateDeliverForm(checkedCheckers) { |
|||
const reg = /[a-zA-z]+:\/\/[^\s]*/; |
|||
if (!reg.test(linkValue.value)) { |
|||
// 显示toast信息 |
|||
message.info('请输入正确的链接'); |
|||
return false; |
|||
} |
|||
// 没有检查人 提示选择检查人 |
|||
if (!checkedCheckers || !checkedCheckers.length) { |
|||
message.info('请选择检查人'); |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
// 重置控件的初始状态 |
|||
function resetControlState() { |
|||
submitBtnLoading.value = false; // 隐藏loading |
|||
reviewerRef.value.collapsed = true; // 折叠检查人面板 |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.duration-title { |
|||
width: 85px; |
|||
height: 20px; |
|||
line-height: 20px; |
|||
text-align: center; |
|||
top: -25px; |
|||
left: 10px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,130 @@ |
|||
<template> |
|||
<a-modal v-model:visible="visible" centered :footer="null" :closable="false" @cancel="handleHide"> |
|||
<div> |
|||
<!-- 审核标题 --> |
|||
<div class="modal-content-head">{{ data.mode === 'RESOLVE' ? '审核通过' : '审核驳回' }}</div> |
|||
|
|||
<div class="modal-content-body mt-5"> |
|||
<!-- 评分 --> |
|||
<div class="flex justify-between items-center mb-4" v-show="data.mode === 'RESOLVE'"> |
|||
<a-input-number v-model:value="score" :max="100" :min="0" :step="1"> </a-input-number> |
|||
<div class="w-64"> |
|||
<a-progress :percent="score" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<a-textarea v-model:value="commit" /> |
|||
|
|||
<div class="common-list"> |
|||
<div v-for="(item, index) in words" :key="index" class="leading-12" @click="commit = item"> |
|||
{{ item }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="modal-content-foot mt-3 flex justify-center items-center"> |
|||
<a-button class="mr-4" @click="handleHide">取消</a-button> |
|||
<a-button class="" type="primary" @click="handleSubmit(data.mode)">确定</a-button> |
|||
</div> |
|||
</div> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, watch, inject, defineProps, defineEmits } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { checkDeliver } from 'apis'; |
|||
import { quickWords } from '@/utils/deliver'; |
|||
|
|||
const props = defineProps({ |
|||
data: { type: Object, default: () => {} }, |
|||
msgId: { default: '', type: String }, |
|||
}); |
|||
|
|||
const emits = defineEmits(['hide', 'submit-end']); |
|||
|
|||
const store = useStore(); |
|||
const task = inject('task'); |
|||
const projectId = computed(() => store.getters['project/projectId']); |
|||
const sessionProjectId = sessionStorage.getItem('projectId'); |
|||
const roleId = computed(() => store.state.role.roleId); |
|||
const words = computed(() => quickWords[props.data.mode]); // 快捷用语 |
|||
|
|||
const visible = ref(false); |
|||
|
|||
const commit = ref(''); // 提交的信息 |
|||
const score = ref(100); // 评分 |
|||
|
|||
watch(props, () => { |
|||
visible.value = props.data.mode !== 'HIDE'; |
|||
}); |
|||
|
|||
/** |
|||
* 提交评审信息 |
|||
* 提交成功后隐藏modal 重置表单控件 |
|||
* 给父组件信息 更新值 |
|||
* @param {string} mode 'RESOLVE'|'REJECT' |
|||
*/ |
|||
async function handleSubmit(mode) { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const deliverRecordId = props.data.deliverRecordId(); |
|||
const param = { |
|||
param: { |
|||
projectId: projectId.value || sessionProjectId, |
|||
roleId: roleId.value, |
|||
deliverRecordId, |
|||
type: mode === 'RESOLVE' ? 1 : 2, |
|||
remark: commit.value, |
|||
score: mode === 'RESOLVE' ? score.value : '', |
|||
msgId: task.msgId, |
|||
}, |
|||
}; |
|||
|
|||
await checkDeliver(param, url); |
|||
handleHide(); // 隐藏 + 重置 |
|||
message.info('审核信息提交成功'); |
|||
// 通知父组件评审成功 更新信息 |
|||
emits('submit-end', param); |
|||
} catch (error) { |
|||
console.error('error: ', error); |
|||
message.info('审核信息提交失败, 请稍后重试'); |
|||
} |
|||
} |
|||
|
|||
// 隐藏及 重置 |
|||
function handleHide() { |
|||
emits('hide'); |
|||
// 重置相关数据 |
|||
score.value = 100; |
|||
commit.value = ''; |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.modal-content-head { |
|||
text-align: center; |
|||
margin-top: 40rpx; |
|||
margin-bottom: 20rpx; |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.common-list { |
|||
height: 12.5rem; |
|||
overflow-y: scroll; |
|||
} |
|||
|
|||
.common-list::-webkit-scrollbar { |
|||
display: none; |
|||
} |
|||
|
|||
.common-list div { |
|||
border-bottom: 1px solid #e5e7eb; |
|||
} |
|||
|
|||
.common-list div:last-child { |
|||
border-bottom: none; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,72 @@ |
|||
<template> |
|||
<div class="h-full"> |
|||
<div class="text-base text-center font-semibold" style="height: 44px; line-height: 44px; background: rgb(248, 248, 248)">审核记录</div> |
|||
|
|||
<div class="p-3"> |
|||
<div class="bg-white p-3 text-gray-400 rounded-md" v-if="checkerList && checkerList.length"> |
|||
<div v-for="(item, index) in checkerList" :key="index" class="flex justify-between"> |
|||
<div> |
|||
<div class="mb-1 text-gray-800"> |
|||
{{ item.checkerName }} |
|||
</div> |
|||
<div class="mb-1 text-xs"> |
|||
{{ item.remark }} |
|||
</div> |
|||
<div class="text-xs" v-if="+item.checkTime > 0"> |
|||
{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="flex flex-col justify-center"> |
|||
<div class="mb-1 text-green-600" v-if="item.status === 1">已通过</div> |
|||
<div class="mb-1 text-red-600" v-if="item.status === 2">已驳回</div> |
|||
<div v-if="+item.score > 0"> |
|||
<a-progress v-if="item.score" type="circle" :percent="item.score" strokeColor="#FA8C16" :width="40" :strokeWidth="10"> |
|||
<template #format="percent"> |
|||
<span |
|||
class="inline-block text-center text-white text-sm rounded-full" |
|||
style="background: #fa8c16; width: 24px; height: 24px; line-height: 24px" |
|||
> |
|||
{{ percent }} |
|||
</span> |
|||
</template> |
|||
</a-progress> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { ref, onMounted, computed, watch } from 'vue'; |
|||
import dayjs from 'dayjs'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { queryCheckLog } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
const checkerList = ref([]); |
|||
const deliverRecordId = computed(() => store.state.task.deliverRecordId); |
|||
const sessionDeliverRecordId = sessionStorage.getItem('deliverRecordId'); |
|||
|
|||
onMounted(() => { |
|||
getQueryCheckLog(); |
|||
}); |
|||
|
|||
watch(deliverRecordId, () => { |
|||
getQueryCheckLog(); |
|||
}); |
|||
|
|||
async function getQueryCheckLog() { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const param = { param: { deliverRecordId: deliverRecordId.value || sessionDeliverRecordId } }; |
|||
const data = await queryCheckLog(param, url); |
|||
checkerList.value = data; |
|||
} catch (error) { |
|||
message.info('获取检查交付物历史失败'); |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,130 @@ |
|||
<template> |
|||
<div> |
|||
<!-- 审核标题 --> |
|||
<div class="flex justify-between items-center" @click="collapsed = !collapsed"> |
|||
<div>{{ deliverData ? deliverData.deliverName : '' }}审核状态</div> |
|||
<DownOutlined v-if="!collapsed" /> |
|||
<UpOutlined v-else /> |
|||
</div> |
|||
|
|||
<!-- 审核结果 --> |
|||
<div v-show="collapsed"> |
|||
<!-- 提交人和时间 --> |
|||
<div class="my-2 flex justify-between items-center"> |
|||
<div class="text-gray-400 text-xs"> |
|||
<span class="mr-4" v-if="deliverData.submitMemberName">{{ deliverData.submitMemberName }}</span> |
|||
<span v-if="deliverData.submitTime"> {{ dayjs(+deliverData.submitTime).format('MM-DD HH:mm') }}</span> |
|||
</div> |
|||
|
|||
<span class="text-blue-400 text-xs text-right" @click="openDeliverHistory">历史交付物</span> |
|||
</div> |
|||
|
|||
<div @click="openLink" class="break-all text-blue-400 text-xs my-1" v-if="deliverData.details && deliverData.details[0]"> |
|||
{{ deliverData.details[0] }} |
|||
</div> |
|||
|
|||
<!-- 审核人 标题 --> |
|||
<div class="text-gray-400 flex justify-between mt-3"> |
|||
<span>审核</span> |
|||
<span class="text-blue-400 text-xs" @click="openMoreRecords">更多审核记录</span> |
|||
</div> |
|||
|
|||
<!-- 审核人 列表 --> |
|||
<div v-if="deliverData.checkerList"> |
|||
<div class="mt-2 text-sm flex justify-between" v-for="(item, index) in deliverData.checkerList" :key="index"> |
|||
<div> |
|||
<div class="font-semibold">{{ item.checkerName }}</div> |
|||
<div class="text-xs text-gray-400">{{ item.remark }}</div> |
|||
<div class="text-xs text-gray-400" v-if="+item.checkTime > 0">{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }}</div> |
|||
</div> |
|||
|
|||
<!-- 不是自己 显示审核状态 --> |
|||
<div v-show="item.isMine !== 1" class="text-xs"> |
|||
<span v-if="item.status === 1" class="text-green-600"> 已通过 </span> |
|||
<span v-else-if="item.status === 2" class="text-red-600"> 已驳回 </span> |
|||
<span v-else class="text-gray-400"> 待审核 </span> |
|||
</div> |
|||
|
|||
<!-- 自己是当前审核人 且未审核状态 --> |
|||
<div v-show="item.isMine === 1 && (item.status === null || item.status === 0)"> |
|||
<a-button size="small" shape="round" class="mr-4 h-1-4 leading-1-4" type="primary" @click="checkModal.mode = 'RESOLVE'"> |
|||
通过 |
|||
</a-button> |
|||
<a-button size="small" shape="round" class="h-1-4 leading-1-4" type="primary" danger @click="checkModal.mode = 'REJECT'"> |
|||
驳回 |
|||
</a-button> |
|||
</div> |
|||
|
|||
<!-- 自己是审核人 且审核过 当前审核人的审核状态并展示得分情况 --> |
|||
<div v-show="item.isMine === 1 && item.status > 0" class="text-xs"> |
|||
<div class="mb-1"> |
|||
<span v-if="item.status === 1" class="text-green-600"> 已通过 </span> |
|||
<span v-else-if="item.status === 2" class="text-red-600"> 已驳回 </span> |
|||
</div> |
|||
|
|||
<a-progress v-if="item.score" type="circle" :percent="item.score" strokeColor="#FA8C16" :width="40" :strokeWidth="10"> |
|||
<template #format="percent"> |
|||
<span |
|||
class="inline-block text-center text-white text-sm rounded-full" |
|||
style="background: #fa8c16; width: 24px; height: 24px; line-height: 24px" |
|||
> |
|||
{{ percent }} |
|||
</span> |
|||
</template> |
|||
</a-progress> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<checkFormModal :data="checkModal" @hide="checkModal.mode = 'HIDE'" @submit-end="$emit('check-success')" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, reactive, inject, defineEmits } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'; |
|||
import checkFormModal from './check-form-modal.vue'; |
|||
|
|||
const store = useStore(); |
|||
const deliverData = inject('deliver'); |
|||
const collapsed = ref(false); // 展开/关闭 |
|||
defineEmits(['check-success']); |
|||
|
|||
// 交付物链接 |
|||
function openLink() { |
|||
window.open(deliverData.details[0], '_blank'); |
|||
} |
|||
|
|||
const checkModal = reactive({ |
|||
mode: 'HIDE', // HIDE->隐藏 RESOLVE->通过 REJECT->驳回 |
|||
deliverRecordId: () => (deliverData.value ? deliverData.value.deliverRecordId : ''), // 交付物记录id |
|||
}); |
|||
|
|||
// 历史交付物 |
|||
function openDeliverHistory() { |
|||
const { deliverId } = deliverData.value; |
|||
store.commit('task/setDeliverId', deliverId); // 设置交付物id |
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', 'deliverHistory'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
|
|||
// 审核记录 |
|||
function openMoreRecords() { |
|||
const { deliverRecordId } = deliverData.value; |
|||
store.commit('task/setDeliverRecordId', deliverRecordId); // 设置交付物记录id |
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', 'auditRecords'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.ant-progress .ant-progress-inner { |
|||
width: 40px !important; |
|||
height: 40px !important; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,106 @@ |
|||
<template> |
|||
<div> |
|||
<div class="text-base text-center font-semibold" style="height: 44px; line-height: 44px; background: rgb(248, 248, 248)"> |
|||
交付物上传记录 |
|||
</div> |
|||
|
|||
<!-- 历史记录 --> |
|||
<div class="px-3 pt-1" v-if="listRef && listRef.length"> |
|||
<div class="bg-white my-2 rounded-md p-3 text-gray-400" v-for="(item, index) in listRef" :key="index"> |
|||
<!-- 插件名称和提交时间显示 --> |
|||
<div class="flex justify-between mb-2"> |
|||
<div class="text-gray-800">{{ deliverName }}</div> |
|||
<div class="text-xs">{{ dayjs(+item.submitTime).format('MM-DD HH:mm') }}</div> |
|||
</div> |
|||
|
|||
<!-- 提交的链接 --> |
|||
<div @click="openLink(item)" class="my-1 break-all text-blue-400 text-xs cursor-pointer" v-if="item.details[0]"> |
|||
{{ item.details[0] }} |
|||
</div> |
|||
|
|||
<!-- 该插件物的审核人 --> |
|||
<div class="mb-1 mt-3">审核人</div> |
|||
|
|||
<div class="flex justify-between mb-2" v-for="(checkItem, checkIndex) in item.checkerList" :key="checkIndex"> |
|||
<div> |
|||
<div class="mb-1 text-gray-800 font-semibold"> |
|||
{{ checkItem.checkerName }} |
|||
</div> |
|||
<div class="mb-1 text-xs"> |
|||
{{ checkItem.remark }} |
|||
</div> |
|||
<div class="mb-1 text-xs" v-if="+checkItem.checkTime > 0"> |
|||
{{ dayjs(+checkItem.checkTime).format('MM-DD HH:mm') }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="text-center text-xs"> |
|||
<div v-if="checkItem.status == null" class="text-gray-400">待审核</div> |
|||
<div v-else-if="checkItem.status === 1"> |
|||
<div class="text-green-600 mb-1">已通过</div> |
|||
|
|||
<a-progress |
|||
v-if="checkItem.score" |
|||
type="circle" |
|||
:percent="checkItem.score" |
|||
strokeColor="#FA8C16" |
|||
:width="40" |
|||
:strokeWidth="10" |
|||
> |
|||
<template #format="percent"> |
|||
<span |
|||
class="inline-block text-center text-white text-sm rounded-full" |
|||
style="background: #fa8c16; width: 24px; height: 24px; line-height: 24px" |
|||
> |
|||
{{ percent }} |
|||
</span> |
|||
</template> |
|||
</a-progress> |
|||
</div> |
|||
<div class="text-red-600" v-else>已驳回</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { ref, onMounted, computed, watch } from 'vue'; |
|||
import dayjs from 'dayjs'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { getDeliverHistory } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
const listRef = ref([]); |
|||
const deliverName = ref(''); |
|||
const deliverId = computed(() => store.state.task.deliverId); |
|||
const sessionDeliverId = sessionStorage.getItem('deliverId'); |
|||
|
|||
onMounted(() => { |
|||
getHistory(); |
|||
}); |
|||
|
|||
watch(deliverId, () => { |
|||
getHistory(); |
|||
}); |
|||
|
|||
async function getHistory() { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const param = { param: { deliverId: deliverId.value || sessionDeliverId } }; |
|||
const data = await getDeliverHistory(param, url); |
|||
deliverName.value = data.deliverName; |
|||
listRef.value = data.deliverRecordList; |
|||
} catch (error) { |
|||
message.info('获取交付物历史失败'); |
|||
} |
|||
} |
|||
|
|||
// 交付物链接 |
|||
function openLink(item) { |
|||
console.log(item.details[0]); |
|||
window.open(item.details[0], '_blank'); |
|||
} |
|||
</script> |
|||
@ -0,0 +1,140 @@ |
|||
<template> |
|||
<div @longpress.prevent="showMask = true"> |
|||
<!-- 插件名称和提交按钮 --> |
|||
<div class="flex item-center justify-between"> |
|||
<div class="flex-1"> |
|||
<div v-if="deliver.deliverName" class="relative inline-block"> |
|||
{{ deliver.deliverName }} |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 提交 --> |
|||
<a-button type="primary" size="small" @click="submit" :disabled="submitState" :loading="submitBtnLoading"> 提交 </a-button> |
|||
</div> |
|||
|
|||
<!-- 插件上传方式 --> |
|||
<div class="mt-3"> |
|||
<div class="link-box"> |
|||
<a-input v-model:value="linkValue" placeholder="请输入交付物地址/链接" /> |
|||
</div> |
|||
<div class="mt-3"> |
|||
<!-- <a-button type="primary" size="small" class="mr-3" @click="paste">粘贴</a-button> --> |
|||
|
|||
<div class="inline-block"> |
|||
<a-upload name="param" :action="action" :headers="headers" :showUploadList="false" :max-count="1" @change="handleChange"> |
|||
<a-button type="primary" size="small" class="mr-3">文件</a-button> |
|||
</a-upload> |
|||
</div> |
|||
|
|||
<!-- <a-button type="primary" size="small" class="mr-3" @click="uploadPhoto">拍照</a-button> --> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 审核人 --> |
|||
<Reviewer class="mt-3" ref="reviewerRef" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { ref, inject, computed, defineEmits } from 'vue'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { submitDeliverInfo, uploadImg } from 'apis'; |
|||
import Reviewer from '@/components/tall/Reviewer/Reviewer.vue'; |
|||
|
|||
const store = useStore(); |
|||
const projectId = computed(() => store.getters['project/projectId']); |
|||
const sessionProjectId = sessionStorage.getItem('projectId'); |
|||
const roleId = computed(() => store.state.role.roleId); |
|||
|
|||
const deliver = inject('deliver'); // 交付物初始值 |
|||
const task = inject('task'); // 任务信息 |
|||
|
|||
const linkValue = ref(''); // 链接的值 |
|||
|
|||
const action = uploadImg; |
|||
const token = computed(() => store.state.user.token); |
|||
const headers = { Authorization: `Bearer ${token.value}` }; |
|||
|
|||
// 判断提交按钮的状态 |
|||
const submitState = computed(() => !linkValue.value); // 按钮状态 |
|||
const submitBtnLoading = ref(false); // 是否加载中 |
|||
|
|||
const reviewerRef = ref(null); // 审核人组件 |
|||
|
|||
const showMask = ref(false); // 编辑和删除页面是否显示 |
|||
|
|||
const emits = defineEmits(['upload-success']); |
|||
|
|||
function handleChange(info) { |
|||
console.log('info', info); |
|||
} |
|||
|
|||
// 提交交付物 |
|||
async function submit() { |
|||
console.log(reviewerRef.value); |
|||
const { checkedCheckers } = reviewerRef.value; // 拿到选择检查人组件中选中的检查人 |
|||
// 提交前的验证 |
|||
if (!validateDeliverForm(checkedCheckers)) return; |
|||
|
|||
submitBtnLoading.value = true; // 按钮loading |
|||
|
|||
try { |
|||
const checkerList = []; |
|||
checkedCheckers.forEach(item => { |
|||
checkerList.push(item.memberId); |
|||
}); |
|||
|
|||
const { url } = store.state.projects.project; |
|||
|
|||
const params = { |
|||
param: { |
|||
projectId: projectId.value || sessionProjectId, |
|||
roleId: roleId.value, |
|||
deliverId: deliver.value.deliverId, |
|||
fileList: [linkValue.value], |
|||
checkerList, |
|||
msgId: task.msgId, |
|||
}, |
|||
}; |
|||
|
|||
const data = await submitDeliverInfo(params, url); |
|||
message.info('提交交付物信息成功'); |
|||
resetControlState(); // 重置控件的初始状态 |
|||
emits('upload-success'); |
|||
} catch (error) { |
|||
message.info('提交交付物信息失败'); |
|||
submitBtnLoading.value = false; // 按钮loading |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 粘贴 |
|||
// function paste() {} |
|||
|
|||
// 拍照 |
|||
// function uploadPhoto() {} |
|||
|
|||
// 验证提交的交付物信息格式 |
|||
function validateDeliverForm(checkedCheckers) { |
|||
const reg = /[a-zA-z]+:\/\/[^\s]*/; |
|||
if (!reg.test(linkValue.value)) { |
|||
// 显示toast信息 |
|||
message.info('请输入正确的链接'); |
|||
return false; |
|||
} |
|||
// 没有检查人 提示选择检查人 |
|||
if (!checkedCheckers || !checkedCheckers.length) { |
|||
message.info('请选择检查人'); |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
// 重置控件的初始状态 |
|||
function resetControlState() { |
|||
submitBtnLoading.value = false; // 隐藏loading |
|||
linkValue.value = ''; // 清空输入框内容 |
|||
reviewerRef.value.collapsed = true; // 折叠检查人面板 |
|||
} |
|||
</script> |
|||
@ -0,0 +1,43 @@ |
|||
<template> |
|||
<!-- 任务名插件 --> |
|||
<div v-if="deliver" class="task-card-plugin"> |
|||
<p-deliver-upload v-if="deliver" @upload-success="getDeliverData"></p-deliver-upload> |
|||
|
|||
<p-deliver-check |
|||
class="mt-3" |
|||
v-if="deliver && deliver.details && deliver.details.length" |
|||
@check-success="getDeliverData" |
|||
></p-deliver-check> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { ref, inject, provide } from 'vue'; |
|||
import { getDeliverByTaskId } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
import pDeliverUpload from '@/plugins/p-deliver/p-deliver-upload.vue'; |
|||
import pDeliverCheck from '@/plugins/p-deliver/p-deliver-check.vue'; |
|||
|
|||
const store = useStore(); |
|||
const task = inject('task'); |
|||
const pluginInfo = inject('pluginInfo'); |
|||
const deliver = ref(null); // 服务端返回的交付物信息 |
|||
|
|||
deliver.value = pluginInfo && pluginInfo.data ? JSON.parse(pluginInfo.data) : null; |
|||
provide('deliver', deliver); |
|||
|
|||
// 根据任务id获取交付物信息 |
|||
async function getDeliverData() { |
|||
try { |
|||
const { url } = store.state.projects.project; |
|||
const { id: taskId } = task; |
|||
if (!taskId) return; |
|||
const param = { param: { taskId } }; |
|||
const data = await getDeliverByTaskId(param, url); |
|||
deliver.value = data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,8 @@ |
|||
<!-- 项目版本管理 --> |
|||
<template> |
|||
<div class="text-center"> |
|||
<a-image src="/src/assets/exarresources.png" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
@ -0,0 +1,18 @@ |
|||
<!-- 域资源管理 --> |
|||
<template> |
|||
<div class="text-center task-card-plugin"> |
|||
<a-button type="primary" style="width: 250px" @click="openSourceManage">域资源管理</a-button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
function openSourceManage() { |
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', 'exarresources'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
</script> |
|||
@ -0,0 +1,41 @@ |
|||
<template> |
|||
<div class="flex justify-around task-card-plugin"> |
|||
<a-button type="primary" style="width: 125px" @click="openAudit">财务审批</a-button> |
|||
<a-button type="primary" style="width: 125px" @click="openStatistical">财务统计</a-button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { computed, inject } from 'vue'; |
|||
|
|||
const store = useStore(); |
|||
const task = inject('task'); |
|||
const token = computed(() => store.state.user.token); |
|||
const project = computed(() => store.state.projects.project); |
|||
const sessionProject = sessionStorage.getItem('project'); |
|||
|
|||
if (sessionProject && !project.value.id) { |
|||
store.commit('projects/setProject', JSON.parse(sessionProject)); |
|||
} |
|||
|
|||
// 打开财务审批详情页 |
|||
function openAudit() { |
|||
const param = `name=财务审批&token=${token.value}&projectId=${project.value.id}&id=${task.detailId}&pn=${project.value.name}&tn=${task.name}`; |
|||
const url = `http://121.36.3.207/finance/financial-approval?${param}`; |
|||
|
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', url); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', ''); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
|
|||
// 打开财务统计详情页 |
|||
function openStatistical() { |
|||
const param = `name=财务统计&token=${token.value}&projectId=${project.value.id}&id=${task.detailId}&pn=${project.value.name}&tn=${task.name}`; |
|||
const url = `http://121.36.3.207/finance/financial-approval?${param}`; |
|||
|
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', url); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', ''); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
</script> |
|||
@ -0,0 +1,95 @@ |
|||
<!-- 财务条内置组件 --> |
|||
<template> |
|||
<div class="finance-wrap task-card-plugin" v-if="data" @click="openFinance"> |
|||
<!-- 预算和奖金 --> |
|||
<div class="finance-row"> |
|||
<div |
|||
class="finance-item" |
|||
:style="{ width: `${(+data.budget * 100) / (+data.budget + +data.bonus)}%`, 'background-color': '#93C5FD' }" |
|||
> |
|||
预算{{ (data.budget ? data.budget : 0) / 100 }} |
|||
</div> |
|||
<div class="finance-item" :style="{ width: `${(+data.bonus * 100) / (+data.budget + +data.bonus)}%`, 'background-color': '#12c77e' }"> |
|||
奖金{{ (data.bonus ? data.bonus : 0) / 100 }} |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 项目采购日常采购 --> |
|||
<div class="finance-row"> |
|||
<div |
|||
class="finance-item" |
|||
:style="{ width: `${(+data.projectExpend * 100) / (+data.projectExpend + +data.dailyExpend)}%`, 'background-color': '#FBBF24' }" |
|||
> |
|||
项目采购{{ (data.dailyExpend ? data.dailyExpend : 0) / 100 }} |
|||
</div> |
|||
<div |
|||
class="finance-item" |
|||
:style="{ width: `${(+data.dailyExpend * 100) / (+data.projectExpend + +data.dailyExpend)}%`, 'background-color': '#a1fd93' }" |
|||
> |
|||
日常采购{{ +(data.dailyExpend ? data.dailyExpend : 0) / 100 }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { ref, computed, inject } from 'vue'; |
|||
import { getFinanceByTask } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
const task = inject('task'); |
|||
const data = ref(null); |
|||
const pluginInfo = inject('pluginInfo'); |
|||
|
|||
const token = computed(() => store.state.user.token); |
|||
const project = computed(() => store.state.projects.project); |
|||
const sessionProject = sessionStorage.getItem('project'); |
|||
|
|||
if (sessionProject && !project.value.id) { |
|||
store.commit('projects/setProject', JSON.parse(sessionProject)); |
|||
} |
|||
|
|||
data.value = pluginInfo && pluginInfo.data ? JSON.parse(pluginInfo.data) : null; |
|||
|
|||
// 查询任务上的财务条数据 |
|||
async function getFinanceByTaskData() { |
|||
try { |
|||
const { detailId } = task; |
|||
const { url } = project.value; |
|||
const params = { param: { detailId } }; |
|||
data.value = await getFinanceByTask(params, url); |
|||
} catch (error) { |
|||
console.log('getFinanceByTaskData error: ', error); |
|||
} |
|||
} |
|||
|
|||
// getFinanceByTaskData(); |
|||
|
|||
// 打开详情页 |
|||
function openFinance() { |
|||
// DEBUG: 假数据 换成财务条详情页的地址 |
|||
const param = `name=财务&token=${token.value}&projectId=${project.value.id}&id=${task.detailId}&pn=${project.value.name}&tn=${task.name}`; |
|||
const url = `http://121.36.3.207/finance/applicant?${param}`; |
|||
|
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', url); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', ''); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.finance-wrap .finance-row { |
|||
display: flex; |
|||
} |
|||
|
|||
.finance-wrap .finance-row .finance-item { |
|||
margin: 2px 4px; |
|||
font-size: 12px; |
|||
text-align: center; |
|||
border-radius: 4px; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
overflow-x: visible; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,8 @@ |
|||
<!-- 项目版本管理 --> |
|||
<template> |
|||
<div class="text-center"> |
|||
<a-image src="/src/assets/projectVersion.png" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
@ -0,0 +1,18 @@ |
|||
<!-- 项目版本管理 --> |
|||
<template> |
|||
<div class="text-center task-card-plugin"> |
|||
<a-button type="primary" style="width: 250px" @click="openSourceManage">项目版本管理</a-button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
function openSourceManage() { |
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', ''); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', 'projectVersion'); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
</script> |
|||
@ -0,0 +1,30 @@ |
|||
<!-- 资源管理 --> |
|||
<template> |
|||
<div class="text-center task-card-plugin"> |
|||
<a-button type="primary" class="" style="width: 250px" @click="openSourceManage">资源管理</a-button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { inject, computed } from 'vue'; |
|||
|
|||
const store = useStore(); |
|||
const task = inject('task'); |
|||
const token = computed(() => store.state.user.token); |
|||
const project = computed(() => store.state.projects.project); |
|||
const sessionProject = sessionStorage.getItem('project'); |
|||
|
|||
if (sessionProject && !project.value.id) { |
|||
store.commit('projects/setProject', JSON.parse(sessionProject)); |
|||
} |
|||
|
|||
function openSourceManage() { |
|||
const param = `name=资源管理&token=${token.value}&projectId=${project.value.id}&id=${task.detailId}&pn=${project.value.name}&tn=${task.name}`; |
|||
const url = `http://121.36.3.207/finance/index?${param}`; |
|||
|
|||
store.commit('task/setTaskDetailParams', ''); // 设置详情页参数 |
|||
store.commit('task/setTaskDetailUrl', url); // 设置详情页链接 |
|||
store.commit('task/setTaskDetailShow', ''); // 设置内置组件关键字(根据关键字判断显示的详情页) |
|||
} |
|||
</script> |
|||
@ -0,0 +1,12 @@ |
|||
<template> |
|||
<!-- 任务名插件 --> |
|||
<div class="u-font-14 task-card-plugin"> |
|||
{{ task.name }} |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { defineProps } from 'vue'; |
|||
|
|||
defineProps({ task: { type: Object, default: () => {} } }); |
|||
</script> |
|||
@ -0,0 +1,36 @@ |
|||
<template> |
|||
<!-- 任务名 跳转详情页 --> |
|||
<div class="u-font-14 flex justify-between items-center task-card-plugin"> |
|||
{{ task.name }} |
|||
<right-outlined class="ml-3" @click="toDetail(task)" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useStore } from 'vuex'; |
|||
import { computed, defineProps } from 'vue'; |
|||
import { RightOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
const store = useStore(); |
|||
const projects = computed(() => store.state.projects.projects); |
|||
|
|||
defineProps({ task: { type: Object, default: () => {} } }); |
|||
|
|||
function toDetail(task) { |
|||
const { id, projectId, executorRoleId, businessUrl, businessCode } = task; |
|||
const project = projects.value.find(item => item.projectId); |
|||
|
|||
if (project) { |
|||
store.commit('projects/setProject', project); |
|||
store.commit('task/clearTasks'); // 清空定期任务 |
|||
store.commit('task/clearRealTasks'); // 清空真实任务数据 |
|||
store.commit('socket/setCurrLocationTaskId', id); |
|||
store.commit('task/setUpNextPage', 1); |
|||
store.commit('task/setDownNextPage', 1); |
|||
store.commit('task/setTimeLineType', 1); |
|||
store.commit('role/setRoleId', executorRoleId); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style></style> |
|||
@ -0,0 +1,7 @@ |
|||
<template> |
|||
<div class="text-center"> |
|||
<a-image src="/src/assets/work.jpg" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
@ -0,0 +1,31 @@ |
|||
import { getAllPlugin, getBusinessPlugin } from 'apis'; |
|||
|
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const actions = { |
|||
async getBusinessPlugin({ commit }) { |
|||
try { |
|||
const res = await getBusinessPlugin(); |
|||
sessionStorage.setItem('businessPlugin', JSON.stringify(res) || ''); |
|||
commit('setBusinessPlugin', JSON.stringify(res) || ''); |
|||
return res; |
|||
} catch (error) { |
|||
message.info(error.msg); |
|||
throw error; |
|||
} |
|||
}, |
|||
|
|||
async getAllPlugin({ commit }) { |
|||
try { |
|||
const res = await getAllPlugin(); |
|||
sessionStorage.setItem('allPlugin', JSON.stringify(res) || ''); |
|||
commit('setAllPlugin', JSON.stringify(res) || ''); |
|||
return res; |
|||
} catch (error) { |
|||
message.info(error.msg); |
|||
throw error; |
|||
} |
|||
}, |
|||
}; |
|||
|
|||
export default actions; |
|||
@ -1,8 +1,10 @@ |
|||
import state from './state'; |
|||
import actions from './actions'; |
|||
import mutations from './mutations'; |
|||
import state from './state'; |
|||
|
|||
export default { |
|||
namespaced: true, |
|||
state, |
|||
mutations, |
|||
actions, |
|||
}; |
|||
|
|||
@ -0,0 +1,9 @@ |
|||
// 上传文件的扩展名
|
|||
export const UPLOAD_EXTENSION = ['.xls', '.xlsx', '.zip', '.exe', '.pdf', '.doc', '.docx', '.ppt', '.pptx']; |
|||
|
|||
// 审核的快捷用语
|
|||
export const quickWords = { |
|||
// RESOLVE: ['加油,再接再厉!', '很棒!', '不错,很详细!', '不详细', '还有需要改进的地方', '驳回审批']
|
|||
RESOLVE: ['加油,再接再厉!', '很棒!', '不错,很详细!', '加油,再接再厉'], // 审核通过常用的审批语
|
|||
REJECT: ['不详细', '还有需要改进的地方', '驳回审批1', '驳回审批2'], // 审核驳回常用的审批语
|
|||
}; |
|||
@ -0,0 +1,36 @@ |
|||
// 用闭包实现局部对象storage(注意Storage的方法都重写一遍,不然调用其对象原型方法会报错。)
|
|||
const sessionStorageMock = (function (win) { |
|||
const storage = win.sessionStorage; |
|||
return { |
|||
setItem(key, value) { |
|||
const setItemEvent = new Event('setItemEvent'); |
|||
const oldValue = storage[key]; |
|||
setItemEvent.key = key; |
|||
// 新旧值深度判断,派发监听事件
|
|||
if (oldValue !== value) { |
|||
setItemEvent.newValue = value; |
|||
setItemEvent.oldValue = oldValue; |
|||
win.dispatchEvent(setItemEvent); |
|||
storage[key] = value; |
|||
return true; |
|||
} |
|||
return false; |
|||
}, |
|||
getItem(key) { |
|||
return storage[key]; |
|||
}, |
|||
removeItem(key) { |
|||
storage[key] = null; |
|||
return true; |
|||
}, |
|||
clear: () => { |
|||
storage.clear(); |
|||
return true; |
|||
}, |
|||
key(index) { |
|||
return storage.key(index); |
|||
}, |
|||
}; |
|||
})(window); |
|||
|
|||
Object.defineProperty(window, 'sessionStorage', { value: sessionStorageMock, writable: true }); |
|||
@ -0,0 +1,65 @@ |
|||
import dayjs from 'dayjs'; |
|||
|
|||
const pageTaskCount = 15; |
|||
|
|||
/** |
|||
* 设置时间轴空数据 |
|||
* @param {number} startTime |
|||
* @param {boolean} isUp true 向上加载,false 向下加载 |
|||
* @param {string} timeGranularity 颗粒度 |
|||
* @param {number} pageCount 加载的颗粒度数量 默认值是10 |
|||
*/ |
|||
const setPlaceholderTasks = (startTime, isUp, timeGranularity, pageCount) => { |
|||
const result = []; |
|||
pageCount = pageCount || pageTaskCount; |
|||
for (let i = 0; i < pageCount; i++) { |
|||
const delta = isUp ? `-${i + 1}` - 0 : i; |
|||
|
|||
const item = { |
|||
id: Math.random() * 1000000000000000000, |
|||
panel: {}, |
|||
plugins: [], |
|||
process: 4, |
|||
planStart: dayjs(+startTime) |
|||
.add(+delta, timeGranularity) |
|||
.valueOf(), |
|||
}; |
|||
// console.log('isup: ', isUp, 'result:', new Date(item.planStart).toLocaleDateString());
|
|||
|
|||
isUp ? result.unshift(item) : result.push(item); |
|||
} |
|||
return result; |
|||
}; |
|||
|
|||
/** |
|||
* 超出旧数据上、下限 补齐时间刻度到新数据的起始时间颗粒度 |
|||
* @param {object} option |
|||
* @param {array} option.tasks 旧的已有的任务书籍 |
|||
* @param {array} option.data 新拿到的任务数据 空值已经过滤过了 |
|||
* @param {string} option.timeGranularity 颗粒度 |
|||
*/ |
|||
const computeFillPlaceholderTaskCount = obj => { |
|||
const { tasks, data, timeGranularity } = obj; |
|||
const result = { prev: 0, next: 0 }; |
|||
// 新数据的开始时间 < 旧数据的开始时间
|
|||
// 超出了上限 补上限的时间刻度
|
|||
// 补上 新数据开始时间 到 旧数据开始时间 的刻度
|
|||
if (+data[0].planStart < +tasks[0].planStart) { |
|||
// 找出来需要补几组颗粒度
|
|||
result.prev = dayjs(+tasks[0].planStart).diff(+data[0].planStart, timeGranularity) + 1; |
|||
} |
|||
|
|||
// 新数据的结束时间 > 旧数据的结束时间
|
|||
// 超出了下线 补下限的时间刻度
|
|||
// 补上 旧数据截止时间 到 新数据截止时间 的刻度
|
|||
if (+data[data.length - 1].planStart > +tasks[tasks.length - 1].planStart) { |
|||
result.next = dayjs(+data[data.length - 1].planStart).diff(+tasks[tasks.length - 1].planStart, timeGranularity) + 1; |
|||
} |
|||
return result; |
|||
}; |
|||
|
|||
export default { |
|||
setPlaceholderTasks, |
|||
computeFillPlaceholderTaskCount, |
|||
pageTaskCount, |
|||
}; |
|||
@ -0,0 +1,17 @@ |
|||
export default { |
|||
timeUnits: [ |
|||
// 时间颗粒度
|
|||
{ id: 0, value: '毫秒', format: 'x', cycle: 'YY-M-D HH:mm:ss', granularity: 'millisecond' }, |
|||
{ id: 1, value: '秒', format: 'x', cycle: 'YY-M-D HH:mm:ss', granularity: 'second' }, |
|||
{ id: 2, value: '分', format: 'ss', cycle: 'YY-M-D HH:mm', granularity: 'minute' }, |
|||
{ id: 3, value: '时', format: 'mm', cycle: 'YY-M-D HH时', granularity: 'hour' }, |
|||
{ id: 4, value: '天', format: 'M月D日 HH:mm', cycle: 'YY-M-D', granularity: 'day' }, |
|||
{ id: 5, value: '周', format: 'M月D日 HH:mm', cycle: '', granularity: 'week' }, |
|||
{ id: 6, value: '月', format: 'M月D日 H:m', cycle: 'YYYY年', granularity: 'month' }, |
|||
{ id: 7, value: '季度', format: '', cycle: 'YYYY年', granularity: 'quarter' }, |
|||
{ id: 8, value: '年', format: 'YYYY', cycle: '', granularity: 'year' }, |
|||
{ id: 9, value: '年代', format: '', cycle: '', granularity: '' }, |
|||
{ id: 10, value: '世纪', format: '', cycle: '', granularity: '' }, |
|||
{ id: 11, value: '千年', format: '', cycle: '', granularity: '' }, |
|||
], |
|||
}; |
|||
@ -1,306 +1,65 @@ |
|||
<template> |
|||
<div class="task-detail"> |
|||
<div class="task-con flex"> |
|||
<!-- {{ taskInfo.name }} --> |
|||
<div class="task-con-box"> |
|||
<!-- <div>{{ label }}</div> --> |
|||
<!-- 课题 --> |
|||
<!-- 查看课题进展 --> |
|||
<CheckSubjectProgress v-if="label === 'KT_KTJZ'" /> |
|||
<DetailWebview v-if="taskDetailUrl"></DetailWebview> |
|||
|
|||
<!-- 科研会议管理 --> |
|||
<MeetingManagement v-if="label === 'KT_KYHY'" /> |
|||
<!-- 工作台 --> |
|||
<workbench v-if="taskDetailShow === 'workbench'"></workbench> |
|||
|
|||
<!-- 合同管理 --> |
|||
<ContractManagement v-if="label === 'KT_HTGL'" /> |
|||
<!-- 流水账详情页 --> |
|||
<p-daily-account-detail v-if="taskDetailShow === 'dailyAccount'"></p-daily-account-detail> |
|||
|
|||
<!-- 数据追溯解锁 --> |
|||
<DataUnlock v-if="label === 'KT_SJZSJS'" /> |
|||
<!-- 交付物历史记录 --> |
|||
<p-deliver-history v-if="taskDetailShow === 'deliverHistory'"></p-deliver-history> |
|||
|
|||
<!-- 成员管理 --> |
|||
<MemberManagement v-if="label === 'KT_CYGL' || label === 'ZKT_CYGL'" /> |
|||
<!-- 交付物审核记录 --> |
|||
<p-audit-records v-if="taskDetailShow === 'auditRecords'"></p-audit-records> |
|||
|
|||
<!-- 计划任务书 --> |
|||
<PlanAssignment v-if="label === 'KT_JHRWS'" /> |
|||
<!-- 交付物2详情页 --> |
|||
<p-deliver-second-detail v-if="taskDetailShow === 'deliverSecDetail'"></p-deliver-second-detail> |
|||
|
|||
<!-- 分配课题 --> |
|||
<AssignmentSubject v-if="label === 'KT_FPKT'" /> |
|||
<!-- 项目版本管理详情页 --> |
|||
<p-project-version-management-detail v-if="taskDetailShow === 'projectVersion'"></p-project-version-management-detail> |
|||
|
|||
<!-- 提交中期检查报告 --> |
|||
<InterimInspection v-if="label === 'KT_ZQJC'" /> |
|||
<!-- 域资源管理详情页 --> |
|||
<p-domain-source-manage-detail v-if="taskDetailShow === 'exarresources'"></p-domain-source-manage-detail> |
|||
|
|||
<!-- 项目结题报告 --> |
|||
<Conclusion v-if="label === 'KT_JTBG'" /> |
|||
<!-- 账号管理详情页 --> |
|||
<p-account-management-audit v-if="taskDetailShow === 'personal'"></p-account-management-audit> |
|||
|
|||
<!-- 项目验收报告 --> |
|||
<Result v-if="label === 'KT_YSZS'" /> |
|||
|
|||
<!-- 子课题 --> |
|||
<!-- 子课题进展 --> |
|||
<SubSubjectProgress v-if="label === 'ZKT_KTJZ'" /> |
|||
|
|||
<!-- 子课题科研会议管理 --> |
|||
<SubMeetingManagement v-if="label === 'ZKT_HYGL'" /> |
|||
|
|||
<!-- 发表论文 --> |
|||
<PublishThesis v-if="label === 'ZKT_LW'" /> |
|||
|
|||
<!-- 申请专利 --> |
|||
<PublishPatent v-if="label === 'ZKT_ZL'" /> |
|||
|
|||
<!-- 发表软件著作权 --> |
|||
<PublishWork v-if="label === 'ZKT_RZ'" /> |
|||
|
|||
<!-- 科研成果管理 --> |
|||
<ScientificPayoffs v-if="label === 'ZKT_KYCG'" /> |
|||
|
|||
<!-- 成员管理 --> |
|||
<!-- <SubMemberManagement v-if="label === 'ZKT_CYGL'" /> --> |
|||
|
|||
<!-- 分配实验 --> |
|||
<AssignmentExperiment v-if="label === 'ZKT_FPSY'" /> |
|||
|
|||
<!-- 子课题中期检查报告 --> |
|||
<SubInterimInspection v-if="label === 'ZKT_ZQJC'" /> |
|||
|
|||
<!-- 子课题结题报告 --> |
|||
<SubConclusion v-if="label === 'ZKT_JTBG'" /> |
|||
|
|||
<!-- 子课题验收报告 --> |
|||
<SubResult v-if="label === 'ZKT_YSZS'" /> |
|||
|
|||
<!-- 实验 --> |
|||
<!-- 提交实验报告 --> |
|||
<LabReport v-if="label === 'SY_BG'" /> |
|||
|
|||
<!-- 记录实验过程 --> |
|||
<Procedure v-if="label === 'SY_GC'" /> |
|||
|
|||
<!-- 记录实验数据 --> |
|||
<ExperimentalData v-if="label === 'SY_SJ'" /> |
|||
|
|||
<!-- 实验程序代码 --> |
|||
<ExperimentalCode v-if="label === 'SY_DM'" /> |
|||
|
|||
<!-- 撰写实验结果 --> |
|||
<ExperimentalResult v-if="label === 'SY_JG'" /> |
|||
</div> |
|||
|
|||
<div class="task-con-list" v-if="isShowList"> |
|||
<TaskConList /> |
|||
</div> |
|||
</div> |
|||
<!-- UI配置详情页 --> |
|||
<p-account-management-uidispose v-if="taskDetailShow === 'uidispose'"></p-account-management-uidispose> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed, watch, ref } from 'vue'; |
|||
import { computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import CheckSubjectProgress from 'components/tall/task/CheckSubjectProgress.vue'; // 查看课题进展 |
|||
import MeetingManagement from 'components/tall/task/MeetingManagement.vue'; // 科研会议管理 |
|||
import ContractManagement from 'components/tall/task/ContractManagement.vue'; // 合同管理 |
|||
import DataUnlock from 'components/tall/task/DataUnlock.vue'; // 数据追溯解锁 |
|||
import MemberManagement from 'components/tall/task/MemberManagement.vue'; // 成员管理 |
|||
import PlanAssignment from 'components/tall/task/PlanAssignment.vue'; // 计划任务书 |
|||
import AssignmentSubject from 'components/tall/task/AssignmentSubject.vue'; // 分配课题 |
|||
import InterimInspection from 'components/tall/task/InterimInspection.vue'; // 提交中期检查报告 |
|||
import Conclusion from 'components/tall/task/Conclusion.vue'; // 项目结题报告 |
|||
import Result from 'components/tall/task/Result.vue'; // 项目验收报告 |
|||
|
|||
import SubSubjectProgress from 'components/tall/task/SubSubjectProgress.vue'; // 子课题进展 |
|||
import SubMeetingManagement from 'components/tall/task/SubMeetingManagement.vue'; // 子课题科研会议管理 |
|||
import PublishThesis from 'components/tall/task/PublishThesis.vue'; // 发表论文 |
|||
import PublishPatent from 'components/tall/task/PublishPatent.vue'; // 申请专利 |
|||
import PublishWork from 'components/tall/task/PublishWork.vue'; // 申请软件著作权 |
|||
import ScientificPayoffs from 'components/tall/task/ScientificPayoffs.vue'; // 科研成果管理 |
|||
// import SubMemberManagement from 'components/tall/task/SubMemberManagement.vue'; // 成员管理 |
|||
import AssignmentExperiment from 'components/tall/task/AssignmentExperiment.vue'; // 分配实验 |
|||
import SubInterimInspection from 'components/tall/task/SubInterimInspection.vue'; // 子课题中期检查报告 |
|||
import SubConclusion from 'components/tall/task/SubConclusion.vue'; // 子课题结题报告 |
|||
import SubResult from 'components/tall/task/SubResult.vue'; // 子课题验收报告 |
|||
|
|||
import LabReport from 'components/tall/task/LabReport.vue'; // 提交实验报告 |
|||
import Procedure from 'components/tall/task/Procedure.vue'; // 记录实验过程 |
|||
import ExperimentalData from 'components/tall/task/ExperimentalData.vue'; // 记录实验数据 |
|||
import ExperimentalCode from 'components/tall/task/ExperimentalCode.vue'; // 实验程序代码 |
|||
import ExperimentalResult from 'components/tall/task/ExperimentalResult.vue'; // 撰写实验结果 |
|||
|
|||
import TaskConList from 'components/tall/task/TaskConList.vue'; // 任务详情页列表 |
|||
import DetailWebview from '@/components/tall/Right/DetailWebview.vue'; |
|||
import pDailyAccountDetail from '@/plugins/p-daily-account/p-daily-account-detail.vue'; |
|||
import pDeliverHistory from '@/plugins/p-deliver/p-deliver-history.vue'; |
|||
import pAuditRecords from '@/plugins/p-deliver/p-audit-records.vue'; |
|||
import pDeliverSecondDetail from '@/plugins/p-deliver-second/p-deliver-second-detail.vue'; |
|||
import pProjectVersionManagementDetail from '@/plugins/p-project-version-management/p-project-version-management-detail.vue'; |
|||
import pDomainSourceManageDetail from '@/plugins/p-domain-source-manage/p-domain-source-manage-detail.vue'; |
|||
import pAccountManagementAudit from '@/plugins/p-account-management/p-account-management-audit.vue'; |
|||
import pAccountManagementUidispose from '@/plugins/p-account-management/p-account-management-uidispose.vue'; |
|||
import workbench from '@/plugins/workbench/workbench.vue'; |
|||
|
|||
const store = useStore(); |
|||
const taskDetail = computed(() => store.state.task.taskDetail); // 任务名称 |
|||
const label = ref(null); |
|||
const sessionTaskDetail = sessionStorage.getItem('taskDetail'); |
|||
const isShowList = computed(() => store.state.layout.isShowListStatus); // 是否显示任务详情列表 |
|||
|
|||
if (sessionTaskDetail) { |
|||
const taskInfo = JSON.parse(sessionTaskDetail); |
|||
|
|||
if (taskInfo.plugins && taskInfo.plugins.length > 0) { |
|||
taskInfo.plugins.forEach(item => { |
|||
if (Number(item[0].pluginId) === 1) { |
|||
label.value = item[0].param; |
|||
store.commit('task/setLabel', label.value); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
// 监听任务信息 |
|||
watch([taskDetail], () => { |
|||
if (!taskDetail.value) return; |
|||
|
|||
const taskInfo = taskDetail.value; |
|||
if (taskInfo.plugins && taskInfo.plugins.length > 0) { |
|||
taskInfo.plugins.forEach(item => { |
|||
if (Number(item[0].pluginId) === 1) { |
|||
label.value = item[0].param; |
|||
store.commit('task/setLabel', label.value); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
const taskDetailUrl = computed(() => store.state.task.taskDetailUrl); // iframe详情页链接 |
|||
const taskDetailShow = computed(() => store.state.task.taskDetailShow); // 内置组件详情页是否显示 |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
width: 100%; |
|||
height: 100%; |
|||
padding: 16px; |
|||
/* padding: 16px; */ |
|||
overflow: auto; |
|||
} |
|||
|
|||
.task-con { |
|||
min-width: 760px; |
|||
min-height: 500px; |
|||
} |
|||
|
|||
.task-con-box { |
|||
max-width: 100%; |
|||
flex: 1; |
|||
} |
|||
|
|||
.task-form { |
|||
padding: 24px; |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.task-form :deep(.ant-form) { |
|||
width: 100%; |
|||
max-width: 680px; |
|||
} |
|||
|
|||
.task-con-list { |
|||
min-width: 320px; |
|||
max-width: 460px; |
|||
background: #fff; |
|||
border-radius: 10px; |
|||
box-shadow: -5px 0px 5px 0px rgba(3, 27, 49, 0.1); |
|||
} |
|||
|
|||
:deep(.ant-input) { |
|||
height: 38px; |
|||
border: 1px solid #cccccc; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
:deep(.ant-btn-primary) { |
|||
width: 180px; |
|||
height: 38px; |
|||
font-size: 16px; |
|||
border-radius: 6px; |
|||
letter-spacing: 2px; |
|||
} |
|||
|
|||
:deep(.ant-space) { |
|||
width: 100%; |
|||
} |
|||
|
|||
:deep(.ant-picker) { |
|||
height: 38px; |
|||
width: 100%; |
|||
border: 1px solid #cccccc; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
:deep(.ant-select-single:not(.ant-select-customize-input) .ant-select-selector) { |
|||
height: 38px; |
|||
} |
|||
|
|||
:deep(.ant-select-single:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input) { |
|||
height: 38px; |
|||
} |
|||
|
|||
:deep(.ant-select-single .ant-select-selector .ant-select-selection-item), |
|||
:deep(.ant-select-single .ant-select-selector .ant-select-selection-placeholder) { |
|||
line-height: 36px; |
|||
} |
|||
|
|||
:deep(.ant-upload) { |
|||
width: 100%; |
|||
} |
|||
|
|||
/* 上传样式 */ |
|||
:deep(.ant-upload) .upload-box { |
|||
padding: 20px 0; |
|||
border: 1px dashed #cccccc; |
|||
background: #fafafa; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
:deep(.ant-upload) .upload-box img { |
|||
width: 32px; |
|||
} |
|||
|
|||
:deep(.ant-upload) .upload-box p:first-of-type { |
|||
margin-top: 24px; |
|||
line-height: 1; |
|||
} |
|||
|
|||
:deep(.ant-upload) .upload-box p:last-of-type { |
|||
margin-top: 16px; |
|||
line-height: 1; |
|||
} |
|||
|
|||
:deep(.form-item-dad) { |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
.form-item-son { |
|||
padding-left: 16px; |
|||
} |
|||
|
|||
/* 表格 */ |
|||
:deep(.ant-table-thead > tr > th), |
|||
:deep(.ant-table-tbody > tr > td) { |
|||
height: 60px; |
|||
} |
|||
|
|||
:deep(.ant-table-thead > tr > th) { |
|||
background: #fff; |
|||
font-size: 16px; |
|||
color: #333; |
|||
} |
|||
|
|||
:deep(.ant-table-thead |
|||
> tr |
|||
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before) { |
|||
width: 0; |
|||
} |
|||
|
|||
:deep(.ant-checkbox-group) { |
|||
width: 100%; |
|||
} |
|||
|
|||
/* 单选 */ |
|||
:deep(.ant-radio-group) { |
|||
width: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
:deep(.ant-radio-wrapper) { |
|||
display: flex !important; |
|||
.task-detail::-webkit-scrollbar { |
|||
width: 0 !important; |
|||
} |
|||
</style> |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue