62 changed files with 14071 additions and 4395 deletions
@ -1 +1 @@ |
|||
VITE_API_URL=http://localhost:4001 |
|||
VITE_API_URL=http://localhost:3000 |
|||
|
@ -0,0 +1,80 @@ |
|||
<template> |
|||
<div class="global"> |
|||
<div class="global-box" v-if="permanents && permanents.length > 0"> |
|||
<div class="global-task cursor-pointer" v-for="(item, index) in permanents" :key="index" @click="toDetail(item)"> |
|||
<template v-for="v in item.plugins"> |
|||
<template v-if="v[0].pluginId == 1">{{ item.name }}</template> |
|||
</template> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed, watch } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { getPermanent } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const project = computed(() => store.state.projects.project); // 项目信息 |
|||
const roleId = computed(() => store.state.role.roleId); // 当前角色 |
|||
const permanents = computed(() => store.state.task.permanents); // 日常任务 |
|||
const sessionPermanents = sessionStorage.getItem('permanents'); // 日常任务缓存 |
|||
|
|||
if (sessionPermanents) { |
|||
const arr = JSON.parse(sessionPermanents); |
|||
store.commit('task/setPermanents', arr); |
|||
} |
|||
|
|||
if (project.value) { |
|||
if (roleId.value) { |
|||
getPermanentData(roleId.value); // 根据角色查找永久的日常任务 |
|||
} |
|||
} |
|||
|
|||
watch([project, roleId], async () => { |
|||
if (roleId.value) { |
|||
await getPermanentData(roleId.value); // 根据角色查找永久的日常任务 |
|||
} |
|||
}); |
|||
|
|||
// 日常任务 |
|||
async function getPermanentData(id) { |
|||
const params = { param: { roleId: id } }; |
|||
try { |
|||
const data = await getPermanent(params); |
|||
store.commit('task/setPermanents', data); |
|||
const globalHeight = data.length * 38 + 26; |
|||
store.commit('task/setGlobalHeight', globalHeight); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
function toDetail(item) { |
|||
store.commit('task/setTaskDetail', item); |
|||
store.commit('layout/setListStatus', false); |
|||
store.commit('task/setIntellectualId', ''); |
|||
store.commit('task/setMeetId', ''); |
|||
store.commit('task/setSubMeetId', ''); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.global { |
|||
padding: 16px; |
|||
} |
|||
|
|||
.global-box { |
|||
border: 1px solid #cccccc; |
|||
border-radius: 10px; |
|||
padding: 12px 16px; |
|||
} |
|||
|
|||
.global-task { |
|||
padding: 8px 0; |
|||
line-height: 22px; |
|||
} |
|||
</style> |
@ -0,0 +1,374 @@ |
|||
<template> |
|||
<div> |
|||
<!-- 项目标题 --> |
|||
<div class="navbar flex items-center justify-between"> |
|||
<div class="project-name">{{ project.name }}</div> |
|||
<div class="project-action"> |
|||
<ReloadOutlined :style="{ fontSize: 20 + 'px' }" /> |
|||
<MoreOutlined :style="{ fontSize: 20 + 'px' }" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 角色 --> |
|||
<!-- <Roles /> --> |
|||
<div class="role-list flex items-center"> |
|||
<div class="role-box relative" v-for="(item, index) in roles" :key="index"> |
|||
<div class="role-name" :class="{ mine: roleId === item.id }">{{ item.name }}</div> |
|||
<div class="line-box absolute flex justify-center" v-if="roleId === item.id"><div class="line"></div></div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 日常任务 --> |
|||
<!-- <Global /> --> |
|||
<div class="global"> |
|||
<div class="global-box" v-if="permanents && permanents.length > 0"> |
|||
<div class="global-task cursor-pointer" v-for="(item, index) in permanents" :key="index" @click="toGlobalDetail(item)"> |
|||
<template v-for="v in item.plugins"> |
|||
<template v-if="v[0].pluginId == 1">{{ item.name }}</template> |
|||
</template> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 定期任务 --> |
|||
<!-- <RegularTask /> --> |
|||
<div class="task-list" :style="{ height: 'calc(100vh - 160px - (' + globalHeight + 'px))' }"> |
|||
<div class="task-box" v-for="(item, index) in regularTasks" :key="index"> |
|||
<div class="task-time flex items-center justify-between"> |
|||
<div class="flex items-center"> |
|||
<PlayCircleOutlined style="font-size: 23px; color: #999999" /> |
|||
<span>{{ dayjs(item.planStart).format('D日 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" @click="toDetail(item)"> |
|||
<template v-for="v in item.plugins"> |
|||
<template v-if="v[0].pluginId == 1">{{ item.name }}</template> |
|||
</template> |
|||
</div> |
|||
|
|||
<div class="task-con" v-if="item.sonList && item.sonList.length > 0"> |
|||
<div v-for="(val, key) in item.sonList" :key="key"> |
|||
<!-- <a-checkbox>{{ val.name }}</a-checkbox> --> |
|||
<span class="son-task-name cursor-pointer" @click.stop="toSonDetail(item, val.detailId)">{{ val.name }}</span> |
|||
</div> |
|||
</div> |
|||
<!-- <div class="open-icon" v-if="item.sonList" @click="openCard"> |
|||
<img /> |
|||
</div> --> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed, watch, reactive } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { ReloadOutlined, MoreOutlined, PlayCircleOutlined } from '@ant-design/icons-vue'; |
|||
// import Roles from './Roles.vue'; // 角色 |
|||
import { message } from 'ant-design-vue'; |
|||
import { findShowRole, getPermanent, getRegularTask, findSonTask } from 'apis'; |
|||
// import Global from './Global.vue'; // 日常任务面板 |
|||
// import RegularTask from './RegularTask.vue'; // 定期任务 |
|||
|
|||
const store = useStore(); |
|||
const project = computed(() => store.state.projects.project); // 项目信息 |
|||
|
|||
const roleId = computed(() => store.state.role.roleId); // 当前角色 |
|||
const sessionRoles = sessionStorage.getItem('roles'); // 角色缓存 |
|||
const roles = computed(() => store.state.role.visibleRoles); // 当前项目角色列表 |
|||
|
|||
const permanents = computed(() => store.state.task.permanents); // 日常任务 |
|||
const sessionPermanents = sessionStorage.getItem('permanents'); // 日常任务缓存 |
|||
const globalHeight = computed(() => store.state.task.globalHeight); // 日常任务面板高度 |
|||
const sessionGlobalHeight = sessionStorage.getItem('globalHeight'); // 日常任务面板高度缓存 |
|||
|
|||
const regularTasks = computed(() => store.state.task.regularTasks); // 定期任务 |
|||
const taskObj = reactive({ tasks: [] }); // 定期任务 |
|||
const sessionTasks = sessionStorage.getItem('regularTasks'); // 定期任务缓存 |
|||
const refreshProjects = computed(() => store.state.layout.refreshProjects); // 刷新项目列表 |
|||
|
|||
if (sessionRoles) { |
|||
const roleArr = JSON.parse(sessionRoles); |
|||
store.commit('role/setVisibleRoles', roleArr); |
|||
setInitialRoleId(roleArr); |
|||
} |
|||
|
|||
if (sessionPermanents) { |
|||
const arr = JSON.parse(sessionPermanents); |
|||
store.commit('task/setPermanents', arr); |
|||
} |
|||
|
|||
if (sessionGlobalHeight) { |
|||
store.commit('task/setGlobalHeight', sessionGlobalHeight); |
|||
} |
|||
|
|||
if (sessionTasks) { |
|||
const arr = JSON.parse(sessionTasks); |
|||
store.commit('task/setRegularTasks', arr); |
|||
} |
|||
|
|||
init(); |
|||
|
|||
watch(project, async () => { |
|||
if (project.value.id) { |
|||
await getRoles(project.value.id); // 通过项目id获取角色信息 |
|||
await getPermanentData(roleId.value); // 根据角色查找永久的日常任务 |
|||
await getTasks({ roleId: roleId.value }); // 根据角色查找定期任务 |
|||
} |
|||
}); |
|||
|
|||
watch(refreshProjects, async () => { |
|||
if (project.value.id) { |
|||
await getRoles(project.value.id); // 通过项目id获取角色信息 |
|||
await getPermanentData(roleId.value); // 根据角色查找永久的日常任务 |
|||
await getTasks({ roleId: roleId.value }); // 根据角色查找定期任务 |
|||
} |
|||
}); |
|||
|
|||
// 初始化 |
|||
async function init() { |
|||
if (project.value) { |
|||
await getRoles(project.value.id); // 通过项目id获取角色信息 |
|||
await getPermanentData(roleId.value); // 根据角色查找永久的日常任务; |
|||
await getTasks({ roleId: roleId.value }); // 根据角色查找定期任务 |
|||
} |
|||
} |
|||
|
|||
/** 通过项目id获取角色信息 |
|||
* @param {string} projectId |
|||
* @param {object} params 提交的参数 |
|||
*/ |
|||
async function getRoles(params) { |
|||
try { |
|||
const data = await findShowRole(params); |
|||
store.commit('role/setInvisibleRoles', data ? data.invisibleList : []); |
|||
store.commit('role/setVisibleRoles', data ? data.visibleList : []); |
|||
setInitialRoleId(data ? data.visibleList : []); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 设置 初始显示角色信息 |
|||
function setInitialRoleId(visibleList) { |
|||
if (!visibleList || !visibleList.length) return; |
|||
const index = visibleList.findIndex(item => +item.mine === 1); |
|||
const currentRole = index > 0 ? visibleList[index] : visibleList[0]; |
|||
const currentRoleId = currentRole ? currentRole.id : ''; |
|||
store.commit('role/setRoleId', currentRoleId); |
|||
} |
|||
|
|||
// 日常任务 |
|||
async function getPermanentData(id) { |
|||
const params = { param: { roleId: id } }; |
|||
try { |
|||
const data = await getPermanent(params); |
|||
store.commit('task/setPermanents', data); |
|||
const height = data.length * 38 + 26; |
|||
store.commit('task/setGlobalHeight', height); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 日常任务详情 |
|||
function toGlobalDetail(item) { |
|||
store.commit('task/setTaskDetail', item); |
|||
store.commit('layout/setListStatus', false); |
|||
store.commit('task/setIntellectualId', ''); |
|||
store.commit('task/setMeetId', ''); |
|||
store.commit('task/setSubMeetId', ''); |
|||
} |
|||
|
|||
/** |
|||
* 根据时间基准点和角色查找定期任务 |
|||
* @param {object} query |
|||
* @param {string} query.roleId 角色id |
|||
* @param {string} query.timeNode 时间基准点 默认当前 |
|||
* @param {string} query.timeUnit 时间颗粒度 默认天 |
|||
* @param {string} query.queryNum 查找颗粒度数量 默认3个 |
|||
* @param {number} query.queryType 0向上查找 1向下查找(默认) 下查包含自己,上查不包含 |
|||
*/ |
|||
async function getTasks(query) { |
|||
const params = { param: query }; |
|||
try { |
|||
const data = await getRegularTask(params); |
|||
taskObj.tasks = data; |
|||
data.forEach(item => { |
|||
item.plugins.forEach(val => { |
|||
if (Number(val[0].pluginId) === 6) { |
|||
getSonTask(item.detailId); |
|||
} |
|||
}); |
|||
}); |
|||
store.commit('task/setRegularTasks', data); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 根据id获取子任务 |
|||
async function getSonTask(id) { |
|||
const params = { param: { detailId: id } }; |
|||
try { |
|||
const data = await findSonTask(params); |
|||
taskObj.tasks.forEach(item => { |
|||
if (item.detailId === id) { |
|||
item.sonList = data; |
|||
} |
|||
}); |
|||
store.commit('task/setRegularTasks', taskObj.tasks); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
function toDetail(item) { |
|||
store.commit('task/setTaskDetail', item); |
|||
store.commit('task/sonDetailId', ''); |
|||
store.commit('task/sonExperimentationId', ''); |
|||
store.commit('layout/setListStatus', false); |
|||
} |
|||
|
|||
function toSonDetail(item, id) { |
|||
store.commit('task/setTaskDetail', item); |
|||
store.commit('task/sonDetailId', id); |
|||
store.commit('task/sonExperimentationId', id); |
|||
store.commit('layout/setListStatus', false); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.navbar { |
|||
padding: 0 16px; |
|||
height: 44px; |
|||
} |
|||
|
|||
.project-name { |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.project-action .anticon { |
|||
margin-left: 16px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.role-list { |
|||
padding: 0 16px; |
|||
height: 36px; |
|||
border-bottom: 1px solid #cccccc; |
|||
} |
|||
.role-box { |
|||
margin-right: 16px; |
|||
height: 36px; |
|||
} |
|||
|
|||
.role-name { |
|||
font-size: 14px; |
|||
line-height: 36px; |
|||
color: #333333; |
|||
} |
|||
|
|||
.role-name.mine { |
|||
font-weight: 600; |
|||
color: #1890ff; |
|||
} |
|||
|
|||
.role-box .line-box { |
|||
width: 100%; |
|||
bottom: 0; |
|||
} |
|||
|
|||
.line-box .line { |
|||
width: 16px; |
|||
height: 2px; |
|||
background-color: #1890ff; |
|||
} |
|||
|
|||
.global { |
|||
padding: 16px; |
|||
} |
|||
|
|||
.global-box { |
|||
border: 1px solid #cccccc; |
|||
border-radius: 10px; |
|||
padding: 12px 16px; |
|||
} |
|||
|
|||
.global-task { |
|||
padding: 8px 0; |
|||
line-height: 22px; |
|||
} |
|||
|
|||
.task-list { |
|||
padding: 0 16px 50px; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.task-time { |
|||
height: 32px; |
|||
} |
|||
|
|||
.task-time .anticon { |
|||
margin-right: 16px; |
|||
} |
|||
|
|||
.task-time span { |
|||
font-size: 14px; |
|||
color: #595959; |
|||
} |
|||
|
|||
.task-info { |
|||
margin: 8px 0; |
|||
padding-left: 11px; |
|||
} |
|||
|
|||
.task-info > div { |
|||
padding-left: 27px; |
|||
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; |
|||
padding-left: 16px; |
|||
} |
|||
|
|||
.task-con > div { |
|||
height: 30px; |
|||
} |
|||
|
|||
:deep(.ant-checkbox + span) { |
|||
color: #607d8b; |
|||
} |
|||
|
|||
.son-task-name { |
|||
color: #607d8b; |
|||
} |
|||
|
|||
.task-list::-webkit-scrollbar { |
|||
width: 0 !important; |
|||
} |
|||
</style> |
@ -0,0 +1,197 @@ |
|||
<template> |
|||
<div class="task-list" :style="{ height: 'calc(100vh - 160px - (' + globalHeight + 'px))' }"> |
|||
<div class="task-box" v-for="(item, index) in regularTasks" :key="index"> |
|||
<div class="task-time flex items-center justify-between"> |
|||
<div class="flex items-center"> |
|||
<PlayCircleOutlined style="font-size: 23px; color: #999999" /> |
|||
<span>{{ dayjs(item.planStart).format('D日 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" @click="toDetail(item)"> |
|||
<template v-for="v in item.plugins"> |
|||
<template v-if="v[0].pluginId == 1">{{ item.name }}</template> |
|||
</template> |
|||
</div> |
|||
|
|||
<div class="task-con" v-if="item.sonList && item.sonList.length > 0"> |
|||
<div v-for="(val, key) in item.sonList" :key="key"> |
|||
<!-- <a-checkbox>{{ val.name }}</a-checkbox> --> |
|||
<span class="son-task-name cursor-pointer" @click.stop="toSonDetail(item, val.detailId)">{{ val.name }}</span> |
|||
</div> |
|||
</div> |
|||
<!-- <div class="open-icon" v-if="item.sonList" @click="openCard"> |
|||
<img /> |
|||
</div> --> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed, watch, reactive } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { PlayCircleOutlined } from '@ant-design/icons-vue'; |
|||
import { getRegularTask, findSonTask } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const project = computed(() => store.state.projects.project); // 项目信息 |
|||
const roleId = computed(() => store.state.role.roleId); // 当前角色 |
|||
const globalHeight = computed(() => store.state.task.globalHeight); // 日常任务面板高度 |
|||
const sessionGlobalHeight = sessionStorage.getItem('globalHeight'); // 日常任务面板高度缓存 |
|||
const regularTasks = computed(() => store.state.task.regularTasks); // 定期任务 |
|||
const taskObj = reactive({ tasks: [] }); // 定期任务 |
|||
const sessionTasks = sessionStorage.getItem('regularTasks'); // 定期任务缓存 |
|||
const refreshProjects = computed(() => store.state.layout.refreshProjects); // 刷新项目列表 |
|||
|
|||
if (sessionGlobalHeight) { |
|||
store.commit('task/setGlobalHeight', sessionGlobalHeight); |
|||
} |
|||
|
|||
if (sessionTasks) { |
|||
const arr = JSON.parse(sessionTasks); |
|||
store.commit('task/setRegularTasks', arr); |
|||
} |
|||
|
|||
if (project.value) { |
|||
if (roleId.value) { |
|||
getTasks({ roleId: roleId.value }); // 根据角色查找定期任务 |
|||
} |
|||
} |
|||
|
|||
watch([project, roleId, refreshProjects], async () => { |
|||
if (roleId.value) { |
|||
await getTasks({ roleId: roleId.value }); // 根据角色查找定期任务 |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* 根据时间基准点和角色查找定期任务 |
|||
* @param {object} query |
|||
* @param {string} query.roleId 角色id |
|||
* @param {string} query.timeNode 时间基准点 默认当前 |
|||
* @param {string} query.timeUnit 时间颗粒度 默认天 |
|||
* @param {string} query.queryNum 查找颗粒度数量 默认3个 |
|||
* @param {number} query.queryType 0向上查找 1向下查找(默认) 下查包含自己,上查不包含 |
|||
*/ |
|||
async function getTasks(query) { |
|||
const params = { param: query }; |
|||
try { |
|||
const data = await getRegularTask(params); |
|||
taskObj.tasks = data; |
|||
|
|||
data.forEach(item => { |
|||
item.plugins.forEach(val => { |
|||
if (Number(val[0].pluginId) === 6) { |
|||
getSonTask(item.detailId); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
store.commit('task/setRegularTasks', data); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 根据id获取子任务 |
|||
async function getSonTask(id) { |
|||
const params = { param: { detailId: id } }; |
|||
try { |
|||
const data = await findSonTask(params); |
|||
taskObj.tasks.forEach(item => { |
|||
if (item.detailId === id) { |
|||
item.sonList = data; |
|||
} |
|||
}); |
|||
|
|||
store.commit('task/setRegularTasks', taskObj.tasks); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
function toDetail(item) { |
|||
store.commit('task/setTaskDetail', item); |
|||
store.commit('task/sonDetailId', ''); |
|||
store.commit('task/sonExperimentationId', ''); |
|||
} |
|||
|
|||
function toSonDetail(item, id) { |
|||
store.commit('task/setTaskDetail', item); |
|||
store.commit('task/sonDetailId', id); |
|||
store.commit('task/sonExperimentationId', id); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-list { |
|||
padding: 0 16px 50px; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.task-box { |
|||
} |
|||
|
|||
.task-time { |
|||
height: 32px; |
|||
} |
|||
|
|||
.task-time .anticon { |
|||
margin-right: 16px; |
|||
} |
|||
|
|||
.task-time span { |
|||
font-size: 14px; |
|||
color: #595959; |
|||
} |
|||
|
|||
.task-info { |
|||
margin: 8px 0; |
|||
padding-left: 11px; |
|||
} |
|||
|
|||
.task-info > div { |
|||
padding-left: 27px; |
|||
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; |
|||
padding-left: 16px; |
|||
} |
|||
|
|||
.task-con > div { |
|||
height: 30px; |
|||
} |
|||
|
|||
:deep(.ant-checkbox + span) { |
|||
color: #607d8b; |
|||
} |
|||
|
|||
.son-task-name { |
|||
color: #607d8b; |
|||
} |
|||
|
|||
.task-list::-webkit-scrollbar { |
|||
width: 0 !important; |
|||
} |
|||
</style> |
@ -0,0 +1,99 @@ |
|||
<template> |
|||
<div class="role-list flex items-center"> |
|||
<div class="role-box relative" v-for="(item, index) in roles" :key="index"> |
|||
<div class="role-name" :class="{ mine: roleId === item.id }">{{ item.name }}</div> |
|||
<div class="line-box absolute flex justify-center" v-if="roleId === item.id"><div class="line"></div></div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed, watch } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { findShowRole } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const roleId = computed(() => store.state.role.roleId); // 当前角色 |
|||
const sessionRoles = sessionStorage.getItem('roles'); // 角色缓存 |
|||
const roles = computed(() => store.state.role.visibleRoles); // 当前项目角色列表 |
|||
|
|||
const project = computed(() => store.state.projects.project); // 项目信息 |
|||
|
|||
if (sessionRoles) { |
|||
const roleArr = JSON.parse(sessionRoles); |
|||
store.commit('role/setVisibleRoles', roleArr); |
|||
setInitialRoleId(roleArr); |
|||
} |
|||
|
|||
if (project.value) { |
|||
getRoles(project.value.id); // 通过项目id获取角色信息 |
|||
} |
|||
|
|||
watch(project, async () => { |
|||
if (project.value.id) { |
|||
await getRoles(project.value.id); // 通过项目id获取角色信息 |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* 通过项目id获取角色信息 |
|||
* @param {string} projectId |
|||
* @param {object} params 提交的参数 |
|||
*/ |
|||
async function getRoles(params) { |
|||
try { |
|||
const data = await findShowRole(params); |
|||
store.commit('role/setInvisibleRoles', data ? data.invisibleList : []); |
|||
store.commit('role/setVisibleRoles', data ? data.visibleList : []); |
|||
setInitialRoleId(data ? data.visibleList : []); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
// 设置 初始显示角色信息 |
|||
function setInitialRoleId(visibleList) { |
|||
if (!visibleList || !visibleList.length) return; |
|||
const index = visibleList.findIndex(item => +item.mine === 1); |
|||
const currentRole = index > 0 ? visibleList[index] : visibleList[0]; |
|||
const currentRoleId = currentRole ? currentRole.id : ''; |
|||
store.commit('role/setRoleId', currentRoleId); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.role-list { |
|||
padding: 0 16px; |
|||
height: 36px; |
|||
border-bottom: 1px solid #cccccc; |
|||
} |
|||
|
|||
.role-box { |
|||
margin-right: 16px; |
|||
height: 36px; |
|||
} |
|||
|
|||
.role-name { |
|||
font-size: 14px; |
|||
line-height: 36px; |
|||
color: #333333; |
|||
} |
|||
|
|||
.role-name.mine { |
|||
font-weight: 600; |
|||
color: #1890ff; |
|||
} |
|||
|
|||
.role-box .line-box { |
|||
width: 100%; |
|||
bottom: 0; |
|||
} |
|||
|
|||
.line-box .line { |
|||
width: 16px; |
|||
height: 2px; |
|||
background-color: #1890ff; |
|||
} |
|||
</style> |
@ -1,13 +1,125 @@ |
|||
<template> |
|||
<Calendar /> |
|||
<Projects /> |
|||
<div class="relative"> |
|||
<Calendar @changeTime="changeTime" /> |
|||
|
|||
<PlusCircleFilled class="upload-btn absolute cursor-pointer" :style="{ fontSize: 42 + 'px', color: '#1890FF' }" @click="showModal" /> |
|||
|
|||
<!-- <div class="upload-card absolute" v-if="isShowCard"> |
|||
<div class="model-list"> |
|||
<div class="model-name cursor-pointer">模板项目</div> |
|||
<div class="model-name cursor-pointer">模板项目</div> |
|||
</div> |
|||
<div class="upload-box relative"> |
|||
<a-button type="primary">导入WBS</a-button> |
|||
<input @change="handleUpload" class="btn-file absolute cursor-pointer" name="file" type="file" /> |
|||
</div> |
|||
</div> --> |
|||
|
|||
<a-modal v-model:visible="visible" title="新建课题" @ok="handleOk"> |
|||
<p>是否新建课题?</p> |
|||
</a-modal> |
|||
|
|||
<Projects /> |
|||
<!-- ref="projectsRef" --> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
// import dayjs from 'dayjs'; |
|||
import { PlusCircleFilled } from '@ant-design/icons-vue'; |
|||
import { create } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
import Calendar from './Calendar.vue'; |
|||
import Projects from './Projects.vue'; |
|||
|
|||
// import { importWbs } from 'apis'; |
|||
|
|||
const store = useStore(); |
|||
const visible = ref(false); |
|||
// const projectsRef = ref(null); |
|||
const showModal = () => { |
|||
visible.value = true; |
|||
}; |
|||
|
|||
const handleOk = async () => { |
|||
visible.value = false; |
|||
await createExperiment(); |
|||
}; |
|||
|
|||
// 新建课题 |
|||
async function createExperiment() { |
|||
try { |
|||
const data = await create(); |
|||
const obj = { |
|||
id: data.projectId, |
|||
name: data.projectName, |
|||
}; |
|||
store.commit('projects/setProject', obj); |
|||
store.commit('task/setTaskDetail', null); |
|||
store.commit('layout/setRefreshProjects'); |
|||
return data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
function changeTime(data) { |
|||
store.commit('layout/setSelectTime', data); |
|||
} |
|||
</script> |
|||
|
|||
<script> |
|||
export default { name: 'LeftIndex' }; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.upload-btn { |
|||
left: 250px; |
|||
top: 310px; |
|||
z-index: 999; |
|||
} |
|||
|
|||
.upload-card { |
|||
left: 250px; |
|||
top: 352px; |
|||
-moz-box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.16); |
|||
-webkit-box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.16); |
|||
box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.16); |
|||
width: 296px; |
|||
z-index: 999; |
|||
border-radius: 8px; |
|||
background: #fff; |
|||
} |
|||
|
|||
.model-list { |
|||
padding: 16px; |
|||
} |
|||
|
|||
.model-name { |
|||
height: 32px; |
|||
line-height: 32px; |
|||
} |
|||
|
|||
.upload-box { |
|||
padding: 16px; |
|||
border-top: 1px solid #cccccc; |
|||
} |
|||
|
|||
.btn-file { |
|||
top: 16px; |
|||
left: 16px; |
|||
width: 264px; |
|||
height: 38px; |
|||
opacity: 0; |
|||
} |
|||
|
|||
.upload-box .ant-btn-primary { |
|||
width: 100%; |
|||
height: 38px; |
|||
border-radius: 6px; |
|||
font-size: 16px; |
|||
} |
|||
</style> |
|||
|
@ -1,5 +1,362 @@ |
|||
<template>项目列表</template> |
|||
<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" @click="toDetail(item)"> |
|||
<div class="icon" @click.stop="showActionCard(item)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> |
|||
<div class="detail"> |
|||
<div class="name-box flex items-center"> |
|||
<div class="name truncate">{{ item.name }}</div> |
|||
<!-- <div class="precent-num">50%</div> --> |
|||
</div> |
|||
|
|||
<script setup></script> |
|||
<div class="time"> |
|||
{{ dayjs(Number(item.startTime)).format('MM-DD HH:mm') }} 至 |
|||
{{ dayjs(Number(item.endTime)).format('MM-DD HH:mm') }} |
|||
</div> |
|||
</div> |
|||
|
|||
<style scoped></style> |
|||
<div class="right" @click.stop="openMenu"> |
|||
<RightOutlined v-if="!item.show" @click="changeShow(item)" /> |
|||
<DownOutlined v-else @click="changeShow(item)" /> |
|||
</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" @click="toDetail(sonItem, item)"> |
|||
<div class="icon" @click.stop="showActionCard(sonItem)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> |
|||
<div class="detail"> |
|||
<div class="name-box flex items-center"> |
|||
<div class="name truncate">{{ sonItem.name }}</div> |
|||
<!-- <div class="precent-num">50%</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 class="three-box" v-if="sonItem.show"> |
|||
<div |
|||
class="three-level h-70 cursor-pointer flex items-center" |
|||
v-for="(thirdItem, thirdIndex) in sonItem.sonProjectList" |
|||
:key="thirdIndex" |
|||
@click="toDetail(thirdItem, sonItem, item)" |
|||
> |
|||
<div class="icon" @click.stop="showActionCard(thirdItem)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> |
|||
<div class="detail"> |
|||
<div class="name-box flex items-center"> |
|||
<div class="name truncate">{{ thirdItem.name }}</div> |
|||
<!-- <div class="precent-num">50%</div> --> |
|||
</div> |
|||
|
|||
<div class="time"> |
|||
{{ dayjs(Number(thirdItem.startTime)).format('MM-DD HH:mm') }} 至 |
|||
{{ dayjs(Number(thirdItem.endTime)).format('MM-DD HH:mm') }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<a-modal v-model:visible="visible" title="删除" @ok="handleOk"> |
|||
<p>确定要删除吗?</p> |
|||
</a-modal> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, watch, computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import dayjs from 'dayjs'; |
|||
import { getProjects, delProject, getExperimentation } from 'apis'; |
|||
import { RightOutlined, DownOutlined } from '@ant-design/icons-vue'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
|
|||
const visible = ref(false); |
|||
const deleteId = ref(null); |
|||
|
|||
// const projectId = sessionStorage.getItem('projectId'); // 项目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 planStartTime = sessionStorage.getItem('planStartTime'); |
|||
const planEndTime = sessionStorage.getItem('planEndTime'); |
|||
// 子课题时间缓存 |
|||
const subStartTime = sessionStorage.getItem('subStartTime'); |
|||
const subEndTime = sessionStorage.getItem('subEndTime'); |
|||
// 实验时间缓存 |
|||
const expreStartTime = sessionStorage.getItem('expreStartTime'); |
|||
const expreEndTime = sessionStorage.getItem('expreEndTime'); |
|||
|
|||
if (planStartTime) { |
|||
store.commit('layout/setFirPlanTime', { startTime: planStartTime, endTime: planEndTime }); |
|||
} |
|||
|
|||
if (subStartTime) { |
|||
store.commit('layout/setSecPlanTime', { startTime: subStartTime, endTime: subEndTime }); |
|||
} |
|||
|
|||
if (expreStartTime) { |
|||
store.commit('layout/setThirdPlanTime', { startTime: expreStartTime, endTime: expreEndTime }); |
|||
} |
|||
|
|||
if (sessionProject) { |
|||
// const projectList = ref([]); |
|||
|
|||
// 如果有缓存,则将缓存信息放到store中,一般用于页面刷新 |
|||
if (!projectInfo.value.id) { |
|||
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(); |
|||
}); |
|||
|
|||
// 点击操作面试显示隐藏 |
|||
const showActionCard = item => { |
|||
visible.value = true; |
|||
deleteId.value = item.id; |
|||
}; |
|||
|
|||
// 获取项目列表 |
|||
async function getProjectsList() { |
|||
try { |
|||
const data = await getProjects(startTime.value, endTime.value); |
|||
|
|||
data.forEach(item => { |
|||
item.show = false; |
|||
|
|||
if (item.id === projectInfo.value.id) { |
|||
item.show = true; |
|||
} |
|||
|
|||
if (item.sonProjectList) { |
|||
item.sonProjectList.forEach(sonItem => { |
|||
sonItem.show = false; |
|||
|
|||
if (sonItem.id === projectInfo.value.id) { |
|||
item.show = true; |
|||
sonItem.show = true; |
|||
} |
|||
|
|||
if (sonItem.sonProjectList) { |
|||
sonItem.sonProjectList.forEach(val => { |
|||
val.show = false; |
|||
if (val.id === projectInfo.value.id) { |
|||
item.show = true; |
|||
sonItem.show = true; |
|||
val.show = true; |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
// projectList.value = [...data]; |
|||
store.commit('projects/setProjects', data); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
|
|||
function toDetail(item, sec, fir) { |
|||
item.show = true; |
|||
|
|||
const obj = { |
|||
id: item.id, |
|||
name: item.name, |
|||
url: item.url, |
|||
}; |
|||
|
|||
// projectList.value.forEach(item => { |
|||
// item.show = false; |
|||
// if (item.id === data.id) { |
|||
// item.show = true; |
|||
// } |
|||
// if (item.sonProjectList) { |
|||
// item.sonProjectList.forEach(sonItem => { |
|||
// sonItem.show = false; |
|||
// if (sonItem.id === data.id) { |
|||
// item.show = true; |
|||
// sonItem.show = true; |
|||
// } |
|||
|
|||
// if (sonItem.sonProjectList) { |
|||
// sonItem.sonProjectList.forEach(val => { |
|||
// val.show = false; |
|||
// if (val.id === data.id) { |
|||
// item.show = true; |
|||
// sonItem.show = true; |
|||
// val.show = true; |
|||
// } |
|||
// }); |
|||
// } |
|||
// }); |
|||
// } |
|||
// }); |
|||
|
|||
if (sec && fir) { |
|||
store.commit('layout/setFirPlanTime', { startTime: fir.startTime, endTime: fir.endTime }); |
|||
store.commit('layout/setSecPlanTime', { startTime: sec.startTime, endTime: sec.endTime }); |
|||
store.commit('layout/setThirdPlanTime', { startTime: item.startTime, endTime: item.endTime }); |
|||
getSubProject(item.id); |
|||
} else if (sec) { |
|||
store.commit('layout/setFirPlanTime', { startTime: sec.startTime, endTime: sec.endTime }); |
|||
store.commit('layout/setSecPlanTime', { startTime: item.startTime, endTime: item.endTime }); |
|||
} else { |
|||
store.commit('layout/setFirPlanTime', { startTime: item.startTime, endTime: item.endTime }); |
|||
} |
|||
|
|||
// store.commit('projects/setProjects', projectList.value); |
|||
store.commit('projects/setProject', obj); |
|||
store.commit('task/setTaskDetail', null); |
|||
} |
|||
|
|||
function changeShow(item) { |
|||
item.show = !item.show; |
|||
} |
|||
|
|||
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); |
|||
} |
|||
} |
|||
|
|||
// 根据ID查询实验信息 |
|||
async function getSubProject(id) { |
|||
try { |
|||
const params = { param: { id } }; |
|||
const data = await getExperimentation(params); |
|||
store.commit('projects/setExpreimentStatus', data.status); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
</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-70 { |
|||
height: 70px; |
|||
} |
|||
|
|||
.one-level { |
|||
padding: 0 16px; |
|||
} |
|||
|
|||
.two-level { |
|||
padding: 0 16px 0 32px; |
|||
} |
|||
|
|||
.three-level { |
|||
padding: 0 16px 0 48px; |
|||
} |
|||
|
|||
.item-box .icon { |
|||
margin-right: 8px; |
|||
width: 24px; |
|||
height: 24px; |
|||
} |
|||
|
|||
.detail { |
|||
width: calc(100% - 76px); |
|||
} |
|||
|
|||
.name-box { |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
.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; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.time { |
|||
font-size: 12px; |
|||
color: #999999; |
|||
} |
|||
|
|||
.right { |
|||
width: 14px; |
|||
margin-left: 30px; |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,60 @@ |
|||
<template> |
|||
<div class="intro-flex flex justify-between"> |
|||
<div class="intro-wrap left"> |
|||
<img src="https://www.tall.wiki/staticrec/experiment/topic.png" /> |
|||
</div> |
|||
|
|||
<div class="intro-wrap center"> |
|||
<img src="https://www.tall.wiki/staticrec/experiment/sub-topic.png" /> |
|||
</div> |
|||
|
|||
<div class="intro-wrap right"> |
|||
<img src="https://www.tall.wiki/staticrec/experiment/experiment.png" /> |
|||
</div> |
|||
<!-- <img src="https://www.tall.wiki/staticrec/experiment/start.png" /> --> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
|
|||
<style scoped> |
|||
.intro-flex { |
|||
padding: 16px; |
|||
width: 100%; |
|||
height: 100%; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
/* .intro-flex>img { |
|||
max-width: 100%; |
|||
max-height: 100%; |
|||
} */ |
|||
|
|||
.intro-wrap { |
|||
padding: 30px 0; |
|||
width: calc((100% - 32px) / 3); |
|||
height: 100%; |
|||
border-radius: 10px; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.intro-wrap img { |
|||
width: 80%; |
|||
margin: 0 auto; |
|||
} |
|||
|
|||
.intro-wrap.left { |
|||
border: 1px solid #1890ff; |
|||
background: rgba(24, 144, 255, 0.05); |
|||
} |
|||
|
|||
.intro-wrap.center { |
|||
border: 1px solid #3cc282; |
|||
background: rgba(60, 194, 130, 0.05); |
|||
} |
|||
|
|||
.intro-wrap.right { |
|||
border: 1px solid #fa8c16; |
|||
background: rgba(250, 140, 22, 0.05); |
|||
} |
|||
</style> |
@ -0,0 +1,210 @@ |
|||
<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> |
@ -0,0 +1,453 @@ |
|||
<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> |
@ -0,0 +1,192 @@ |
|||
<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> |
@ -0,0 +1,246 @@ |
|||
<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> |
@ -0,0 +1,235 @@ |
|||
<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> |
@ -0,0 +1,172 @@ |
|||
<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> |
@ -0,0 +1,287 @@ |
|||
<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> |
@ -0,0 +1,287 @@ |
|||
<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> |
@ -0,0 +1,277 @@ |
|||
<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> |
@ -0,0 +1,246 @@ |
|||
<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> |
@ -0,0 +1,271 @@ |
|||
<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) { |
|||
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> |
@ -0,0 +1,292 @@ |
|||
<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> |
@ -0,0 +1,258 @@ |
|||
<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> |
@ -0,0 +1,389 @@ |
|||
<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> |
@ -0,0 +1,271 @@ |
|||
<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> |
@ -0,0 +1,308 @@ |
|||
<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> |
@ -0,0 +1,251 @@ |
|||
<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> |
@ -0,0 +1,251 @@ |
|||
<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> |
@ -0,0 +1,236 @@ |
|||
<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> |
@ -0,0 +1,284 @@ |
|||
<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> |
@ -0,0 +1,246 @@ |
|||
<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> |
@ -0,0 +1,246 @@ |
|||
<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> |
@ -0,0 +1,244 @@ |
|||
<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> |
@ -0,0 +1,246 @@ |
|||
<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> |
@ -0,0 +1,195 @@ |
|||
<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> |
@ -0,0 +1,144 @@ |
|||
<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> |
@ -1,20 +1,52 @@ |
|||
<template> |
|||
<menu-unfold-outlined v-if="collapsed" @click="toggleCollapse" /> |
|||
<menu-fold-outlined v-else class="trigger" @click="toggleCollapse" /> |
|||
<h1>课题数据库</h1> |
|||
<div class="flex justify-between items-center"> |
|||
<h1>{{ taskDetail.name }}</h1> |
|||
<div |
|||
class="flex items-center cursor-pointer" |
|||
:style="{ color: isShowList ? '#1890FF' : '' }" |
|||
v-if="label == 'KT_KYHY' || label == 'ZKT_HYGL' || label == 'ZKT_LW' || label == 'ZKT_ZL' || label == 'ZKT_RZ'" |
|||
@click="changeShowStatus" |
|||
> |
|||
<ClockCircleOutlined style="font-size: 18px" /> |
|||
<span style="font-size: 16px; margin-left: 8px">历史记录</span> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
// eslint-disable-next-line import/no-extraneous-dependencies |
|||
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue'; |
|||
import { ClockCircleOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
const store = useStore(); |
|||
const collapsed = computed(() => store.state.layout.display.left); // 是否显示左栏 |
|||
const taskDetail = computed(() => store.state.task.taskDetail); // 任务名称 |
|||
const sessionTaskDetail = sessionStorage.getItem('taskDetail'); |
|||
const label = computed(() => store.state.task.label); // 任务code |
|||
const sessionLabel = sessionStorage.getItem('label'); // 任务code |
|||
const isShowList = computed(() => store.state.layout.isShowListStatus); // 是否显示任务详情列表 |
|||
|
|||
// toggle left window display |
|||
function toggleCollapse() { |
|||
store.commit('layout/setDisplay', { prop: 'left', show: !collapsed.value }); |
|||
if (sessionTaskDetail) { |
|||
const obj = JSON.parse(sessionTaskDetail); |
|||
store.commit('task/setTaskDetail', obj); |
|||
} |
|||
|
|||
if (sessionLabel) { |
|||
store.commit('task/setLabel', sessionLabel); |
|||
} |
|||
|
|||
// 监听任务信息 |
|||
// watch([taskDetail, label], () => {}); |
|||
|
|||
// 改变列表展示状态 |
|||
function changeShowStatus() { |
|||
store.commit('layout/setListStatus', !isShowList.value); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
h1 { |
|||
font-size: 16px; |
|||
color: #666666; |
|||
margin: 0; |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,74 @@ |
|||
<template> |
|||
<div class="flex justify-between items-center"> |
|||
<h1 class=""> |
|||
<span>实验平台</span> |
|||
<menu-unfold-outlined v-if="collapsed" @click="toggleCollapse" /> |
|||
<menu-fold-outlined v-else class="trigger" @click="toggleCollapse" /> |
|||
</h1> |
|||
<div class="user-info relative flex items-center cursor-pointer"> |
|||
<a-image :width="30" src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" /> |
|||
<div class="mr-3 ml-3">{{ account }}</div> |
|||
<CaretDownOutlined /> |
|||
|
|||
<div class="user-action absolute"> |
|||
<div @click="signOut">退出登录</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
import { useRouter } from 'vue-router'; |
|||
// eslint-disable-next-line import/no-extraneous-dependencies |
|||
import { MenuUnfoldOutlined, MenuFoldOutlined, CaretDownOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
const store = useStore(); |
|||
const router = useRouter(); |
|||
const collapsed = computed(() => store.state.layout.display.left); // 是否显示左栏 |
|||
const account = computed(() => store.getters['user/account']); |
|||
|
|||
// toggle left window display |
|||
function toggleCollapse() { |
|||
store.commit('layout/setDisplay', { prop: 'left', show: !collapsed.value }); |
|||
} |
|||
|
|||
// 退出登录 |
|||
function signOut() { |
|||
store.commit('user/setUser', null); |
|||
store.commit('projects/setProject', null); |
|||
store.commit('task/setTaskDetail', null); |
|||
router.push({ path: '/experiment/user/signin' }); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
h1 { |
|||
margin: 0; |
|||
} |
|||
|
|||
h1 span { |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
margin-right: 10px; |
|||
} |
|||
|
|||
.user-info { |
|||
height: 48px; |
|||
} |
|||
|
|||
.user-action { |
|||
display: none; |
|||
top: 48px; |
|||
right: 0; |
|||
width: 150px; |
|||
text-align: center; |
|||
box-shadow: 0px 0 6px 0px rgba(0, 0, 0, 0.3); |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.user-info:hover .user-action { |
|||
display: block; |
|||
} |
|||
</style> |
@ -1,5 +1,8 @@ |
|||
import { createStore } from 'vuex'; |
|||
import user from './tall/user'; |
|||
import layout from './tall/layout'; |
|||
import projects from './tall/projects'; |
|||
import role from './tall/role'; |
|||
import task from './tall/task'; |
|||
import user from './tall/user'; |
|||
|
|||
export default createStore({ modules: { user, layout } }); |
|||
export default createStore({ modules: { user, projects, role, task, layout } }); |
|||
|
@ -0,0 +1,108 @@ |
|||
export default { |
|||
namespaced: true, |
|||
|
|||
state: { |
|||
project: { name: '加载中...' }, // 当前项目信息
|
|||
projects: [], // 项目列表
|
|||
dotList: [], // 小红点
|
|||
newProject: {}, // 新建项目信息
|
|||
expreStatus: 0, // 实验状态
|
|||
}, |
|||
|
|||
getters: { |
|||
/** |
|||
* 当前项目的id |
|||
* @param {object} project |
|||
*/ |
|||
projectId({ project }) { |
|||
return project.id; |
|||
}, |
|||
}, |
|||
|
|||
mutations: { |
|||
/** |
|||
* 设置state projects书籍 |
|||
* @param {object} state |
|||
* @param {array} projects 项目列表 |
|||
*/ |
|||
setProjects(state, projects) { |
|||
if (!projects || !projects.length) { |
|||
state.projects = []; |
|||
} else { |
|||
state.projects = [...projects]; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 设置state project新建项目信息 |
|||
* @param {object} state |
|||
* @param {array} data 项目信息 |
|||
*/ |
|||
setNewProject(state, data) { |
|||
state.newProject = data; |
|||
}, |
|||
|
|||
/** |
|||
* 设置子项目收缩展开 |
|||
* @param { object } state |
|||
* @param { object } options options:{ index,show } |
|||
*/ |
|||
setProjectItemShow(state, options) { |
|||
if (options.show) { |
|||
for (let i = 0; i < state.projects.length; i++) { |
|||
if (i === options.index) { |
|||
state.projects[i].show = true; |
|||
} else { |
|||
state.projects[i].show = false; |
|||
} |
|||
} |
|||
} else { |
|||
state.projects[options.index].show = false; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 设置当前项目信息 |
|||
* @param { object } state |
|||
* @param { object } data |
|||
*/ |
|||
setProject(state, data) { |
|||
state.project = data || { name: '加载中...' }; |
|||
if (data) { |
|||
sessionStorage.setItem('projectId', data.id); |
|||
sessionStorage.setItem('project', JSON.stringify(data)); |
|||
} else { |
|||
sessionStorage.removeItem('projectId'); |
|||
sessionStorage.removeItem('project'); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 设置当前项目名称 |
|||
* @param { object } state |
|||
* @param { string } data |
|||
*/ |
|||
setProjectName(state, data) { |
|||
state.project.name = data; |
|||
}, |
|||
|
|||
/** |
|||
* 设置小红点 |
|||
* @param { object } state |
|||
* @param { string } data |
|||
*/ |
|||
setDotList(state, data) { |
|||
state.dotList = data; |
|||
}, |
|||
|
|||
/** |
|||
* 设置实验状态 |
|||
*/ |
|||
setExpreimentStatus(state, data) { |
|||
state.expreStatus = data || 0; |
|||
sessionStorage.setItem('expreStatus', state.expreStatus); |
|||
}, |
|||
}, |
|||
|
|||
actions: {}, |
|||
}; |
@ -0,0 +1,22 @@ |
|||
import { queryChecker } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const actions = { |
|||
/** |
|||
* 根据项目id查找所有成员信息 |
|||
* @param {*} commit |
|||
* @param {object} params |
|||
*/ |
|||
async getAllMembers({ commit }, params) { |
|||
try { |
|||
const data = await queryChecker(params); |
|||
commit('setMembers', data); |
|||
return data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
}, |
|||
}; |
|||
|
|||
export default actions; |
@ -0,0 +1,14 @@ |
|||
const getters = { |
|||
// 是不是负责人
|
|||
isMine({ roleId, invisibleRoles, visibleRoles }) { |
|||
console.log(roleId, invisibleRoles, visibleRoles); |
|||
// if (!visibleRoles || !visibleRoles.length) return false;
|
|||
// const visible = visibleRoles.find(visible => visible.id === roleId);
|
|||
// if (visible) return visible.mine;
|
|||
// const invisible = invisibleRoles.find(invisible => invisible.id === roleId);
|
|||
// if (invisible) return visible.mine;
|
|||
return false; |
|||
}, |
|||
}; |
|||
|
|||
export default getters; |
@ -0,0 +1,12 @@ |
|||
import state from './state'; |
|||
import getters from './getters'; |
|||
import mutations from './mutations'; |
|||
import actions from './actions'; |
|||
|
|||
export default { |
|||
namespaced: true, |
|||
state, |
|||
getters, |
|||
mutations, |
|||
actions, |
|||
}; |
@ -0,0 +1,40 @@ |
|||
const mutations = { |
|||
/** |
|||
* 设置不展示的角色信息 |
|||
* @param {Object} state |
|||
* @param {Array} data 服务端返回的模板数组 |
|||
*/ |
|||
setInvisibleRoles(state, data) { |
|||
state.invisibleRoles = data || []; |
|||
}, |
|||
|
|||
/** |
|||
* 设置展示的角色信息 |
|||
* @param {Object} state |
|||
* @param {Array} data 服务端返回的模板数组 |
|||
*/ |
|||
setVisibleRoles(state, data) { |
|||
state.visibleRoles = data || []; |
|||
sessionStorage.setItem('roles', JSON.stringify(data)); |
|||
}, |
|||
|
|||
/** |
|||
* 设置当前角色信息 |
|||
* @param {Object} state |
|||
* @param {string} roleId 当前正在展示的角色的id |
|||
*/ |
|||
setRoleId(state, roleId) { |
|||
state.roleId = roleId; |
|||
}, |
|||
|
|||
/** |
|||
* 设置项目下所有成员信息 |
|||
* @param {Object} state |
|||
* @param {Array} data 服务端返回的模板数组 |
|||
*/ |
|||
setMembers(state, data) { |
|||
state.members = data || []; |
|||
}, |
|||
}; |
|||
|
|||
export default mutations; |
@ -0,0 +1,8 @@ |
|||
const state = { |
|||
invisibleRoles: [], // 不展示的角色信息
|
|||
visibleRoles: [], // 展示的角色信息
|
|||
roleId: '', // 当前展示查看的角色id
|
|||
members: [], // 项目下所有成员
|
|||
}; |
|||
|
|||
export default state; |
@ -0,0 +1,497 @@ |
|||
import { getByCode, submitAnswer, getIntellectual, getIntellectualList, submitIntellectual } from 'apis'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
export default { |
|||
namespaced: true, |
|||
|
|||
state: { |
|||
scrollTop: 0, |
|||
scrollToTaskId: '', // 时间轴自动滚动的位置
|
|||
isShrink: false, // true: 收起, false:展开
|
|||
tip: { |
|||
taskId: '', // 当前正在修改状态的任务的id
|
|||
show: false, |
|||
status: 0, // 所点击任务的当前状态码
|
|||
text: '', |
|||
left: 0, // 鼠标点击位置距离左边的距离
|
|||
top: 0, // 鼠标点击位置距离上边的距离
|
|||
}, |
|||
timeNode: new Date().getTime(), // 时间基准点
|
|||
timeUnit: 4, // 时间颗粒度
|
|||
topEnd: false, // 时间轴向上查任务到顶了
|
|||
bottomEnd: false, // 时间轴向下查任务到底了
|
|||
permanents: [], // 永久日常任务
|
|||
dailyTasks: [], // 日常任务
|
|||
tasks: [], // 所有的定期任务
|
|||
showSkeleton: false, // 定期任务骨架屏
|
|||
newProjectInfo: {}, |
|||
showScrollTo: false, // 是否可以设置时间轴自动滚动的位置
|
|||
taskDetail: {}, // 当前点击任务信息
|
|||
label: '', // 任务code
|
|||
members: [], // 成员列表
|
|||
globalHeight: 0, // 日常任务面板高度
|
|||
detailId: '', // 子课题Id
|
|||
subProjectInfo: null, // 当前子课题信息
|
|||
experimentationId: '', // 实验ID
|
|||
experimentationInfo: null, // 当前实验信息
|
|||
regularTasks: [], // 定期任务
|
|||
intellectualId: '', // 当前选择的知识产权ID
|
|||
meetId: '', // 当前选择的会议ID
|
|||
subMeetId: '', // 当前选择的子课题会议ID
|
|||
question: null, |
|||
}, |
|||
|
|||
getters: { |
|||
// 所有的日常任务 永久 + 可变 日常任务
|
|||
globals({ dailyTasks, permanents }) { |
|||
return [...permanents, ...dailyTasks]; |
|||
}, |
|||
|
|||
// unitConfig({ timeUnit }) {
|
|||
// const target = uni.$t.timeConfig.timeUnits.find(item => item.id === timeUnit);
|
|||
// return target;
|
|||
// },
|
|||
|
|||
// 计算任务开始时间的格式
|
|||
// startTimeFormat(state, { unitConfig }) {
|
|||
// return unitConfig.format || 'D日 HH:mm';
|
|||
// },
|
|||
|
|||
// 计算颗粒度 对应的 dayjs add 的单位
|
|||
// timeGranularity(state, { unitConfig }) {
|
|||
// return unitConfig.granularity;
|
|||
// },
|
|||
}, |
|||
|
|||
mutations: { |
|||
/** |
|||
* 记录时间轴向上滚动的距离 |
|||
* @param { object } state |
|||
* @param { number } num |
|||
*/ |
|||
setScrollTop(state, num) { |
|||
state.scrollTop = num; |
|||
}, |
|||
|
|||
/** |
|||
* 记录时间轴向上滚动的距离 |
|||
* @param { object } state |
|||
* @param {string} taskId |
|||
*/ |
|||
setScrollToTaskId(state, taskId) { |
|||
state.scrollToTaskId = taskId; |
|||
}, |
|||
|
|||
/** |
|||
* 设置日常任务当前是否应该处于收缩状态 |
|||
* @param { object } state |
|||
* @param { boolean } data |
|||
*/ |
|||
setShrink(state, data) { |
|||
state.isShrink = data; |
|||
}, |
|||
|
|||
/** |
|||
* 设置tip的值 |
|||
* @param {object} state |
|||
* @param {object} data |
|||
*/ |
|||
setTip(state, data) { |
|||
if (!data) return; |
|||
state.tip = { ...data }; |
|||
}, |
|||
|
|||
/** |
|||
* 是否显示tips |
|||
* @param { object } state |
|||
* @param { boolean } show |
|||
*/ |
|||
setTipShow(state, show) { |
|||
state.tip.show = show; |
|||
}, |
|||
|
|||
/** |
|||
* 是否显示tips |
|||
* @param { object } state |
|||
* @param { number } status |
|||
*/ |
|||
setStatus(state, status) { |
|||
state.tip.status = status; |
|||
}, |
|||
|
|||
/** |
|||
* 设置时间基准点 |
|||
* @param { object } state |
|||
* @param { number } data |
|||
*/ |
|||
setTimeNode(state, data) { |
|||
state.timeNode = data; |
|||
}, |
|||
|
|||
/** |
|||
* 设置时间颗粒度 |
|||
* @param { object } state |
|||
* @param { number } data |
|||
*/ |
|||
setTimeUnit(state, data) { |
|||
state.timeUnit = data; |
|||
}, |
|||
|
|||
/** |
|||
* 设置向上查到的定期任务数据 |
|||
* @param {Object} state |
|||
* @param {Array} data 服务端返回的模板数组 |
|||
*/ |
|||
setUpTasks(state, data) { |
|||
if (!state.tasks.length) { |
|||
state.tasks = [...data]; // 原来没有数据
|
|||
} else { |
|||
state.tasks = [...data, ...state.tasks]; |
|||
|
|||
const arr = []; |
|||
let flag = false; |
|||
state.tasks.forEach(task => { |
|||
arr.forEach(item => { |
|||
if (task.id === item.id) { |
|||
flag = true; |
|||
} |
|||
}); |
|||
|
|||
if (!flag) { |
|||
arr.push(task); |
|||
} |
|||
}); |
|||
|
|||
state.tasks = [...arr]; |
|||
// state.tasks = [...data.concat(state.tasks)];
|
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 设置向下查到的定期任务数据 |
|||
* @param {Object} state |
|||
* @param {Array} data 服务端返回的模板数组 |
|||
*/ |
|||
setDownTasks(state, data) { |
|||
if (!state.tasks && !state.tasks.length) { |
|||
state.tasks = [...data]; |
|||
} else { |
|||
state.tasks = [...state.tasks, ...data]; |
|||
|
|||
const arr = []; |
|||
let flag = false; |
|||
state.tasks.forEach(task => { |
|||
arr.forEach(item => { |
|||
if (task.id === item.id) { |
|||
flag = true; |
|||
} |
|||
}); |
|||
|
|||
if (!flag) { |
|||
arr.push(task); |
|||
} |
|||
}); |
|||
|
|||
state.tasks = [...arr]; |
|||
// state.tasks = [...state.tasks.concat(data)];
|
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 添加任务后更新tasks |
|||
* @param {Object} state |
|||
* @param {Array} data 新添加的task |
|||
*/ |
|||
updateTasks(state, data) { |
|||
state.tasks = [...data]; |
|||
}, |
|||
|
|||
/** |
|||
* 设置添加任务的位置 |
|||
* @param {*} state |
|||
* @param {*} data |
|||
*/ |
|||
setAddPosition(state, data) { |
|||
console.log('data: ', data); |
|||
}, |
|||
|
|||
/** |
|||
* 设置日常任务数据 |
|||
* @param {Object} state |
|||
* @param {Array} data 服务端返回的模板数组 |
|||
*/ |
|||
setDailyTasks(state, data) { |
|||
state.dailyTasks = data || []; |
|||
}, |
|||
|
|||
/** |
|||
* 设置永久固定任务 |
|||
* @param {object} state |
|||
* @param {array} tasks 服务端查询到的永久日常任务书籍 |
|||
*/ |
|||
setPermanents(state, tasks) { |
|||
state.permanents = tasks || []; |
|||
sessionStorage.setItem('permanents', JSON.stringify(tasks)); |
|||
}, |
|||
|
|||
/** |
|||
* 定期任务 |
|||
* @param {object} state |
|||
* @param {array} tasks 服务端查询到的定期任务书籍 |
|||
*/ |
|||
setRegularTasks(state, tasks) { |
|||
state.regularTasks = tasks || []; |
|||
sessionStorage.setItem('regularTasks', JSON.stringify(tasks)); |
|||
}, |
|||
|
|||
/** |
|||
* 设置时间轴是否继续向上查任务 |
|||
* @param {Object} state |
|||
* @param {Boolean} show |
|||
*/ |
|||
setTopEnd(state, show) { |
|||
state.topEnd = show; |
|||
}, |
|||
|
|||
/** |
|||
* 设置时间轴是否继续向下查任务 |
|||
* @param {Object} state |
|||
* @param {Boolean} show |
|||
*/ |
|||
setBottomEnd(state, show) { |
|||
state.bottomEnd = show; |
|||
}, |
|||
|
|||
// 清空标志位 如切换角色等使用
|
|||
clearEndFlag(state) { |
|||
state.topEnd = false; |
|||
state.bottomEnd = false; |
|||
}, |
|||
|
|||
// 清空定期任务
|
|||
clearTasks(state) { |
|||
state.tasks = []; |
|||
}, |
|||
|
|||
/** |
|||
* 收到消息设置任务状态 |
|||
* @param {Object} state |
|||
* @param {Array} data 服务端返回的模板数组 |
|||
*/ |
|||
setTaskStatus(state, data) { |
|||
const item = state.tasks.find(i => i.id === data.id); |
|||
item.process = data.taskStatus; |
|||
}, |
|||
|
|||
/** |
|||
* 收到打开新项目消息状态 |
|||
* @param {Object} state |
|||
* @param {Array} data 服务端返回的模板数组 |
|||
*/ |
|||
setNewProjectInfo(state, data) { |
|||
state.newProjectInfo = data; |
|||
}, |
|||
|
|||
/** |
|||
* 设置骨架屏是否显示 |
|||
* @param {Object} state |
|||
* @param {Boolean} show |
|||
*/ |
|||
setShowSkeleton(state, show) { |
|||
state.showSkeleton = show; |
|||
}, |
|||
|
|||
/** |
|||
* 是否设置时间轴自动滚动的位置 |
|||
* @param {Object} state |
|||
* @param {Boolean} show |
|||
*/ |
|||
setShowScrollTo(state, show) { |
|||
state.showScrollTo = show; |
|||
}, |
|||
|
|||
/** |
|||
* 设置当前点击的任务信息 |
|||
*/ |
|||
setTaskDetail(state, data) { |
|||
state.taskDetail = data; |
|||
if (data) { |
|||
sessionStorage.setItem('taskId', data.id); |
|||
sessionStorage.setItem('taskDetail', JSON.stringify(data)); |
|||
} else { |
|||
sessionStorage.removeItem('taskId'); |
|||
sessionStorage.removeItem('taskDetail'); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 设置当前任务的code |
|||
*/ |
|||
setLabel(state, data) { |
|||
state.label = data; |
|||
if (data) { |
|||
sessionStorage.setItem('label', data); |
|||
} else { |
|||
sessionStorage.removeItem('label'); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 设置成员列表 |
|||
*/ |
|||
setMembers(state, data) { |
|||
state.members = data || []; |
|||
}, |
|||
|
|||
/** |
|||
* 设置日常任务面板高度 |
|||
*/ |
|||
setGlobalHeight(state, data) { |
|||
state.globalHeight = data; |
|||
sessionStorage.setItem('globalHeight', data); |
|||
}, |
|||
|
|||
/** |
|||
* 子课题Id |
|||
*/ |
|||
sonDetailId(state, data) { |
|||
state.detailId = data; |
|||
}, |
|||
|
|||
/** |
|||
* 设置当前子课题信息 |
|||
*/ |
|||
setSubProjectInfo(state, data) { |
|||
state.subProjectInfo = data; |
|||
}, |
|||
|
|||
/** |
|||
* 实验Id |
|||
*/ |
|||
sonExperimentationId(state, data) { |
|||
state.experimentationId = data; |
|||
}, |
|||
|
|||
/** |
|||
* 设置当前实验信息 |
|||
*/ |
|||
setExperimentationInfo(state, data) { |
|||
state.experimentationInfo = data; |
|||
}, |
|||
|
|||
/** |
|||
* 知识产权ID |
|||
*/ |
|||
setIntellectualId(state, data) { |
|||
state.intellectualId = data; |
|||
}, |
|||
|
|||
/** |
|||
* 会议ID |
|||
*/ |
|||
setMeetId(state, data) { |
|||
state.meetId = data; |
|||
}, |
|||
|
|||
/** |
|||
* 子课题会议ID |
|||
*/ |
|||
setSubMeetId(state, data) { |
|||
state.subMeetId = data; |
|||
}, |
|||
|
|||
setQuestion(state, data) { |
|||
state.question = data; |
|||
}, |
|||
}, |
|||
|
|||
actions: { |
|||
/** |
|||
* 根据code查询所有试题 |
|||
* @param {string} param.code |
|||
* @param {integer} param.projectId |
|||
* @param {integer} param.intellectualId |
|||
*/ |
|||
async getByCode({ commit }, param) { |
|||
try { |
|||
const data = await getByCode(param); |
|||
commit('setQuestion', data || null); |
|||
return data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 答案 |
|||
* @param {string} param.code |
|||
* @param {integer} param.projectId |
|||
* @param {array} param.questionAndAnswerList |
|||
*/ |
|||
async submitAnswer({ commit }, param) { |
|||
try { |
|||
const data = await submitAnswer(param); |
|||
commit('setQuestion', data || null); |
|||
message.info('保存成功'); |
|||
return data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 查询知识产权题目(论文、专利、软著) |
|||
* @param {string} param.code |
|||
* @param {integer} param.projectId |
|||
* @param {integer} param.intellectualId |
|||
*/ |
|||
async getIntellectual({ commit }, param) { |
|||
try { |
|||
const data = await getIntellectual(param); |
|||
commit('setQuestion', data || null); |
|||
return data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 查询知识产权列表 (论文、专利、软著) |
|||
* @param {string} param.code |
|||
* @param {integer} param.projectId |
|||
* @param {integer} param.intellectualId |
|||
*/ |
|||
async getIntellectualList({ commit }, param) { |
|||
try { |
|||
const data = await getIntellectualList(param); |
|||
commit('setQuestion', data || null); |
|||
return data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 提交知识产权信息(论文、专利、软著) |
|||
* @param {string} param.code |
|||
* @param {integer} param.projectId |
|||
* @param {integer} param.intellectualId |
|||
* @param {array} param.questionAndAnswerList |
|||
*/ |
|||
async submitIntellectual({ commit }, param) { |
|||
try { |
|||
const data = await submitIntellectual(param); |
|||
commit('setQuestion', data || null); |
|||
message.info('保存成功'); |
|||
return data; |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
}, |
|||
}, |
|||
}; |
@ -1,3 +1,306 @@ |
|||
<template>测试</template> |
|||
<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'" /> |
|||
|
|||
<script setup></script> |
|||
<!-- 科研会议管理 --> |
|||
<MeetingManagement v-if="label === 'KT_KYHY'" /> |
|||
|
|||
<!-- 合同管理 --> |
|||
<ContractManagement v-if="label === 'KT_HTGL'" /> |
|||
|
|||
<!-- 数据追溯解锁 --> |
|||
<DataUnlock v-if="label === 'KT_SJZSJS'" /> |
|||
|
|||
<!-- 成员管理 --> |
|||
<MemberManagement v-if="label === 'KT_CYGL' || label === 'ZKT_CYGL'" /> |
|||
|
|||
<!-- 计划任务书 --> |
|||
<PlanAssignment v-if="label === 'KT_JHRWS'" /> |
|||
|
|||
<!-- 分配课题 --> |
|||
<AssignmentSubject v-if="label === 'KT_FPKT'" /> |
|||
|
|||
<!-- 提交中期检查报告 --> |
|||
<InterimInspection v-if="label === 'KT_ZQJC'" /> |
|||
|
|||
<!-- 项目结题报告 --> |
|||
<Conclusion v-if="label === 'KT_JTBG'" /> |
|||
|
|||
<!-- 项目验收报告 --> |
|||
<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> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed, watch, ref } 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'; // 任务详情页列表 |
|||
|
|||
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); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.task-detail { |
|||
width: 100%; |
|||
height: 100%; |
|||
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; |
|||
} |
|||
</style> |
|||
|
@ -1,12 +1,115 @@ |
|||
<template> |
|||
<div> |
|||
<div>左边 时间轴</div> |
|||
<!-- <div class="task-detail"> |
|||
<div class="task-con">任务详情页</div> |
|||
<div> |
|||
<router-view></router-view> |
|||
</div> |
|||
</div> |
|||
</div> --> |
|||
|
|||
<a-config-provider :locale="locale"> |
|||
<a-layout> |
|||
<a-layout-header style="background: #fff"> <TopNavbar /> </a-layout-header> |
|||
<a-layout> |
|||
<!-- 日历页--> |
|||
<a-layout-sider v-show="collapsed" style="background: #fff"><Left /></a-layout-sider> |
|||
|
|||
<a-layout> |
|||
<Intro v-if="!projectId" /> |
|||
|
|||
<a-layout-sider v-if="projectId" class="project-detail"><Center /></a-layout-sider> |
|||
|
|||
<a-layout v-if="taskId"> |
|||
<!-- 导航栏--> |
|||
<a-layout-header style="background: #fff"> |
|||
<Navbar /> |
|||
</a-layout-header> |
|||
<!-- 内容区--> |
|||
<a-layout-content><router-view></router-view></a-layout-content> |
|||
<!-- 脚部--> |
|||
<!-- <a-layout-footer>Footer</a-layout-footer>--> |
|||
</a-layout> |
|||
</a-layout> |
|||
</a-layout> |
|||
</a-layout> |
|||
</a-config-provider> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
<script setup> |
|||
import { computed, watch, ref } from 'vue'; |
|||
import { useStore } from 'vuex'; |
|||
// import { useRoute, useRouter } from 'vue-router'; |
|||
import zhCN from 'ant-design-vue/es/locale/zh_CN'; |
|||
import Left from 'components/tall/left/Index.vue'; |
|||
import Navbar from 'components/tall/top/Navbar.vue'; |
|||
import TopNavbar from 'components/tall/top/TopNavbar.vue'; |
|||
import Center from 'components/tall/center/Index.vue'; |
|||
import Intro from 'components/tall/right/Intro.vue'; |
|||
|
|||
const locale = zhCN; |
|||
const store = useStore(); |
|||
const collapsed = computed(() => store.state.layout.display.left); // 是否显示左栏 |
|||
|
|||
const projectId = ref(null); |
|||
const project = computed(() => store.state.projects.project); // 项目信息 |
|||
const sessionProjectId = sessionStorage.getItem('projectId'); |
|||
projectId.value = sessionProjectId; |
|||
|
|||
const taskId = ref(null); |
|||
const sessionTaskId = sessionStorage.getItem('taskId'); |
|||
taskId.value = sessionTaskId; |
|||
const taskDetail = computed(() => store.state.task.taskDetail); // 任务信息 |
|||
|
|||
// 监听项目信息 |
|||
watch(project, () => { |
|||
if (project.value) { |
|||
projectId.value = project.value.id; |
|||
} else { |
|||
projectId.value = null; |
|||
} |
|||
}); |
|||
|
|||
// 监听任务信息 |
|||
watch(taskDetail, () => { |
|||
if (taskDetail.value) { |
|||
taskId.value = taskDetail.value.id; |
|||
} else { |
|||
taskId.value = null; |
|||
} |
|||
}); |
|||
|
|||
// 验证 获取query中u参数 获取token |
|||
// const route = useRoute(); |
|||
// const router = useRouter(); |
|||
// const userString = sessionStorage.getItem('user'); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.ant-layout { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.ant-layout-header { |
|||
height: 48px; |
|||
line-height: 48px; |
|||
padding: 0 16px; |
|||
border-bottom: 1px solid #cccccc; |
|||
} |
|||
|
|||
.ant-layout-sider { |
|||
width: 300px !important; |
|||
max-width: 300px !important; |
|||
min-width: 300px !important; |
|||
border-right: 1px solid #cccccc; |
|||
flex: 0 0 300px !important; |
|||
} |
|||
|
|||
<style scoped></style> |
|||
.project-detail { |
|||
width: 400px !important; |
|||
max-width: 400px !important; |
|||
min-width: 400px !important; |
|||
border-right: 1px solid #cccccc; |
|||
flex: 0 0 400px !important; |
|||
background: #fff; |
|||
} |
|||
</style> |
|||
|
@ -1,3 +1,136 @@ |
|||
<template>登录</template> |
|||
<template> |
|||
<div class="login-box relative flex justify-between" style="width: 100%; height: 100%"> |
|||
<div class="login-wrap absolute right-0"> |
|||
<div class="login-form relative"> |
|||
<h1>科研实验管理云平台系统</h1> |
|||
<!-- 用户名密码输入 --> |
|||
<a-form ref="signInFormRef" :model="signInForm" :rules="rules"> |
|||
<a-form-item name="username"> |
|||
<label style="font-size: 16px"><span style="color: #ff5353; margin-right: 5px">*</span>用户名:</label> |
|||
<a-input v-model:value="signInForm.username" placeholder="请输入用户名" /> |
|||
</a-form-item> |
|||
<a-form-item name="password"> |
|||
<label style="font-size: 16px"><span style="color: #ff5353; margin-right: 5px">*</span>密码:</label> |
|||
<a-input v-model:value="signInForm.password" type="password" placeholder="请输入密码" /> |
|||
</a-form-item> |
|||
<a-form-item> |
|||
<a-button type="primary" html-type="submit" @click="handleSingIn">登录</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
<script setup> |
|||
import { reactive, ref } from 'vue'; |
|||
import { signIn } from 'apis'; |
|||
import { useStore } from 'vuex'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
const store = useStore(); |
|||
const router = useRouter(); |
|||
const signInFormRef = ref(null); |
|||
const signInForm = reactive({ |
|||
username: '', |
|||
password: '', |
|||
}); |
|||
|
|||
const rules = reactive({ |
|||
username: [ |
|||
{ |
|||
required: true, |
|||
message: '请输入账号', |
|||
trigger: 'blur', |
|||
}, |
|||
], |
|||
password: [ |
|||
{ |
|||
required: true, |
|||
message: '请输入密码', |
|||
trigger: 'blur', |
|||
}, |
|||
], |
|||
}); |
|||
|
|||
// 提交 |
|||
function handleSingIn() { |
|||
signInFormRef.value |
|||
.validate() |
|||
.then(async () => { |
|||
if (signInForm.username && signInForm.password) { |
|||
try { |
|||
const params = { |
|||
client: 1, |
|||
data: { |
|||
identifier: signInForm.username, |
|||
credential: signInForm.password, |
|||
}, |
|||
type: 3, |
|||
}; |
|||
const resData = await signIn(params); |
|||
store.commit('user/setUser', resData); |
|||
// ElMessage.success('登录成功, 欢迎回来'); |
|||
console.log('登录成功, 欢迎回来'); |
|||
setTimeout(() => { |
|||
// 跳转到首页 |
|||
router.push({ path: '/experiment/home' }); |
|||
}, 1000); |
|||
} catch (error) { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
} |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
message.info(error); |
|||
throw new Error(error); |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.login-box { |
|||
width: 100%; |
|||
height: 100%; |
|||
background: url('https://www.tall.wiki/staticrec/icon/bg.png') no-repeat; |
|||
background-size: cover; |
|||
} |
|||
|
|||
.login-wrap { |
|||
background: #e9f6ff; |
|||
width: 446px; |
|||
height: 100%; |
|||
-moz-box-shadow: -5px 0px 25px 0px rgba(24, 144, 255, 0.12); |
|||
-webkit-box-shadow: -5px 0px 25px 0px rgba(24, 144, 255, 0.12); |
|||
box-shadow: -5px 0px 25px 0px rgba(24, 144, 255, 0.12); |
|||
} |
|||
|
|||
.login-form { |
|||
width: 375px; |
|||
margin: 0 auto; |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
} |
|||
|
|||
h1 { |
|||
margin-bottom: 40px; |
|||
font-size: 28px; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.ant-input { |
|||
height: 45px; |
|||
margin-top: 15px; |
|||
border-radius: 6px; |
|||
} |
|||
|
|||
.ant-btn { |
|||
margin-top: 32px; |
|||
width: 100%; |
|||
height: 45px; |
|||
border-radius: 6px; |
|||
font-size: 18px; |
|||
} |
|||
</style> |
|||
|
File diff suppressed because it is too large
Loading…
Reference in new issue