Compare commits

...

37 Commits

Author SHA1 Message Date
xuesinan e376206137 feat: 拖拽改变层级关系 4 years ago
xuesinan e251753eac feat: 导入、导出、删除 4 years ago
xuesinan 3fd7b44661 feat: pC端插件 4 years ago
xuesinan 24cfd362e9 fix: 流水账表格分页数据 4 years ago
xuesinan 3e7778b0a7 fix: 栈溢出问题解决 4 years ago
xuesinan 090668626d feat: 资源管理、个人和终端按钮、域资源管理、项目版本管理 4 years ago
xuesinan 22fb409623 feat: 交付物2 4 years ago
xuesinan 7ceafb9a2e feat: 流水账样式 4 years ago
xuesinan 517f26c59b feat: 删除打印数据 4 years ago
xuesinan 021b53c8c2 feat: 流水账样式 4 years ago
xuesinan b281baa9ba feat: 筛选框样式 4 years ago
xuesinan 05dfeda0a9 fix: 流水账重置 4 years ago
xuesinan 5ce927be9e feat: 交付物2 提交、审核人 4 years ago
xuesinan 4755d51838 feat: 交付物历史记录、审核记录 4 years ago
xuesinan c7680a2237 feat: 交付物插件 4 years ago
xuesinan ffb83e3521 feat: 网站名称 4 years ago
xuesinan d10f550d1d fix: 成员数据为空 4 years ago
xuesinan 46b04f6059 feat: 打卡、导出 4 years ago
xuesinan 48557a4d83 feat: 角色样式 4 years ago
xuesinan 821f211b02 style: 删除打印数据 4 years ago
xuesinan c0e48fd52c fix: 流水账修改 4 years ago
xuesinan 1e22e5dd29 feat: 路径配置 4 years ago
xuesinan 8e0a2db881 feat: 流水账插件全屏 4 years ago
xuesinan d6becd2631 feat: 流水账插件 4 years ago
xuesinan c4720b7fa0 feat: 流水账详情页分页、打卡 4 years ago
xuesinan 980ab9ab0d feat: 交付物提交和检查 4 years ago
xuesinan 882b490f2f feat: 流水账表格表单 4 years ago
xuesinan 831c44871b feat: 流水账插件 4 years ago
xuesinan d1cccce9ec feat: 打卡插件样式 4 years ago
xuesinan c88644c7e7 feat: 打卡插件和详情页 4 years ago
xuesinan 64b22a38d6 feat: 打卡插件 4 years ago
xuesinan 9d6fb5329a feat: 打卡插件 4 years ago
xuesinan a1d29ba33a feat: 切换项目角色改变任务 4 years ago
xuesinan 352056f3ba feat: 时间轴定期任务 4 years ago
xuesinan 142eb8a24d feat: 时间轴数据处理 4 years ago
xuesinan 5444512d68 feat: 时间轴刻度 4 years ago
xuesinan 966361de11 feat: tall PC端项目列表、项目详情、角色列表 4 years ago
  1. 219
      .drone.yml
  2. 6
      .env.development
  3. 6
      .env.production
  4. 6
      .env.test
  5. 5
      .eslintrc.js
  6. 2
      index.html
  7. 77
      package-lock.json
  8. 5
      package.json
  9. 181
      src/App.vue
  10. 190
      src/apis/index.js
  11. BIN
      src/assets/exarresources.png
  12. 0
      src/assets/index.css
  13. BIN
      src/assets/personal.png
  14. BIN
      src/assets/projectVersion.png
  15. BIN
      src/assets/uidispose.png
  16. BIN
      src/assets/work.jpg
  17. 140
      src/components/tall/Render/Render.vue
  18. 61
      src/components/tall/Reviewer/Reviewer.vue
  19. 117
      src/components/tall/Reviewer/ReviewerSecond.vue
  20. 116
      src/components/tall/center/Global.vue
  21. 354
      src/components/tall/center/Index.vue
  22. 377
      src/components/tall/center/RegularTask.vue
  23. 80
      src/components/tall/center/Roles.vue
  24. 59
      src/components/tall/center/nested.vue
  25. 123
      src/components/tall/left/Index.vue
  26. 508
      src/components/tall/left/Projects copy.vue
  27. 509
      src/components/tall/left/Projects.vue
  28. 74
      src/components/tall/plugin/Plugin.vue
  29. 11
      src/components/tall/right/DetailWebview.vue
  30. 210
      src/components/tall/task/AssignmentExperiment.vue
  31. 453
      src/components/tall/task/AssignmentSubject.vue
  32. 192
      src/components/tall/task/CheckSubjectProgress.vue
  33. 246
      src/components/tall/task/Conclusion.vue
  34. 235
      src/components/tall/task/ContractManagement.vue
  35. 172
      src/components/tall/task/DataUnlock.vue
  36. 287
      src/components/tall/task/ExperimentalCode.vue
  37. 287
      src/components/tall/task/ExperimentalData.vue
  38. 277
      src/components/tall/task/ExperimentalResult.vue
  39. 246
      src/components/tall/task/InterimInspection.vue
  40. 274
      src/components/tall/task/LabReport.vue
  41. 292
      src/components/tall/task/MeetingManagement.vue
  42. 258
      src/components/tall/task/MemberManagement.vue
  43. 389
      src/components/tall/task/PlanAssignment.vue
  44. 271
      src/components/tall/task/Procedure.vue
  45. 308
      src/components/tall/task/PublishPatent.vue
  46. 251
      src/components/tall/task/PublishThesis.vue
  47. 251
      src/components/tall/task/PublishWork.vue
  48. 236
      src/components/tall/task/Result.vue
  49. 284
      src/components/tall/task/ScientificPayoffs.vue
  50. 246
      src/components/tall/task/SubConclusion.vue
  51. 246
      src/components/tall/task/SubInterimInspection.vue
  52. 244
      src/components/tall/task/SubMeetingManagement.vue
  53. 246
      src/components/tall/task/SubResult.vue
  54. 195
      src/components/tall/task/SubSubjectProgress.vue
  55. 144
      src/components/tall/task/TaskConList.vue
  56. 3
      src/components/tall/top/Navbar.vue
  57. 17
      src/components/tall/top/TopNavbar.vue
  58. 4
      src/main.js
  59. 7
      src/plugins/p-account-management/p-account-management-audit.vue
  60. 7
      src/plugins/p-account-management/p-account-management-uidispose.vue
  61. 26
      src/plugins/p-account-management/p-account-management.vue
  62. 695
      src/plugins/p-daily-account/p-daily-account-detail.vue
  63. 17
      src/plugins/p-daily-account/p-daily-account.vue
  64. 362
      src/plugins/p-deliver-second/p-deliver-check-second.vue
  65. 290
      src/plugins/p-deliver-second/p-deliver-second-detail.vue
  66. 63
      src/plugins/p-deliver-second/p-deliver-second.vue
  67. 224
      src/plugins/p-deliver-second/p-deliver-upload-second.vue
  68. 130
      src/plugins/p-deliver/check-form-modal.vue
  69. 72
      src/plugins/p-deliver/p-audit-records.vue
  70. 130
      src/plugins/p-deliver/p-deliver-check.vue
  71. 106
      src/plugins/p-deliver/p-deliver-history.vue
  72. 140
      src/plugins/p-deliver/p-deliver-upload.vue
  73. 43
      src/plugins/p-deliver/p-deliver.vue
  74. 8
      src/plugins/p-domain-source-manage/p-domain-source-manage-detail.vue
  75. 18
      src/plugins/p-domain-source-manage/p-domain-source-manage.vue
  76. 41
      src/plugins/p-finance/p-finance-audit.vue
  77. 95
      src/plugins/p-finance/p-finance.vue
  78. 8
      src/plugins/p-project-version-management/p-project-version-management-detail.vue
  79. 18
      src/plugins/p-project-version-management/p-project-version-management.vue
  80. 30
      src/plugins/p-source-manage/p-source-manage.vue
  81. 12
      src/plugins/p-task-title.vue
  82. 36
      src/plugins/p-task-to-detail/p-task-to-detail.vue
  83. 7
      src/plugins/workbench/workbench.vue
  84. 12
      src/routers/index.js
  85. 31
      src/store/tall/layout/actions.js
  86. 4
      src/store/tall/layout/index.js
  87. 44
      src/store/tall/layout/mutations.js
  88. 11
      src/store/tall/layout/state.js
  89. 9
      src/store/tall/projects/index.js
  90. 11
      src/store/tall/role/getters.js
  91. 12
      src/store/tall/role/mutations.js
  92. 1
      src/store/tall/role/state.js
  93. 291
      src/store/tall/task/index.js
  94. 35
      src/store/tall/user/index.js
  95. 49
      src/utils/axios.js
  96. 9
      src/utils/deliver.js
  97. 36
      src/utils/storage.js
  98. 65
      src/utils/task.js
  99. 17
      src/utils/time.js
  100. 311
      src/views/detail/Test.vue

219
.drone.yml

@ -1,219 +0,0 @@
---
kind: pipeline
type: docker
name: development
# 常量值
constants:
- &DEVELOPMENT_HOST test.tall.wiki
- &DEVELOPMENT_CMD
- npm config set registry http://registry.npm.taobao.org
- npm i
- npm run build
- &DEVELOPMENT_SCP_TARGET /home/experiment
- &DEVELOPMENT_URL https://test.tall.wiki/experiment/
- &DEVELOPMENT_PORT 22
- &DEVELOPMENT_NODE_VERSION node:16
- &DEVELOPMENT_BRANCH develop
- &DEVELOPMENT_SCP_SOURCE dist/*
- &SCP_STRIP_DIR_LEVEL 1
- &NOTIFY_WECHATROBOT_WEBHOOK https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=428e5c5d-f992-4349-939d-9c99556e50b8
# 挂载的主机卷,可以映射到docker容器中
volumes:
# maven构建缓存(宿主机目录)
- name: ssh_key
host:
path: /root/.ssh/
- name: cache
host:
path: /var/lib/cache
- name: data
host:
path: /var/lib/data
steps:
- name: restore-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
restore: true
mount:
- ./node_modules
- name: build
image: *DEVELOPMENT_NODE_VERSION
pull: if-not-exists # default always
# volumes:
# - name: cache
# path: /root/.m2
commands: *DEVELOPMENT_CMD
- name: rebuild-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
rebuild: true
mount:
- ./node_modules
- name: deploy-scp
image: appleboy/drone-scp
pull: if-not-exists
volumes:
- name: ssh_key
path: /root/.ssh/
settings:
host: *DEVELOPMENT_HOST
port: *DEVELOPMENT_PORT
username: root
key_path: /root/.ssh/id_rsa
rm: true # true则会删除目标目录重建
target: *DEVELOPMENT_SCP_TARGET
source: *DEVELOPMENT_SCP_SOURCE
strip_components: 1 # 去除的目录层数,如果没有该选项,则拷贝过去是 target/xxx.jar,1代表去除target
- name: notify-wechatwork
image: fifsky/drone-wechat-work
pull: if-not-exists
settings:
url: *NOTIFY_WECHATROBOT_WEBHOOK
msgtype: markdown
content: |
{{if eq .Status "success" }}
#### 🎉 ${DRONE_REPO} 测试环境构建成功
> Branch: ${DRONE_BRANCH}
> Commit: [${DRONE_COMMIT_MESSAGE} ](${DRONE_COMMIT_LINK})
> Author: ${DRONE_COMMIT_AUTHOR}
> PATH: https://test.tall.wiki/experiment/
> [点击查看](https://test.tall.wiki/experiment/)
{{else}}
#### ❌ ${DRONE_REPO} 测试环境构建失败
> Branch: ${DRONE_BRANCH}
> Commit: [${DRONE_COMMIT_MESSAGE} ](${DRONE_COMMIT_LINK})
> Author: ${DRONE_COMMIT_AUTHOR}
> 请立即修复!!!
> [点击查看](https://test.tall.wiki/experiment/)
{{end}}
when:
status:
- failure
- success
trigger:
branch:
- *DEVELOPMENT_BRANCH
---
kind: pipeline
type: docker
name: production
# 常量值
constants:
- &PRODUCTION_HOST www.tall.wiki
- &PRODUCTION_CMD
- npm config set registry http://registry.npm.taobao.org
- npm i
- npm run build:prod
- &PRODUCTION_SCP_TARGET /home/experiment
- &PRODUCTION_BRANCH master
- &PRODUCTION_PORT 22
- &PRODUCTION_NODE_VERSION node:16
- &PRODUCTION_SCP_SOURCE dist/*
- &SCP_STRIP_DIR_LEVEL 1
- &NOTIFY_WECHATROBOT_WEBHOOK https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=428e5c5d-f992-4349-939d-9c99556e50b8
# 挂载的主机卷,可以映射到docker容器中
volumes:
# maven构建缓存(宿主机目录)
- name: ssh_key
host:
path: /root/.ssh/
- name: cache
host:
path: /var/lib/cache
- name: data
host:
path: /var/lib/data
steps:
- name: restore-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
restore: true
mount:
- ./node_modules
- name: build
image: *PRODUCTION_NODE_VERSION
pull: if-not-exists # default always
# volumes:
# - name: cache
# path: /root/.m2
commands: *PRODUCTION_CMD
- name: rebuild-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
rebuild: true
mount:
- ./node_modules
- name: deploy-scp
image: appleboy/drone-scp
pull: if-not-exists
volumes:
- name: ssh_key
path: /root/.ssh/
settings:
host: *PRODUCTION_HOST
port: *PRODUCTION_PORT
username: root
key_path: /root/.ssh/id_rsa
rm: true # true则会删除目标目录重建
target: *PRODUCTION_SCP_TARGET
source: *PRODUCTION_SCP_SOURCE
strip_components: 1 # 去除的目录层数,如果没有该选项,则拷贝过去是 target/xxx.jar,1代表去除target
- name: notify-wechatwork
image: fifsky/drone-wechat-work
pull: if-not-exists
settings:
url: *NOTIFY_WECHATROBOT_WEBHOOK
msgtype: markdown
content: |
{{if eq .Status "success" }}
#### 🎉 ${DRONE_REPO} 生产环境构建成功
> Branch: ${DRONE_BRANCH}
> Commit: [${DRONE_COMMIT_MESSAGE}](${DRONE_COMMIT_LINK})
> Author: ${DRONE_COMMIT_AUTHOR}
> PATH: https://www.tall.wiki/experiment/
> [点击查看](https://www.tall.wiki/experiment/)
{{else}}
#### ❌ ${DRONE_REPO} 生产环境构建失败
> Branch: ${DRONE_BRANCH}
> Commit: [${DRONE_COMMIT_MESSAGE}](${DRONE_COMMIT_LINK})
> Author: ${DRONE_COMMIT_AUTHOR}
> 请立即修复!!!
> [点击查看](https://www.tall.wiki/experiment/)
{{end}}
when:
status:
- failure
- success
trigger:
branch:
- *PRODUCTION_BRANCH

6
.env.development

@ -1 +1,5 @@
VITE_API_URL=http://localhost:3000
VITE_BASE_URL=http://101.201.226.163
VITE_API_URL=http://101.201.226.163/gateway
VITE_MSG_URL=ws://101.201.226.163:8196/message/v4.0/ws
VITE_SERVICELIST=['ZERO', 'CONTEST', 'PT']
VITE_VERSION=v4.0.0

6
.env.production

@ -1 +1,5 @@
VITE_API_URL=https://www.tall.wiki
VITE_BASE_URL=http://101.201.226.163
VITE_API_URL=http://101.201.226.163/gateway
VITE_MSG_URL=ws://101.201.226.163:8196/message/v4.0/ws
VITE_SERVICELIST=['ZERO', 'CONTEST', 'PT']
VITE_VERSION=v4.0.0

6
.env.test

@ -1 +1,5 @@
VITE_API_URL=https://test.tall.wiki
VITE_BASE_URL=http://101.201.226.163
VITE_API_URL=http://101.201.226.163/gateway
VITE_MSG_URL=ws://101.201.226.163:8196/message/v4.0/ws
VITE_SERVICELIST=['ZERO', 'CONTEST', 'PT']
VITE_VERSION=v4.0.0

5
.eslintrc.js

@ -8,8 +8,13 @@ module.exports = {
ecmaVersion: 12,
sourceType: 'module',
},
parser: "vue-eslint-parser", // 添加这一句
plugins: ['vue'],
rules: {
"prefer-destructuring": ["error", {"object": true, "array": false}],
'no-nested-ternary': 'off',
'no-unused-vars': 'off',
'no-loop-func': 'off',
'import/no-unresolved': 0,
'import/extensions': 0,
'import/no-extraneous-dependencies': 0,

2
index.html

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>山大实验室管理平台</title>
<title>PT</title>
</head>
<body>
<div id="app"></div>

77
package-lock.json

@ -14,11 +14,14 @@
"dayjs": "^1.10.7",
"echarts": "^5.2.2",
"lodash": "^4.17.21",
"uuid": "^8.3.2",
"vite": "^2.6.4",
"vite-plugin-compression": "^0.3.5",
"vite-plugin-windicss": "^1.4.11",
"vue": "^3.2.16",
"vue-draggable-next": "^2.1.1",
"vue-router": "^4.0.12",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.2",
"windicss": "^3.1.9"
},
@ -33,7 +36,7 @@
"eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^7.19.1",
"eslint-plugin-vue": "^7.20.0",
"husky": "^7.0.2",
"lint-staged": "^11.2.3",
"prettier": "^2.4.1",
@ -4925,11 +4928,10 @@
}
},
"node_modules/eslint-plugin-vue": {
"version": "7.19.1",
"resolved": "https://registry.npmmirror.com/eslint-plugin-vue/download/eslint-plugin-vue-7.19.1.tgz",
"integrity": "sha1-Q1+yznEoQqlTCyjqy4g2gOjqpPM=",
"version": "7.20.0",
"resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-7.20.0.tgz",
"integrity": "sha512-oVNDqzBC9h3GO+NTgWeLMhhGigy6/bQaQbHS+0z7C4YEu/qK/yxHvca/2PTZtGNPsCrHwOTgKMrwu02A9iPBmw==",
"dev": true,
"license": "MIT",
"dependencies": {
"eslint-utils": "^2.1.0",
"natural-compare": "^1.4.0",
@ -4940,7 +4942,7 @@
"node": ">=8.10"
},
"peerDependencies": {
"eslint": "^6.2.0 || ^7.0.0 || ^8.0.0-0"
"eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/eslint-plugin-vue/node_modules/semver": {
@ -7829,6 +7831,11 @@
"node": ">=10"
}
},
"node_modules/sortablejs": {
"version": "1.14.0",
"resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz",
@ -8487,6 +8494,14 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.nlark.com/v8-compile-cache/download/v8-compile-cache-2.3.0.tgz",
@ -8811,6 +8826,15 @@
"@vue/shared": "3.2.20"
}
},
"node_modules/vue-draggable-next": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/vue-draggable-next/-/vue-draggable-next-2.1.1.tgz",
"integrity": "sha512-f5lmA7t6LMaL4viR7dU30zzvqJzaKQs0ymL0Jy9UDT9uiZ2tXF3MzPzEvpTH2UODXZJkT+SnjeV1fXHMsgXLYA==",
"peerDependencies": {
"sortablejs": "^1.14.0",
"vue": "^3.2.2"
}
},
"node_modules/vue-eslint-parser": {
"version": "7.11.0",
"resolved": "https://registry.npmmirror.com/vue-eslint-parser/download/vue-eslint-parser-7.11.0.tgz?cache=0&sync_timestamp=1634602895439&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fvue-eslint-parser%2Fdownload%2Fvue-eslint-parser-7.11.0.tgz",
@ -8890,6 +8914,17 @@
"vue": "^3.0.0"
}
},
"node_modules/vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"dependencies": {
"sortablejs": "1.14.0"
},
"peerDependencies": {
"vue": "^3.0.1"
}
},
"node_modules/vuex": {
"version": "4.0.2",
"resolved": "https://registry.nlark.com/vuex/download/vuex-4.0.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fvuex%2Fdownload%2Fvuex-4.0.2.tgz",
@ -12922,9 +12957,9 @@
}
},
"eslint-plugin-vue": {
"version": "7.19.1",
"resolved": "https://registry.npmmirror.com/eslint-plugin-vue/download/eslint-plugin-vue-7.19.1.tgz",
"integrity": "sha1-Q1+yznEoQqlTCyjqy4g2gOjqpPM=",
"version": "7.20.0",
"resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-7.20.0.tgz",
"integrity": "sha512-oVNDqzBC9h3GO+NTgWeLMhhGigy6/bQaQbHS+0z7C4YEu/qK/yxHvca/2PTZtGNPsCrHwOTgKMrwu02A9iPBmw==",
"dev": true,
"requires": {
"eslint-utils": "^2.1.0",
@ -15188,6 +15223,11 @@
"is-fullwidth-code-point": "^3.0.0"
}
},
"sortablejs": {
"version": "1.14.0",
"resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz",
@ -15694,6 +15734,11 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.nlark.com/v8-compile-cache/download/v8-compile-cache-2.3.0.tgz",
@ -15933,6 +15978,12 @@
"@vue/shared": "3.2.20"
}
},
"vue-draggable-next": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/vue-draggable-next/-/vue-draggable-next-2.1.1.tgz",
"integrity": "sha512-f5lmA7t6LMaL4viR7dU30zzvqJzaKQs0ymL0Jy9UDT9uiZ2tXF3MzPzEvpTH2UODXZJkT+SnjeV1fXHMsgXLYA==",
"requires": {}
},
"vue-eslint-parser": {
"version": "7.11.0",
"resolved": "https://registry.npmmirror.com/vue-eslint-parser/download/vue-eslint-parser-7.11.0.tgz?cache=0&sync_timestamp=1634602895439&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fvue-eslint-parser%2Fdownload%2Fvue-eslint-parser-7.11.0.tgz",
@ -15989,6 +16040,14 @@
"is-plain-object": "3.0.1"
}
},
"vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"requires": {
"sortablejs": "1.14.0"
}
},
"vuex": {
"version": "4.0.2",
"resolved": "https://registry.nlark.com/vuex/download/vuex-4.0.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fvuex%2Fdownload%2Fvuex-4.0.2.tgz",

5
package.json

@ -20,11 +20,14 @@
"dayjs": "^1.10.7",
"echarts": "^5.2.2",
"lodash": "^4.17.21",
"uuid": "^8.3.2",
"vite": "^2.6.4",
"vite-plugin-compression": "^0.3.5",
"vite-plugin-windicss": "^1.4.11",
"vue": "^3.2.16",
"vue-draggable-next": "^2.1.1",
"vue-router": "^4.0.12",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.2",
"windicss": "^3.1.9"
},
@ -39,7 +42,7 @@
"eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^7.19.1",
"eslint-plugin-vue": "^7.20.0",
"husky": "^7.0.2",
"lint-staged": "^11.2.3",
"prettier": "^2.4.1",

181
src/App.vue

@ -2,74 +2,82 @@
<div style="width: 100%; height: 100%">
<router-view></router-view>
</div>
<!-- <a-config-provider v-else :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> -->
<!-- <a-layout-sider class="project-detail"><ProjectDetail /></a-layout-sider> -->
<!-- <a-layout> -->
<!-- 导航栏-->
<!-- <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>
// import { computed } from 'vue';
import { useStore } from 'vuex';
import { 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 ProjectDetail from 'components/tall/center/ProjectDetail.vue';
// const locale = zhCN;
import { computed, watch } from 'vue';
import { v4 as uuidv4 } from 'uuid';
const store = useStore();
// const collapsed = computed(() => store.state.layout.display.left); //
const projectInfo = computed(() => store.state.projects.project); //
const sessionProject = sessionStorage.getItem('project'); //
// queryu token
// const route = useRoute();
const router = useRouter();
const userString = sessionStorage.getItem('user');
const sessionToken = sessionStorage.getItem('token');
if (sessionProject && !projectInfo.value.id) {
const info = JSON.parse(sessionProject);
store.commit('projects/setProject', info);
store.commit('task/setBusinessCode', info && info.businessCode ? info.businessCode : '');
}
if (projectInfo.value.id) {
getMemberList();
}
watch(projectInfo, () => {
if (projectInfo.value.id) {
getMemberList();
}
});
//
store.commit('layout/setDeviceId', uuidv4().split('-')[0]);
if (userString) {
const user = JSON.parse(userString);
store.commit('user/setUser', user);
} else {
router.push({ path: '/experiment/user/signIn' });
}
// useRouter()
// .isReady()
// .then(async () => {
// const u = computed(() => route.query.u);
// if (!u.value) {
// // urlu,
// console.log('');
// router.push({ path: '/experiment/user/signIn' });
// } else {
// // userId token
// await store.dispatch('user/getTokenByUserId', u.value);
// }
// });
// const token = computed(() => store.getters['user/token']);
}
if (sessionToken) {
store.commit('user/setToken', sessionToken);
}
//
if (!sessionStorage.getItem('businessPlugin')) {
getServices();
getPlugins();
}
setInterval(() => {
getServices();
getPlugins();
}, 60000);
//
function getMemberList() {
const projectId = projectInfo.value.id;
const { url } = projectInfo.value;
const param = { projectId };
store.dispatch('role/getAllMembers', { param, url });
}
/**
* 查询服务
*/
async function getServices() {
store.dispatch('layout/getBusinessPlugin');
}
/**
* 查询插件
*/
async function getPlugins() {
store.dispatch('layout/getAllPlugin');
}
</script>
<style>
@ -78,6 +86,7 @@ body,
#app,
#app > section {
height: 100%;
font-size: 14px;
}
.ant-layout-header {
@ -124,10 +133,6 @@ body,
color: #cccccc !important;
}
.border-radius-10 {
border-radius: 10px;
}
.ant-form label {
display: block;
margin-bottom: 5px;
@ -141,6 +146,7 @@ body,
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
color: rgba(0, 0, 0, 0.85);
z-index: 9999;
}
.ant-message .ant-message-notice {
@ -163,4 +169,59 @@ body,
color: #1890ff;
font-size: 16px;
}
.border {
border-width: 1px;
}
.border-radius-10 {
border-radius: 10px;
}
.flex-1 {
flex: 1 1 0%;
}
.px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.py-1 {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}
.bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgba(59, 130, 246, var(--tw-bg-opacity));
}
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.text-white {
--tw-text-opacity: 1;
color: rgba(255, 255, 255, var(--tw-text-opacity));
}
.rounded-md {
border-radius: 0.375rem;
}
/* 任务面板 */
.task-card-plugin {
padding: 12px;
border-radius: 10px;
-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);
}
</style>

190
src/apis/index.js

@ -2,141 +2,145 @@
import http from 'utils/axios';
const apiUrl = import.meta.env.VITE_API_URL;
const users = `${apiUrl}/gateway/tall3/v3.0/users`;
const tall = `${apiUrl}/gateway/tall3/v3.0`;
const experiment = `${apiUrl}/gateway/experiment`;
// const apiUrl = import.meta.env.VITE_API_URL;
// const users = `${apiUrl}/gateway/tall3/v3.0/users`;
// const tall = `${apiUrl}/gateway/tall3/v3.0`;
// const filedeal = `${apiUrl}/filedeal`; // 测试
// const filedeal = `http://101.201.226.21:7180`; // 生产
const baseUrl = import.meta.env.VITE_BASE_URL;
const apiUrl = import.meta.env.VITE_API_URL;
const tall = `${apiUrl}/ptostall`;
const experiment = `${apiUrl}/experiment`;
// 根据userId 获取token
// eslint-disable-next-line import/prefer-default-export
export const getToken = userId => http.get(`${users}/userId`, { params: { userId } });
export const getToken = userId => http.get(`${tall}/users/userId`, { userId });
// 根据refreshToken重新获取token
export const getNewToken = refreshToken => http.get(`${tall}/users/refreshToken?refreshToken=${refreshToken}`);
// 登录api
export const signIn = params => http.post(`${users}/signin`, params);
export const signIn = params => http.post(`${tall}/users/signin`, params);
// 获取图片验证码
export const getImageCode = () => http.get(`${tall}/users/code`);
// 获取短信验证码
export const getSmsCode = params => http.get(`${tall}/users/smscode`, { params });
// 查询服务
export const getBusinessPlugin = params => http.post(`${tall}/business/query/businessPlugin`, { params });
// 查询本域所有插件
export const getAllPlugin = params => http.post(`${tall}/business/query/plugin`, { params });
// 获取插件信息
export const getOtherPlugin = params => http.post(`${apiUrl}/opt/business/businessPluginById`, { params });
// 根据插件id获取服务config
export const getConfigInfo = params => http.post(`${apiUrl}/ptostall/business/byBusinessPluginId`, { params });
/**
* 项目相关
*/
// 获取项目列表
export const getProjects = (startTime, endTime) => http.post(`${tall}/project/query`, { param: { startTime, endTime } });
// 项目排序
export const setProjectSort = params => http.post(`${tall}/project/drag`, params);
// 服务列表
export const getBusinessList = params => http.post(`${tall}/business/import/query`, { params });
// 根据id获取项目信息
export const findProjectById = projectId => http.post(`${experiment}/project/findProjectById`, { param: { projectId } });
export const findProjectById = (projectId, url) => http.post(`${url}/tall/project/findProjectById`, { param: { projectId } });
// 删除项目
export const delProject = projectId => http.post(`${tall}/project/deleteProject`, { param: { projectId } });
export const delProject = (projectId, url) => http.post(`${url}/tall/project/delete`, { param: { projectId } });
// 根据项目id查找角色
export const findShowRole = projectId => http.post(`${experiment}/role/show`, { param: { projectId } });
export const findShowRole = (projectId, url) => http.post(`${url}/tall/role/show`, { param: { projectId } });
// 根据项目id查找所有成员
export const queryChecker = param => http.post(`${tall}/deliver/queryChecker`, param);
export const queryChecker = param => http.post(`${param.url}/deliver/queryChecker`, param);
// 查找带时间的日常任务
export const getGlobal = params => http.post(`${experiment}/task/global`, params);
export const getGlobal = (params, url) => http.post(`${url}/tall/task/global`, params);
// 查找永久日常任务
export const getPermanent = params => http.post(`${experiment}/task/permanent`, params);
export const getPermanent = (params, url) => http.post(`${url}/tall/task/permanent`, params);
// 查找定期任务
export const getRegularTask = params => http.post(`${experiment}/task/regular`, params);
// 添加任务
export const saveTask = params => http.post(`${experiment}/task/save`, params);
// 查询子任务
export const findSonTask = params => http.post(`${experiment}/task/findSonTask`, params);
export const getRegularTask = (params, url) => http.post(`${url}/tall/task/regular/page`, params);
/**
* 导入wbs
* @param {object} e
* 交付物
*/
export const importWbs = async e => {
const file = e.target.files[0];
const param = new FormData();
param.append('file', file);
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
const result = await http.post(`${experiment}/wbs`, param, config);
return result;
};
// 新建课题 -- 根据模板新建课题
export const create = param => http.post(`${experiment}/experiment/create`, { params: { param } });
// 上传文件
// export const uploadImg = `${filedeal}/file/upload/multiple`;
export const uploadImg = `${experiment}/import/upload`;
// 添加/编辑计划任务书
export const savePlanTask = params => http.post(`${experiment}/experiment/savePlanTask`, params);
// 查看任务计划书
export const getPlanTask = params => http.post(`${experiment}/experiment/getPlanTask`, params);
// 添加/编辑子课题
export const saveSubExperiment = params => http.post(`${experiment}/experiment/saveSubExperiment`, params);
// 查看子课题
export const getSubExperiment = params => http.post(`${experiment}/experiment/getSubExperiment`, params);
// 查询成员
export const memberQuery = params => http.post(`${experiment}/organization/query`, params);
// 添加成员
export const saveMember = params => http.post(`${experiment}/organization/save`, params);
// 编辑成员
export const updateMember = params => http.post(`${experiment}/organization/update`, params);
// 删除成员
export const delMember = params => http.post(`${experiment}/organization/del`, params);
// 提交交付物信息
export const submitDeliverInfo = (params, url) => http.post(`${url}/deliver/submitDeliver`, params);
// 分配实验
export const createExperiment = params => http.post(`${experiment}/subExperiment/create`, params);
// 根据任务id获取任务的交付物信息
export const getDeliverByTaskId = (params, url) => http.post(`${url}/deliver/getDeliver`, params);
// 根据ID查询实验信息
export const getExperimentation = params => http.post(`${experiment}/subExperiment/getExperimentation`, params);
// 检查交付物
export const checkDeliver = (params, url) => http.post(`${url}/deliver/checkDeliver`, params);
// 根据code查询所有试题
export const getByCode = params => http.post(`${experiment}/question/getByCode`, params);
// 交付物历史记录
export const getDeliverHistory = (params, url) => http.post(`${url}/deliver/queryRecord`, params);
// 提交答案
export const submitAnswer = params => http.post(`${experiment}/question/submit`, params);
// 查看检查记录
export const queryCheckLog = (params, url) => http.post(`${url}/deliver/queryCheckLog`, params);
// 查询知识产权题目(论文、专利、软著)
export const getIntellectual = params => http.post(`${experiment}/question/getIntellectual`, params);
/**
* 流水账
*/
// 查询知识产权列表 (论文、专利、软著
export const getIntellectualList = params => http.post(`${experiment}/question/getIntellectualList`, params);
// 获取基本信息 (成员列表、项目列表)
export const getBasicInfo = url => http.post(`${url}/dailyAccount/info`);
// 提交知识产权信息(论文、专利、软著)
export const submitIntellectual = params => http.post(`${experiment}/question/submitIntellectual`, params);
// 获取任务列表
export const queryTasks = (params, url) => http.post(`${url}/dailyAccount/queryTasks`, params);
// 查询会议列表
export const getMeetQuery = params => http.post(`${experiment}/meeting/query`, params);
// 提交任务
export const submitTask = (params, url) => http.post(`${url}/dailyAccount/submitTask`, params);
// 添加/修改会议信息
export const saveMeeting = params => http.post(`${experiment}/meeting/save`, params);
// 导出
export const exportQuery = (params, url) => http.post(`${url}/dailyAccount/export`, params);
// 根据会议id查询会议详细信息
export const getMeetDetail = params => http.post(`${experiment}/meeting/get`, params);
// 当前打卡状态
export const clockQuery = (params, url) => http.post(`${url}/clock/query`, params);
// 科研成果列表
export const queryExperimentation = params => http.post(`${experiment}/subExperiment/queryExperimentation`, params);
// 打卡
export const clockPunch = (params, url) => http.post(`${url}/clock/punch`, params);
// 审核科研成果
export const examineExperimentation = params => http.post(`${experiment}/subExperiment/examineExperimentation`, params);
/**
* 财务条
*/
// 申请解锁科研结果
export const applyUnlock = params => http.post(`${experiment}/subExperiment/applyUnlock`, params);
// 查询任务上的财务条数据
export const getFinanceByTask = (params, url) => http.post(`${url}/finance/getByTask`, params);
// 审批解锁申请
export const examineUnlock = params => http.post(`${experiment}/experiment/examineUnlock`, params);
/**
* 导入wbs
* @param {object} e
*/
export const importWbs = (url, formData, parentId) => http.post(`${url}/tall/project/wbs?parentId=${parentId}`, formData);
// 查找数据追溯解锁信息
export const retrospectUnlock = params => http.post(`${experiment}/experiment/retrospectUnlock`, params);
// 导出
export const exportWbs = (params, url) => http.post(`${url}/tall/project/exportWbs`, params);
// 课题进度
export const getExperimentProgress = params => http.post(`${experiment}/experiment/getExperimentProgress`, params);
// export const importWbs = async e => {
// const file = e.target.files[0];
// const param = new FormData();
// param.append('file', file);
// const config = { headers: { 'Content-Type': 'multipart/form-data' } };
// const result = await http.post(`${experiment}/wbs`, param, config);
// return result;
// };
// 子课题进度
export const getProgress = params => http.post(`${experiment}/subExperiment/getProgress`, params);
// 上传文件
export const uploadImg = `${baseUrl}/filedeal/file/upload/multiple`;

BIN
src/assets/exarresources.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

0
src/components/tall/task/SubMemberManagement.vue → src/assets/index.css

BIN
src/assets/personal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
src/assets/projectVersion.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
src/assets/uidispose.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/work.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

140
src/components/tall/Render/Render.vue

@ -0,0 +1,140 @@
<template>
<div class="render-box shadow-lg block w-full task-card-plugin">
<div
class="render-content block w-full"
:id="`render-${props.task.id}`"
:data-did="props.task.detailId"
:data-param="props.param"
:data-pdu="props.task.planDuration"
:data-pid="project.id"
:data-pstart="props.task.planStart"
:data-rdu="props.task.realDuration"
:data-rid="roleId"
:data-tid="props.task.id"
:data-tname="props.task.name"
:data-token="token"
:data-rstart="props.task.realStart"
:data-uid="userId"
:data-url="project.url"
:data-type="1"
></div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, defineProps } from 'vue';
import { useStore } from 'vuex';
import { getOtherPlugin, getConfigInfo } from 'apis';
const props = defineProps({
task: { default: () => {}, type: Object },
pluginId: { default: '1', type: String },
styleType: { default: 0, type: Number },
pluginTaskId: { default: '', type: String },
businessPluginId: { default: '', type: String },
param: { type: String, default: '' },
});
const store = useStore();
const project = computed(() => store.state.projects.project); //
const userId = computed(() => store.getters['user/userId']);
const roleId = computed(() => store.state.role.roleId); // id
const allPlugin = computed(() => store.state.layout.allPlugin); //
const businessPlugin = computed(() => store.state.layout.businessPlugin); //
const token = computed(() => store.state.user.token); // token
const pluginInfo = ref(null);
onMounted(async () => {
await getPlugin();
if (props.param && pluginInfo.value) {
// const configParam = JSON.parse(props.param);
pluginInfo.value.config = `var p${props.pluginId}_config = ${props.param}`;
} else {
await getConfig();
}
if (pluginInfo.value.html) {
// : 2022130 this.pluginTaskIdAPP
// bug
const content = window.document.getElementById(`render-${pluginInfo.value.renderId}`);
content.innerHTML = pluginInfo.value.html;
}
if (pluginInfo.value.config) {
const scriptConfig = window.document.createElement('script');
scriptConfig.innerHTML = pluginInfo.value.config;
window.document.body.appendChild(scriptConfig);
}
if (pluginInfo.value.js) {
const scriptJs = window.document.createElement('script');
scriptJs.innerHTML = pluginInfo.value.js;
window.document.body.appendChild(scriptJs);
}
});
async function getPlugin() {
const plugins = allPlugin.value || sessionStorage.getItem('allPlugin');
if (plugins && JSON.parse(plugins)) {
//
try {
const pluginLists = JSON.parse(plugins);
// pluginLists find,catch
const pluginTarget = pluginLists.find(item => item.id === props.pluginId);
pluginTarget.renderId = props.task.id;
pluginInfo.value = pluginTarget || null;
} catch (error) {
console.error('error: ', error);
pluginInfo.value = null;
}
} else {
// API
const params = { businessPluginId: props.businessPluginId };
try {
const res = await getOtherPlugin(params);
res.renderId = props.task.id;
pluginInfo.value = res || null;
} catch (err) {
pluginInfo.value = null;
console.error('err: ', err);
}
}
}
async function getConfig() {
const businessPlugins = businessPlugin.value || sessionStorage.getItem('businessPlugin');
if (businessPlugins && JSON.parse(businessPlugins)) {
//
const businessPluginLists = JSON.parse(businessPlugins);
businessPluginLists.forEach(item => {
if (item.pluginConfigs) {
const pluginConfig = item.pluginConfigs.find(plugin => plugin.businessPluginId === props.businessPluginId);
if (pluginConfig && pluginConfig.config && pluginInfo.value) {
pluginInfo.value.config = pluginConfig.config;
}
}
});
} else {
// API
const params = { id: props.businessPluginId };
try {
const res = await getConfigInfo(params);
if (pluginInfo.value && res.config) {
pluginInfo.value.config = res.config;
}
} catch (err) {
console.error('err: ', err);
}
}
}
</script>
<style scoped>
.render-box {
overflow: hidden;
}
</style>

61
src/components/tall/Reviewer/Reviewer.vue

@ -0,0 +1,61 @@
<template>
<div class="px-2 py-1 border rounded-sm" @click="collapsed = !collapsed">
<div class="flex justify-between items-center">
<div>审核人</div>
<div class="flex items-center justify-end flex-1 text-sm">
<div class="mx-1" v-for="(item, index) in showCheckers" :key="index">{{ item.name }}</div>
<div class="mx-1" v-show="checkedCheckers.length > 3">...</div>
<DownOutlined v-if="!collapsed" />
<UpOutlined v-else />
</div>
</div>
<!-- 隐藏的审核人选项 -->
<div v-show="collapsed" class="foot mt-2 flex flex-wrap">
<a-button
v-for="(item, index) in checkers"
:key="index"
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'"
size="small"
class="my-1 mx-2"
@click.stop="handleSelectChecker(item)"
>
{{ item.name }}
</a-button>
</div>
</div>
</template>
<script setup>
import { ref, computed, defineExpose } from 'vue';
import { useStore } from 'vuex';
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
const store = useStore();
//
const collapsed = ref(false);
// store
//
const checkers = computed(() => store.state.role.members);
//
const checkedCheckers = ref([]);
// 3...
const showCheckers = computed(() => (checkedCheckers.value.length > 3 ? checkedCheckers.value.slice(0, 3) : checkedCheckers.value));
defineExpose({ checkedCheckers, collapsed });
/**
* 点击成员 切换检查人的选中状态
* @param {object} member 成员对象
*/
function handleSelectChecker(member) {
const target = checkedCheckers.value.find(item => item.memberId === member.memberId);
if (target) {
//
checkedCheckers.value = checkedCheckers.value.filter(item => item.memberId !== member.memberId);
} else {
checkedCheckers.value.push(member);
}
}
</script>

117
src/components/tall/Reviewer/ReviewerSecond.vue

@ -0,0 +1,117 @@
<template>
<div class="border border-solid rounded-sm mt-5 p-2 pt-4 pl-1" style="border-color: #d9d9d9" @click="collapsed = !collapsed">
<div class="relative flex justify-between">
<div class="absolute text-sm bg-white reviewer-title">审核人</div>
<div class="flex flex-wrap">
<div v-for="(item, index) in checkers" :key="index">
<a-button
v-if="index < 4"
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'"
size="small"
class="m-1"
@click.stop="handleSelectChecker(item)"
>
{{ item.name }}
</a-button>
</div>
</div>
<DownOutlined style="line-height: 32px" v-if="!collapsed" />
<UpOutlined style="line-height: 32px" v-else />
</div>
<!-- 隐藏的审核人选项 -->
<div v-show="!collapsed" class="mt-2 flex flex-wrap">
<div v-for="(item, index) in checkers" :key="index">
<a-button
v-if="index >= 4"
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'"
size="small"
class="m-1"
@click.stop="handleSelectChecker(item)"
>
{{ item.name }}
</a-button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, defineExpose, defineProps } from 'vue';
import { useStore } from 'vuex';
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
const props = defineProps({
dataCheckers: {
type: Array,
default: () => [],
},
});
const store = useStore();
//
const collapsed = ref(true);
// store
//
const checkers = computed(() => store.state.role.members);
//
const checkedCheckers = ref([]);
if (props.dataCheckers && checkers.value.length > 0) {
props.dataCheckers.forEach(item => {
item.name = item.checkerName;
item.memberId = item.checkerId;
});
checkedCheckers.value = props.dataCheckers;
const arr = [];
checkers.value.forEach(item => {
const data = props.dataCheckers.find(checker => checker.memberId === item.memberId);
if (data) {
arr.push(item);
}
});
checkers.value.forEach(item => {
const data = props.dataCheckers.find(checker => checker.memberId === item.memberId);
if (!data) {
arr.push(item);
}
});
store.commit('role/setMembers', arr);
}
// 3...
// const showCheckers = computed(() => (checkedCheckers.value.length > 3 ? checkedCheckers.value.slice(0, 3) : checkedCheckers.value));
defineExpose({ checkedCheckers, collapsed });
/**
* 点击成员 切换检查人的选中状态
* @param {object} member 成员对象
*/
function handleSelectChecker(member) {
const target = checkedCheckers.value.find(item => item.memberId === member.memberId);
if (target) {
//
checkedCheckers.value = checkedCheckers.value.filter(item => item.memberId !== member.memberId);
} else {
checkedCheckers.value.push(member);
}
}
</script>
<style scoped>
.reviewer-title {
width: 60px;
height: 20px;
line-height: 20px;
text-align: center;
top: -25px;
left: 10px;
}
</style>

116
src/components/tall/center/Global.vue

@ -1,11 +1,42 @@
<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
class="global-box"
v-if="globals.length || (project.businessCode === 'ZERO' && roleInfo && roleInfo.id === roleId && roleInfo.name === '管理员')"
style="max-height: calc((100vh - 50px - 44px - 36px - 10px) / 5 * 4)"
>
<div
class="task-card-plugin my-3"
v-if="project.businessCode === 'ZERO' && roleInfo && roleInfo.id === roleId && roleInfo.name === '管理员'"
>
<a-button type="primary" block @click="toWorkbench">工作台</a-button>
</div>
<template v-if="globals.length > 0">
<div class="global-task cursor-pointer" v-for="(item, index) in globals" :key="index">
<template v-if="item.plugins && item.plugins.length">
<div v-for="(pluginArr, i) in item.plugins" :key="i">
<div class="my-3" v-if="pluginArr.length">
<Plugin
v-for="plugin in pluginArr"
:key="plugin.pluginTaskId"
:plugin-id="plugin.pluginId"
:plugin-task-id="plugin.pluginTaskId"
:business-plugin-id="plugin.businessPluginId"
:plugin-info="plugin"
:param="plugin.param"
:style-type="plugin.styleType || 0"
:task="item"
/>
</div>
</div>
</template>
<div v-else class="pb-3">
<Plugin plugin-id="1" :task="item" />
</div>
</div>
</template>
</div>
</div>
</template>
@ -13,68 +44,81 @@
<script setup>
import { computed, watch } from 'vue';
import { useStore } from 'vuex';
import { getPermanent } from 'apis';
import { getPermanent, getGlobal } from 'apis';
import { message } from 'ant-design-vue';
import Plugin from 'components/tall/plugin/Plugin.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'); //
const roleInfo = computed(() => store.state.role.roleInfo); //
const roleId = computed(() => store.state.role.roleId); // id
const globals = computed(() => store.getters['task/globals']); //
if (sessionPermanents) {
const arr = JSON.parse(sessionPermanents);
store.commit('task/setPermanents', arr);
if (project.value.id && roleId.value) {
getPermanentData(roleId.value); //
getGlobalData(roleId.value);
}
if (project.value) {
if (roleId.value) {
watch([project, roleId], () => {
if (project.value.id && roleId.value) {
getPermanentData(roleId.value); //
}
}
watch([project, roleId], async () => {
if (roleId.value) {
await getPermanentData(roleId.value); //
getGlobalData(roleId.value);
}
});
//
async function getPermanentData(id) {
const params = { param: { roleId: id } };
const { url } = store.state.projects.project;
try {
const data = await getPermanent(params);
const data = await getPermanent(params, url);
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', '');
async function getGlobalData(id) {
const params = { param: { roleId: id } };
const { url } = store.state.projects.project;
try {
const data = await getGlobal(params, url);
store.commit('task/setDailyTasks', data);
} catch (error) {
message.info(error);
throw new Error(error);
}
}
function toWorkbench() {
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', 'workbench'); //
}
</script>
<style scoped>
.global {
padding: 16px;
padding-bottom: 16px;
/* border-bottom: 1px solid #cccccc; */
}
.global-box {
border: 1px solid #cccccc;
border-radius: 10px;
padding: 12px 16px;
border-bottom: 1px solid #cccccc;
/* border-radius: 10px; */
padding: 4px 16px;
}
.global-task {
padding: 8px 0;
line-height: 22px;
/* .global-task {
line-height: 38px;
} */
.task-card-plugin {
padding: 12px;
border-radius: 10px;
-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);
}
</style>

354
src/components/tall/center/Index.vue

@ -2,253 +2,47 @@
<div>
<!-- 项目标题 -->
<div class="navbar flex items-center justify-between">
<div class="project-name">{{ project.name }}</div>
<div class="project-action">
<div class="project-name truncate">{{ project.name }}</div>
<div class="project-action flex-shrink-0">
<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>
<Roles />
<!-- 日常任务 -->
<!-- <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>
<Global ref="globalRef" />
<!-- 定期任务 -->
<!-- <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>
<RegularTask :style="{ height: tasksHeight + 'px' }" />
</div>
</template>
<script setup>
import { computed, watch, reactive } from 'vue';
import { computed, watch, ref, nextTick } 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'; //
import { ReloadOutlined, MoreOutlined } from '@ant-design/icons-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 }); //
}
const globals = computed(() => store.getters['task/globals']); //
const globalRef = ref(null);
const globalHeight = ref(0);
const clientHeight = ref(0); //
const tasksHeight = ref(null); //
watch(globals, () => {
nextTick(() => {
setTimeout(() => {
clientHeight.value = `${document.documentElement.clientHeight}`; //
globalHeight.value = globalRef.value.$el.clientHeight;
tasksHeight.value = clientHeight.value - 48 - 44 - 36 - globalHeight.value;
}, 30);
});
});
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>
@ -267,108 +61,4 @@ function toSonDetail(item, id) {
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>

377
src/components/tall/center/RegularTask.vue

@ -1,32 +1,45 @@
<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 id="tasks" class="task-list">
<div class="-mt-3">
<div class="task-box" :class="{ 'pt-3': index === 0 }" v-for="(item, index) in tasks" :key="index">
<div class="task-time flex items-center justify-between">
<div class="flex items-center">
<PlayCircleOutlined style="font-size: 23px; color: #999999" />
<span v-if="item.process === 4">{{ dayjs(+item.planStart).format('M月D日') }}</span>
<span v-else>{{ dayjs(+item.planStart).format('M月D日 HH:mm') }}</span>
</div>
<div class="task-action"></div>
</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-info">
<div>
<div class="task-card-null" v-if="item.process === 4"></div>
<div class="task-card" v-else>
<div class="task-name cursor-pointer">
<template v-if="item.plugins && item.plugins.length">
<div v-for="(pluginArr, i) in item.plugins" :key="i">
<div :class="{ 'pt-3': i > 0 }" v-if="pluginArr.length">
<Plugin
v-for="plugin in pluginArr"
:key="plugin.pluginTaskId"
:plugin-id="plugin.pluginId"
:plugin-task-id="plugin.pluginTaskId"
:business-plugin-id="plugin.businessPluginId"
:plugin-info="plugin"
:param="plugin.param"
:style-type="plugin.styleType || 0"
:task="item"
/>
</div>
</div>
</template>
<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 v-else class="">
<Plugin plugin-id="1" :task="item" />
</div>
</div>
</div>
<!-- <div class="open-icon" v-if="item.sonList" @click="openCard">
<img />
</div> -->
</div>
</div>
</div>
@ -35,44 +48,70 @@
</template>
<script setup>
import { computed, watch, reactive } from 'vue';
import { computed, watch, onMounted } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import uTask from 'utils/task';
import { PlayCircleOutlined } from '@ant-design/icons-vue';
import { getRegularTask, findSonTask } from 'apis';
import { flatten } from 'lodash';
import { getRegularTask } 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); //
const tasks = computed(() => store.state.task.tasks); //
const realTasks = computed(() => store.state.task.realTasks); //
if (sessionGlobalHeight) {
store.commit('task/setGlobalHeight', sessionGlobalHeight);
}
const timeNode = computed(() => store.state.task.timeNode); //
const timeUnit = computed(() => store.state.task.timeUnit); //
const currLocationTaskId = computed(() => store.state.task.currLocationTaskId); // id
const businessCode = computed(() => store.state.task.businessCode); //
const timeGranularity = computed(() => store.getters['task/timeGranularity']); //
const timeLineType = computed(() => store.state.task.timeLineType); //
const downNextPage = computed(() => store.state.task.downNextPage); //
const upNextPage = computed(() => store.state.task.upNextPage); //
if (sessionTasks) {
const arr = JSON.parse(sessionTasks);
store.commit('task/setRegularTasks', arr);
}
onMounted(() => {
document.getElementById('tasks').addEventListener('scroll', handleScroll, true);
});
if (project.value) {
if (roleId.value) {
getTasks({ roleId: roleId.value }); //
}
if (project.value.id && roleId.value) {
getTasks({ roleId: roleId.value }); //
}
watch([project, roleId, refreshProjects], async () => {
if (roleId.value) {
watch([project, roleId], async () => {
if (project.value.id && roleId.value) {
await getTasks({ roleId: roleId.value }); //
}
});
//
function handleScroll(e) {
// scrollTop
const { scrollTop } = e.target;
// windowHeight
const windowHeight = e.target.clientHeight;
// scrollHeight
const { scrollHeight } = e.target;
//
if (scrollTop + windowHeight === scrollHeight) {
//
console.warn('滚动到底部: ');
// setNextPlaceholderTasks(); //
const params = { pageNum: downNextPage.value, queryType: 1, triggerType: 0 };
dataRender(params);
}
//
if (scrollTop === 0) {
console.warn('滚动到顶部: ');
// setPrevPlaceholderTasks(); //
const params = { pageNum: upNextPage.value, queryType: 0, triggerType: 0 };
dataRender(params);
}
}
/**
* 根据时间基准点和角色查找定期任务
* @param {object} query
@ -83,60 +122,238 @@ watch([project, roleId, refreshProjects], async () => {
* @param {number} query.queryType 0向上查找 1向下查找(默认) 下查包含自己上查不包含
*/
async function getTasks(query) {
const params = { param: query };
const params = { param: generateGetTaskParam(query) };
const { url } = store.state.projects.project;
try {
const data = await getRegularTask(params);
taskObj.tasks = data;
const data = await getRegularTask(params, url);
data.forEach(item => {
item.plugins.forEach(val => {
if (Number(val[0].pluginId) === 6) {
getSonTask(item.detailId);
}
});
});
if (params.param.queryType === 0) {
store.commit('task/setUpRealTasks', data.list);
store.commit('task/setUpNextPage', data.nextPage); //
} else {
store.commit('task/setDownRealTasks', data.list);
store.commit('task/setDownNextPage', data.nextPage); //
}
store.commit('task/setRegularTasks', data);
dataRender(params.param);
} 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;
/**
* 生成getTasks所用的参数
* @param {object} query getTasks传递的参数
*/
function generateGetTaskParam(query) {
return {
roleId: roleId.value,
timeNode: query.timeNode || timeNode.value,
timeUnit: query.timeUnit || timeUnit.value,
queryType: query.queryType === 0 ? 0 : 1,
pageNum: query.pageNum || 1,
pageSize: query.pageSize || uTask.pageTaskCount,
taskId: query.taskId || currLocationTaskId.value,
businessCode: query.businessCode || businessCode.value,
triggerType: query.triggerType || 1,
};
}
//
function dataRender(params) {
timeLineType.value === 1 ? renderScaleTask(params) : renderConTask(params);
}
//
async function renderScaleTask(query) {
const params = generateGetTaskParam(query);
//
const centerData = (await showTaskTime(params, realTasks.value, tasks.value)) || [];
await handleTasksData(params, centerData, realTasks.value);
}
//
function renderConTask(params) {
console.log(params);
}
/**
* 正待处理的真实数据
*/
async function showTaskTime(params, reals, showTasks) {
// --
let centerData = [];
if (reals.length > params.pageSize && params.queryType === 0) {
centerData = reals.slice(reals.length - params.pageSize);
} else {
centerData = reals.slice(0, params.pageSize);
}
//
const firstDetailIndex = showTasks.findIndex(task => task.detailId); //
if (firstDetailIndex > -1) {
const firstId = showTasks[firstDetailIndex].id; // id
//
let lastDetailIndex = -1;
showTasks.forEach((item, index) => {
if (item.detailId) {
lastDetailIndex = index;
}
});
const lastId = showTasks[lastDetailIndex].id; // id
store.commit('task/setRegularTasks', taskObj.tasks);
} catch (error) {
message.info(error);
throw new Error(error);
reals.forEach((item, index) => {
if (params.queryType === 1 && item.id === lastId) {
centerData = reals.slice(index + 1, index + 1 + params.pageSize);
} else if (params.queryType === 0 && item.id === firstId) {
centerData = index >= params.pageSize ? reals.slice(index - params.pageSize, index) : reals.slice(0, index);
}
});
}
return centerData;
}
//
async function handleTasksData(params, centerData) {
let showTasks = tasks.value;
//
const firstTime = showTasks.length > 0 ? showTasks[0].planStart : new Date().getTime();
const upTargetTime = dayjs(+firstTime).subtract(params.pageSize, timeGranularity.value); //
// +1 ||
const lastTime =
showTasks.length > 0 ? dayjs(+showTasks[showTasks.length - 1].planStart).add(1, timeGranularity.value) : new Date().getTime();
const downTargetTime = dayjs(+lastTime).add(params.pageSize - 1, timeGranularity.value); //
const nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; //
if (centerData.length) {
const arr = [];
centerData.forEach(v => {
let isLimit = false;
let isExceed = false; // 10
if (params.queryType) {
isLimit =
dayjs(+lastTime).isBefore(+v.planStart, timeGranularity.value) || dayjs(+lastTime).isSame(+v.planStart, timeGranularity.value);
isExceed = isLimit && dayjs(+downTargetTime).isAfter(+v.planStart, timeGranularity.value);
} else {
isLimit =
dayjs(+firstTime).isAfter(+v.planStart, timeGranularity.value) || dayjs(+firstTime).isSame(+v.planStart, timeGranularity.value);
isExceed = isLimit && dayjs(+upTargetTime).isBefore(+v.planStart, timeGranularity.value);
}
if (isExceed) {
arr.push(v);
}
});
if (arr.length) {
if (arr.length === centerData.length && centerData.length < params.pageSize && nextPage > 0) {
getTasks({ pageNum: nextPage, queryType: params.queryType });
} else {
/**
* 符合条件的数据 < centerData的数据
* 或者
* centerData的数据全部符合条件但是 < 10而且没有下一页不能向下查
* 数据需要补齐10天的刻度
* 所以循环长度为params.pageSize
*/
let newArr = [];
let tempLen = -1; //
let currTime = params.queryType === 0 ? upTargetTime : lastTime; //
// centerData = 10 =
if (arr.length === centerData.length && centerData.length === params.pageSize) {
tempLen = params.pageSize;
currTime = arr[0].planStart;
}
//
const sameTime = params.queryType === 0 ? firstTime : dayjs(+lastTime).subtract(1, timeGranularity.value);
//
const firstArr = arr.filter(item => dayjs(+item.planStart).isSame(+sameTime, timeGranularity.value));
//
const repeatArr = showTasks.filter(item => dayjs(+item.planStart).isSame(+sameTime, timeGranularity.value));
//
if (repeatArr.length === 1 && !repeatArr[0].detailId) {
showTasks = params.queryType === 0 ? showTasks.slice(1) : showTasks.slice(0, showTasks.length - 2);
}
if (firstArr.length) {
newArr = params.queryType === 0 ? [...firstArr, ...newArr] : [...newArr, ...firstArr];
}
for (let i = 0; i < params.pageSize; i++) {
const termArr = arr.filter(item => dayjs(+item.planStart).isSame(+currTime, timeGranularity.value));
if (termArr.length === 0) {
let newTasks = [];
newTasks = uTask.setPlaceholderTasks(+currTime, false, timeGranularity.value, 1);
newArr = [...newArr, ...newTasks];
} else {
newArr = [...newArr, ...termArr];
if (tempLen) tempLen--;
}
if (tempLen === 0) break;
// <params.pageSize
currTime = dayjs(+currTime).add(1, timeGranularity.value);
}
showTasks = params.queryType === 0 ? [...newArr, ...showTasks] : [...showTasks, ...newArr];
showTasks = flatten(showTasks); // 1
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
} else {
params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
}
} else if (nextPage > 0) {
getTasks({ pageNum: nextPage, queryType: params.queryType });
} else {
params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
}
}
function toDetail(item) {
store.commit('task/setTaskDetail', item);
store.commit('task/sonDetailId', '');
store.commit('task/sonExperimentationId', '');
//
function setPrevPlaceholderTasks() {
store.commit('task/setTopEnd', true);
let startTime = '';
if (!tasks.value || !tasks.value.length) {
startTime = Date.now(); //
} else {
startTime = tasks.value[0].planStart - 0; //
}
const placeholderTasks = uTask.setPlaceholderTasks(startTime, true, timeGranularity.value);
// store.commit('task/setCurrUpTimeNode', startTime);
store.commit('task/setUpTasks', placeholderTasks);
}
function toSonDetail(item, id) {
store.commit('task/setTaskDetail', item);
store.commit('task/sonDetailId', id);
store.commit('task/sonExperimentationId', id);
//
function setNextPlaceholderTasks() {
// store.commit('task/setBottomEnd', true);
let startTime = '';
if (!tasks.value || !tasks.value.length) {
startTime = Date.now();
} else {
startTime = dayjs(+tasks.value[tasks.value.length - 1].planStart)
.add(1, timeGranularity.value)
.valueOf();
}
const initData = uTask.setPlaceholderTasks(startTime, false, timeGranularity.value);
// store.commit('task/setCurrDownTimeNode', startTime);
store.commit('task/setDownTasks', initData);
}
</script>
<style scoped>
.task-list {
padding: 0 16px 50px;
padding: 0 16px;
overflow-y: auto;
}
@ -166,12 +383,8 @@ function toSonDetail(item, id) {
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-card-null {
height: 20px;
}
.task-con {

80
src/components/tall/center/Roles.vue

@ -1,32 +1,36 @@
<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 class="role-list relative" ref="roleBox">
<div class="role-abs absolute flex items-center" ref="roleAbs">
<div class="role-box relative cursor-pointer" ref="roleItem" v-for="(item, index) in roleList" :key="index" @click="changeRole(item)">
<div class="role-name" :class="{ mine: item.mine === 1, selected: 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>
</div>
</template>
<script setup>
import { computed, watch } from 'vue';
import { computed, watch, ref, onMounted, nextTick } 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 roleList = computed(() => store.state.role.visibleRoles); //
const project = computed(() => store.state.projects.project); //
const sessionRoleId = sessionStorage.getItem('roleId'); // id
const taskDetailShow = computed(() => store.state.task.taskDetailShow);
const roleBox = ref(null);
const roleAbs = ref(null);
const roleItem = ref(null);
if (sessionRoles) {
const roleArr = JSON.parse(sessionRoles);
store.commit('role/setVisibleRoles', roleArr);
setInitialRoleId(roleArr);
if (sessionRoleId && !roleId.value) {
store.commit('role/setRoleId', sessionRoleId);
}
if (project.value) {
if (project.value && project.value.id) {
getRoles(project.value.id); // id
}
@ -36,6 +40,12 @@ watch(project, async () => {
}
});
onMounted(() => {
nextTick(() => {
console.log('roleBox', roleBox.value.clientWidth, 'roleAbs', roleAbs.value.clientWidth);
});
});
/**
* 通过项目id获取角色信息
* @param {string} projectId
@ -43,7 +53,8 @@ watch(project, async () => {
*/
async function getRoles(params) {
try {
const data = await findShowRole(params);
const { url } = store.state.projects.project;
const data = await findShowRole(params, url);
store.commit('role/setInvisibleRoles', data ? data.invisibleList : []);
store.commit('role/setVisibleRoles', data ? data.visibleList : []);
setInitialRoleId(data ? data.visibleList : []);
@ -56,11 +67,41 @@ async function getRoles(params) {
//
function setInitialRoleId(visibleList) {
if (!visibleList || !visibleList.length) return;
const index = visibleList.findIndex(item => +item.mine === 1);
let index = visibleList.findIndex(item => +item.mine === 1);
//
if (taskDetailShow.value === 'workbench') {
index = visibleList.findIndex(item => item.name === '管理员');
}
// id
if (roleId.value) {
index = visibleList.findIndex(item => item.id === roleId.value);
}
const currentRole = index > 0 ? visibleList[index] : visibleList[0];
const currentRoleId = currentRole ? currentRole.id : '';
store.commit('role/setRoleInfo', currentRole || null);
store.commit('role/setRoleId', currentRoleId);
}
function changeRole(item) {
store.commit('role/setRoleId', item.id);
store.commit('role/setRoleInfo', item);
//
store.commit('task/setPermanents', []);
store.commit('task/setDailyTasks', []);
//
store.commit('task/clearTasks');
//
store.commit('task/clearRealTasks');
store.commit('task/setUpNextPage', 1);
store.commit('task/setDownNextPage', 1);
//
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', '');
store.commit('task/setTaskDetailShow', '');
}
</script>
<style scoped>
@ -79,9 +120,14 @@ function setInitialRoleId(visibleList) {
font-size: 14px;
line-height: 36px;
color: #333333;
white-space: nowrap;
}
.role-name.mine {
color: #f59e0b !important;
}
.role-name.selected {
font-weight: 600;
color: #1890ff;
}
@ -96,4 +142,8 @@ function setInitialRoleId(visibleList) {
height: 2px;
background-color: #1890ff;
}
.role-name.mine + .line-box .line {
background-color: #f59e0b;
}
</style>

59
src/components/tall/center/nested.vue

@ -0,0 +1,59 @@
<template>
<draggable class="dragArea" tag="ul" :list="proList" :group="{ name: 'g1' }" item-key="name" :move="checkMove" @end="onEnd">
<template #item="{ element }">
<li>
<p :data-id="element.id">{{ element.name }}</p>
<nested :lists="element.sonProjectList || []" @changeData="handleChangeData" />
</li>
</template>
</draggable>
</template>
<script>
import { defineComponent, ref } from 'vue';
import draggable from 'vuedraggable';
export default defineComponent({
name: 'nested',
props: {
lists: {
type: Array,
default: () => [],
},
},
components: { draggable },
emit: ['changeData'],
setup(props, context) {
const proList = ref(props.lists);
const moveProjectId = ref('');
const businessCode = ref('');
const checkMove = e => {
moveProjectId.value = e.draggedContext.element.id;
businessCode.value = e.draggedContext.element.businessCode;
};
const onEnd = e => {
console.log('Check', e, proList.value);
const param = {
moveProjectId: moveProjectId.value,
businessCode: businessCode.value,
};
context.emit('changeData', param);
};
const handleChangeData = data => {
context.emit('changeData', data);
};
return { proList, checkMove, onEnd, handleChangeData };
},
});
</script>
<style scoped>
.dragArea {
min-height: 5px;
/* outline: 1px dashed; */
}
</style>

123
src/components/tall/left/Index.vue

@ -2,67 +2,80 @@
<div class="relative">
<Calendar @changeTime="changeTime" />
<PlusCircleFilled class="upload-btn absolute cursor-pointer" :style="{ fontSize: 42 + 'px', color: '#1890FF' }" @click="showModal" />
<PlusCircleFilled class="upload-btn absolute cursor-pointer" :style="{ fontSize: 42 + 'px', color: '#1890FF' }" @click="showDrawer" />
<!-- <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-drawer v-model:visible="visible" class="custom-class" :closable="false" placement="right">
<div class="business-box">
<input class="import-project hidden" type="file" @change="toUpload" />
<div class="business-wrap cursor-pointer" v-for="(item, index) in list" :key="index">
<div class="business-info">
<div class="name" @click="importProject(item)">{{ item.name }}</div>
</div>
<a-modal v-model:visible="visible" title="新建课题" @ok="handleOk">
<p>是否新建课题</p>
</a-modal>
<div class="mwbs-list" v-if="item.mwbsList && item.mwbsList.length > 0">
<div class="mwbs-item" v-for="(val, key) in item.mwbsList" :key="key">
<input type="file" @click="toUpload(val.url)" />
<div class="name">{{ val.projectName }}</div>
</div>
</div>
</div>
</div>
</a-drawer>
<Projects />
<!-- ref="projectsRef" -->
</div>
</template>
<script setup>
import { ref } from 'vue';
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
// import dayjs from 'dayjs';
import { PlusCircleFilled } from '@ant-design/icons-vue';
import { create } from 'apis';
import { getBusinessList, importWbs } 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 = () => {
const list = ref([]); //
const refreshProjects = computed(() => store.state.layout.refreshProjects);
const currUrl = ref(null);
getList();
const showDrawer = () => {
visible.value = true;
};
const handleOk = async () => {
//
async function getList() {
try {
const res = await getBusinessList();
list.value = res;
} catch (error) {
message.info(error);
}
}
//
async function importProject(item) {
visible.value = false;
await createExperiment();
};
currUrl.value = item.url;
document.querySelector('.import-project').click();
}
//
async function createExperiment() {
async function toUpload(e) {
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;
const file = e.target.files[0];
const param = new FormData();
param.append('file', file);
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
const res = await importWbs(currUrl.value, param, '');
store.commit('layout/setRefreshProjects', !refreshProjects.value);
} catch (error) {
message.info(error);
throw new Error(error);
message.info(error || '导入失败');
}
}
@ -71,10 +84,6 @@ function changeTime(data) {
}
</script>
<script>
export default { name: 'LeftIndex' };
</script>
<style scoped>
.upload-btn {
left: 250px;
@ -122,4 +131,32 @@ export default { name: 'LeftIndex' };
border-radius: 6px;
font-size: 16px;
}
.business-wrap {
width: 100%;
box-sizing: border-box;
}
.business-wrap .business-info,
.business-wrap .mwbs-item {
padding: 10px;
border-bottom: 1px solid #eeeeee;
}
.business-wrap .name {
line-height: 36px;
}
.business-wrap .desc {
font-size: 12px;
color: #999;
}
.business-wrap .mwbs-list {
padding-left: 16px;
}
.business-wrap .mwbs-list .mwbs-item:last-child {
border-bottom: none;
}
</style>

508
src/components/tall/left/Projects copy.vue

@ -0,0 +1,508 @@
<template>
<a-divider />
<div class="list-flex">
<div class="item-box" v-for="(item, index) in projects" :key="index">
<div class="one-level h-70 cursor-pointer flex items-center">
<!-- <div class="icon" @click.stop="showActionCard(item)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> -->
<div class="flex-none">
<a-popover placement="bottomLeft">
<template #content>
<p class="cursor-pointer" @click="showImportCard(item)">导入</p>
<p class="cursor-pointer" @click="exportProject(item.id)">导出</p>
<p class="m-0 cursor-pointer" @click="showDelCard(item)">删除</p>
</template>
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div>
</a-popover>
</div>
<div class="w-full flex items-center justify-between" @click="toDetail(item)">
<div class="detail">
<div class="name-box flex items-center" :class="{ 'mb-2': item.businessCode !== 'ZERO' }">
<div class="name truncate">{{ item.name }}</div>
<div class="precent-num">
{{ item.status === 1 ? '进行中' : item.status === 2 ? '已结束' : item.status === 0 ? '未开始' : '暂停' }}
</div>
</div>
<div class="time" v-if="item.businessCode !== 'ZERO'">
{{ dayjs(Number(item.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(item.endTime)).format('MM-DD HH:mm') }}
</div>
</div>
<div class="right flex justify-end items-center" @click.stop="openMenu">
<a-button v-if="item.businessCode === 'ZERO'" class="mr-2" shape="round" type="primary" @click="toWorkbench(item)">
工作台
</a-button>
<RightOutlined v-if="!item.show" @click="changeShow(item)" />
<DownOutlined v-else @click="changeShow(item)" />
</div>
</div>
</div>
<div class="two-box" v-if="item.show">
<div class="two-flex" v-for="(sonItem, sonIndex) in item.sonProjectList" :key="sonIndex">
<div class="two-level h-70 cursor-pointer flex items-center">
<!-- <div class="icon" @click.stop="showActionCard(sonItem)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> -->
<div class="flex-none">
<a-popover placement="bottomLeft">
<template #content>
<!-- <p class="cursor-pointer" @click="showImportCard(sonItem)">导入</p> -->
<p class="cursor-pointer" @click="exportProject(sonItem.id)">导出</p>
<p class="m-0 cursor-pointer" @click="showDelCard(sonItem)">删除</p>
</template>
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div>
</a-popover>
</div>
<div class="flex items-center justify-between" style="width: calc(100% - 32px)" @click="toDetail(sonItem)">
<div class="detail">
<div class="name-box mb-2 flex items-center">
<div class="name truncate">{{ sonItem.name }}</div>
<div class="precent-num">
{{ item.status === 1 ? '进行中' : item.status === 2 ? '已结束' : item.status === 0 ? '未开始' : '暂停' }}
</div>
</div>
<div class="time">
{{ dayjs(Number(sonItem.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(sonItem.endTime)).format('MM-DD HH:mm') }}
</div>
</div>
<div class="right" @click.stop="openMenu">
<RightOutlined v-if="!sonItem.show" @click="changeShow(sonItem)" />
<DownOutlined v-else @click="changeShow(sonItem)" />
</div>
</div>
</div>
</div>
</div>
</div>
<input class="import-wbs hidden" type="file" @change="toImport" />
<a-modal v-model:visible="importVisible" title="导入" @ok="handleImport">
<p>确定要导入到{{ importParent }}</p>
</a-modal>
<a-modal v-model:visible="visible" title="删除" @ok="handleOk">
<p>确定要删除吗</p>
</a-modal>
<draggable
class="dragArea p-0 list-none"
tag="ul"
:list="projects"
:group="{ name: 'g1' }"
item-key="name"
:move="checkMove"
@end="onEnd"
>
<template #item="{ element }">
<li class="item-box">
<!-- <p>{{ element.name }}</p> -->
<div
class="one-level cursor-pointer h-60 flex items-center"
:class="{ 'h-70': element.sonProjectList && element.sonProjectList.length > 0 }"
:style="{ 'padding-top': element.sonProjectList && element.sonProjectList.length > 0 ? 0 : '5px' }"
>
<!-- <div class="icon" @click.stop="showActionCard(item)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> -->
<div class="flex-none">
<a-popover placement="bottomLeft">
<template #content>
<p class="cursor-pointer" @click="showImportCard(element)">导入</p>
<p class="cursor-pointer" @click="exportProject(element.id)">导出</p>
<p class="m-0 cursor-pointer" @click="showDelCard(element)">删除</p>
</template>
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div>
</a-popover>
</div>
<div class="w-full flex items-center justify-between" @click="toDetail(element)">
<div class="detail">
<div class="name-box flex items-center" :class="{ 'mb-2': element.businessCode !== 'ZERO' }">
<div class="name truncate">{{ element.name }}</div>
<div class="precent-num">
{{ element.status === 1 ? '进行中' : element.status === 2 ? '已结束' : element.status === 0 ? '未开始' : '暂停' }}
</div>
</div>
<div class="time" v-if="element.businessCode !== 'ZERO'">
{{ dayjs(Number(element.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(element.endTime)).format('MM-DD HH:mm') }}
</div>
</div>
<div class="right flex justify-end items-center" @click.stop="openMenu">
<a-button v-if="element.businessCode === 'ZERO'" class="mr-2" shape="round" type="primary" @click="toWorkbench(element)">
工作台
</a-button>
<RightOutlined v-if="!element.show" @click="changeShow(element)" />
<DownOutlined v-else @click="changeShow(element)" />
</div>
</div>
</div>
<draggable
class="dragArea p-0 list-none two-box"
tag="ul"
:list="element.sonProjectList"
:group="{ name: 'g1' }"
item-key="name"
:move="checkMove"
@end="onEnd"
>
<template #item="{ element }">
<li class="two-flex">
<!-- <p>{{ element.name }}</p> -->
<div class="two-level h-70 cursor-pointer flex items-center">
<!-- <div class="icon" @click.stop="showActionCard(sonItem)">
<img src="https://www.tall.wiki/staticrec/drag.svg" />
</div> -->
<div class="flex-none">
<a-popover placement="bottomLeft">
<template #content>
<!-- <p class="cursor-pointer" @click="showImportCard(sonItem)">导入</p> -->
<p class="cursor-pointer" @click="exportProject(element.id)">导出</p>
<p class="m-0 cursor-pointer" @click="showDelCard(element)">删除</p>
</template>
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div>
</a-popover>
</div>
<div class="flex items-center justify-between" style="width: calc(100% - 32px)" @click="toDetail(element)">
<div class="detail">
<div class="name-box mb-2 flex items-center">
<div class="name truncate">{{ element.name }}</div>
<div class="precent-num">
{{ element.status === 1 ? '进行中' : element.status === 2 ? '已结束' : element.status === 0 ? '未开始' : '暂停' }}
</div>
</div>
<div class="time">
{{ dayjs(Number(element.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(element.endTime)).format('MM-DD HH:mm') }}
</div>
</div>
<div class="right" @click.stop="openMenu">
<RightOutlined v-if="!element.show" @click="changeShow(element)" />
<DownOutlined v-else @click="changeShow(element)" />
</div>
</div>
</div>
</li>
</template>
</draggable>
</li>
</template>
</draggable>
</div>
</template>
<script setup>
import { ref, reactive, watch, computed } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import dayjs from 'dayjs';
import { getProjects, delProject, setProjectSort, exportWbs, importWbs } from 'apis';
import { RightOutlined, DownOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import draggable from 'vuedraggable';
const store = useStore();
const router = useRouter();
const visible = ref(false); //
const importVisible = ref(false); //
const importParent = ref(null); //
const importParentId = ref(null); // ID
const currUrl = ref(null); // url
const deleteId = ref(null); // id
const sessionProject = sessionStorage.getItem('project'); //
const projectInfo = computed(() => store.state.projects.project); //
const projects = computed(() => store.state.projects.projects); //
const startTime = computed(() => store.state.layout.startTime); //
const endTime = computed(() => store.state.layout.endTime); //
const refreshProjects = computed(() => store.state.layout.refreshProjects); //
const moveProjectId = ref(''); // id
const moveBusinessCode = ref(''); //
const targetProjectId = ref(''); // id
if (sessionProject && !projectInfo.value.id) {
// storestorestore
const info = JSON.parse(sessionProject);
store.commit('projects/setProject', info);
}
//
if (!startTime.value) {
const data = {
startTime: dayjs().startOf('day').format('x'),
endTime: dayjs().endOf('day').format('x'),
};
store.commit('layout/setSelectTime', data);
}
getProjectsList();
watch([startTime, endTime, refreshProjects], () => {
getProjectsList();
});
//
async function getProjectsList() {
try {
const data = await getProjects(startTime.value, endTime.value);
data.forEach(item => {
item.show = false;
if (item.sonProjectList.length > 0) {
item.show = true;
}
});
store.commit('projects/setProjects', data);
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
function toDetail(item) {
clearRolesData();
clearTasksData();
store.commit('projects/setProject', item);
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', '');
router.push({ path: '/tall/pc/home/test' });
}
//
function changeShow(item) {
item.show = !item.show;
}
//
const showImportCard = item => {
importVisible.value = true;
importParent.value = item.name;
importParentId.value = item.id;
currUrl.value = item.url;
};
//
async function handleImport() {
importVisible.value = false;
await importProject();
getProjectsList();
}
//
async function importProject() {
document.querySelector('.import-wbs').click();
}
async function toImport(e) {
try {
const file = e.target.files[0];
const param = new FormData();
param.append('file', file);
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
const res = await importWbs(currUrl.value, param, importParentId.value);
} catch (error) {
message.info(error || '导入失败');
}
}
//
const showDelCard = item => {
visible.value = true;
deleteId.value = item.id;
};
//
async function handleOk() {
visible.value = false;
await deleteProject(deleteId.value);
getProjectsList();
}
//
async function deleteProject(param) {
try {
await delProject(param);
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
async function exportProject(id) {
try {
const params = { param: { projectId: id } };
const { url } = store.state.projects.project;
const data = await exportWbs(params, url);
window.open(data.url, '_blank');
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
function clearRolesData() {
store.commit('role/setInvisibleRoles', []);
store.commit('role/setVisibleRoles', []);
store.commit('role/setRoleId', '');
}
//
function clearTasksData() {
//
store.commit('task/setPermanents', []);
store.commit('task/setDailyTasks', []);
//
store.commit('task/clearTasks');
//
store.commit('task/clearRealTasks');
store.commit('task/setUpNextPage', 1);
store.commit('task/setDownNextPage', 1);
}
//
function toWorkbench(item) {
toDetail(item);
store.commit('task/setTaskDetailShow', 'workbench'); //
}
function checkMove(e) {
moveProjectId.value = e.draggedContext.element.id;
moveBusinessCode.value = e.draggedContext.element.businessCode;
targetProjectId.value = '';
}
function onEnd(e) {
projects.value.forEach(item => {
if (item.sonProjectList) {
const index = item.sonProjectList.findIndex(sec => sec.id === moveProjectId.value);
if (index > -1) targetProjectId.value = item.id;
}
});
const params = {
moveProjectId: moveProjectId.value,
targetProjectId: targetProjectId.value,
businessCode: moveBusinessCode.value,
};
handleSort(params);
}
//
async function handleSort(param) {
try {
const params = { param };
await setProjectSort(params);
message.info('层级关系修改成功');
} catch (error) {
message.info(error.msg || '层级关系修改失败');
throw new Error(error);
}
getProjectsList();
}
</script>
<style scoped>
.list-flex {
height: calc(100vh - 48px - 272px - 56px - 16px - 2px);
overflow-y: auto;
}
.list-flex::-webkit-scrollbar {
width: 0 !important;
}
.ant-divider-horizontal {
height: 16px;
background: #eeeeee;
margin: 0;
}
.project-list {
padding: 16px 0;
}
.h-60 {
height: 60px;
}
.h-70 {
height: 70px;
}
.one-level {
padding: 0 16px;
}
.two-level {
padding: 0 16px 0 32px;
}
.three-level {
padding: 0 16px 0 48px;
}
.icon {
margin-right: 8px;
width: 24px;
height: 24px;
}
.detail {
width: calc(100% - 46px);
}
.name {
margin-right: 8px;
font-size: 14px;
line-height: 1;
font-weight: 600;
max-width: calc(100% - 56px);
color: #333333;
}
.precent-num {
width: 48px;
height: 18px;
line-height: 18px;
text-align: center;
border-radius: 18px;
background-color: rgba(24, 144, 255, 0.2);
color: #1890ff;
font-size: 12px;
}
.time {
font-size: 12px;
color: #999999;
}
.right {
width: 14px;
margin-left: 30px;
}
.dragArea {
min-height: 5px;
/* outline: 1px dashed; */
}
</style>

509
src/components/tall/left/Projects.vue

@ -1,72 +1,205 @@
<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>
<div class="time">
{{ dayjs(Number(item.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(item.endTime)).format('MM-DD HH:mm') }}
<div class="list-flex">
<template v-for="(item, index) in projects">
<div class="item-box" v-if="item.businessCode === 'ZERO'" :key="index">
<div class="one-level h-70 cursor-pointer flex items-center">
<!-- <div class="icon" @click.stop="showActionCard(item)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> -->
<div class="flex-none">
<a-popover placement="bottomLeft">
<template #content>
<p class="cursor-pointer" @click="showImportCard(item)">导入</p>
<p class="m-0 cursor-pointer" @click="exportProject(item.id)">导出</p>
<!-- <p class="m-0 cursor-pointer" @click="showDelCard(item)">删除</p> -->
</template>
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div>
</a-popover>
</div>
</div>
<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="w-full flex items-center justify-between" @click="toDetail(item)">
<div class="detail">
<div class="name-box flex items-center">
<div class="name truncate">{{ sonItem.name }}</div>
<!-- <div class="precent-num">50%</div> -->
<div class="name-box flex items-center" :class="{ 'mb-2': item.businessCode !== 'ZERO' }">
<div class="name truncate">{{ item.name }}</div>
<div class="precent-num">
{{ item.status === 1 ? '进行中' : item.status === 2 ? '已结束' : item.status === 0 ? '未开始' : '暂停' }}
</div>
</div>
<div class="time">
{{ dayjs(Number(sonItem.startTime)).format('MM-DD HH:mm') }} {{ dayjs(Number(sonItem.endTime)).format('MM-DD HH:mm') }}
<div class="time" v-if="item.businessCode !== 'ZERO'">
{{ dayjs(Number(item.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(item.endTime)).format('MM-DD HH:mm') }}
</div>
</div>
<div class="right" @click.stop="openMenu">
<RightOutlined v-if="!sonItem.show" @click="changeShow(sonItem)" />
<DownOutlined v-else @click="changeShow(sonItem)" />
<div class="right flex justify-end items-center" @click.stop="openMenu">
<a-button v-if="item.businessCode === 'ZERO'" class="mr-2" shape="round" type="primary" @click="toWorkbench(item)">
工作台
</a-button>
<RightOutlined v-if="!item.show" @click="changeShow(item)" />
<DownOutlined v-else @click="changeShow(item)" />
</div>
</div>
</div>
<div class="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 class="two-box" v-if="item.show">
<div class="two-flex" v-for="(sonItem, sonIndex) in item.sonProjectList" :key="sonIndex">
<div class="two-level h-70 cursor-pointer flex items-center">
<!-- <div class="icon" @click.stop="showActionCard(sonItem)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> -->
<div class="flex-none">
<a-popover placement="bottomLeft">
<template #content>
<!-- <p class="cursor-pointer" @click="showImportCard(sonItem)">导入</p> -->
<p class="cursor-pointer" @click="exportProject(sonItem.id)">导出</p>
<p class="m-0 cursor-pointer" @click="showDelCard(sonItem)">删除</p>
</template>
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div>
</a-popover>
</div>
<div class="flex items-center justify-between" style="width: calc(100% - 32px)" @click="toDetail(sonItem)">
<div class="detail">
<div class="name-box mb-2 flex items-center">
<div class="name truncate">{{ sonItem.name }}</div>
<div class="precent-num">
{{ item.status === 1 ? '进行中' : item.status === 2 ? '已结束' : item.status === 0 ? '未开始' : '暂停' }}
</div>
</div>
<div class="time">
{{ dayjs(Number(sonItem.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(sonItem.endTime)).format('MM-DD HH:mm') }}
</div>
</div>
<div class="time">
{{ dayjs(Number(thirdItem.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(thirdItem.endTime)).format('MM-DD HH:mm') }}
<div class="right" @click.stop="openMenu">
<RightOutlined v-if="!sonItem.show" @click="changeShow(sonItem)" />
<DownOutlined v-else @click="changeShow(sonItem)" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<draggable
class="dragArea p-0 list-none"
tag="ul"
:list="projects"
:group="{ name: 'g1' }"
item-key="name"
:move="checkMove"
@end="onEnd"
>
<template #item="{ element }">
<li class="item-box" v-if="element.businessCode !== 'ZERO'">
<!-- <p>{{ element.name }}</p> -->
<div
class="one-level cursor-pointer h-60 flex items-center"
:class="{ 'h-70': element.sonProjectList && element.sonProjectList.length > 0 }"
:style="{ 'padding-top': element.sonProjectList && element.sonProjectList.length > 0 ? 0 : '5px' }"
>
<!-- <div class="icon" @click.stop="showActionCard(item)"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div> -->
<div class="flex-none">
<a-popover placement="bottomLeft">
<template #content>
<p class="cursor-pointer" @click="showImportCard(element)">导入</p>
<p class="cursor-pointer" @click="exportProject(element.id)">导出</p>
<p class="m-0 cursor-pointer" @click="showDelCard(element)">删除</p>
</template>
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div>
</a-popover>
</div>
<div class="w-full flex items-center justify-between" @click="toDetail(element)">
<div class="detail">
<div class="name-box flex items-center" :class="{ 'mb-2': element.businessCode !== 'ZERO' }">
<div class="name truncate">{{ element.name }}</div>
<div class="precent-num">
{{ element.status === 1 ? '进行中' : element.status === 2 ? '已结束' : element.status === 0 ? '未开始' : '暂停' }}
</div>
</div>
<div class="time" v-if="element.businessCode !== 'ZERO'">
{{ dayjs(Number(element.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(element.endTime)).format('MM-DD HH:mm') }}
</div>
</div>
<div class="right flex justify-end items-center" @click.stop="openMenu">
<a-button v-if="element.businessCode === 'ZERO'" class="mr-2" shape="round" type="primary" @click="toWorkbench(element)">
工作台
</a-button>
<RightOutlined v-if="!element.show" @click="changeShow(element)" />
<DownOutlined v-else @click="changeShow(element)" />
</div>
</div>
</div>
<draggable
class="dragArea p-0 list-none two-box"
tag="ul"
:list="element.sonProjectList"
:group="{ name: 'g1' }"
item-key="name"
:move="checkMove"
@end="onEnd"
>
<template #item="{ element }">
<li class="two-flex">
<!-- <p>{{ element.name }}</p> -->
<div class="two-level h-70 cursor-pointer flex items-center">
<!-- <div class="icon" @click.stop="showActionCard(sonItem)">
<img src="https://www.tall.wiki/staticrec/drag.svg" />
</div> -->
<div class="flex-none">
<a-popover placement="bottomLeft">
<template #content>
<!-- <p class="cursor-pointer" @click="showImportCard(sonItem)">导入</p> -->
<p class="cursor-pointer" @click="exportProject(element.id)">导出</p>
<p class="m-0 cursor-pointer" @click="showDelCard(element)">删除</p>
</template>
<div class="icon"><img src="https://www.tall.wiki/staticrec/drag.svg" /></div>
</a-popover>
</div>
<div class="flex items-center justify-between" style="width: calc(100% - 32px)" @click="toDetail(element)">
<div class="detail">
<div class="name-box mb-2 flex items-center">
<div class="name truncate">{{ element.name }}</div>
<div class="precent-num">
{{ element.status === 1 ? '进行中' : element.status === 2 ? '已结束' : element.status === 0 ? '未开始' : '暂停' }}
</div>
</div>
<div class="time">
{{ dayjs(Number(element.startTime)).format('MM-DD HH:mm') }}
{{ dayjs(Number(element.endTime)).format('MM-DD HH:mm') }}
</div>
</div>
<div class="right" @click.stop="openMenu">
<RightOutlined v-if="!element.show" @click="changeShow(element)" />
<DownOutlined v-else @click="changeShow(element)" />
</div>
</div>
</div>
</li>
</template>
</draggable>
</li>
</template>
</draggable>
<input class="import-wbs hidden" type="file" @change="toImport" />
<a-modal v-model:visible="importVisible" title="导入" @ok="handleImport">
<p>确定要导入到{{ importParent }}</p>
</a-modal>
<a-modal v-model:visible="visible" title="删除" @ok="handleOk">
<p>确定要删除吗</p>
@ -75,59 +208,38 @@
</template>
<script setup>
import { ref, watch, computed } from 'vue';
import { ref, reactive, watch, computed } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import dayjs from 'dayjs';
import { getProjects, delProject, getExperimentation } from 'apis';
import { getProjects, delProject, setProjectSort, exportWbs, importWbs } from 'apis';
import { RightOutlined, DownOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import draggable from 'vuedraggable';
const store = useStore();
const visible = ref(false);
const deleteId = ref(null);
// const projectId = sessionStorage.getItem('projectId'); // ID
const router = useRouter();
const visible = ref(false); //
const importVisible = ref(false); //
const importParent = ref(null); //
const importParentId = ref(null); // ID
const currUrl = ref(null); // url
const deleteId = ref(null); // id
const sessionProject = sessionStorage.getItem('project'); //
const projectInfo = computed(() => store.state.projects.project); //
const projects = computed(() => store.state.projects.projects); //
const startTime = computed(() => store.state.layout.startTime); //
const endTime = computed(() => store.state.layout.endTime); //
const refreshProjects = computed(() => store.state.layout.refreshProjects); //
//
const 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([]);
const moveProjectId = ref(''); // id
const moveBusinessCode = ref(''); //
const targetProjectId = ref(''); // id
// store
if (!projectInfo.value.id) {
const info = JSON.parse(sessionProject);
store.commit('projects/setProject', info);
}
if (sessionProject && !projectInfo.value.id) {
// storestorestore
const info = JSON.parse(sessionProject);
store.commit('projects/setProject', info);
}
//
@ -145,112 +257,80 @@ 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) {
if (item.sonProjectList.length > 0) {
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);
getSubProject();
} catch (error) {
message.info(error);
throw new Error(error);
}
}
function toDetail(item, sec, fir) {
item.show = true;
//
function toDetail(item) {
clearRolesData();
clearTasksData();
store.commit('projects/setProject', item);
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', '');
router.push({ path: '/tall/pc/home/test' });
}
const obj = {
id: item.id,
name: item.name,
url: item.url,
};
//
function changeShow(item) {
item.show = !item.show;
}
// 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 });
} 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 });
}
//
const showImportCard = item => {
importVisible.value = true;
importParent.value = item.name;
importParentId.value = item.id;
currUrl.value = item.url;
};
// store.commit('projects/setProjects', projectList.value);
store.commit('projects/setProject', obj);
store.commit('task/setTaskDetail', null);
//
async function handleImport() {
importVisible.value = false;
await importProject();
getProjectsList();
}
function changeShow(item) {
item.show = !item.show;
//
async function importProject() {
document.querySelector('.import-wbs').click();
}
async function toImport(e) {
try {
const file = e.target.files[0];
const param = new FormData();
param.append('file', file);
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
const res = await importWbs(currUrl.value, param, importParentId.value);
} catch (error) {
message.info(error || '导入失败');
}
}
//
const showDelCard = item => {
visible.value = true;
deleteId.value = item.id;
};
//
async function handleOk() {
visible.value = false;
await deleteProject(deleteId.value);
@ -267,17 +347,82 @@ async function deleteProject(param) {
}
}
// ID
async function getSubProject() {
//
async function exportProject(id) {
try {
const params = { param: { id: projectInfo.value.id } };
const data = await getExperimentation(params);
store.commit('projects/setExpreimentStatus', data ? data.status : 0);
const params = { param: { projectId: id } };
const { url } = store.state.projects.project;
const data = await exportWbs(params, url);
window.open(data.url, '_blank');
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
function clearRolesData() {
store.commit('role/setInvisibleRoles', []);
store.commit('role/setVisibleRoles', []);
store.commit('role/setRoleId', '');
}
//
function clearTasksData() {
//
store.commit('task/setPermanents', []);
store.commit('task/setDailyTasks', []);
//
store.commit('task/clearTasks');
//
store.commit('task/clearRealTasks');
store.commit('task/setUpNextPage', 1);
store.commit('task/setDownNextPage', 1);
}
//
function toWorkbench(item) {
toDetail(item);
store.commit('task/setTaskDetailShow', 'workbench'); //
}
function checkMove(e) {
moveProjectId.value = e.draggedContext.element.id;
moveBusinessCode.value = e.draggedContext.element.businessCode;
targetProjectId.value = '';
}
function onEnd(e) {
projects.value.forEach(item => {
if (item.sonProjectList) {
const index = item.sonProjectList.findIndex(sec => sec.id === moveProjectId.value);
if (index > -1) targetProjectId.value = item.id;
}
});
const params = {
moveProjectId: moveProjectId.value,
targetProjectId: targetProjectId.value,
businessCode: moveBusinessCode.value,
};
handleSort(params);
}
//
async function handleSort(param) {
try {
const params = { param };
await setProjectSort(params);
message.info('层级关系修改成功');
} catch (error) {
message.info(error.msg || '层级关系修改失败');
throw new Error(error);
}
getProjectsList();
}
</script>
<style scoped>
@ -299,6 +444,10 @@ async function getSubProject() {
padding: 16px 0;
}
.h-60 {
height: 60px;
}
.h-70 {
height: 70px;
}
@ -315,18 +464,14 @@ async function getSubProject() {
padding: 0 16px 0 48px;
}
.item-box .icon {
.icon {
margin-right: 8px;
width: 24px;
height: 24px;
}
.detail {
width: calc(100% - 76px);
}
.name-box {
margin-bottom: 8px;
width: calc(100% - 46px);
}
.name {
@ -347,7 +492,6 @@ async function getSubProject() {
background-color: rgba(24, 144, 255, 0.2);
color: #1890ff;
font-size: 12px;
font-weight: 600;
}
.time {
@ -359,4 +503,9 @@ async function getSubProject() {
width: 14px;
margin-left: 30px;
}
.dragArea {
min-height: 5px;
/* outline: 1px dashed; */
}
</style>

74
src/components/tall/plugin/Plugin.vue

@ -0,0 +1,74 @@
<template>
<p-task-title :task="task" v-if="pluginId === '1'" />
<!-- 交付物插件 -->
<p-deliver v-else-if="pluginId === '15'" />
<!-- 交付物插件2 -->
<p-deliver-second v-else-if="pluginId === '25'" />
<!-- 资源管理 -->
<p-source-manage v-else-if="pluginId === '16'" />
<!-- 财务审批统计 -->
<p-finance-audit v-else-if="pluginId === '17'" />
<!-- 财务条 -->
<p-finance v-else-if="pluginId === '18'" />
<!-- 个人和终端按钮-->
<p-account-management v-else-if="pluginId === '19'" />
<!-- 域资源管理 -->
<p-domain-source-manage v-else-if="pluginId === '20'" />
<!-- 项目版本管理 -->
<p-project-version-management v-else-if="pluginId === '21'" />
<!-- 任务名和跳转详情页箭头 -->
<p-task-to-detail :task="task" v-else-if="pluginId === '24'"></p-task-to-detail>
<!-- 流水账插件 -->
<p-daily-account v-else-if="pluginId === '26'"></p-daily-account>
<Render
v-else
:task="task"
:pluginId="pluginId"
:styleType="styleType"
:pluginTaskId="pluginTaskId"
:businessPluginId="businessPluginId"
:param="param"
/>
</template>
<script setup>
import { provide, defineProps } from 'vue';
// import { useStore } from 'vuex';
import pTaskTitle from '@/plugins/p-task-title.vue';
import pDeliver from '@/plugins/p-deliver/p-deliver.vue';
import pDeliverSecond from '@/plugins/p-deliver-second/p-deliver-second.vue';
import pDailyAccount from '@/plugins/p-daily-account/p-daily-account.vue';
import pSourceManage from '@/plugins/p-source-manage/p-source-manage.vue';
import pProjectVersionManagement from '@/plugins/p-project-version-management/p-project-version-management.vue';
import pDomainSourceManage from '@/plugins/p-domain-source-manage/p-domain-source-manage.vue';
import pAccountManagement from '@/plugins/p-account-management/p-account-management.vue';
import pFinance from '@/plugins/p-finance/p-finance.vue';
import pFinanceAudit from '@/plugins/p-finance/p-finance-audit.vue';
import pTaskToDetail from '@/plugins/p-task-to-detail/p-task-to-detail.vue';
const props = defineProps({
task: { default: () => {}, type: Object },
pluginId: { default: '1', type: String },
styleType: { default: 0, type: Number },
pluginTaskId: { default: '', type: String },
businessPluginId: { default: '', type: String },
param: { type: String, default: '' },
pluginInfo: { default: () => {}, type: Object },
});
provide('task', props.task);
provide('pluginInfo', props.pluginInfo);
// const store = useStore();
// const roleId = computed(() => store.state.role.roleId);
// const token = computed(() => store.state.user.token);
// const userId = computed(() => store.getters['user/userId']);
// const projectId = computed(() => store.getters['project/projectId']);
</script>

11
src/components/tall/right/DetailWebview.vue

@ -0,0 +1,11 @@
<template>
<iframe :src="taskDetailUrl" class="w-full h-full"></iframe>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const taskDetailUrl = computed(() => store.state.task.taskDetailUrl); // iframe
</script>

210
src/components/tall/task/AssignmentExperiment.vue

@ -1,210 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form ref="formRef" :model="assignExperFormData">
<a-form-item>
<label class="color-3">实验名称</label>
<a-input v-model:value="assignExperFormData.name" placeholder="实验名称" />
</a-form-item>
<a-form-item>
<label class="color-3">完成期限</label>
<a-space direction="vertical" :size="12">
<a-range-picker v-model:value="assignExperFormData.date" />
</a-space>
</a-form-item>
<a-form-item>
<label class="color-3">负责人</label>
<a-select
v-model:value="assignExperFormData.memberId"
show-search
optionFilterProp="label"
placeholder="负责人"
:options="options"
:filter-option="filterOption"
@search="handleSearch"
:getPopupContainer="
triggerNode => {
return triggerNode.parentNode || document.body;
}
"
></a-select>
</a-form-item>
<a-form-item>
<label class="color-3">实验目标</label>
<a-textarea v-model:value="assignExperFormData.target" placeholder="实验目标" />
</a-form-item>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">确定</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { createExperiment, memberQuery, getExperimentation } from 'apis';
import { message } from 'ant-design-vue';
const store = useStore();
const formRef = ref(null);
const projectId = computed(() => store.getters['projects/projectId']);
const options = ref([]);
const experimentationId = computed(() => store.state.task.experimentationId); // ID
//
// const subStartTime = computed(() => store.state.layout.subStartTime);
// const subEndTime = computed(() => store.state.layout.subEndTime);
// console.log(subStartTime.value, subEndTime.value);
if (experimentationId.value) {
getSubProject(experimentationId.value);
}
watch(experimentationId, async () => {
await getSubProject(experimentationId.value);
});
const assignExperFormData = ref({
projectId: projectId.value,
id: '',
name: '',
memberId: '',
date: [],
startTime: '',
endTime: '',
target: '',
});
getList(); //
const handleSearch = async value => {
await getList(value); //
};
const filterOption = (input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const onSubmit = async () => {
if (assignExperFormData.value.date.length > 0) {
assignExperFormData.value.date.forEach((item, index) => {
if (index === 0) {
assignExperFormData.value.startTime = dayjs(item).format('x');
} else {
assignExperFormData.value.endTime = dayjs(item).format('x');
}
});
// if (subStartTime.value && assignExperFormData.value.startTime < subStartTime.value) {
// message.info('');
// return false;
// }
// if (subEndTime.value && assignExperFormData.value.endTime > subEndTime.value) {
// message.info('');
// return false;
// }
}
try {
const params = { param: assignExperFormData.value };
await createExperiment(params);
store.commit('layout/setThirdPlanTime', { startTime: assignExperFormData.value.startTime, endTime: assignExperFormData.value.endTime });
await getSubProject(experimentationId.value);
} catch (error) {
message.info(error);
}
};
//
async function getList(name) {
try {
const params = { param: { projectId: projectId.value, name } };
const data = await memberQuery(params);
store.commit('task/setMembers', data);
options.value = [];
data.forEach(item => {
const obj = {
label: item.memberName,
value: item.memberId,
};
options.value.push(obj);
});
} catch (error) {
message.info(error);
throw new Error(error);
}
}
async function getSubProject(id) {
try {
const params = { param: { id } };
const data = await getExperimentation(params);
store.commit('layout/setRefreshProjects');
if (data) {
data.date = [];
if (data.startTime) {
const start = dayjs(Number(data.startTime));
const end = dayjs(Number(data.endTime));
data.date = [start, end];
}
data.projectId = projectId.value;
assignExperFormData.value = data;
} else {
assignExperFormData.value = {
projectId: projectId.value,
id: '',
name: '',
memberId: '',
date: [],
startTime: '',
endTime: '',
target: '',
};
}
} catch (error) {
message.info(error);
throw new Error(error);
}
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

453
src/components/tall/task/AssignmentSubject.vue

@ -1,453 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form ref="formRef" :model="topicSubFormData">
<a-form-item>
<label class="color-3">子课题名称</label>
<a-input v-model:value="topicSubFormData.name" placeholder="子课题名称" />
</a-form-item>
<a-form-item>
<label class="color-3">子课题负责人</label>
<a-select
v-model:value="topicSubFormData.memberId"
show-search
optionFilterProp="label"
placeholder="负责人"
:options="options"
:filter-option="filterOption"
@search="handleSearch"
:getPopupContainer="
triggerNode => {
return triggerNode.parentNode || document.body;
}
"
></a-select>
</a-form-item>
<a-form-item>
<label class="color-3">完成期限</label>
<a-space direction="vertical" :size="12">
<a-range-picker v-model:value="topicSubFormData.date" />
</a-space>
</a-form-item>
<a-form-item class="form-item-dad">
<div class="flex items-center" style="margin-bottom: 5px">
<label class="color-3" style="margin-bottom: 0; margin-right: 8px">进度安排</label>
<PlusCircleOutlined style="color: #1890ff; font-size: 16px" @click="addMilestones" />
</div>
<div class="form-item-son" style="padding-left: 16px">
<div v-for="(item, index) in stageList" :key="index">
<a-form-item>
<label class="color-3">时间</label>
<a-space direction="vertical" :size="12">
<a-range-picker v-model:value="item.date" />
</a-space>
</a-form-item>
<a-form-item>
<label class="color-3">指标</label>
<a-checkbox-group v-model:value="item.checkContent">
<a-row>
<a-col :span="5" class="deliverables">
<a-checkbox value="1" @change="handleChange($event, item)">
<span class="color-6">论文</span>
<a-input v-model:value="item.thesis" @change="handleInput($event, item, '1')" />
</a-checkbox>
</a-col>
<a-col :span="15" class="deliverables">
<a-checkbox value="2" @change="handleChange($event, item)">
<span class="color-6">专利</span>
<a-input v-model:value="item.patent" @change="handleInput($event, item, '2')" />
</a-checkbox>
</a-col>
<a-col :span="4" class="deliverables">
<a-checkbox value="3" @change="handleChange($event, item)">
<span class="color-6">软著</span>
<a-input v-model:value="item.theSoft" @change="handleInput($event, item, '3')" />
</a-checkbox>
</a-col>
<a-col :span="5" class="deliverables-son" style="padding-left: 14px">
<a-checkbox value="4" @change="handleChange($event, item)">
<span class="color-6">SCI论文</span>
<a-input v-model:value="item.sciThesis" @change="handleInput($event, item, '4')" />
</a-checkbox>
</a-col>
<a-col :span="5" class="deliverables-son" style="padding-left: 14px">
<a-checkbox value="5" @change="handleChange($event, item)">
<span class="color-6">发明专利</span>
<a-input v-model:value="item.inventPatent" @change="handleInput($event, item, '5')" />
</a-checkbox>
</a-col>
<a-col :span="5" class="deliverables-son">
<a-checkbox value="6" @change="handleChange($event, item)">
<span class="color-6">实用新型</span>
<a-input v-model:value="item.practicalPatent" @change="handleInput($event, item, '6')" />
</a-checkbox>
</a-col>
<a-col :span="5" class="deliverables-son">
<a-checkbox value="7" @change="handleChange($event, item)">
<span class="color-6">外观专利</span>
<a-input v-model:value="item.facadePatent" @change="handleInput($event, item, '7')" />
</a-checkbox>
</a-col>
<a-col :span="4" class="deliverables-son"></a-col>
</a-row>
</a-checkbox-group>
</a-form-item>
</div>
</div>
</a-form-item>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">确定</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
import { PlusCircleOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { saveSubExperiment, memberQuery, getSubExperiment } from 'apis';
import dayjs from 'dayjs';
const store = useStore();
const formRef = ref(null);
const projectId = computed(() => store.getters['projects/projectId']); // ID
const options = ref([]);
const detailId = computed(() => store.state.task.detailId); // ID
//
const planStartTime = computed(() => store.state.layout.planStartTime);
const planEndTime = computed(() => store.state.layout.planEndTime);
const topicSubFormData = ref({
projectId: projectId.value,
id: detailId.value,
name: '',
memberId: '',
date: [],
startTime: '',
endTime: '',
stageDtoList: [],
});
const stageList = ref([
{
date: [],
startTime: '',
endTime: '',
checkContent: [],
thesis: '',
sciThesis: '',
patent: '',
inventPatent: '',
practicalPatent: '',
facadePatent: '',
theSoft: '',
},
]);
if (detailId.value) {
getSubProject(detailId.value);
} else {
renderData();
}
watch(detailId, async () => {
if (detailId.value) {
await getSubProject(detailId.value);
} else {
renderData();
}
});
getList(); //
//
function addMilestones() {
stageList.value.push({
date: [],
startTime: '',
endTime: '',
checkContent: [],
thesis: '',
sciThesis: '',
patent: '',
inventPatent: '',
practicalPatent: '',
facadePatent: '',
theSoft: '',
});
}
const handleChange = (e, data) => {
if (e.target.checked) {
if (e.target.value === '4') {
if (data.checkContent.indexOf('1') === -1) {
data.checkContent.push('1');
}
} else if (e.target.value === '5' || e.target.value === '6' || e.target.value === '7') {
if (data.checkContent.indexOf('2') === -1) {
data.checkContent.push('2');
}
}
} else if (e.target.value === '1') {
data.thesis = 0;
} else if (e.target.value === '2') {
data.patent = 0;
} else if (e.target.value === '3') {
data.theSoft = 0;
} else if (e.target.value === '4') {
data.sciThesis = 0;
} else if (e.target.value === '5') {
data.inventPatent = 0;
} else if (e.target.value === '6') {
data.practicalPatent = 0;
} else if (e.target.value === '7') {
data.facadePatent = 0;
}
};
const handleInput = (e, data, label) => {
if (e.data > 0) {
if (data.checkContent.indexOf(label) === -1) {
data.checkContent.push(label);
}
if (label === '4') {
if (data.checkContent.indexOf('1') === -1) data.checkContent.push('1');
}
if (label === '5' || label === '6' || label === '7') {
if (data.checkContent.indexOf('2') === -1) data.checkContent.push('2');
}
} else {
if (data.checkContent.indexOf(label) > -1) {
data.checkContent.splice(data.checkContent.indexOf(label), 1);
}
if (label === '4') {
if (data.checkContent.indexOf('1') > -1) data.checkContent.splice(data.checkContent.indexOf('1'), 1);
}
if (label === '5' || label === '6' || label === '7') {
if (data.checkContent.indexOf('2') > -1) data.checkContent.splice(data.checkContent.indexOf('2'), 1);
}
}
};
const handleSearch = async value => {
await getList(value); //
};
const filterOption = (input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
//
async function getList(name) {
try {
const params = { param: { projectId: projectId.value, name } };
const data = await memberQuery(params);
store.commit('task/setMembers', data);
options.value = [];
data.forEach(item => {
const obj = {
label: item.memberName,
value: item.memberId,
};
options.value.push(obj);
});
} catch (error) {
message.info(error);
throw new Error(error);
}
}
const onSubmit = async () => {
let msgText = '';
if (topicSubFormData.value.date) {
topicSubFormData.value.date.forEach((item, index) => {
if (index === 0) {
topicSubFormData.value.startTime = dayjs(item).format('x');
} else {
topicSubFormData.value.endTime = dayjs(item).format('x');
}
});
if (topicSubFormData.value.startTime < planStartTime.value) {
msgText = '子课题开始时间不能小于项目起始时间';
}
if (topicSubFormData.value.endTime > planEndTime.value) {
msgText = '子课题结束时间不能大于项目终止时间';
}
}
stageList.value.forEach(item => {
if (item.date.length > 0) {
item.date.forEach((val, key) => {
if (key === 0) {
item.startTime = dayjs(val).format('x');
} else {
item.endTime = dayjs(val).format('x');
}
});
if (item.startTime < topicSubFormData.value.startTime || item.endTime > topicSubFormData.value.endTime) {
msgText = '子课题进度安排起止必须在子课题起止时间';
}
}
if (item.checkContent.indexOf('1') > -1) {
if (!item.thesis) msgText = '请填写论文数量';
}
if (item.checkContent.indexOf('2') > -1) {
if (!item.patent) msgText = '请填写专利数量';
}
if (item.checkContent.indexOf('3') > -1) {
if (!item.theSoft) msgText = '请填写软著数量';
}
if (item.checkContent.indexOf('4') > -1) {
if (!item.sciThesis) msgText = '请填写SCI论文数量';
}
if (item.checkContent.indexOf('5') > -1) {
if (!item.inventPatent) msgText = '请填写发明专利数量';
}
if (item.checkContent.indexOf('6') > -1) {
if (!item.practicalPatent) msgText = '请填写实用新型专利数量';
}
if (item.checkContent.indexOf('7') > -1) {
if (!item.facadePatent) msgText = '请填写外观专利数量';
}
if (item.thesis < item.sciThesis) msgText = 'SCI论文数量不能比总论文数量大';
const totalPatent = Number(item.inventPatent) + Number(item.practicalPatent) + Number(item.facadePatent);
if (item.patent < totalPatent) msgText = '发明专利数量、实用新型专利数量、外观专利数量总和不能比总专利数量大';
});
if (msgText) {
message.info(msgText);
return false;
}
topicSubFormData.value.stageDtoList = [...stageList.value];
const params = { param: topicSubFormData.value };
await saveSubExperiment(params);
store.commit('layout/setRefreshProjects');
store.commit('layout/setSecPlanTime', { startTime: topicSubFormData.value.startTime, endTime: topicSubFormData.value.endTime });
if (detailId.value) {
getSubProject(detailId.value);
} else {
renderData();
}
};
async function getSubProject(id) {
try {
const params = { param: { taskDetailId: id } };
const data = await getSubExperiment(params);
if (data) {
const start = dayjs(Number(data.startTime));
const end = dayjs(Number(data.endTime));
data.date = [start, end];
data.projectId = projectId.value;
topicSubFormData.value = data;
data.subExperimentStageDtoList.forEach(item => {
item.startTime = item.stageStartTime;
item.endTime = item.stageEndTime;
console.log(item.startTime !== '0');
if (item.startTime !== '0') {
item.date = [dayjs(Number(item.startTime)), dayjs(Number(item.endTime))];
}
item.checkContent = [];
if (item.thesis) item.checkContent.push('1');
if (item.patent) item.checkContent.push('2');
if (item.theSoft) item.checkContent.push('3');
if (item.sciThesis) item.checkContent.push('4');
if (item.inventPatent) item.checkContent.push('5');
if (item.practicalPatent) item.checkContent.push('6');
if (item.facadePatent) item.checkContent.push('7');
});
stageList.value = data.subExperimentStageDtoList;
} else {
//
renderData();
}
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
function renderData() {
topicSubFormData.value = {
projectId: projectId.value,
id: detailId.value,
name: '',
memberId: '',
date: [],
startTime: '',
endTime: '',
stageDtoList: [],
};
stageList.value = [
{
date: [],
startTime: '',
endTime: '',
checkContent: [],
thesis: '',
sciThesis: '',
patent: '',
inventPatent: '',
practicalPatent: '',
facadePatent: '',
theSoft: '',
},
];
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

192
src/components/tall/task/CheckSubjectProgress.vue

@ -1,192 +0,0 @@
<template>
<div class="task-progress flex flex-wrap justify-between">
<div class="wrap overflow-hidden">
<a-card title="任务目标">
<div class="flex flex-wrap justify-center">
<div class="achievements border border-right border-bottom text-center">
<p class="num">{{ infoOne.thesis }}/{{ infoOne.totalThesis }}</p>
<p class="name">论文</p>
</div>
<div class="achievements border border-bottom text-center">
<p class="num">{{ infoOne.patent }}/{{ infoOne.totalPatent }}</p>
<p class="name">专利</p>
</div>
<div class="achievements border border-right text-center">
<p class="num">{{ infoOne.theSoft }}/{{ infoOne.totalTheSoft }}</p>
<p class="name">软著</p>
</div>
<div class="achievements border text-center">
<p class="num">{{ infoOne.meeting }}/{{ infoOne.totalMeeting }}</p>
<p class="name">会议</p>
</div>
</div>
</a-card>
</div>
<div class="wrap overflow-hidden">
<a-card title="概览">
<div class="topic">
<p>{{ infoOne.name }}</p>
<a-progress
:percent="infoOne.masterSchedule"
:strokeWidth="22"
:show-info="false"
:stroke-color="'#1890FF'"
:trail-color="'rgba(24, 144, 255, 0.2)'"
/>
</div>
<div class="sub-topic flex justify-between flex-wrap">
<div class="topic" v-for="(item, index) in infoSec" :key="index">
<p>{{ item.name }}</p>
<a-progress
:percent="item.masterSchedule"
:strokeWidth="22"
:show-info="false"
:stroke-color="colorList[index % 4].color"
:trail-color="colorList[index % 4].bgColor"
/>
</div>
</div>
</a-card>
</div>
<div class="wrap overflow-hidden" v-for="(item, index) in infoSec" :key="index">
<a-card :title="item.name">
<div class="flex flex-wrap justify-center">
<div class="achievements border border-right border-bottom text-center">
<p class="num">{{ item.thesis }}/{{ item.totalThesis }}</p>
<p class="name">论文</p>
</div>
<div class="achievements border border-bottom text-center">
<p class="num">{{ item.patent }}/{{ item.totalPatent }}</p>
<p class="name">专利</p>
</div>
<div class="achievements border border-right text-center">
<p class="num">{{ item.theSoft }}/{{ item.totalTheSoft }}</p>
<p class="name">软著</p>
</div>
<div class="achievements border text-center">
<p class="num">{{ item.meeting }}/{{ item.totalMeeting }}</p>
<p class="name">会议</p>
</div>
</div>
</a-card>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { getExperimentProgress } from 'apis';
import { useStore } from 'vuex';
import { message } from 'ant-design-vue';
const store = useStore();
const projectId = computed(() => store.getters['projects/projectId']);
const infoOne = ref({});
const infoSec = ref([]);
const colorList = ref([
{ color: '#FF9191', bgColor: 'rgba(255, 145, 145, 0.2)' },
{ color: '#FF934B', bgColor: 'rgba(255, 147, 75, 0.2)' },
{ color: '#B991FF', bgColor: 'rgba(185, 145, 255, 0.2)' },
{ color: '#91C1FF', bgColor: 'rgba(145, 193, 255, 0.2)' },
]);
progress();
async function progress() {
try {
const params = { param: { projectId: projectId.value } };
const data = await getExperimentProgress(params);
infoOne.value = data.target;
infoSec.value = data.subTargetList;
} catch (error) {
message.info(error);
throw new Error(error);
}
}
</script>
<style scoped>
.wrap {
margin-top: 16px;
width: calc((100% - 16px) / 2);
background-color: #fff;
border-radius: 10px;
border: 1px solid #cccccc;
}
.wrap:nth-child(-n + 2) {
margin-top: 0;
}
:deep(.ant-card-head) {
padding: 0 16px;
min-height: 45px;
max-height: 45px;
border-color: #cccccc;
font-size: 14px;
}
:deep(.ant-card-head-title) {
padding: 0;
line-height: 45px;
}
.wrap .num {
font-size: 22px;
color: #4b8aff;
}
.wrap .name {
color: #666666;
}
.wrap p {
margin: 0;
}
.border {
border-color: #fff;
}
.border-right {
border-right-color: #cccccc;
}
.border-left {
border-left-color: #cccccc;
}
.border-top {
border-top-color: #cccccc;
}
.border-bottom {
border-bottom-color: #cccccc;
}
.achievements {
width: 40%;
padding: 25px 0;
}
.topic p {
margin-bottom: 8px;
color: #666666;
}
.sub-topic .topic {
margin-top: 40px;
width: calc((100% - 32px) / 2);
}
.ant-card {
height: 100%;
}
.ant-card :deep(.ant-card-body) {
height: calc(100% - 48px);
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

246
src/components/tall/task/Conclusion.vue

@ -1,246 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传项目结题报告</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
import { message } from 'ant-design-vue';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
//
const planStartTime = computed(() => store.state.layout.planStartTime);
const planEndTime = computed(() => store.state.layout.planEndTime);
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
const time = dayjs(item.date).format('x');
if (time < planStartTime.value || time > planEndTime.value) {
message.info('中期检查时间必须在项目起止时间之内');
return false;
}
obj.answerList.push(time);
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

235
src/components/tall/task/ContractManagement.vue

@ -1,235 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传合同扫描件 </a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
obj.answerList.push(dayjs(item.date).format('x'));
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

172
src/components/tall/task/DataUnlock.vue

@ -1,172 +0,0 @@
<template>
<div class="flex flex-wrap">
<a-card title="解锁申请" v-for="(item, index) in unlockList" :key="index">
<p>
<span class="color-9">申请人</span><span class="color-3">{{ item.proposer }}</span>
</p>
<p>
<span class="color-9">实验名称</span><span class="color-3">{{ item.experimentName }}</span>
</p>
<p>
<span class="color-9">申请时间</span><span class="color-3">{{ dayjs(Number(item.time)).format('YYYY-MM-DD HH:mm') }}</span>
</p>
<p>
<span class="color-9">申请原因</span><span class="color-3">{{ item.remark }}</span>
</p>
<div class="flex justify-end">
<a-button class="action-btn edit-btn" type="primary" @click="toExamine(0, item.experimentId)">通过</a-button>
<a-button class="action-btn del-btn" type="primary" @click="showModal(item.experimentId)">驳回</a-button>
</div>
</a-card>
</div>
<!-- 拒绝模态框 -->
<a-modal v-model:visible="visible" :closable="false" @ok="handleOk">
<div class="modal-title flex items-center">
<CloseCircleFilled style="margin-right: 8px; font-size: 18px; color: #ff5353" />
<span class="color-3" style="font-size: 18px; font-weight: 600">确定要驳回该条解锁申请吗</span>
</div>
<div class="modal-con color-9" style="padding-left: 24px; margin-top: 16px">
<div style="margin-bottom: 5px; font-size: 16px; line-height: 26px">驳回原因</div>
<a-textarea v-model:value="remark" placeholder="驳回原因" :auto-size="{ minRows: 2, maxRows: 5 }" />
</div>
</a-modal>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { retrospectUnlock, examineUnlock } from 'apis';
import { CloseCircleFilled } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
const store = useStore();
const projectId = computed(() => store.getters['projects/projectId']);
const unlockList = ref([]);
const visible = ref(false);
const remark = ref(null);
const experimentId = ref(null);
getUnlockList();
const showModal = id => {
visible.value = true;
experimentId.value = id;
};
const handleOk = e => {
console.log(e);
visible.value = false;
toExamine(1, experimentId.value);
};
//
async function getUnlockList() {
try {
const params = { param: { projectId: projectId.value } };
const data = await retrospectUnlock(params);
unlockList.value = data;
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
async function toExamine(type, id) {
try {
const params = {
param: {
projectId: projectId.value,
type,
experimentId: id,
remark: remark.value,
},
};
const data = await examineUnlock(params);
unlockList.value = data;
getUnlockList();
} catch (error) {
message.info(error);
throw new Error(error);
}
}
</script>
<style scoped>
.ant-card {
border-radius: 10px;
margin-right: 16px;
margin-bottom: 16px;
}
@media screen and (max-width: 1650px) {
.ant-card {
width: calc(50% - 8px);
}
.ant-card:nth-child(2n) {
margin-right: 0;
}
}
@media screen and (min-width: 1651px) {
.ant-card {
width: calc((100% - 32px) / 3);
}
.ant-card:nth-child(3n) {
margin-right: 0;
}
}
:deep(.ant-card-head) {
padding: 0 16px;
height: 45px;
min-height: 45px;
}
:deep(.ant-card-head-title) {
padding: 0;
line-height: 45px;
font-size: 14px;
color: #333;
}
:deep(.ant-card-body) {
padding: 24px 16px;
}
:deep(.ant-card-body) p {
line-height: 1;
margin-bottom: 16px;
}
:deep(.ant-card-body) p span:first-of-type {
display: inline-block;
width: 80px;
}
.action-btn {
width: 50px !important;
height: 28px !important;
font-size: 14px !important;
padding: 0;
letter-spacing: 0 !important;
}
.edit-btn {
background: #0dc26c;
border: 0;
}
.del-btn {
margin-left: 16px;
background: #ff5353;
border: 0;
}
</style>

287
src/components/tall/task/ExperimentalCode.vue

@ -1,287 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true">
确定
</a-button>
</a-form-item>
</a-form>
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" />
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
// import { message } from 'ant-design-vue';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
const isShowSuccess = ref(false);
const isShowWarning = ref(false);
const tipsMessage = ref('');
//
// const expreStartTime = computed(() => store.state.layout.expreStartTime);
// const expreEndTime = computed(() => store.state.layout.expreEndTime);
//
const expreStatus = computed(() => store.state.projects.expreStatus);
const sessionStatus = sessionStorage.getItem('expreStatus');
if (!expreStatus.value && sessionStatus) {
store.commit('projects/setExpreimentStatus', sessionStatus);
}
if (expreStatus.value) {
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) {
isShowWarning.value = true;
tipsMessage.value = '数据已锁定,不可操作';
} else {
isShowSuccess.value = true;
tipsMessage.value = '数据未锁定,可操作';
}
setTimeout(() => {
isShowWarning.value = false;
isShowSuccess.value = false;
}, 3000);
}
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
const time = dayjs(item.date).format('x');
// if (time < expreStartTime.value || time > expreEndTime.value) {
// message.info('');
// return false;
// }
obj.answerList.push(time);
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-form {
position: relative;
}
.task-form :deep(.ant-alert) {
position: absolute;
right: 30px;
}
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

287
src/components/tall/task/ExperimentalData.vue

@ -1,287 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true">
确定
</a-button>
</a-form-item>
</a-form>
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" />
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
// import { message } from 'ant-design-vue';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
const isShowSuccess = ref(false);
const isShowWarning = ref(false);
const tipsMessage = ref('');
//
// const expreStartTime = computed(() => store.state.layout.expreStartTime);
// const expreEndTime = computed(() => store.state.layout.expreEndTime);
//
const expreStatus = computed(() => store.state.projects.expreStatus);
const sessionStatus = sessionStorage.getItem('expreStatus');
if (!expreStatus.value && sessionStatus) {
store.commit('projects/setExpreimentStatus', sessionStatus);
}
if (expreStatus.value) {
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) {
isShowWarning.value = true;
tipsMessage.value = '数据已锁定,不可操作';
} else {
isShowSuccess.value = true;
tipsMessage.value = '数据未锁定,可操作';
}
setTimeout(() => {
isShowWarning.value = false;
isShowSuccess.value = false;
}, 3000);
}
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
const time = dayjs(item.date).format('x');
// if (time < expreStartTime.value || time > expreEndTime.value) {
// message.info('');
// return false;
// }
obj.answerList.push(time);
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-form {
position: relative;
}
.task-form :deep(.ant-alert) {
position: absolute;
right: 30px;
}
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

277
src/components/tall/task/ExperimentalResult.vue

@ -1,277 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true">
确定
</a-button>
</a-form-item>
</a-form>
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" />
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
const isShowSuccess = ref(false);
const isShowWarning = ref(false);
const tipsMessage = ref('');
//
const expreStatus = computed(() => store.state.projects.expreStatus);
const sessionStatus = sessionStorage.getItem('expreStatus');
if (!expreStatus.value && sessionStatus) {
store.commit('projects/setExpreimentStatus', sessionStatus);
}
if (expreStatus.value) {
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) {
isShowWarning.value = true;
tipsMessage.value = '数据已锁定,不可操作';
} else {
isShowSuccess.value = true;
tipsMessage.value = '数据未锁定,可操作';
}
setTimeout(() => {
isShowWarning.value = false;
isShowSuccess.value = false;
}, 3000);
}
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
obj.answerList.push(dayjs(item.date).format('x'));
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-form {
position: relative;
}
.task-form :deep(.ant-alert) {
position: absolute;
right: 30px;
}
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

246
src/components/tall/task/InterimInspection.vue

@ -1,246 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传中期检查报告</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
import { message } from 'ant-design-vue';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
//
const planStartTime = computed(() => store.state.layout.planStartTime);
const planEndTime = computed(() => store.state.layout.planEndTime);
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
const time = dayjs(item.date).format('x');
if (time < planStartTime.value || time > planEndTime.value) {
message.info('中期检查时间必须在项目起止时间之内');
return false;
}
obj.answerList.push(time);
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

274
src/components/tall/task/LabReport.vue

@ -1,274 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="false"
:action="action"
:headers="headers"
:accept="'.pdf'"
@change="handleChange($event, index)"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true">
确定
</a-button>
</a-form-item>
</a-form>
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" />
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
// import { message } from 'ant-design-vue';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
const isShowSuccess = ref(false);
const isShowWarning = ref(false);
const tipsMessage = ref('');
//
// const expreStartTime = computed(() => store.state.layout.expreStartTime);
// const expreEndTime = computed(() => store.state.layout.expreEndTime);
//
const expreStatus = computed(() => store.state.projects.expreStatus);
const sessionStatus = sessionStorage.getItem('expreStatus');
if (!expreStatus.value && sessionStatus) {
store.commit('projects/setExpreimentStatus', sessionStatus);
}
if (expreStatus.value) {
console.log(expreStatus.value);
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) {
isShowWarning.value = true;
isShowSuccess.value = false;
tipsMessage.value = '数据已锁定,不可操作';
} else {
isShowWarning.value = false;
isShowSuccess.value = true;
tipsMessage.value = '数据未锁定,可操作';
}
setTimeout(() => {
isShowWarning.value = false;
isShowSuccess.value = false;
}, 3000);
}
getDataByCode();
const handleChange = (info, index) => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach((item, key) => {
if (key === resFileList.length - 1) {
arr.value.push(item);
}
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[index].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
const time = dayjs(item.date).format('x');
// if (time < expreStartTime.value || time > expreEndTime.value) {
// message.info('');
// return false;
// }
obj.answerList.push(time);
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-form {
position: relative;
}
.task-form :deep(.ant-alert) {
position: absolute;
right: 30px;
}
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

292
src/components/tall/task/MeetingManagement.vue

@ -1,292 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form :model="topicMeetFormData">
<a-form-item>
<label class="color-3">会议名称</label>
<a-input v-model:value="topicMeetFormData.name" placeholder="会议名称" />
</a-form-item>
<a-form-item>
<label class="color-3">会议日期</label>
<a-space direction="vertical" :size="12">
<a-range-picker v-model:value="topicMeetFormData.date" />
</a-space>
</a-form-item>
<a-form-item>
<label class="color-3">会议地点</label>
<a-input v-model:value="topicMeetFormData.address" placeholder="会议地点" />
</a-form-item>
<a-form-item>
<label class="color-3">会议通知</label>
<a-upload-dragger
v-model:fileList="notificationList"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
@change="handleChange($event, 1)"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
<a-form-item>
<label class="color-3">会议纪要</label>
<a-upload-dragger
v-model:fileList="summaryList"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
@change="handleChange($event, 2)"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
<a-form-item>
<label class="color-3">照片附件/其他</label>
<a-upload-dragger
v-model:fileList="attachmentList"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.jpg,.jpeg,.rar,.zip,.png'"
@change="handleChange($event, 3)"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式jpgjpegrarzip</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>

258
src/components/tall/task/MemberManagement.vue

@ -1,258 +0,0 @@
<template>
<div>
<a-table
class="member-list"
:columns="columns"
:data-source="dataList"
:row-class-name="(_record, index) => (index % 2 === 1 ? null : 'table-striped')"
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.key === 'action'">
<template v-if="record.isEdit === 0">
<a-button class="action-btn edit-btn" type="primary" @click="toEdit(record.key)">编辑</a-button>
<a-button class="action-btn del-btn" type="primary" @click="toDelete(record.key)">删除</a-button>
</template>
<template v-if="record.isEdit === 1">
<a-button class="action-btn edit-btn" type="primary" @click="save(record.key)">保存</a-button>
<a-button class="action-btn del-btn" type="primary" @click="cancel(record.key)">取消</a-button>
</template>
</template>
<template v-else-if="['memberName', 'phone'].includes(column.dataIndex)">
<div>
<a-input v-if="record.isEdit === 1" v-model:value="record[column.dataIndex]" style="margin: -5px 0" />
<template v-else>
{{ text }}
</template>
</div>
</template>
</template>
<template #footer><PlusCircleOutlined @click="addMember" style="font-size: 24px; color: #1890ff" /></template>
</a-table>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import { PlusCircleOutlined } from '@ant-design/icons-vue';
import { memberQuery, saveMember, updateMember, delMember } from 'apis';
import { message } from 'ant-design-vue';
const store = useStore();
const projectId = computed(() => store.getters['projects/projectId']);
const isAdd = ref(false);
const columns = ref([
{
title: '序号',
dataIndex: 'indexId',
key: 'indexId',
},
{
title: '成员名',
dataIndex: 'memberName',
key: 'memberName',
},
{
title: '手机号',
dataIndex: 'phone',
key: 'phone',
},
{
title: '角色',
dataIndex: 'roles',
key: 'roles',
},
{
title: '操作',
key: 'action',
dataIndex: 'action',
},
]);
const dataList = ref([]);
getList();
async function getList() {
try {
const params = { param: { projectId: projectId.value } };
const data = await memberQuery(params);
store.commit('task/setMembers', data);
dataList.value = [];
data.forEach((item, index) => {
const obj = {
key: index + 1,
indexId: index + 1,
memberId: item.memberId,
memberName: item.memberName,
phone: item.phone,
roles: item.experimentNameList.toString(),
isEdit: 0,
};
dataList.value.push(obj);
});
} catch (error) {
message.info(error);
throw new Error(error);
}
}
const addMember = () => {
const num = dataList.value.length + 1;
const obj = { key: num, indexId: num, memberName: '', phone: '', isEdit: 1 };
dataList.value.push(obj);
isAdd.value = true;
};
function toEdit(key) {
isAdd.value = false;
dataList.value.forEach(item => {
if (item.key === key) {
item.isEdit = 1;
}
});
}
function cancel(key) {
let ind = -1;
dataList.value.filter((item, index) => {
if (item.key === key) {
if (isAdd.value) {
ind = index;
} else {
getList();
}
}
});
if (ind > -1) {
dataList.value.splice(ind, 1);
}
isAdd.value = false;
}
async function save(key) {
isAdd.value = false;
const params = {
param: {
projectId: projectId.value,
id: '',
name: '',
phone: '',
},
};
dataList.value.forEach(item => {
if (key === item.key) {
params.param.id = item.memberId;
params.param.name = item.memberName;
params.param.phone = item.phone;
}
});
const myreg = /^[1][3,4,5,7,8,9][0-9]{9}$/;
if (!myreg.test(params.param.phone)) {
message.info('请填写正确的手机号');
return false;
}
try {
if (params.param.id) {
await updateMember(params);
} else {
await saveMember(params);
}
getList();
} catch (error) {
message.info(error);
throw new Error(error);
}
}
async function toDelete(key) {
console.log(key);
const params = { param: { id: '' } };
dataList.value.forEach(item => {
if (key === item.key) {
params.param.id = item.memberId;
}
});
try {
await delMember(params);
getList();
} catch (error) {
message.info(error);
throw new Error(error);
}
}
</script>
<style scoped>
.member-list {
border-radius: 10px 10px 0 0;
overflow: hidden;
}
:deep(.table-striped) td {
background-color: #fafafa;
}
.action-btn {
width: 50px !important;
height: 28px !important;
font-size: 14px !important;
padding: 0;
letter-spacing: 0 !important;
}
:deep(.ant-table-pagination.ant-pagination) {
height: 0;
margin: 0;
}
.add-btn {
height: 60px;
background: #fff;
padding-left: 36px;
}
.edit-btn {
background: #0dc26c;
border: 0;
}
.del-btn {
margin-left: 16px;
background: #ff5353;
border: 0;
}
:deep(.ant-table-container table > thead > tr:first-child th:first-child) {
width: 80px;
text-align: center;
}
:deep(.ant-table-container table > thead > tr:first-child th:nth-child(2)) {
min-width: 100px;
}
:deep(.ant-table-container table > tbody > tr > td:first-child) {
text-align: center;
}
:deep(.ant-table-container table > thead > tr:first-child th:last-child) {
min-width: 160px;
}
</style>

389
src/components/tall/task/PlanAssignment.vue

@ -1,389 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form ref="formRef" :model="topicMeetFormData">
<a-form-item>
<label class="color-3">课题名称</label>
<a-input v-model:value="topicMeetFormData.name" placeholder="课题名称" />
</a-form-item>
<a-form-item>
<label class="color-3">起止时间</label>
<a-space direction="vertical" :size="12">
<a-range-picker v-model:value="topicMeetFormData.date" />
</a-space>
</a-form-item>
<a-form-item>
<label class="color-3">负责人</label>
<a-select
v-model:value="topicMeetFormData.memberId"
show-search
optionFilterProp="label"
placeholder="负责人"
:options="options"
:filter-option="filterOption"
@search="handleSearch"
:getPopupContainer="
triggerNode => {
return triggerNode.parentNode || document.body;
}
"
></a-select>
</a-form-item>
<a-form-item class="form-item-dad">
<div class="flex items-center" style="margin-bottom: 5px">
<label class="color-3" style="margin-bottom: 0; margin-right: 8px">项目分阶段具体实施目标</label>
<PlusCircleOutlined style="color: #1890ff; font-size: 16px" @click="addMilestones" />
</div>
<div class="form-item-son" style="padding-left: 16px">
<div v-for="(item, index) in planTaskStageList" :key="index">
<a-form-item>
<label class="color-3">{{ index + 1 }}阶段</label>
<a-space direction="vertical" :size="12">
<a-range-picker v-model:value="item.date" />
</a-space>
</a-form-item>
<a-form-item>
<label class="color-3">实施内容与目标</label>
<a-textarea v-model:value="item.remark" placeholder="实施内容与目标" />
</a-form-item>
</div>
</div>
</a-form-item>
<a-form-item>
<label class="color-3">主要技术指标</label>
<a-textarea v-model:value="topicMeetFormData.technicalIndicator" placeholder="主要技术指标" />
</a-form-item>
<a-form-item>
<label class="color-3">经济指标</label>
<a-textarea v-model:value="topicMeetFormData.economicIndicators" placeholder="经济指标" />
</a-form-item>
<a-form-item>
<label class="color-3">社会效益</label>
<a-textarea v-model:value="topicMeetFormData.socialBenefit" placeholder="社会效益" />
</a-form-item>
<a-form-item>
<label class="color-3">验收内容</label>
<a-checkbox-group v-model:value="topicMeetFormData.checkContentList">
<a-row>
<a-col :span="5">
<a-checkbox value="1"><span class="color-6">1工作报告</span></a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox value="2"><span class="color-6">2技术报告</span></a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox value="3"><span class="color-6">3计算机软件</span></a-checkbox>
</a-col>
<a-col :span="7">
<a-checkbox value="4"><span class="color-6">4生物品种</span></a-checkbox>
</a-col>
<a-col :span="5">
<a-checkbox value="5"><span class="color-6">5样品或样机</span></a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox value="6"><span class="color-6">6成套技术设备</span></a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox value="7"><span class="color-6">7科技论文或著作</span></a-checkbox>
</a-col>
<a-col :span="7">
<a-checkbox value="8"><span class="color-6">8科技报告收录证书(必选)</span></a-checkbox>
</a-col>
</a-row>
</a-checkbox-group>
</a-form-item>
<!-- 自定义 -->
<div class="flex items-center cursor-pointer" style="margin-bottom: 20px" @click="addDefined">
<PlusCircleOutlined style="color: #1890ff; font-size: 16px" /><span class="color-3" style="margin-left: 8px">自定义</span>
</div>
<div class="form-item-son" style="padding-left: 16px">
<div v-for="(item, index) in planTaskDefinedList" :key="index">
<a-form-item>
<label class="color-3">自定义名称</label>
<a-input v-model:value="item.key" placeholder="自定义名称" />
</a-form-item>
<a-form-item>
<label class="color-3">自定义内容</label>
<a-textarea v-model:value="item.value" placeholder="自定义内容" />
</a-form-item>
</div>
</div>
<a-form-item>
<label class="color-3">计划任务书</label>
<a-upload-dragger
v-model:fileList="fileList"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传计划任务书</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import { InboxOutlined, PlusCircleOutlined } from '@ant-design/icons-vue';
import { uploadImg, memberQuery, savePlanTask, getPlanTask } from 'apis';
import dayjs from 'dayjs';
import { message } from 'ant-design-vue';
const store = useStore();
const formRef = ref(null);
const projectInfo = computed(() => store.state.projects.project); //
const projectId = computed(() => store.getters['projects/projectId']);
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
const fileList = ref([]);
const topicMeetFormData = ref({
projectId: projectInfo.value.id,
name: '',
date: [],
startTime: '',
endTime: '',
memberId: '',
technicalIndicator: '',
economicIndicators: '',
socialBenefit: '',
checkContentList: [],
fileList: [],
planTaskStageList: [],
planTaskDefinedList: [],
});
const options = ref([]);
const planTaskStageList = ref([{ date: [], stageStartTime: '', stageEndTime: '', remark: '' }]);
const planTaskDefinedList = ref([]);
checkPlanTask(); //
getList(); //
const handleSearch = async value => {
await getList(value); //
};
const filterOption = (input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
fileList.value = arr.value;
};
//
async function checkPlanTask() {
try {
const params = { param: { projectId: projectId.value } };
const data = await getPlanTask(params);
if (data.checkContent) data.checkContentList = data.checkContent.split(',');
store.commit('layout/setFirPlanTime', { startTime: data.startTime, endTime: data.endTime });
data.date = [];
if (data.startTime) {
const start = dayjs(Number(data.startTime));
const end = dayjs(Number(data.endTime));
data.date = [start, end];
}
data.projectId = data.id;
topicMeetFormData.value = data;
planTaskDefinedList.value = data.planTaskDefinedList;
data.planTaskStageList.forEach(item => {
item.date = [];
if (item.stageStartTime) {
const planStart = dayjs(Number(item.stageStartTime));
const planEnd = dayjs(Number(item.stageEndTime));
item.date = [planStart, planEnd];
}
});
planTaskStageList.value = data.planTaskStageList;
const fileArr = [];
data.fileList.forEach(item => {
const fileInfo = {
id: item.fileId,
name: item.fileName,
url: item.filePath,
status: 'done',
};
fileArr.push(fileInfo);
});
fileList.value = fileArr;
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
async function getList(name) {
try {
const params = { param: { projectId: projectId.value, name } };
const data = await memberQuery(params);
store.commit('task/setMembers', data);
options.value = [];
data.forEach(item => {
const obj = {
label: item.memberName,
value: item.memberId,
};
options.value.push(obj);
});
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
function addMilestones() {
planTaskStageList.value.push({ date: [], stageStartTime: '', stageEndTime: '', remark: '' });
}
//
function addDefined() {
planTaskDefinedList.value.push({ key: '', value: '' });
}
const onSubmit = async () => {
fileList.value.forEach(item => {
const obj = {
fileId: item.id,
fileName: item.name,
filePath: item.url,
};
topicMeetFormData.value.fileList.push(obj);
});
if (topicMeetFormData.value.date) {
topicMeetFormData.value.date.forEach((item, index) => {
if (index === 0) {
topicMeetFormData.value.startTime = dayjs(item).format('x');
} else {
topicMeetFormData.value.endTime = dayjs(item).format('x');
}
});
}
topicMeetFormData.value.planTaskStageList = [];
planTaskStageList.value.forEach(item => {
const obj = {
stageStartTime: '',
stageEndTime: '',
remark: item.remark,
};
if (item.date.length > 0) {
item.date.forEach((val, key) => {
if (key === 0) {
obj.stageStartTime = dayjs(val).format('x');
} else {
obj.stageEndTime = dayjs(val).format('x');
}
});
if (obj.stageStartTime < topicMeetFormData.value.startTime) {
message.info('阶段任务开始时间不能小于项目起始时间');
return false;
}
if (obj.stageEndTime > topicMeetFormData.value.endTime) {
message.info('阶段任务结束时间不能小于项目终止时间');
return false;
}
}
topicMeetFormData.value.planTaskStageList.push(obj);
});
topicMeetFormData.value.planTaskDefinedList = planTaskDefinedList.value;
const params = { param: topicMeetFormData.value };
await savePlanTask(params);
projectInfo.value.name = topicMeetFormData.value.name;
store.commit('projects/setProject', projectInfo.value);
store.commit('layout/setRefreshProjects');
checkPlanTask();
};
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
</style>

271
src/components/tall/task/Procedure.vue

@ -1,271 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
@change="handleChange($event, index)"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit" :disabled="expreStatus == 0 || expreStatus == 2 ? false : true">
确定
</a-button>
</a-form-item>
</a-form>
<a-alert v-if="isShowSuccess" :message="tipsMessage" type="success" />
<a-alert v-if="isShowWarning" :message="tipsMessage" type="warning" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
// import { message } from 'ant-design-vue';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
const isShowSuccess = ref(false);
const isShowWarning = ref(false);
const tipsMessage = ref('');
//
// const expreStartTime = computed(() => store.state.layout.expreStartTime);
// const expreEndTime = computed(() => store.state.layout.expreEndTime);
//
const expreStatus = computed(() => store.state.projects.expreStatus);
const sessionStatus = sessionStorage.getItem('expreStatus');
if (!expreStatus.value && sessionStatus) {
store.commit('projects/setExpreimentStatus', sessionStatus);
}
if (expreStatus.value) {
if (expreStatus.value === 1 || expreStatus.value === 3 || expreStatus.value === 4) {
isShowWarning.value = true;
tipsMessage.value = '数据已锁定,不可操作';
} else {
isShowSuccess.value = true;
tipsMessage.value = '数据未锁定,可操作';
}
setTimeout(() => {
isShowWarning.value = false;
isShowSuccess.value = false;
}, 3000);
}
getDataByCode();
const handleChange = (info, index) => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach((item, key) => {
if (key === resFileList.length - 1) {
arr.value.push(item);
}
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[index].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
const time = dayjs(item.date).format('x');
// if (time < expreStartTime.value || time > expreEndTime.value) {
// message.info('');
// return false;
// }
obj.answerList.push(time);
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-form {
position: relative;
}
.task-form :deep(.ant-alert) {
position: absolute;
right: 30px;
}
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

308
src/components/tall/task/PublishPatent.vue

@ -1,308 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-radio-group v-model:value="item.con" name="radioGroup" @change="handleChangeRadio">
<a-radio :value="val.submitValue" :id="val.optionId" v-for="(val, key) in item.optionList" :key="key">{{
val.showValue
}}</a-radio>
</a-radio-group>
</a-form-item>
</template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-select ref="select" v-model:value="item.con">
<a-select-option :value="val.submitValue" v-for="(val, key) in item.optionList" :key="key">{{
val.showValue
}}</a-select-option>
</a-select>
</a-form-item>
</template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
@change="handleChange($event, index)"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
const intellectualId = computed(() => store.state.task.intellectualId); // ID
getDataByCode();
watch(intellectualId, () => {
getDataByCode();
});
//
const handleChangeRadio = value => {
const selectedName = value.target.value;
const selectedId = value.target.id;
let addQuestion = [];
const getQuestions = questionList.value;
let selectedIndex = -1;
questionList.value.forEach((item, index) => {
if (item.type === 3) {
item.optionList.forEach(val => {
if (val.optionId === selectedId && val.questionInfos && val.questionInfos.length > 0) {
addQuestion = val.questionInfos;
selectedIndex = index;
}
});
}
});
addQuestion.forEach((item, index) => {
let flag = false;
getQuestions.forEach(v => {
if (v.question === '授权日期') {
flag = true;
}
});
if (!flag) {
if (selectedName === '授权') {
const addIndex = selectedIndex + index + 1;
getQuestions.splice(addIndex, 0, item);
}
}
});
if (selectedName === '申请') {
let flag = false;
let delIndex = -1;
getQuestions.forEach((v, k) => {
if (v.question === '授权日期') {
flag = true;
delIndex = k;
}
});
if (flag) {
getQuestions.splice(delIndex, 1);
}
}
questionList.value = getQuestions;
};
//
const handleChange = (info, currIndex) => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: intellectualId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2 || item.type === 3 || item.type === 5) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
obj.answerList.push(dayjs(item.date).format('x'));
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitIntellectual', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: intellectualId.value,
},
};
const data = await store.dispatch('task/getIntellectual', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2 || item.type === 3 || item.type === 5) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

251
src/components/tall/task/PublishThesis.vue

@ -1,251 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-select ref="select" v-model:value="item.con">
<a-select-option :value="val.submitValue" v-for="(val, key) in item.optionList" :key="key">{{
val.showValue
}}</a-select-option>
</a-select>
</a-form-item>
</template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
const intellectualId = computed(() => store.state.task.intellectualId); // ID
getDataByCode();
watch(intellectualId, () => {
getDataByCode();
});
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: intellectualId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2 || item.type === 5) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
obj.answerList.push(dayjs(item.date).format('x'));
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitIntellectual', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: intellectualId.value,
},
};
const data = await store.dispatch('task/getIntellectual', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2 || item.type === 5) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

251
src/components/tall/task/PublishWork.vue

@ -1,251 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-select ref="select" v-model:value="item.con">
<a-select-option :value="val.submitValue" v-for="(val, key) in item.optionList" :key="key">{{
val.showValue
}}</a-select-option>
</a-select>
</a-form-item>
</template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
const intellectualId = computed(() => store.state.task.intellectualId); // ID
getDataByCode();
watch(intellectualId, () => {
getDataByCode();
});
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: intellectualId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2 || item.type === 5) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
obj.answerList.push(dayjs(item.date).format('x'));
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitIntellectual', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: intellectualId.value,
},
};
const data = await store.dispatch('task/getIntellectual', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2 || item.type === 5) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

236
src/components/tall/task/Result.vue

@ -1,236 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传验收证书</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
obj.answerList.push(dayjs(item.date).format('x'));
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

284
src/components/tall/task/ScientificPayoffs.vue

@ -1,284 +0,0 @@
<template>
<div>
<a-table
class="member-list"
:columns="columns"
:data-source="dataList"
:row-class-name="(_record, index) => (index % 2 === 1 ? null : 'table-striped')"
>
<template #bodyCell="{ column, text, record }">
<div class="flex items-center" style="min-width: 160px" v-if="column.key === 'action'">
<a-button
:disabled="record.status === 0 ? false : true"
class="action-btn edit-btn"
type="primary"
@click="showModal(record.id, 'success')"
>
通过
</a-button>
<a-button
:disabled="record.status === 0 ? false : true"
class="action-btn del-btn"
type="primary"
@click="showModal(record.id, 'fail')"
>
驳回
</a-button>
<img v-if="record.status === 0" style="width: 28px" src="https://www.tall.wiki/staticrec/experiment/unlock.png" />
<img
v-if="record.status === 1"
class="cursor-pointer"
style="width: 28px"
src="https://www.tall.wiki/staticrec/experiment/locking.png"
@click="showModal(record.id, 'tips')"
/>
<div v-if="record.status === 3" class="status-btn" style="background: #cccccc"></div>
<div
v-if="record.status === 4"
class="status-btn cursor-pointer"
style="background: #ff5353"
@click="showModal(record.id, 'tips')"
>
</div>
</div>
<template v-else-if="['information', 'result', 'sourceCode'].includes(column.dataIndex)">
<a class="break-all" style="color: #1890ff" :href="text" target="_blank" :title="text">{{ text }}</a>
</template>
<template v-else-if="['report', 'course'].includes(column.dataIndex)">
<a
class="break-all"
style="color: #1890ff"
:href="!text || !text.url ? '' : text.url"
target="_blank"
:title="!text || !text.name ? '' : text.name"
>
{{ !text || !text.name ? '' : text.name }}
</a>
</template>
</template>
</a-table>
</div>
<!-- 确定模态框 -->
<a-modal v-model:visible="success" :closable="false" @ok="handleOk('success')">
<div class="modal-title flex items-center">
<CheckCircleFilled style="margin-right: 8px; font-size: 18px; color: #52c41a" />
<span class="color-3" style="font-size: 18px; font-weight: 600">确定要审核通过该实验交付物吗</span>
</div>
<div class="modal-con color-9" style="padding-left: 24px; margin-top: 16px; font-size: 16px; line-height: 26px">
确定通过审核后改实验交付物将被锁定如需修改需向课题主持人提交解锁请求通过后即可修改
</div>
</a-modal>
<!-- 拒绝模态框 -->
<a-modal v-model:visible="fail" :closable="false" @ok="handleOk('fail')">
<div class="modal-title flex items-center">
<CloseCircleFilled style="margin-right: 8px; font-size: 18px; color: #ff5353" />
<span class="color-3" style="font-size: 18px; font-weight: 600">确定要驳回该实验交付物吗</span>
</div>
<div class="modal-con color-9" style="padding-left: 24px; margin-top: 16px">
<div style="margin-bottom: 5px; font-size: 16px; line-height: 26px">驳回原因</div>
<a-textarea v-model:value="remark1" placeholder="驳回原因" :auto-size="{ minRows: 2, maxRows: 5 }" />
</div>
</a-modal>
<!-- 提示模态框 -->
<a-modal v-model:visible="tips" :closable="false" @ok="handleOk('tips')">
<div class="modal-title flex items-center">
<ExclamationCircleFilled style="margin-right: 8px; font-size: 18px; color: #fa8c16" />
<span class="color-3" style="font-size: 18px; font-weight: 600">确定要申请解锁该项实验交付物吗</span>
</div>
<div class="modal-con color-9" style="padding-left: 24px; margin-top: 16px">
<div style="margin-bottom: 5px; font-size: 16px; line-height: 26px">申请原因</div>
<a-textarea v-model:value="remark2" placeholder="申请原因" :auto-size="{ minRows: 2, maxRows: 5 }" />
</div>
</a-modal>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import { queryExperimentation, examineExperimentation, applyUnlock } from 'apis';
import { CheckCircleFilled, CloseCircleFilled, ExclamationCircleFilled } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
const store = useStore();
const projectId = computed(() => store.getters['projects/projectId']);
const success = ref(false);
const fail = ref(false);
const tips = ref(false);
const currId = ref(null);
const remark1 = ref(null);
const remark2 = ref(null);
const columns = ref([
{ title: '序号', dataIndex: 'indexId', key: 'indexId' },
{ title: '实验名称', dataIndex: 'name', key: 'name' },
{ title: '实验报告', dataIndex: 'report', key: 'report' },
{ title: '实验过程', dataIndex: 'course', key: 'course' },
{ title: '实验数据', dataIndex: 'information', key: 'information' },
{ title: '程序代码', dataIndex: 'sourceCode', key: 'sourceCode' },
{ title: '实验结果', dataIndex: 'result', key: 'result' },
{ title: '审核', key: 'action', dataIndex: 'action' },
]);
const dataList = ref([]);
getExperimentations();
//
const showModal = (id, tip) => {
currId.value = id;
if (tip === 'success') {
success.value = true;
} else if (tip === 'fail') {
fail.value = true;
} else if (tip === 'tips') {
tips.value = true;
}
};
//
const handleOk = async tip => {
if (tip === 'success') {
success.value = false;
await examine(0);
} else if (tip === 'fail') {
fail.value = false;
await examine(1);
} else if (tip === 'tips') {
tips.value = false;
await toUnlock();
}
};
async function getExperimentations() {
try {
const params = { param: { projectId: projectId.value } };
const data = await queryExperimentation(params);
data.forEach((item, index) => {
item.indexId = index + 1;
if (item.report) item.report = JSON.parse(item.report);
if (item.course) item.course = JSON.parse(item.course);
});
dataList.value = data;
} catch (error) {
message.info(error);
throw new Error(error);
}
}
// /
async function examine(type) {
try {
const params = {
param: {
id: currId.value,
type,
remark: remark1.value ? remark1.value : '',
},
};
await examineExperimentation(params);
getExperimentations();
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
async function toUnlock() {
try {
const params = {
param: {
id: currId.value,
remark: remark2.value ? remark2.value : '',
},
};
await applyUnlock(params);
getExperimentations();
} catch (error) {
message.info(error);
throw new Error(error);
}
}
</script>
<style scoped>
.member-list {
border-radius: 10px 10px 0 0;
overflow: hidden;
}
:deep(.table-striped) td {
background-color: #fafafa;
}
.action-btn {
flex-shrink: 0;
width: 50px !important;
height: 28px !important;
font-size: 14px !important;
padding: 0;
letter-spacing: 0 !important;
}
:deep(.ant-table-pagination.ant-pagination) {
height: 0;
margin: 0;
}
.add-btn {
height: 60px;
background: #fff;
padding-left: 36px;
}
.edit-btn {
background: #0dc26c;
border: 0;
}
.del-btn {
margin-left: 16px;
margin-right: 16px;
background: #ff5353;
border: 0;
}
.status-btn {
width: 28px;
height: 28px;
color: #fff;
border-radius: 50%;
text-align: center;
line-height: 28px;
}
:deep(.ant-table-container table > thead > tr th) {
min-width: 100px;
}
:deep(.ant-table-container table > thead > tr th:first-child) {
min-width: 70px;
text-align: center;
}
:deep(.ant-table-container table > tbody > tr > td:first-child) {
text-align: center;
}
:deep(.ant-table-container table > thead > tr th:last-child) {
min-width: 192px;
}
:deep(.ant-btn-primary[disabled]) {
color: #fff;
background: #cccccc;
}
</style>

246
src/components/tall/task/SubConclusion.vue

@ -1,246 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传课题结题报告</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
// import { message } from 'ant-design-vue';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
//
// const subStartTime = computed(() => store.state.layout.subStartTime);
// const subEndTime = computed(() => store.state.layout.subEndTime);
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
const time = dayjs(item.date).format('x');
// if (time < subStartTime.value || time > subEndTime.value) {
// message.info('');
// return false;
// }
obj.answerList.push(time);
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

246
src/components/tall/task/SubInterimInspection.vue

@ -1,246 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传中期检查报告</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
// import { message } from 'ant-design-vue';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
//
// const subStartTime = computed(() => store.state.layout.subStartTime);
// const subEndTime = computed(() => store.state.layout.subEndTime);
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
const time = dayjs(item.date).format('x');
// if (time < subStartTime.value || time > subEndTime.value) {
// message.info('');
// return false;
// }
obj.answerList.push(time);
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

244
src/components/tall/task/SubMeetingManagement.vue

@ -1,244 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form :model="topicMeetFormData">
<a-form-item>
<label class="color-3">会议名称</label>
<a-input v-model:value="topicMeetFormData.name" placeholder="会议名称" />
</a-form-item>
<a-form-item>
<label class="color-3">会议日期</label>
<a-space direction="vertical" :size="12">
<a-range-picker v-model:value="topicMeetFormData.date" />
</a-space>
</a-form-item>
<a-form-item>
<label class="color-3">会议地点</label>
<a-input v-model:value="topicMeetFormData.address" placeholder="会议地点" />
</a-form-item>
<a-form-item>
<label class="color-3">会议纪要</label>
<a-upload-dragger
v-model:fileList="summaryList"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
@change="handleChange($event, 1)"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
<a-form-item>
<label class="color-3">照片附件/其他</label>
<a-upload-dragger
v-model:fileList="attachmentList"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.jpg,.jpeg,.rar,.zip,.png'"
@change="handleChange($event, 2)"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式jpgjpegrarzip</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>

246
src/components/tall/task/SubResult.vue

@ -1,246 +0,0 @@
<template>
<div class="task-form bg-white border-radius-10">
<a-form>
<div v-for="(item, index) in questionList" :key="index">
<template v-if="item.type === 1">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-input v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 2">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-textarea v-model:value="item.con" :placeholder="item.question" />
</a-form-item>
</template>
<template v-if="item.type === 3"> </template>
<template v-if="item.type === 4"> </template>
<template v-if="item.type === 5"> </template>
<template v-if="item.type === 6">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-date-picker v-model:value="item.date" />
</a-form-item>
</template>
<template v-if="item.type === 7">
<a-form-item>
<label class="color-3">{{ item.question }}</label>
<a-upload-dragger
v-model:fileList="item.files"
name="param"
:multiple="true"
:action="action"
:headers="headers"
:accept="'.pdf'"
:before-upload="beforeUpload(index)"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text color-3 font-14">点击或拖拽文件到区域内上传交付物</p>
<p class="ant-upload-hint color-c">格式pdf</p>
</a-upload-dragger>
</a-form-item>
</template>
</div>
<a-form-item class="text-right">
<a-button type="primary" html-type="submit" @click="onSubmit">上传验收证书</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { InboxOutlined } from '@ant-design/icons-vue';
import { uploadImg } from 'apis';
// import { message } from 'ant-design-vue';
const store = useStore();
//
const token = computed(() => store.getters['user/token']);
const headers = { Authorization: `Bearer ${token.value}` };
const action = uploadImg;
//
const projectId = computed(() => store.getters['projects/projectId']); // ID
const code = computed(() => store.state.task.label); // code
const questionList = ref([]);
//
const currIndex = ref(null);
//
// const subStartTime = computed(() => store.state.layout.subStartTime);
// const subEndTime = computed(() => store.state.layout.subEndTime);
getDataByCode();
const beforeUpload = index => {
currIndex.value = index;
};
const handleChange = info => {
const resFileList = [...info.fileList];
//
const arr = ref([]);
resFileList.forEach(file => {
let num = -1;
arr.value.forEach((item, index) => {
if (file.name === item.name) {
num = index;
}
});
if (num > -1) {
arr.value.splice(num, 1);
}
arr.value.push(file);
});
//
arr.value = arr.value.map(file => {
if (file.response) {
file.url = file.response.data.filePath;
file.id = file.response.data.fileId;
}
return file;
});
questionList.value[currIndex.value].files = arr.value;
};
const onSubmit = async () => {
const params = {
param: {
code: code.value,
projectId: projectId.value,
questionAndAnswerList: [],
},
};
const arr = [];
questionList.value.forEach(item => {
const obj = {
questionId: item.questionId,
answerList: [],
};
if (item.type === 1 || item.type === 2) {
obj.answerList.push(item.con);
}
if (item.type === 6 && item.date) {
const time = dayjs(item.date).format('x');
// if (time < subStartTime.value || time > subEndTime.value) {
// message.info('');
// return false;
// }
obj.answerList.push(time);
}
if (item.type === 7) {
item.files.forEach(val => {
const file = {
id: val.id,
name: val.name,
url: val.url,
};
obj.answerList.push(JSON.stringify(file));
});
}
arr.push(obj);
});
params.param.questionAndAnswerList = arr;
await store.dispatch('task/submitAnswer', params);
getDataByCode();
};
async function getDataByCode() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
intellectualId: null,
},
};
const data = await store.dispatch('task/getByCode', params);
data.forEach(item => {
if (item.type === 1 || item.type === 2) {
item.con = '';
if (item.answerList.length > 0) {
item.con = item.answerList[0].answer;
}
}
if (item.type === 6) {
item.date = null;
if (item.answerList.length > 0) {
if (item.answerList[0].answer) {
item.date = dayjs(Number(item.answerList[0].answer)).format('YYYY-MM-DD');
item.date = dayjs(item.date, 'YYYY-MM-DD');
}
}
}
if (item.type === 7) {
item.files = [];
if (item.answerList.length > 0) {
item.answerList.forEach(val => {
if (val.answer) val.answer = JSON.parse(val.answer);
item.files.push(val.answer);
});
}
}
});
questionList.value = data;
}
</script>
<style scoped>
.task-detail {
background-color: #fff;
}
.ant-col {
margin-top: 10px;
}
.ant-col:nth-child(-n + 4) {
margin-top: 2px;
}
.deliverables .ant-input,
.deliverables-son .ant-input {
width: 23px;
height: 14px;
border-radius: 0;
padding: 0;
font-size: 12px;
color: #1890ff;
text-align: center;
margin-left: 5px;
}
.deliverables-son {
margin-top: 10px !important;
}
</style>

195
src/components/tall/task/SubSubjectProgress.vue

@ -1,195 +0,0 @@
<template>
<div class="task-progress flex flex-wrap justify-between">
<div class="wrap overflow-hidden">
<a-card title="任务目标">
<div class="flex flex-wrap justify-center">
<div class="achievements border border-right border-bottom text-center">
<p class="num">{{ infoOne.thesis }}/{{ infoOne.totalThesis }}</p>
<p class="name">论文</p>
</div>
<div class="achievements border border-bottom text-center">
<p class="num">{{ infoOne.patent }}/{{ infoOne.totalPatent }}</p>
<p class="name">专利</p>
</div>
<div class="achievements border border-right text-center">
<p class="num">{{ infoOne.theSoft }}/{{ infoOne.totalTheSoft }}</p>
<p class="name">软著</p>
</div>
<div class="achievements border text-center">
<p class="num">{{ infoOne.meeting }}/{{ infoOne.totalMeeting }}</p>
<p class="name">会议</p>
</div>
</div>
</a-card>
</div>
<div class="wrap overflow-hidden">
<a-card title="概览">
<div class="topic">
<p>{{ infoOne.name }}</p>
<a-progress
:percent="infoOne.masterSchedule"
:strokeWidth="22"
:show-info="false"
:stroke-color="'#1890FF'"
:trail-color="'rgba(24, 144, 255, 0.2)'"
/>
</div>
<div class="sub-topic flex justify-between flex-wrap">
<div class="topic" v-for="(item, index) in infoSec" :key="index">
<p>{{ item.name }}</p>
<a-progress
:percent="item.masterSchedule"
:strokeWidth="22"
:show-info="false"
:stroke-color="colorList[index % 4].color"
:trail-color="colorList[index % 4].bgColor"
/>
</div>
</div>
</a-card>
</div>
<div class="wrap overflow-hidden" v-for="(item, index) in infoSec" :key="index">
<a-card :title="item.name">
<div class="flex flex-wrap justify-center">
<div class="achievements border border-right border-bottom text-center">
<p class="num">{{ item.report }}</p>
<p class="name">实验报告</p>
</div>
<div class="achievements border border-bottom text-center">
<p class="num">{{ item.course }}</p>
<p class="name">实验过程</p>
</div>
<div class="achievements border border-right text-center">
<p class="num">{{ item.information }}</p>
<p class="name">实验数据</p>
</div>
<div class="achievements border text-center">
<p class="num">{{ item.result }}</p>
<p class="name">实验结果</p>
</div>
</div>
</a-card>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import { getProgress } from 'apis';
import { message } from 'ant-design-vue';
const store = useStore();
const projectId = computed(() => store.getters['projects/projectId']);
const infoOne = ref({});
const infoSec = ref([]);
const colorList = ref([
{ color: '#FF9191', bgColor: 'rgba(255, 145, 145, 0.2)' },
{ color: '#FF934B', bgColor: 'rgba(255, 147, 75, 0.2)' },
{ color: '#B991FF', bgColor: 'rgba(185, 145, 255, 0.2)' },
{ color: '#91C1FF', bgColor: 'rgba(145, 193, 255, 0.2)' },
]);
progress();
async function progress() {
try {
const params = { param: { projectId: projectId.value } };
const data = await getProgress(params);
infoOne.value = data.target;
infoSec.value = data.subTargetList;
} catch (error) {
message.info(error);
throw new Error(error);
}
}
</script>
<style scoped>
.wrap {
margin-top: 16px;
width: calc((100% - 16px) / 2);
background-color: #fff;
border-radius: 10px;
border: 1px solid #cccccc;
}
.wrap:nth-child(-n + 2) {
margin-top: 0;
}
:deep(.ant-card-head) {
padding: 0 16px;
min-height: 45px;
max-height: 45px;
border-color: #cccccc;
font-size: 14px;
}
:deep(.ant-card-head-title) {
padding: 0;
line-height: 45px;
}
.wrap .num {
font-size: 22px;
color: #4b8aff;
}
.wrap .name {
color: #666666;
}
.wrap p {
margin: 0;
}
.border {
border-color: #fff;
}
.border-right {
border-right-color: #cccccc;
}
.border-left {
border-left-color: #cccccc;
}
.border-top {
border-top-color: #cccccc;
}
.border-bottom {
border-bottom-color: #cccccc;
}
.achievements {
width: 40%;
padding: 25px 0;
}
.topic p {
margin-bottom: 8px;
color: #666666;
}
.sub-topic .topic {
margin-top: 40px;
width: calc((100% - 32px) / 2);
}
.ant-card {
height: 100%;
}
.ant-card :deep(.ant-card-body) {
height: calc(100% - 48px);
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

144
src/components/tall/task/TaskConList.vue

@ -1,144 +0,0 @@
<template>
<div class="list-box">
<div class="task-box" v-for="(item, index) in lists" :key="index">
<div class="task-time flex items-center justify-between">
<div class="flex items-center">
<div class="circular"></div>
<span>{{ dayjs(Number(item.time)).format('YYYY年MM月DD日 HH:mm') }}</span>
</div>
<div class="task-action"></div>
</div>
<div class="task-info">
<div>
<div class="task-card">
<div class="task-name cursor-pointer flex justify-between items-center" @click="toDetail(item)">
<span class="leading-none truncate color-3" style="width: calc(100% - 30px)" :title="item.name">{{ item.name }}</span>
<RightOutlined style="color: #666" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed, ref } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { RightOutlined } from '@ant-design/icons-vue';
import { getMeetQuery } from 'apis';
import { message } from 'ant-design-vue';
const store = useStore();
const code = computed(() => store.state.task.label); // code
const projectId = computed(() => store.getters['projects/projectId']); // ID
const lists = ref([]);
init();
async function init() {
if (code.value === 'ZKT_LW' || code.value === 'ZKT_ZL' || code.value === 'ZKT_RZ') {
await getAchievementsList();
} else if (code.value === 'ZKT_HYGL') {
await getMeetList(1);
} else if (code.value === 'KT_KYHY') {
await getMeetList(0);
}
}
//
async function getAchievementsList() {
const params = {
param: {
code: code.value,
projectId: projectId.value,
},
};
const data = await store.dispatch('task/getIntellectualList', params);
lists.value = [...data];
}
//
async function getMeetList(type) {
const params = {
param: {
type,
projectId: projectId.value,
},
};
try {
const data = await getMeetQuery(params);
data.forEach(item => {
item.time = item.startTime;
});
lists.value = [...data];
} catch (error) {
message.info(error);
throw new Error(error);
}
}
function toDetail(item) {
if (code.value === 'ZKT_HYGL') {
store.commit('task/setSubMeetId', item.id);
} else if (code.value === 'KT_KYHY') {
store.commit('task/setMeetId', item.id);
} else if (code.value === 'ZKT_LW' || code.value === 'ZKT_ZL' || code.value === 'ZKT_RZ') {
store.commit('task/setIntellectualId', item.intellectualId);
}
}
</script>
<style scoped>
.list-box {
padding: 10px 16px 50px;
overflow-y: auto;
}
.task-time {
height: 32px;
}
.task-time .circular {
margin-right: 8px;
width: 14px;
height: 14px;
border-radius: 50%;
background-color: #1890ff;
}
.task-time span {
font-size: 14px;
color: #595959;
}
.task-info {
margin: 8px 0;
padding-left: 8px;
}
.task-info > div {
padding-left: 15px;
border-left: 1px solid #d2d2d2;
}
.task-info .task-card {
padding: 16px;
border-radius: 8px;
-moz-box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.12);
-webkit-box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.12);
box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.12);
}
.task-con {
margin-top: 16px;
}
.task-con > div {
height: 30px;
}
</style>

3
src/components/tall/top/Navbar.vue

@ -27,7 +27,8 @@ const isShowList = computed(() => store.state.layout.isShowListStatus); // 是
if (sessionTaskDetail) {
const obj = JSON.parse(sessionTaskDetail);
store.commit('task/setTaskDetail', obj);
console.log(obj);
// store.commit('task/setTaskDetail', obj);
}
if (sessionLabel) {

17
src/components/tall/top/TopNavbar.vue

@ -1,7 +1,7 @@
<template>
<div class="flex justify-between items-center">
<h1 class="">
<span>实验平台</span>
<span>传控科技行政管理平台</span>
<menu-unfold-outlined v-if="collapsed" @click="toggleCollapse" />
<menu-fold-outlined v-else class="trigger" @click="toggleCollapse" />
</h1>
@ -11,7 +11,8 @@
<CaretDownOutlined />
<div class="user-action absolute">
<div @click="signOut">退出登录</div>
<div v-if="!userId" @click="signUp">登录</div>
<div v-if="userId" @click="signOut">退出登录</div>
</div>
</div>
</div>
@ -28,18 +29,25 @@ const store = useStore();
const router = useRouter();
const collapsed = computed(() => store.state.layout.display.left); //
const account = computed(() => store.getters['user/account']);
const userId = computed(() => store.getters['user/userId']);
// toggle left window display
function toggleCollapse() {
store.commit('layout/setDisplay', { prop: 'left', show: !collapsed.value });
}
function signUp() {
router.push({ path: '/tall/pc/user/signin' });
}
// 退
function signOut() {
store.commit('user/setUser', null);
store.commit('projects/setProject', null);
store.commit('task/setTaskDetail', null);
router.push({ path: '/experiment/user/signin' });
store.commit('user/setToken', '');
store.commit('role/setRoleId', '');
// store.commit('task/setTaskDetail', null);
// router.push({ path: '/experiment/user/signin' });
}
</script>
@ -66,6 +74,7 @@ h1 span {
text-align: center;
box-shadow: 0px 0 6px 0px rgba(0, 0, 0, 0.3);
background-color: #fff;
z-index: 9;
}
.user-info:hover .user-action {

4
src/main.js

@ -3,11 +3,13 @@
import 'virtual:windi.css';
import { createApp } from 'vue';
// eslint-disable-next-line no-unused-vars
import App from './App.vue';
// eslint-disable-next-line no-unused-vars
import router from './routers/index';
import store from './store/index';
// import '@/assets/index.css';
const app = createApp(App);
app.use(router).use(store).mount('#app');

7
src/plugins/p-account-management/p-account-management-audit.vue

@ -0,0 +1,7 @@
<template>
<div class="text-center">
<a-image src="/src/assets/personal.png" />
</div>
</template>
<script setup></script>

7
src/plugins/p-account-management/p-account-management-uidispose.vue

@ -0,0 +1,7 @@
<template>
<div class="text-center">
<a-image src="/src/assets/uidispose.png" />
</div>
</template>
<script setup></script>

26
src/plugins/p-account-management/p-account-management.vue

@ -0,0 +1,26 @@
<template>
<div class="flex justify-around task-card-plugin">
<a-button type="primary" style="width: 125px" @click="openAudit">账号管理</a-button>
<a-button type="primary" style="width: 125px" @click="openStatistical">UI配置</a-button>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
const store = useStore();
//
function openAudit() {
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', 'personal'); //
}
// UI
function openStatistical() {
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', 'uidispose'); //
}
</script>

695
src/plugins/p-daily-account/p-daily-account-detail.vue

@ -0,0 +1,695 @@
<template>
<div class="p-4">
<!-- 标题 -->
<div class="mb-8 flex justify-between items-center">
<div class="text-2xl font-semibold">流水账</div>
<div class="flex items-center">
<a-button type="primary" v-if="!morning || morning == 0" @click="punch(0)"> 早打卡 </a-button>
<a-button type="primary" v-else>
{{ dayjs(+morning).format('HH:mm') }}
</a-button>
<a-button class="mx-5" type="primary" v-if="!night || night == 0" @click="punch(1)"> 晚打卡 </a-button>
<a-button class="mx-5" type="primary" v-else> {{ dayjs(+night).format('HH:mm') }} </a-button>
<FullscreenExitOutlined v-if="isFullScreen" class="text-lg" style="color: #777" @click="changeIsFullScreen(false)" />
<FullscreenOutlined v-else class="text-lg" style="color: #777" @click="changeIsFullScreen(true)" />
</div>
</div>
<!-- 筛选 -->
<a-form class="flex flex-wrap" :model="formState">
<a-form-item name="timeRange" label="时间" style="width: 280px; margin-right: 20px; line-height: 32px; margin-bottom: 12px">
<a-range-picker v-model:value="formState.timeRange" />
</a-form-item>
<a-form-item name="staffRange" label="员工" style="width: 280px; margin-right: 20px; line-height: 32px; margin-bottom: 12px">
<a-select
class="select-box overflow-hidden"
v-model:value="formState.staffRange"
:options="memberList"
:field-names="{ label: 'empName', value: 'empName' }"
mode="multiple"
placeholder="请选择员工"
></a-select>
</a-form-item>
<a-form-item name="programName" label="项目" style="width: 280px; margin-right: 20px; line-height: 32px; margin-bottom: 12px">
<a-select
class="select-box overflow-hidden"
v-model:value="formState.programName"
:options="proList"
:field-names="{ label: 'projectName', value: 'projectName' }"
mode="multiple"
placeholder="请输入项目名称"
></a-select>
</a-form-item>
<div class="block w-full">
<a-button type="primary" html-type="submit" @click="handleSubmit">筛选</a-button>
<a-button class="mx-3" type="primary" @click="handleExport">导出</a-button>
<a-button @click="resetData">重置</a-button>
</div>
</a-form>
<!-- 表格 -->
<a-table
sticky
class="mt-6"
:columns="columns"
:data-source="columnDatas"
bordered
:pagination="false"
:rowClassName="rowClassName"
:scroll="{ x: 1000, y: 660 }"
>
<template #bodyCell="{ column, text, record }">
<div
style="height: 50px"
:class="{ 'text-left': column.dataIndex === 'program' }"
class="overflow-y-auto task-today"
@dblclick="showModal(record, column.proId)"
>
<template v-if="column.dataIndex === 'program'">
<template v-for="item in text">
<template v-if="column.proId === item.proId">
<div class="mb-3" v-for="(task, key) in item.tasks" :key="key">
<PushpinOutlined class="mr-2" v-if="task.cooperation === 1" title="协作任务" />
<span>
我今日计划结果{{ key + 1 }}{{ task.taskName }}交付物是{{ task.deliverName }}截止时间{{
dayjs(+task.deadline).format('MM-DD')
}}时长{{ task.duration / 3600000 }}检查人{{ task.checker }}
</span>
</div>
</template>
</template>
</template>
<template v-else>
{{ text }}
</template>
</div>
</template>
</a-table>
<a-pagination
class="text-right"
v-model:current="current"
v-model:pageSize="pageSize"
:total="dataTotal"
@change="handlePage"
show-less-items
/>
</div>
<a-modal
v-model:visible="visible"
title="今日计划"
centered
:footer="null"
width="800px"
style="padding: 0; overflow-y: auto"
:body-style="bodystyle"
>
<div style="padding: 0 96px">
<div class="mb-4">
当前操作项目
<span class="text-red-500">{{ currProName }} </span> 当前操作成员
<span class="text-red-500"> {{ currEmpName }} </span>
</div>
<a-form :model="modalFormState">
<div v-for="(item, index) in modalFormState" :key="index">
<div class="flex justify-between items-center text-gray-400">
<div class="mb-5 text-right" style="width: 120px">计划结果{{ index + 1 }}</div>
<DeleteOutlined v-if="modalFormState.length > 1 && !isDisabled" @click="delFormItem(index)" />
</div>
<a-form-item name="taskName" style="margin-bottom: 20px">
<div class="flex">
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px">
<span style="color: #ff5353; margin-right: 5px">*</span>今日计划结果{{ index + 1 }}
</label>
<a-textarea v-model:value="item.taskName" :disabled="isDisabled" placeholder="请输入今日计划" />
</div>
</a-form-item>
<a-form-item name="deliverName" style="margin-bottom: 20px">
<div class="flex">
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px">
<span style="color: #ff5353; margin-right: 5px">*</span>交付物
</label>
<a-input v-model:value="item.deliverName" :disabled="isDisabled" placeholder="请输入交付物" />
</div>
</a-form-item>
<a-form-item name="showDeadLine" style="margin-bottom: 20px">
<div class="flex">
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px">
<span style="color: #ff5353; margin-right: 5px">*</span>截止时间
</label>
<a-date-picker class="w-full" v-model:value="item.showDeadLine" :disabled="isDisabled" />
</div>
</a-form-item>
<a-form-item name="showDuration" style="margin-bottom: 20px">
<div class="flex">
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px">
<span style="color: #ff5353; margin-right: 5px">*</span>时长
</label>
<a-input-group compact class="items-center" style="width: 100%; display: flex">
<a-auto-complete
style="width: calc(100% - 35px)"
v-model:value="item.showDuration"
:options="workDurations"
placeholder="请输入工作时长"
:disabled="isDisabled"
@change="handleDuration($event, index)"
/>
<div style="width: 35px; border: none" class="text-right">小时</div>
</a-input-group>
</div>
</a-form-item>
<a-form-item name="checker" style="margin-bottom: 20px">
<div class="flex">
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px">
<span style="color: #ff5353; margin-right: 5px">*</span>检查人
</label>
<a-select
v-model:value="item.checker"
:options="memberList"
:field-names="{ label: 'empName', value: 'empName' }"
show-search
placeholder="请选择检查人"
:disabled="isDisabled"
:filter-option="filterOption"
@change="handleInspector($event, index)"
></a-select>
</div>
</a-form-item>
<a-form-item name="cooperation" style="margin-bottom: 20px">
<div class="flex">
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px"> 是否协作 </label>
<a-radio-group v-model:value="item.cooperation" class="w-full items-center" style="display: flex">
<a-radio class="items-center" style="display: flex" :value="1" :disabled="isDisabled"></a-radio>
<a-radio class="items-center" style="display: flex" :value="0" :disabled="isDisabled"></a-radio>
</a-radio-group>
</div>
</a-form-item>
<a-form-item name="deliverLink" style="margin-bottom: 20px">
<div class="flex">
<label class="flex-shrink-0 text-right" style="width: 120px; line-height: 32px"> 交付物链接 </label>
<a-textarea v-model:value="item.deliverLink" :disabled="isDisabled" placeholder="请输入交付物链接" />
</div>
</a-form-item>
</div>
<a-form-item v-if="!isDisabled">
<div class="flex">
<label class="flex-shrink-0 text-right" style="width: 120px"></label>
<a-button style="color: #1890ff; border-color: #1890ff" type="dashed" @click="addFormItem">+ 添加</a-button>
</div>
</a-form-item>
<a-form-item v-if="!isDisabled">
<div class="flex items-center">
<label class="flex-shrink-0 text-right" style="width: 120px"></label>
<a-button class="mr-4" type="primary" @click="submitForm">提交</a-button>
<a-button @click="cancleModal">取消</a-button>
</div>
</a-form-item>
</a-form>
</div>
</a-modal>
</template>
<script setup>
import { useStore } from 'vuex';
import { reactive, ref, onMounted, computed, watch } from 'vue';
import dayjs from 'dayjs';
import { FullscreenExitOutlined, FullscreenOutlined, DeleteOutlined, PushpinOutlined } from '@ant-design/icons-vue';
import { getBasicInfo, queryTasks, submitTask, clockQuery, clockPunch, exportQuery } from 'apis';
import { message } from 'ant-design-vue';
const store = useStore();
const projectId = computed(() => store.getters['project/projectId']);
const sessionProjectId = sessionStorage.getItem('projectId');
const roleId = computed(() => store.state.role.roleId);
const members = computed(() => store.state.role.members);
const userId = computed(() => store.getters['user/userId']); // id
const isFullScreen = computed(() => store.state.layout.isFullScreen); //
const visible = ref(false); //
const isDisabled = ref(false); //
const morning = ref(false); //
const night = ref(false); //
const checkerId = ref(null); // id
const recordId = ref(null); // id
const memberId = ref(null); // id
//
const formState = reactive({
timeRange: [dayjs(+new Date().getTime()), dayjs(+new Date().getTime()).add(1, 'day')], //
staffRange: [], //
programName: [], //
});
const startTime = dayjs(+new Date().getTime());
const endTime = dayjs(+new Date().getTime()).add(1, 'day');
//
const memberList = ref([]); //
const proList = ref([]); //
const emps = ref([]); //
const proDatas = ref([]); //
//
const columns = ref();
const current = ref(1); //
const pageSize = ref(10); //
const dataTotal = ref(0); //
//
const columnDatas = ref([]);
const bodystyle = {
height: '650px',
overflow: 'hidden',
overflowY: 'scroll',
};
//
const modalFormState = ref([]);
//
const workDurations = [...Array(8)].map((_, i) => ({ value: `${i + 1}` }));
const isSubmitDeliver = ref(false); //
const currEmpId = ref(null); // id
const currEmpName = ref(null); //
const currProId = ref(null); // id
const currProName = ref(null); //
onMounted(async () => {
getClockQuery();
await getInfo();
await getQueryTasks();
});
//
function changeIsFullScreen(data) {
store.commit('layout/setIsFullScreen', data);
}
//
async function getInfo() {
try {
const { url } = store.state.projects.project;
const data = await getBasicInfo(url);
memberList.value = data.emps;
pageSize.value = data.emps.length;
proList.value = data.pros;
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
async function getQueryTasks() {
try {
const { url } = store.state.projects.project;
const params = {
param: {
startTime: startTime.value || formState.timeRange[0].startOf('day').valueOf(),
endTime: endTime.value || formState.timeRange[1].startOf('day').valueOf(),
emps: emps.value || [],
pros: proDatas.value || [],
},
};
//
const days = dayjs(+formState.timeRange[1].startOf('day')).diff(+formState.timeRange[0].startOf('day'), 'day');
dataTotal.value = pageSize.value * days; //
const data = await queryTasks(params, url);
columns.value = [
{
title: '时间',
width: 80,
dataIndex: 'time',
key: 'time',
fixed: 'left',
align: 'center',
},
{
title: '员工',
width: 90,
dataIndex: 'staff',
key: 'staff',
fixed: 'left',
align: 'center',
},
];
columnDatas.value = [];
data.pros.forEach((proId, index) => {
const proInfo = proList.value.find(item => item.id === proId);
columns.value.push({
title: proInfo.projectShortName,
dataIndex: 'program',
key: `${index}`,
proId,
width: 200,
align: 'center',
});
});
memberList.value.forEach((member, key) => {
const obj = {
empId: member.id,
key,
time: dayjs(+params.param.startTime).format('MM-DD'),
staff: `${member.empName}`,
program: [],
bgColor: key % 2 === 1 ? 1 : 0,
};
const currMember = data.recs.filter(item => member.id === item.empId);
if (currMember.length > 0) {
const { pros } = currMember[0];
obj.program = pros;
}
columnDatas.value.push(obj);
});
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
function handleSubmit() {
emps.value = [];
proDatas.value = [];
memberList.value.forEach(item => {
const index = formState.staffRange.findIndex(emp => emp === item.empName);
if (index > -1) emps.value.push(item.id);
});
proList.value.forEach(item => {
const index = formState.programName.findIndex(pro => pro === item.projectName);
if (index > -1) proDatas.value.push(item.id);
});
startTime.value = dayjs(+formState.timeRange[0].startOf('day')).valueOf();
const end = dayjs(+startTime.value).add(1, 'day');
endTime.value = dayjs(+end).valueOf();
getQueryTasks();
current.value = 1;
}
//
function resetData() {
formState.timeRange = [dayjs(+new Date().getTime()), dayjs(+new Date().getTime()).add(1, 'day')];
formState.staffRange = [];
formState.programName = [];
startTime.value = dayjs(+formState.timeRange[0].startOf('day')).valueOf();
endTime.value = dayjs(+formState.timeRange[1].startOf('day')).valueOf();
emps.value = [];
proDatas.value = [];
getQueryTasks();
}
//
async function handleExport() {
try {
const start = formState.timeRange[0].startOf('day').valueOf();
const end = formState.timeRange[1].startOf('day').valueOf();
const params = {
param: {
projectId: projectId.value || sessionProjectId,
roleId: roleId.value,
memberIdList: [],
startTime: start,
endTime: end,
},
};
const { url } = store.state.projects.project;
const data = await exportQuery(params, url);
window.open(data, '_blank');
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
function rowClassName(record, index) {
const bgColor = record.bgColor ? 'bg-style-color' : '';
return bgColor;
}
//
function handlePage(e) {
const start = dayjs(+formState.timeRange[0].startOf('day')).add(e - 1, 'day');
const end = dayjs(+start).add(1, 'day');
startTime.value = dayjs(+start).valueOf();
endTime.value = dayjs(+end).valueOf();
getQueryTasks();
}
//
function showModal(data, proId) {
currEmpId.value = data.empId;
currProId.value = proId;
const currEmpInfo = memberList.value.find(item => item.id === data.empId);
currEmpName.value = currEmpInfo.empName;
const currProInfo = proList.value.find(item => item.id === proId);
currProName.value = currProInfo.projectName;
isDisabled.value = dayjs(+new Date().getTime()).format('MM-DD') !== data.time;
visible.value = true;
modalFormState.value = [
{
taskName: '',
deliverName: '',
showDeadLine: dayjs(+new Date().getTime()),
deadline: '',
showDuration: '2',
duration: '',
checker: '',
cooperation: 0,
deliverLink: '',
sequence: 0,
},
];
data.program.forEach(item => {
if (proId === item.proId) {
modalFormState.value = [...item.tasks];
}
});
modalFormState.value.forEach((item, index) => {
item.showDeadLine = item.deadline ? dayjs(+item.deadline) : dayjs(+new Date().getTime());
item.showDuration = item.duration ? `${item.duration / 3600000}` : '2';
item.sequence = index;
});
}
//
function filterOption(input, option) {
return option.empName.indexOf(input) >= 0;
}
//
function handleDuration(e, index) {
modalFormState.value[index].duration = e;
}
//
function handleInspector(e, index) {
modalFormState.value[index].checker = e;
}
//
function addFormItem() {
const len = modalFormState.value.length;
modalFormState.value.push({
taskName: '',
deliverName: '',
showDeadLine: dayjs(+new Date().getTime()),
deadline: '',
showDuration: '2',
duration: '',
checker: '',
cooperation: 0,
deliverLink: '',
sequence: len,
});
}
//
const delFormItem = index => {
modalFormState.value.splice(index, 1);
};
//
function cancleModal() {
visible.value = false;
}
//
async function submitForm() {
let flag = false;
for (let i = 0; i < modalFormState.value.length; i++) {
const data = modalFormState.value[i];
data.deadline = dayjs(+data.showDeadLine).valueOf();
data.duration = data.showDuration * 3600000;
if (!data.taskName) {
message.info(`请输入今日计划结果${i + 1}`);
return false;
}
if (!data.deliverName) {
message.info(`请输入交付物`);
return false;
}
if (!data.duration) {
message.info(`请输入时长`);
return false;
}
if (!data.checker) {
message.info(`请选择检查人`);
return false;
}
const reg = /[a-zA-z]+:\/\/[^\s]*/;
if (data.deliverLink && !reg.test(data.deliverLink)) {
message.info(`请输入正确的链接`);
return false;
}
flag = true;
}
if (flag) {
isSubmitDeliver.value = true;
}
const params = {
param: {
time: new Date().getTime(),
empId: currEmpId.value,
proId: currProId.value,
tasks: modalFormState.value,
},
};
try {
const { url } = store.state.projects.project;
const data = await submitTask(params, url);
visible.value = false;
// punch();
getQueryTasks();
} catch (error) {
message.info(error);
throw new Error(error);
}
}
//
async function getClockQuery() {
const start = dayjs(+new Date().getTime())
.startOf('day')
.valueOf();
const end = dayjs(+new Date().getTime())
.endOf('day')
.valueOf();
const params = {
param: {
projectId: projectId.value || sessionProjectId,
roleId: roleId.value,
memberIdList: [],
startTime: start,
endTime: end,
},
};
try {
const { url } = store.state.projects.project;
const data = await clockQuery(params, url);
//
if (data[0].recordList[0].lastCheckerId) {
checkerId.value = data[0].recordList[0].lastCheckerId;
} else if (data[0].recordList[0].checkerId) {
checkerId.value = data[0].recordList[0].checkerId;
} else {
checkerId.value = members.value.length ? members.value[0].memberId : '';
}
// id
if (data[0].recordList[0].isMine === 1) {
memberId.value = data[0].recordList[0].memberId;
}
recordId.value = data[0].recordList[0].id;
morning.value = data[0].recordList[0].morning; //
night.value = data[0].recordList[0].night; //
} catch (error) {
message.info(error);
throw new Error(error);
}
}
async function punch(clockType) {
if ((clockType === 0 && morning.value > 0) || (clockType === 1 && night.value > 0)) return;
const dateTime = dayjs(+new Date().getTime()).valueOf(); //
const params = { param: { checkerId: checkerId.value, memberId: memberId.value, id: recordId.value, clockType, dateTime } };
try {
const { url } = store.state.projects.project;
const data = await clockPunch(params, url);
getClockQuery();
} catch (error) {
message.info(`打卡失败,${error}`);
throw new Error(error);
}
}
</script>
<style scoped>
.task-today::-webkit-scrollbar {
width: 0 !important;
}
.select-box {
position: relative;
height: 32px;
}
.select-box :deep(.ant-select-selector) {
position: absolute;
left: 0;
}
.select-box :deep(.ant-select-selection-overflow) {
width: 225px;
flex-wrap: nowrap;
overflow: hidden;
}
:deep(.bg-style-color) td {
background-color: #fafafa;
}
</style>

17
src/plugins/p-daily-account/p-daily-account.vue

@ -0,0 +1,17 @@
<template>
<div class="w-full block text-center task-card-plugin">
<a-button type="primary" style="width: 250px" @click="openDailyAccount">流水账</a-button>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
const store = useStore();
function openDailyAccount() {
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', '');
store.commit('task/setTaskDetailShow', 'dailyAccount');
}
</script>

362
src/plugins/p-deliver-second/p-deliver-check-second.vue

@ -0,0 +1,362 @@
<template>
<div>
<!-- 审核标题 -->
<div class="flex justify-between items-center">
<div>{{ deliverData ? deliverData.deliverName : '' }}审核状态</div>
</div>
<!-- 审核结果 -->
<div>
<!-- 提交人和时间 -->
<div class="my-2 flex justify-between items-center">
<div class="text-gray-400 text-xs">
<span class="mr-4" v-if="deliverData.submitMemberName">{{ deliverData.submitMemberName }}</span>
<span v-if="deliverData.submitTime"> {{ dayjs(+deliverData.submitTime).format('MM-DD HH:mm') }}</span>
</div>
</div>
<div
@click="openLink"
class="break-all text-blue-400 text-xs my-1 cursor-pointer"
v-if="deliverData.details && deliverData.details[0]"
>
{{ deliverData.details[0] }}
</div>
<!-- 审核人 标题 -->
<div class="text-gray-400 flex justify-between mt-3">
<span>审核</span>
</div>
<!-- 审核人 列表 -->
<div v-if="deliverData.checkerList">
<div v-for="(item, index) in deliverData.checkerList" :key="index">
<template v-if="item.isMine === 1">
<div class="mt-2 text-sm flex justify-between" v-show="item.status > 0 && isRepeatCheck === 0">
<div>
<div class="font-semibold">{{ item.checkerName }}</div>
<div class="text-xs text-gray-400">{{ item.remark }}</div>
<div class="text-xs text-gray-400" v-if="+item.checkTime > 0">{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }}</div>
</div>
<div class="time-box" v-if="item.checkDuration">
<div class="relative">
<div class="initial-duration bg-yellow-400" :style="{ width: item.initialPercent + '%' }"></div>
<div class="absolute duration-value">默认值{{ deliverData.initialDuration / 3600000 }}小时</div>
</div>
<div class="relative">
<div class="duration bg-blue-400" :style="{ width: item.currPercent + '%' }"></div>
<div class="absolute duration-value">工作量时长{{ deliverData.duration / 3600000 }}小时</div>
</div>
<div class="relative">
<div class="check-duration bg-green-400" :style="{ width: item.checkPercent + '%' }"></div>
<div class="absolute duration-value">确认工作{{ item.checkDuration / 3600000 }}小时</div>
</div>
</div>
<!-- 自己是审核人 且审核过 当前审核人的审核状态并展示得分情况 -->
<div class="text-xs">
<div class="mb-1">
<div v-if="item.status === 1" class="text-green-600">已通过</div>
<div v-else-if="item.status === 2" class="text-red-600">已驳回</div>
</div>
<div class="text-yellow-500 font-medium text-base">{{ item.score }}</div>
<!-- <view class="text-blue-500" @click="repeatCheck(1)">重新审核</view> -->
</div>
</div>
<div v-if="item.status === null || item.status === 0 || isRepeatCheck === 1">
<!-- <view v-if="isRepeatCheck === 1" class="text-blue-500 text-right" @click="repeatCheck(0)">取消重新审核</view> -->
<div class="mt-3 pb-2" style="border-bottom: 1px solid #d1d5db">
<div class="flex justify-between">
<div class="mr-1 text-sm flex items-center">
<div class="mr-2">确认工作</div>
<div class="flex flex-wrap">
<a-button
:type="checkedIndex === 0 ? 'primary' : 'default'"
size="small"
class="my-1 ml-0 mr-2"
@click="handleSelectTime(0)"
>
半小时
</a-button>
<a-button
:type="checkedIndex === 1 ? 'primary' : 'default'"
size="small"
class="my-1 ml-0 mr-2"
@click="handleSelectTime(1)"
>
1小时
</a-button>
<a-button
:type="checkedIndex === 2 ? 'primary' : 'default'"
size="small"
class="my-1 ml-0 mr-2"
@click="handleSelectTime(2)"
>
2小时
</a-button>
</div>
</div>
<!-- 时长 -->
<div class="flex items-center justify-end flex-1 text-sm">
<a-input
v-model="checkDuration"
type="text"
placeholder="工作量时长"
class="input"
style="text-align: right; width: 120px"
></a-input>
小时
</div>
</div>
</div>
<div class="mt-3 flex justify-between items-center">
<div>交付物质量</div>
<div class="flex justify-end items-center">
<a-input-number v-model:value="score" :max="100" :min="0" :step="1"> </a-input-number>
<div class="w-64 ml-3">
<a-progress :percent="score" />
</div>
</div>
</div>
<div class="mt-3 relative">
<a-textarea style="height: 80px" v-model:value="commit" placeholder="鼓励一下小伙伴" />
<div
class="absolute border border-solid border-gray-300 rounded-md text-center text-base cursor-pointer word-btn"
@click="showWords = !showWords"
>
</div>
</div>
<div class="common-list" v-if="showWords">
<div v-for="(item, index) in words" :key="index" class="px-2 leading-12 word-item" @click="commit = item">
{{ item }}
</div>
</div>
<div class="mt-4 flex justify-center items-center">
<a-button class="mx-4" type="primary" @click="handleSubmit(1)"> 通过 </a-button>
<a-button class="mx-4" type="primary" danger @click="handleSubmit(2)"> 驳回 </a-button>
</div>
</div>
</template>
</div>
<div v-for="(item, index) in deliverData.checkerList" :key="index">
<!-- 不是我 -->
<template v-if="item.isMine !== 1">
<div class="mt-2 text-sm flex justify-between">
<div>
<div class="font-semibold">{{ item.checkerName }}</div>
<div class="text-xs text-gray-400">{{ item.remark }}</div>
<div class="text-xs text-gray-400" v-if="+item.checkTime > 0">{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }}</div>
</div>
<div class="time-box" v-if="item.checkDuration">
<div class="relative">
<div class="initial-duration bg-yellow-400" :style="{ width: item.initialPercent + '%' }"></div>
<div class="absolute duration-value">默认值{{ deliverData.initialDuration / 3600000 }}小时</div>
</div>
<div class="relative">
<div class="duration bg-blue-400" :style="{ width: item.currPercent + '%' }"></div>
<span class="absolute duration-value">工作量时长{{ deliverData.duration / 3600000 }}小时</span>
</div>
<div class="relative">
<div class="check-duration bg-green-400" :style="{ width: item.checkPercent + '%' }"></div>
<span class="absolute duration-value">确认工作{{ item.checkDuration / 3600000 }}小时</span>
</div>
</div>
<!-- 不是自己 显示审核状态 -->
<div class="text-xs">
<span v-if="item.status === 1" class="text-green-600"> 已通过 </span>
<span v-else-if="item.status === 2" class="text-red-600"> 已驳回 </span>
<span v-else class="text-gray-400"> 待审核 </span>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, defineProps, defineEmits } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { message } from 'ant-design-vue';
import { checkDeliver } from 'apis';
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
import { quickWords } from '@/utils/deliver';
const props = defineProps({
deliverData: { type: Object, default: () => {} },
task: { type: Object, default: () => {} },
});
const store = useStore();
const deliverData = computed(() => props.deliverData);
const projectId = computed(() => store.getters['project/projectId']);
const sessionProjectId = sessionStorage.getItem('projectId');
const roleId = computed(() => store.state.role.roleId);
const isRepeatCheck = ref(0); //
const words = computed(() => quickWords.RESOLVE); //
const checkDuration = ref(2); //
const checkedIndex = ref(2); //
const score = ref(100); //
const commit = ref(''); //
const showWords = ref(false); //
const maxDuration = ref(null);
const emits = defineEmits(['check-success']);
if (Object.keys(props.deliverData).length) {
checkDuration.value = Number(props.deliverData.duration) / 3600000; //
checkedIndex.value = checkDuration.value === 0.5 ? 0 : checkDuration.value === 1 ? 1 : checkDuration.value === 2 ? 2 : -1;
handleDataRender(props.deliverData);
}
watch(deliverData, () => {
checkDuration.value = Number(deliverData.value.duration) / 3600000; //
checkedIndex.value = checkDuration.value === 0.5 ? 0 : checkDuration.value === 1 ? 1 : checkDuration.value === 2 ? 2 : -1;
handleDataRender(deliverData.value);
});
//
async function handleDataRender(data) {
maxDuration.value =
deliverData.value.initialDuration > deliverData.value.duration ? deliverData.value.initialDuration : deliverData.value.duration;
data.checkerList.forEach(item => {
if (item.checkDuration) {
maxDuration.value = maxDuration.value > item.checkDuration ? maxDuration.value : item.checkDuration;
if (maxDuration.value === deliverData.value.initialDuration) {
item.initialPercent = 100;
item.currPercent = Math.floor((deliverData.value.duration / deliverData.value.initialDuration) * 100);
item.checkPercent = Math.floor((item.checkDuration / deliverData.value.initialDuration) * 100);
} else if (maxDuration.value === deliverData.value.duration) {
item.currPercent = 100;
item.initialPercent = Math.floor((deliverData.value.initialDuration / deliverData.value.duration) * 100);
item.checkPercent = Math.floor((item.checkDuration / deliverData.value.duration) * 100);
} else if (maxDuration.value === item.checkDuration) {
item.checkPercent = 100;
item.initialPercent = Math.floor((deliverData.value.initialDuration / item.checkDuration) * 100);
item.currPercent = Math.floor((deliverData.value.duration / item.checkDuration) * 100);
}
}
});
return data;
}
//
function openLink() {
window.open(props.deliverData.details[0], '_blank');
}
//
function handleSelectTime(data) {
checkedIndex.value = data;
checkDuration.value = data === 0 ? 0.5 : data === 1 ? 1 : 2;
}
/**
* 提交评审信息
* 提交成功后隐藏modal 重置表单控件
* 给父组件信息 更新值
* @param {string} mode 'RESOLVE'|'REJECT'
*/
async function handleSubmit(mode) {
try {
const { url } = store.state.projects.project;
const deliverRecordId = props.deliverData ? props.deliverData.deliverRecordId : '';
const param = {
param: {
projectId: projectId.value || sessionProjectId,
roleId: roleId.value,
deliverRecordId,
type: mode === 'RESOLVE' ? 1 : 2,
remark: commit.value,
score: score.value,
checkDuration: checkDuration.value * 3600000,
msgId: props.task.msgId,
},
};
await checkDeliver(param, url);
handleHide(); // +
message.info('审核信息提交成功');
//
emits('submit-end', param);
} catch (error) {
console.error('error: ', error);
message.info('审核信息提交失败, 请稍后重试');
}
}
//
function handleHide() {
score.value = 100;
commit.value = '';
}
//
function repeatCheck(data) {
isRepeatCheck.value = data;
}
</script>
<style scoped>
.ant-progress .ant-progress-inner {
width: 40px !important;
height: 40px !important;
}
.word-btn {
right: 10px;
bottom: 10px;
width: 30px;
height: 30px;
line-height: 28px;
}
.word-item {
border-bottom: 1px solid #e5e7eb;
}
.time-box {
width: 120px;
}
.time-box view {
height: 15px;
border-radius: 2px;
margin: 2px 0;
}
.time-box .duration-value {
height: 15px;
line-height: 15px;
font-size: 12px;
top: 0;
left: 0;
}
</style>

290
src/plugins/p-deliver-second/p-deliver-second-detail.vue

@ -0,0 +1,290 @@
<template>
<div class="relative">
<div class="text-base text-center font-semibold" style="height: 44px; line-height: 44px; background: rgb(248, 248, 248)">
交付物上传记录
</div>
<div class="tab-box absolute w-full flex justify-between items-center bg-white">
<div
class="tab-item text-center cursor-pointer"
v-for="(item, index) in clickList"
:key="index"
:class="{ 'tab-curr': item.index === current }"
@click="changeTabs(item.index)"
>
<div class="tab-title px-1 inline-block">{{ item.name }}</div>
</div>
</div>
<!-- 占位 -->
<div style="height: 44px"></div>
<div id="deliverCon" class="scroll-box">
<!-- 提交 -->
<div class="p-3 text-base scroll-0">当前提交</div>
<div class="px-3">
<p-deliver-upload-second
class="p-3 bg-white rounded-md"
v-if="deliverData"
:deliverData="deliverData"
:task="task"
@upload-success="getDeliverData"
></p-deliver-upload-second>
</div>
<!-- 审核 -->
<div class="p-3 text-base scroll-1">审核状态</div>
<div class="px-3">
<p-deliver-check-second
class="p-3 bg-white rounded-md"
v-if="deliverData"
:deliverData="deliverData"
:task="task"
@submit-end="getDeliverData"
></p-deliver-check-second>
</div>
<!-- 历史记录 -->
<div class="p-3 text-base scroll-2">历史记录</div>
<div class="px-3" v-if="listRef && listRef.length">
<div class="bg-white mb-3 rounded-md p-3 text-gray-400" v-for="(item, index) in listRef" :key="index">
<!-- 插件名称和提交时间显示 -->
<div class="flex justify-between mb-2">
<div class="text-gray-800">{{ deliverName }}</div>
<div class="ml-1 text-xs w-24 text-right">{{ dayjs(+item.submitTime).format('MM-DD HH:mm') }}</div>
</div>
<!-- 提交的链接 -->
<div @click="openLink" class="break-all text-blue-400 text-xs my-1 cursor-pointer" v-if="item.details && item.details[0]">
{{ item.details[0] }}
</div>
<!-- 该插件物的审核人 -->
<div class="mb-1 mt-3">审核人</div>
<div class="flex justify-between mb-2" v-for="(checkItem, checkIndex) in item.checkerList" :key="checkIndex">
<div>
<div class="mb-1 text-gray-800 font-semibold">
{{ checkItem.checkerName }}
</div>
<div class="mb-1 text-xs">
{{ checkItem.remark }}
</div>
<div class="mb-1 text-xs" v-if="+checkItem.checkTime > 0">
{{ dayjs(+checkItem.checkTime).format('MM-DD HH:mm') }}
</div>
</div>
<div class="time-box" v-if="checkItem.checkDuration">
<div class="relative">
<div class="initial-duration bg-yellow-400" :style="{ width: checkItem.initialPercent + '%' }"></div>
<span class="absolute duration-value">默认值{{ initialDuration / 3600000 }}小时</span>
</div>
<div class="relative">
<div class="duration bg-blue-400" :style="{ width: checkItem.currPercent + '%' }"></div>
<span class="absolute duration-value">工作量时长{{ item.duration / 3600000 }}小时</span>
</div>
<div class="relative">
<div class="check-duration bg-green-400" :style="{ width: checkItem.checkPercent + '%' }"></div>
<span class="absolute duration-value">确认工作{{ checkItem.checkDuration / 3600000 }}小时</span>
</div>
</div>
<div class="text-center text-xs">
<div v-if="checkItem.status == null" class="text-gray-400">待审核</div>
<div v-else-if="checkItem.status === 1">
<div class="text-green-600 mb-1">已通过</div>
<div class="text-yellow-500 font-medium text-base">{{ checkItem.score }}</div>
</div>
<div class="text-red-600" v-else>已驳回</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { ref, onMounted, computed, watch, provide } from 'vue';
import { getDeliverByTaskId, getDeliverHistory } from 'apis';
import { message } from 'ant-design-vue';
import pDeliverUploadSecond from '@/plugins/p-deliver-second/p-deliver-upload-second.vue';
import pDeliverCheckSecond from '@/plugins/p-deliver-second/p-deliver-check-second.vue';
const store = useStore();
const detailParams = computed(() => store.state.task.detailParams); //
const sessionDetailParams = sessionStorage.getItem('detailParams'); //
const clickList = ref([
{
index: 0,
name: '提交',
},
{
index: 1,
name: '审核',
},
{
index: 2,
name: '历史记录',
},
]);
const current = ref(0); //
const deliverData = ref(null); //
const task = ref(null); //
const deliverName = ref(null);
const listRef = ref([]); //
const initialDuration = ref(null); //
const maxDuration = ref(null);
//
if (sessionDetailParams && !detailParams.value) {
const params = JSON.parse(sessionDetailParams);
store.commit('task/setTaskDetailParams', params);
provide('deliver', params.deliver);
provide('task', params.task);
}
if (detailParams.value) {
deliverData.value = detailParams.value.deliver;
task.value = detailParams.value.task;
provide('deliver', deliverData);
provide('task', task.value);
}
//
watch(detailParams, () => {
deliverData.value = detailParams.value.deliver;
task.value = detailParams.value.task;
});
onMounted(() => {
document.getElementById('deliverCon').addEventListener('scroll', handleScroll, true);
getDeliverData();
});
//
function changeTabs(index) {
current.value = index;
}
//
function handleScroll(e) {
// scrollTop
const { scrollTop } = e.target;
// windowHeight
const windowHeight = e.target.clientHeight;
// scrollHeight
const { scrollHeight } = e.target;
console.log('scrollTop', scrollTop);
}
// id
async function getHistory() {
try {
const { url } = store.state.projects.project;
const param = { param: { deliverId: detailParams.value.deliver.deliverId } };
const data = await getDeliverHistory(param, url);
deliverName.value = data.deliverName;
initialDuration.value = data.initialDuration;
listRef.value = data.deliverRecordList;
listRef.value.forEach(item => {
handleDataRender(item);
});
} catch (error) {
console.log('error: ', error);
message.info('获取交付物历史失败');
}
}
// id
async function getDeliverData() {
try {
const { url } = store.state.projects.project;
const { id: taskId } = task.value;
if (!taskId) return;
const param = { param: { taskId } };
const data = await getDeliverByTaskId(param, url);
deliverData.value = data;
} catch (error) {
message.info(error);
}
}
//
async function handleDataRender(data) {
maxDuration.value =
deliverData.value.initialDuration > deliverData.value.duration ? deliverData.value.initialDuration : deliverData.value.duration;
data.checkerList.forEach(item => {
if (item.checkDuration) {
maxDuration.value = maxDuration.value > item.checkDuration ? maxDuration.value : item.checkDuration;
if (maxDuration.value === deliverData.value.initialDuration) {
item.initialPercent = 100;
item.currPercent = Math.floor((deliverData.value.duration / deliverData.value.initialDuration) * 100);
item.checkPercent = Math.floor((item.checkDuration / deliverData.value.initialDuration) * 100);
} else if (maxDuration.value === deliverData.value.duration) {
item.currPercent = 100;
item.initialPercent = Math.floor((deliverData.value.initialDuration / deliverData.value.duration) * 100);
item.checkPercent = Math.floor((item.checkDuration / deliverData.value.duration) * 100);
} else if (maxDuration.value === item.checkDuration) {
item.checkPercent = 100;
item.initialPercent = Math.floor((deliverData.value.initialDuration / item.checkDuration) * 100);
item.currPercent = Math.floor((deliverData.value.duration / item.checkDuration) * 100);
}
}
});
return data;
}
//
function openLink() {
window.open(deliverData.value.details[0], '_blank');
}
</script>
<style scoped>
.tab-box {
height: 44px;
z-index: 8;
}
.tab-box .tab-item {
width: calc(100% / 3);
height: 44px;
line-height: 40px;
}
.tab-box .tab-curr .tab-title {
color: #2979ff;
border-bottom: 3px solid #2979ff;
}
.time-box {
width: 120px;
}
.time-box view {
height: 15px;
border-radius: 2px;
margin: 2px 0;
}
.time-box .duration-value {
height: 15px;
line-height: 15px;
font-size: 12px;
top: 0;
left: 0;
color: #333;
}
</style>

63
src/plugins/p-deliver-second/p-deliver-second.vue

@ -0,0 +1,63 @@
<template>
<!-- 任务名插件 -->
<div v-if="deliver" class="task-card-plugin">
<p-deliver-upload-second v-if="deliver" @upload-success="getDeliverData"></p-deliver-upload-second>
<!-- <p-deliver-check-second
class="mt-3"
v-if="deliver && deliver.details && deliver.details.length"
@check-success="getDeliverData"
></p-deliver-check-second> -->
<div class="mt-3" v-if="deliver && deliver.details && deliver.details.length" @check-success="getDeliverData">
<!-- 审核标题 -->
<div class="flex justify-between items-center" @click="openDeliverHistory">
<div>{{ deliver ? deliver.deliverName : '' }}审核状态</div>
<right-outlined />
</div>
</div>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { ref, inject, provide } from 'vue';
import { getDeliverByTaskId } from 'apis';
import { message } from 'ant-design-vue';
import { RightOutlined } from '@ant-design/icons-vue';
import pDeliverUploadSecond from '@/plugins/p-deliver-second/p-deliver-upload-second.vue';
// import pDeliverCheckSecond from '@/plugins/p-deliver-second/p-deliver-check-second.vue';
const store = useStore();
const task = inject('task');
const pluginInfo = inject('pluginInfo');
const deliver = ref(null); //
deliver.value = pluginInfo && pluginInfo.data ? JSON.parse(pluginInfo.data) : null;
provide('deliver', deliver);
//
function openDeliverHistory() {
const detailParams = {
deliver: deliver.value,
task,
};
store.commit('task/setTaskDetailParams', detailParams); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', 'deliverSecDetail'); //
}
// id
async function getDeliverData() {
try {
const { url } = store.state.projects.project;
const { id: taskId } = task;
if (!taskId) return;
const param = { param: { taskId } };
const data = await getDeliverByTaskId(param, url);
deliver.value = data;
} catch (error) {
message.info(error);
}
}
</script>

224
src/plugins/p-deliver-second/p-deliver-upload-second.vue

@ -0,0 +1,224 @@
<template>
<div @longpress.prevent="showMask = true">
<!-- 插件名称和提交按钮 -->
<div class="flex item-center justify-between">
<div class="flex-1">
<div v-if="deliver.deliverName" class="relative inline-block">
{{ deliver.deliverName }}
</div>
</div>
<!-- 提交 -->
<a-button type="primary" size="small" @click="submit" :disabled="submitState" :loading="submitBtnLoading"> 提交 </a-button>
</div>
<!-- 插件上传方式 -->
<div class="mt-3">
<div class="link-box">
<a-input v-model:value="linkValue" placeholder="请输入交付物地址/链接" />
</div>
<div class="mt-3">
<!-- <a-button type="primary" size="small" class="mr-3" @click="paste">粘贴</a-button> -->
<div class="inline-block">
<a-upload name="param" :action="action" :headers="headers" :showUploadList="false" :max-count="1" @change="handleChange">
<a-button type="primary" size="small" class="mr-3">文件</a-button>
</a-upload>
</div>
<!-- <a-button type="primary" size="small" class="mr-3" @click="uploadPhoto">拍照</a-button> -->
</div>
</div>
<div class="border border-solid rounded-sm mt-5 p-2 pt-4 pl-1" style="border-color: #d9d9d9">
<div class="relative flex justify-between">
<div class="absolute bg-white text-sm duration-title">工作量时长</div>
<div class="mr-3 flex flex-wrap items-center">
<a-button
:type="checkedIndex === 0 ? 'primary' : 'default'"
size="small"
class="m-1"
style="padding: 0 5px"
@click="handleSelectTime(0)"
>
半小时
</a-button>
<a-button
:type="checkedIndex === 1 ? 'primary' : 'default'"
size="small"
class="m-1"
style="padding: 0 5px"
@click="handleSelectTime(1)"
>
1小时
</a-button>
<a-button
:type="checkedIndex === 2 ? 'primary' : 'default'"
size="small"
class="m-1"
style="padding: 0 5px"
@click="handleSelectTime(2)"
>
2小时
</a-button>
</div>
<!-- 时长 -->
<div class="flex items-center justify-end flex-1 text-sm">
<a-input-number id="inputNumber" v-model:value="duration" :min="0" placeholder="工作量时长" />
<span class="flex-shrink-0">小时</span>
</div>
</div>
</div>
<!-- 插件审核人员选择 -->
<ReviewerSecond class="mt-3" ref="reviewerRef" :dataCheckers="deliver.checkerList ? deliver.checkerList : []" />
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { ref, inject, computed, watch, defineEmits, defineProps } from 'vue';
import { message } from 'ant-design-vue';
import { submitDeliverInfo, uploadImg } from 'apis';
import ReviewerSecond from '@/components/tall/Reviewer/ReviewerSecond.vue';
const props = defineProps({
deliverData: { type: Object, default: () => {} },
task: { type: Object, default: () => {} },
});
const store = useStore();
const projectId = computed(() => store.getters['project/projectId']);
const sessionProjectId = sessionStorage.getItem('projectId');
const roleId = computed(() => store.state.role.roleId);
const deliverInject = inject('deliver'); //
const taskInject = inject('task'); //
const deliver = computed(() => (props.deliverData && Object.keys(props.deliverData).length ? props.deliverData : deliverInject.value));
const task = computed(() => (props.task && Object.keys(props.task).length ? props.task : taskInject));
const linkValue = ref(''); //
const duration = ref(2); //
const checkedIndex = ref(2); //
//
const action = uploadImg;
const token = computed(() => store.state.user.token);
const headers = { Authorization: `Bearer ${token.value}` };
//
const submitState = computed(() => !linkValue.value); //
const submitBtnLoading = ref(false); //
const reviewerRef = ref(null); //
const showMask = ref(false); //
const emits = defineEmits(['upload-success']);
if (Object.keys(deliver.value).length) {
linkValue.value = deliver.value.details[0]; //
//
duration.value = deliver.value.duration ? Number(deliver.value.duration) / 3600000 : Number(deliver.value.initialDuration) / 3600000;
checkedIndex.value = duration.value === 0.5 ? 0 : duration.value === 1 ? 1 : duration.value === 2 ? 2 : -1;
}
watch(deliver, () => {
console.log('deliver', deliver);
linkValue.value = deliver.value.details[0]; //
//
duration.value = deliver.value.duration ? Number(deliver.value.duration) / 3600000 : Number(deliver.value.initialDuration) / 3600000;
checkedIndex.value = duration.value === 0.5 ? 0 : duration.value === 1 ? 1 : duration.value === 2 ? 2 : -1;
});
function handleChange(info) {
console.log('info', info);
}
//
function handleSelectTime(data) {
checkedIndex.value = data;
duration.value = data === 0 ? 0.5 : data === 1 ? 1 : 2;
}
//
async function submit() {
console.log(reviewerRef.value);
const { checkedCheckers } = reviewerRef.value; //
//
if (!validateDeliverForm(checkedCheckers)) return;
submitBtnLoading.value = true; // loading
try {
const checkerList = [];
checkedCheckers.forEach(item => {
checkerList.push(item.memberId);
});
const { url } = store.state.projects.project;
const params = {
param: {
projectId: projectId.value || sessionProjectId,
roleId: roleId.value,
deliverId: deliver.value.deliverId,
fileList: [linkValue.value],
checkerList,
duration: Number(duration.value) * 60 * 60 * 1000,
msgId: task.msgId,
},
};
const data = await submitDeliverInfo(params, url);
message.info('提交交付物信息成功');
resetControlState(); //
emits('upload-success');
} catch (error) {
message.info('提交交付物信息失败');
submitBtnLoading.value = false; // loading
throw new Error(error);
}
}
//
// function paste() {}
//
// function uploadPhoto() {}
//
function validateDeliverForm(checkedCheckers) {
const reg = /[a-zA-z]+:\/\/[^\s]*/;
if (!reg.test(linkValue.value)) {
// toast
message.info('请输入正确的链接');
return false;
}
//
if (!checkedCheckers || !checkedCheckers.length) {
message.info('请选择检查人');
return false;
}
return true;
}
//
function resetControlState() {
submitBtnLoading.value = false; // loading
reviewerRef.value.collapsed = true; //
}
</script>
<style scoped>
.duration-title {
width: 85px;
height: 20px;
line-height: 20px;
text-align: center;
top: -25px;
left: 10px;
}
</style>

130
src/plugins/p-deliver/check-form-modal.vue

@ -0,0 +1,130 @@
<template>
<a-modal v-model:visible="visible" centered :footer="null" :closable="false" @cancel="handleHide">
<div>
<!-- 审核标题 -->
<div class="modal-content-head">{{ data.mode === 'RESOLVE' ? '审核通过' : '审核驳回' }}</div>
<div class="modal-content-body mt-5">
<!-- 评分 -->
<div class="flex justify-between items-center mb-4" v-show="data.mode === 'RESOLVE'">
<a-input-number v-model:value="score" :max="100" :min="0" :step="1"> </a-input-number>
<div class="w-64">
<a-progress :percent="score" />
</div>
</div>
<a-textarea v-model:value="commit" />
<div class="common-list">
<div v-for="(item, index) in words" :key="index" class="leading-12" @click="commit = item">
{{ item }}
</div>
</div>
</div>
<div class="modal-content-foot mt-3 flex justify-center items-center">
<a-button class="mr-4" @click="handleHide">取消</a-button>
<a-button class="" type="primary" @click="handleSubmit(data.mode)">确定</a-button>
</div>
</div>
</a-modal>
</template>
<script setup>
import { ref, computed, watch, inject, defineProps, defineEmits } from 'vue';
import { useStore } from 'vuex';
import { message } from 'ant-design-vue';
import { checkDeliver } from 'apis';
import { quickWords } from '@/utils/deliver';
const props = defineProps({
data: { type: Object, default: () => {} },
msgId: { default: '', type: String },
});
const emits = defineEmits(['hide', 'submit-end']);
const store = useStore();
const task = inject('task');
const projectId = computed(() => store.getters['project/projectId']);
const sessionProjectId = sessionStorage.getItem('projectId');
const roleId = computed(() => store.state.role.roleId);
const words = computed(() => quickWords[props.data.mode]); //
const visible = ref(false);
const commit = ref(''); //
const score = ref(100); //
watch(props, () => {
visible.value = props.data.mode !== 'HIDE';
});
/**
* 提交评审信息
* 提交成功后隐藏modal 重置表单控件
* 给父组件信息 更新值
* @param {string} mode 'RESOLVE'|'REJECT'
*/
async function handleSubmit(mode) {
try {
const { url } = store.state.projects.project;
const deliverRecordId = props.data.deliverRecordId();
const param = {
param: {
projectId: projectId.value || sessionProjectId,
roleId: roleId.value,
deliverRecordId,
type: mode === 'RESOLVE' ? 1 : 2,
remark: commit.value,
score: mode === 'RESOLVE' ? score.value : '',
msgId: task.msgId,
},
};
await checkDeliver(param, url);
handleHide(); // +
message.info('审核信息提交成功');
//
emits('submit-end', param);
} catch (error) {
console.error('error: ', error);
message.info('审核信息提交失败, 请稍后重试');
}
}
//
function handleHide() {
emits('hide');
//
score.value = 100;
commit.value = '';
}
</script>
<style scoped>
.modal-content-head {
text-align: center;
margin-top: 40rpx;
margin-bottom: 20rpx;
font-size: 16px;
font-weight: 600;
}
.common-list {
height: 12.5rem;
overflow-y: scroll;
}
.common-list::-webkit-scrollbar {
display: none;
}
.common-list div {
border-bottom: 1px solid #e5e7eb;
}
.common-list div:last-child {
border-bottom: none;
}
</style>

72
src/plugins/p-deliver/p-audit-records.vue

@ -0,0 +1,72 @@
<template>
<div class="h-full">
<div class="text-base text-center font-semibold" style="height: 44px; line-height: 44px; background: rgb(248, 248, 248)">审核记录</div>
<div class="p-3">
<div class="bg-white p-3 text-gray-400 rounded-md" v-if="checkerList && checkerList.length">
<div v-for="(item, index) in checkerList" :key="index" class="flex justify-between">
<div>
<div class="mb-1 text-gray-800">
{{ item.checkerName }}
</div>
<div class="mb-1 text-xs">
{{ item.remark }}
</div>
<div class="text-xs" v-if="+item.checkTime > 0">
{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }}
</div>
</div>
<div class="flex flex-col justify-center">
<div class="mb-1 text-green-600" v-if="item.status === 1">已通过</div>
<div class="mb-1 text-red-600" v-if="item.status === 2">已驳回</div>
<div v-if="+item.score > 0">
<a-progress v-if="item.score" type="circle" :percent="item.score" strokeColor="#FA8C16" :width="40" :strokeWidth="10">
<template #format="percent">
<span
class="inline-block text-center text-white text-sm rounded-full"
style="background: #fa8c16; width: 24px; height: 24px; line-height: 24px"
>
{{ percent }}
</span>
</template>
</a-progress>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { ref, onMounted, computed, watch } from 'vue';
import dayjs from 'dayjs';
import { message } from 'ant-design-vue';
import { queryCheckLog } from 'apis';
const store = useStore();
const checkerList = ref([]);
const deliverRecordId = computed(() => store.state.task.deliverRecordId);
const sessionDeliverRecordId = sessionStorage.getItem('deliverRecordId');
onMounted(() => {
getQueryCheckLog();
});
watch(deliverRecordId, () => {
getQueryCheckLog();
});
async function getQueryCheckLog() {
try {
const { url } = store.state.projects.project;
const param = { param: { deliverRecordId: deliverRecordId.value || sessionDeliverRecordId } };
const data = await queryCheckLog(param, url);
checkerList.value = data;
} catch (error) {
message.info('获取检查交付物历史失败');
}
}
</script>

130
src/plugins/p-deliver/p-deliver-check.vue

@ -0,0 +1,130 @@
<template>
<div>
<!-- 审核标题 -->
<div class="flex justify-between items-center" @click="collapsed = !collapsed">
<div>{{ deliverData ? deliverData.deliverName : '' }}审核状态</div>
<DownOutlined v-if="!collapsed" />
<UpOutlined v-else />
</div>
<!-- 审核结果 -->
<div v-show="collapsed">
<!-- 提交人和时间 -->
<div class="my-2 flex justify-between items-center">
<div class="text-gray-400 text-xs">
<span class="mr-4" v-if="deliverData.submitMemberName">{{ deliverData.submitMemberName }}</span>
<span v-if="deliverData.submitTime"> {{ dayjs(+deliverData.submitTime).format('MM-DD HH:mm') }}</span>
</div>
<span class="text-blue-400 text-xs text-right" @click="openDeliverHistory">历史交付物</span>
</div>
<div @click="openLink" class="break-all text-blue-400 text-xs my-1" v-if="deliverData.details && deliverData.details[0]">
{{ deliverData.details[0] }}
</div>
<!-- 审核人 标题 -->
<div class="text-gray-400 flex justify-between mt-3">
<span>审核</span>
<span class="text-blue-400 text-xs" @click="openMoreRecords">更多审核记录</span>
</div>
<!-- 审核人 列表 -->
<div v-if="deliverData.checkerList">
<div class="mt-2 text-sm flex justify-between" v-for="(item, index) in deliverData.checkerList" :key="index">
<div>
<div class="font-semibold">{{ item.checkerName }}</div>
<div class="text-xs text-gray-400">{{ item.remark }}</div>
<div class="text-xs text-gray-400" v-if="+item.checkTime > 0">{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }}</div>
</div>
<!-- 不是自己 显示审核状态 -->
<div v-show="item.isMine !== 1" class="text-xs">
<span v-if="item.status === 1" class="text-green-600"> 已通过 </span>
<span v-else-if="item.status === 2" class="text-red-600"> 已驳回 </span>
<span v-else class="text-gray-400"> 待审核 </span>
</div>
<!-- 自己是当前审核人 且未审核状态 -->
<div v-show="item.isMine === 1 && (item.status === null || item.status === 0)">
<a-button size="small" shape="round" class="mr-4 h-1-4 leading-1-4" type="primary" @click="checkModal.mode = 'RESOLVE'">
通过
</a-button>
<a-button size="small" shape="round" class="h-1-4 leading-1-4" type="primary" danger @click="checkModal.mode = 'REJECT'">
驳回
</a-button>
</div>
<!-- 自己是审核人 且审核过 当前审核人的审核状态并展示得分情况 -->
<div v-show="item.isMine === 1 && item.status > 0" class="text-xs">
<div class="mb-1">
<span v-if="item.status === 1" class="text-green-600"> 已通过 </span>
<span v-else-if="item.status === 2" class="text-red-600"> 已驳回 </span>
</div>
<a-progress v-if="item.score" type="circle" :percent="item.score" strokeColor="#FA8C16" :width="40" :strokeWidth="10">
<template #format="percent">
<span
class="inline-block text-center text-white text-sm rounded-full"
style="background: #fa8c16; width: 24px; height: 24px; line-height: 24px"
>
{{ percent }}
</span>
</template>
</a-progress>
</div>
</div>
</div>
</div>
<checkFormModal :data="checkModal" @hide="checkModal.mode = 'HIDE'" @submit-end="$emit('check-success')" />
</div>
</template>
<script setup>
import { ref, reactive, inject, defineEmits } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
import checkFormModal from './check-form-modal.vue';
const store = useStore();
const deliverData = inject('deliver');
const collapsed = ref(false); // /
defineEmits(['check-success']);
//
function openLink() {
window.open(deliverData.details[0], '_blank');
}
const checkModal = reactive({
mode: 'HIDE', // HIDE-> RESOLVE-> REJECT->
deliverRecordId: () => (deliverData.value ? deliverData.value.deliverRecordId : ''), // id
});
//
function openDeliverHistory() {
const { deliverId } = deliverData.value;
store.commit('task/setDeliverId', deliverId); // id
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', 'deliverHistory'); //
}
//
function openMoreRecords() {
const { deliverRecordId } = deliverData.value;
store.commit('task/setDeliverRecordId', deliverRecordId); // id
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', 'auditRecords'); //
}
</script>
<style scoped>
.ant-progress .ant-progress-inner {
width: 40px !important;
height: 40px !important;
}
</style>

106
src/plugins/p-deliver/p-deliver-history.vue

@ -0,0 +1,106 @@
<template>
<div>
<div class="text-base text-center font-semibold" style="height: 44px; line-height: 44px; background: rgb(248, 248, 248)">
交付物上传记录
</div>
<!-- 历史记录 -->
<div class="px-3 pt-1" v-if="listRef && listRef.length">
<div class="bg-white my-2 rounded-md p-3 text-gray-400" v-for="(item, index) in listRef" :key="index">
<!-- 插件名称和提交时间显示 -->
<div class="flex justify-between mb-2">
<div class="text-gray-800">{{ deliverName }}</div>
<div class="text-xs">{{ dayjs(+item.submitTime).format('MM-DD HH:mm') }}</div>
</div>
<!-- 提交的链接 -->
<div @click="openLink(item)" class="my-1 break-all text-blue-400 text-xs cursor-pointer" v-if="item.details[0]">
{{ item.details[0] }}
</div>
<!-- 该插件物的审核人 -->
<div class="mb-1 mt-3">审核人</div>
<div class="flex justify-between mb-2" v-for="(checkItem, checkIndex) in item.checkerList" :key="checkIndex">
<div>
<div class="mb-1 text-gray-800 font-semibold">
{{ checkItem.checkerName }}
</div>
<div class="mb-1 text-xs">
{{ checkItem.remark }}
</div>
<div class="mb-1 text-xs" v-if="+checkItem.checkTime > 0">
{{ dayjs(+checkItem.checkTime).format('MM-DD HH:mm') }}
</div>
</div>
<div class="text-center text-xs">
<div v-if="checkItem.status == null" class="text-gray-400">待审核</div>
<div v-else-if="checkItem.status === 1">
<div class="text-green-600 mb-1">已通过</div>
<a-progress
v-if="checkItem.score"
type="circle"
:percent="checkItem.score"
strokeColor="#FA8C16"
:width="40"
:strokeWidth="10"
>
<template #format="percent">
<span
class="inline-block text-center text-white text-sm rounded-full"
style="background: #fa8c16; width: 24px; height: 24px; line-height: 24px"
>
{{ percent }}
</span>
</template>
</a-progress>
</div>
<div class="text-red-600" v-else>已驳回</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { ref, onMounted, computed, watch } from 'vue';
import dayjs from 'dayjs';
import { message } from 'ant-design-vue';
import { getDeliverHistory } from 'apis';
const store = useStore();
const listRef = ref([]);
const deliverName = ref('');
const deliverId = computed(() => store.state.task.deliverId);
const sessionDeliverId = sessionStorage.getItem('deliverId');
onMounted(() => {
getHistory();
});
watch(deliverId, () => {
getHistory();
});
async function getHistory() {
try {
const { url } = store.state.projects.project;
const param = { param: { deliverId: deliverId.value || sessionDeliverId } };
const data = await getDeliverHistory(param, url);
deliverName.value = data.deliverName;
listRef.value = data.deliverRecordList;
} catch (error) {
message.info('获取交付物历史失败');
}
}
//
function openLink(item) {
console.log(item.details[0]);
window.open(item.details[0], '_blank');
}
</script>

140
src/plugins/p-deliver/p-deliver-upload.vue

@ -0,0 +1,140 @@
<template>
<div @longpress.prevent="showMask = true">
<!-- 插件名称和提交按钮 -->
<div class="flex item-center justify-between">
<div class="flex-1">
<div v-if="deliver.deliverName" class="relative inline-block">
{{ deliver.deliverName }}
</div>
</div>
<!-- 提交 -->
<a-button type="primary" size="small" @click="submit" :disabled="submitState" :loading="submitBtnLoading"> 提交 </a-button>
</div>
<!-- 插件上传方式 -->
<div class="mt-3">
<div class="link-box">
<a-input v-model:value="linkValue" placeholder="请输入交付物地址/链接" />
</div>
<div class="mt-3">
<!-- <a-button type="primary" size="small" class="mr-3" @click="paste">粘贴</a-button> -->
<div class="inline-block">
<a-upload name="param" :action="action" :headers="headers" :showUploadList="false" :max-count="1" @change="handleChange">
<a-button type="primary" size="small" class="mr-3">文件</a-button>
</a-upload>
</div>
<!-- <a-button type="primary" size="small" class="mr-3" @click="uploadPhoto">拍照</a-button> -->
</div>
</div>
<!-- 审核人 -->
<Reviewer class="mt-3" ref="reviewerRef" />
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { ref, inject, computed, defineEmits } from 'vue';
import { message } from 'ant-design-vue';
import { submitDeliverInfo, uploadImg } from 'apis';
import Reviewer from '@/components/tall/Reviewer/Reviewer.vue';
const store = useStore();
const projectId = computed(() => store.getters['project/projectId']);
const sessionProjectId = sessionStorage.getItem('projectId');
const roleId = computed(() => store.state.role.roleId);
const deliver = inject('deliver'); //
const task = inject('task'); //
const linkValue = ref(''); //
const action = uploadImg;
const token = computed(() => store.state.user.token);
const headers = { Authorization: `Bearer ${token.value}` };
//
const submitState = computed(() => !linkValue.value); //
const submitBtnLoading = ref(false); //
const reviewerRef = ref(null); //
const showMask = ref(false); //
const emits = defineEmits(['upload-success']);
function handleChange(info) {
console.log('info', info);
}
//
async function submit() {
console.log(reviewerRef.value);
const { checkedCheckers } = reviewerRef.value; //
//
if (!validateDeliverForm(checkedCheckers)) return;
submitBtnLoading.value = true; // loading
try {
const checkerList = [];
checkedCheckers.forEach(item => {
checkerList.push(item.memberId);
});
const { url } = store.state.projects.project;
const params = {
param: {
projectId: projectId.value || sessionProjectId,
roleId: roleId.value,
deliverId: deliver.value.deliverId,
fileList: [linkValue.value],
checkerList,
msgId: task.msgId,
},
};
const data = await submitDeliverInfo(params, url);
message.info('提交交付物信息成功');
resetControlState(); //
emits('upload-success');
} catch (error) {
message.info('提交交付物信息失败');
submitBtnLoading.value = false; // loading
throw new Error(error);
}
}
//
// function paste() {}
//
// function uploadPhoto() {}
//
function validateDeliverForm(checkedCheckers) {
const reg = /[a-zA-z]+:\/\/[^\s]*/;
if (!reg.test(linkValue.value)) {
// toast
message.info('请输入正确的链接');
return false;
}
//
if (!checkedCheckers || !checkedCheckers.length) {
message.info('请选择检查人');
return false;
}
return true;
}
//
function resetControlState() {
submitBtnLoading.value = false; // loading
linkValue.value = ''; //
reviewerRef.value.collapsed = true; //
}
</script>

43
src/plugins/p-deliver/p-deliver.vue

@ -0,0 +1,43 @@
<template>
<!-- 任务名插件 -->
<div v-if="deliver" class="task-card-plugin">
<p-deliver-upload v-if="deliver" @upload-success="getDeliverData"></p-deliver-upload>
<p-deliver-check
class="mt-3"
v-if="deliver && deliver.details && deliver.details.length"
@check-success="getDeliverData"
></p-deliver-check>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { ref, inject, provide } from 'vue';
import { getDeliverByTaskId } from 'apis';
import { message } from 'ant-design-vue';
import pDeliverUpload from '@/plugins/p-deliver/p-deliver-upload.vue';
import pDeliverCheck from '@/plugins/p-deliver/p-deliver-check.vue';
const store = useStore();
const task = inject('task');
const pluginInfo = inject('pluginInfo');
const deliver = ref(null); //
deliver.value = pluginInfo && pluginInfo.data ? JSON.parse(pluginInfo.data) : null;
provide('deliver', deliver);
// id
async function getDeliverData() {
try {
const { url } = store.state.projects.project;
const { id: taskId } = task;
if (!taskId) return;
const param = { param: { taskId } };
const data = await getDeliverByTaskId(param, url);
deliver.value = data;
} catch (error) {
message.info(error);
}
}
</script>

8
src/plugins/p-domain-source-manage/p-domain-source-manage-detail.vue

@ -0,0 +1,8 @@
<!-- 项目版本管理 -->
<template>
<div class="text-center">
<a-image src="/src/assets/exarresources.png" />
</div>
</template>
<script setup></script>

18
src/plugins/p-domain-source-manage/p-domain-source-manage.vue

@ -0,0 +1,18 @@
<!-- 域资源管理 -->
<template>
<div class="text-center task-card-plugin">
<a-button type="primary" style="width: 250px" @click="openSourceManage">域资源管理</a-button>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
const store = useStore();
function openSourceManage() {
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', 'exarresources'); //
}
</script>

41
src/plugins/p-finance/p-finance-audit.vue

@ -0,0 +1,41 @@
<template>
<div class="flex justify-around task-card-plugin">
<a-button type="primary" style="width: 125px" @click="openAudit">财务审批</a-button>
<a-button type="primary" style="width: 125px" @click="openStatistical">财务统计</a-button>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { computed, inject } from 'vue';
const store = useStore();
const task = inject('task');
const token = computed(() => store.state.user.token);
const project = computed(() => store.state.projects.project);
const sessionProject = sessionStorage.getItem('project');
if (sessionProject && !project.value.id) {
store.commit('projects/setProject', JSON.parse(sessionProject));
}
//
function openAudit() {
const param = `name=财务审批&token=${token.value}&projectId=${project.value.id}&id=${task.detailId}&pn=${project.value.name}&tn=${task.name}`;
const url = `http://121.36.3.207/finance/financial-approval?${param}`;
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', url); //
store.commit('task/setTaskDetailShow', ''); //
}
//
function openStatistical() {
const param = `name=财务统计&token=${token.value}&projectId=${project.value.id}&id=${task.detailId}&pn=${project.value.name}&tn=${task.name}`;
const url = `http://121.36.3.207/finance/financial-approval?${param}`;
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', url); //
store.commit('task/setTaskDetailShow', ''); //
}
</script>

95
src/plugins/p-finance/p-finance.vue

@ -0,0 +1,95 @@
<!-- 财务条内置组件 -->
<template>
<div class="finance-wrap task-card-plugin" v-if="data" @click="openFinance">
<!-- 预算和奖金 -->
<div class="finance-row">
<div
class="finance-item"
:style="{ width: `${(+data.budget * 100) / (+data.budget + +data.bonus)}%`, 'background-color': '#93C5FD' }"
>
预算{{ (data.budget ? data.budget : 0) / 100 }}
</div>
<div class="finance-item" :style="{ width: `${(+data.bonus * 100) / (+data.budget + +data.bonus)}%`, 'background-color': '#12c77e' }">
奖金{{ (data.bonus ? data.bonus : 0) / 100 }}
</div>
</div>
<!-- 项目采购日常采购 -->
<div class="finance-row">
<div
class="finance-item"
:style="{ width: `${(+data.projectExpend * 100) / (+data.projectExpend + +data.dailyExpend)}%`, 'background-color': '#FBBF24' }"
>
项目采购{{ (data.dailyExpend ? data.dailyExpend : 0) / 100 }}
</div>
<div
class="finance-item"
:style="{ width: `${(+data.dailyExpend * 100) / (+data.projectExpend + +data.dailyExpend)}%`, 'background-color': '#a1fd93' }"
>
日常采购{{ +(data.dailyExpend ? data.dailyExpend : 0) / 100 }}
</div>
</div>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { ref, computed, inject } from 'vue';
import { getFinanceByTask } from 'apis';
const store = useStore();
const task = inject('task');
const data = ref(null);
const pluginInfo = inject('pluginInfo');
const token = computed(() => store.state.user.token);
const project = computed(() => store.state.projects.project);
const sessionProject = sessionStorage.getItem('project');
if (sessionProject && !project.value.id) {
store.commit('projects/setProject', JSON.parse(sessionProject));
}
data.value = pluginInfo && pluginInfo.data ? JSON.parse(pluginInfo.data) : null;
//
async function getFinanceByTaskData() {
try {
const { detailId } = task;
const { url } = project.value;
const params = { param: { detailId } };
data.value = await getFinanceByTask(params, url);
} catch (error) {
console.log('getFinanceByTaskData error: ', error);
}
}
// getFinanceByTaskData();
//
function openFinance() {
// DEBUG:
const param = `name=财务&token=${token.value}&projectId=${project.value.id}&id=${task.detailId}&pn=${project.value.name}&tn=${task.name}`;
const url = `http://121.36.3.207/finance/applicant?${param}`;
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', url); //
store.commit('task/setTaskDetailShow', ''); //
}
</script>
<style scoped>
.finance-wrap .finance-row {
display: flex;
}
.finance-wrap .finance-row .finance-item {
margin: 2px 4px;
font-size: 12px;
text-align: center;
border-radius: 4px;
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: visible;
}
</style>

8
src/plugins/p-project-version-management/p-project-version-management-detail.vue

@ -0,0 +1,8 @@
<!-- 项目版本管理 -->
<template>
<div class="text-center">
<a-image src="/src/assets/projectVersion.png" />
</div>
</template>
<script setup></script>

18
src/plugins/p-project-version-management/p-project-version-management.vue

@ -0,0 +1,18 @@
<!-- 项目版本管理 -->
<template>
<div class="text-center task-card-plugin">
<a-button type="primary" style="width: 250px" @click="openSourceManage">项目版本管理</a-button>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
const store = useStore();
function openSourceManage() {
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', ''); //
store.commit('task/setTaskDetailShow', 'projectVersion'); //
}
</script>

30
src/plugins/p-source-manage/p-source-manage.vue

@ -0,0 +1,30 @@
<!-- 资源管理 -->
<template>
<div class="text-center task-card-plugin">
<a-button type="primary" class="" style="width: 250px" @click="openSourceManage">资源管理</a-button>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { inject, computed } from 'vue';
const store = useStore();
const task = inject('task');
const token = computed(() => store.state.user.token);
const project = computed(() => store.state.projects.project);
const sessionProject = sessionStorage.getItem('project');
if (sessionProject && !project.value.id) {
store.commit('projects/setProject', JSON.parse(sessionProject));
}
function openSourceManage() {
const param = `name=资源管理&token=${token.value}&projectId=${project.value.id}&id=${task.detailId}&pn=${project.value.name}&tn=${task.name}`;
const url = `http://121.36.3.207/finance/index?${param}`;
store.commit('task/setTaskDetailParams', ''); //
store.commit('task/setTaskDetailUrl', url); //
store.commit('task/setTaskDetailShow', ''); //
}
</script>

12
src/plugins/p-task-title.vue

@ -0,0 +1,12 @@
<template>
<!-- 任务名插件 -->
<div class="u-font-14 task-card-plugin">
{{ task.name }}
</div>
</template>
<script setup>
import { defineProps } from 'vue';
defineProps({ task: { type: Object, default: () => {} } });
</script>

36
src/plugins/p-task-to-detail/p-task-to-detail.vue

@ -0,0 +1,36 @@
<template>
<!-- 任务名 跳转详情页 -->
<div class="u-font-14 flex justify-between items-center task-card-plugin">
{{ task.name }}
<right-outlined class="ml-3" @click="toDetail(task)" />
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { computed, defineProps } from 'vue';
import { RightOutlined } from '@ant-design/icons-vue';
const store = useStore();
const projects = computed(() => store.state.projects.projects);
defineProps({ task: { type: Object, default: () => {} } });
function toDetail(task) {
const { id, projectId, executorRoleId, businessUrl, businessCode } = task;
const project = projects.value.find(item => item.projectId);
if (project) {
store.commit('projects/setProject', project);
store.commit('task/clearTasks'); //
store.commit('task/clearRealTasks'); //
store.commit('socket/setCurrLocationTaskId', id);
store.commit('task/setUpNextPage', 1);
store.commit('task/setDownNextPage', 1);
store.commit('task/setTimeLineType', 1);
store.commit('role/setRoleId', executorRoleId);
}
}
</script>
<style></style>

7
src/plugins/workbench/workbench.vue

@ -0,0 +1,7 @@
<template>
<div class="text-center">
<a-image src="/src/assets/work.jpg" />
</div>
</template>
<script setup></script>

12
src/routers/index.js

@ -1,14 +1,14 @@
import { createRouter, createWebHistory } from 'vue-router';
// 还有 createWebHashHistory 和 createMemoryHistory
export const user = [{ path: '/experiment/user/signIn', name: 'signIn', component: () => import('views/user/SignIn.vue') }];
export const user = [{ path: '/tall/pc/user/signIn', name: 'signIn', component: () => import('views/user/SignIn.vue') }];
export const routes = [
{
path: '/experiment/home',
path: '/tall/pc/home',
name: 'home',
redirect: '/experiment/home/test',
redirect: '/tall/pc/home/test',
component: () => import('views/home/Index.vue'),
children: [{ path: '/experiment/home/test', name: 'test', component: () => import('views/detail/Test.vue') }],
children: [{ path: '/tall/pc/home/test', name: 'test', component: () => import('views/detail/Test.vue') }],
},
];
@ -16,8 +16,8 @@ const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/experiment',
redirect: '/experiment/home',
path: '/tall/pc',
redirect: '/tall/pc/home',
},
...user,
...routes,

31
src/store/tall/layout/actions.js

@ -0,0 +1,31 @@
import { getAllPlugin, getBusinessPlugin } from 'apis';
import { message } from 'ant-design-vue';
const actions = {
async getBusinessPlugin({ commit }) {
try {
const res = await getBusinessPlugin();
sessionStorage.setItem('businessPlugin', JSON.stringify(res) || '');
commit('setBusinessPlugin', JSON.stringify(res) || '');
return res;
} catch (error) {
message.info(error.msg);
throw error;
}
},
async getAllPlugin({ commit }) {
try {
const res = await getAllPlugin();
sessionStorage.setItem('allPlugin', JSON.stringify(res) || '');
commit('setAllPlugin', JSON.stringify(res) || '');
return res;
} catch (error) {
message.info(error.msg);
throw error;
}
},
};
export default actions;

4
src/store/tall/layout/index.js

@ -1,8 +1,10 @@
import state from './state';
import actions from './actions';
import mutations from './mutations';
import state from './state';
export default {
namespaced: true,
state,
mutations,
actions,
};

44
src/store/tall/layout/mutations.js

@ -9,11 +9,6 @@ const mutations = {
state.display[prop] = show;
},
// 设置是否显示列表
setListStatus(state, data) {
state.isShowListStatus = data;
},
// 设置选择的时间
setSelectTime(state, data) {
state.startTime = data.startTime;
@ -25,28 +20,31 @@ const mutations = {
state.refreshProjects = !state.refreshProjects;
},
// 设置课题开始时间-结束时间
setFirPlanTime(state, data) {
state.planStartTime = data.startTime;
state.planEndTime = data.endTime;
sessionStorage.setItem('planStartTime', data.startTime);
sessionStorage.setItem('planEndTime', data.endTime);
// 设置唯一标识
setDeviceId(state, data) {
state.deviceId = data;
},
/**
* 设置服务列表
* @param {Object} state
* @param {Object} data
*/
setBusinessPlugin(state, data) {
state.businessPlugin = data;
},
// 设置子课题开始时间-结束时间
setSecPlanTime(state, data) {
state.subStartTime = data.startTime;
state.subEndTime = data.endTime;
sessionStorage.setItem('subStartTime', data.startTime);
sessionStorage.setItem('subEndTime', data.endTime);
/**
* 设置插件列表
* @param {Object} state
* @param {Object} data
*/
setAllPlugin(state, data) {
state.allPlugin = data;
},
// 设置实验开始时间-结束时间
setThirdPlanTime(state, data) {
state.expreStartTime = data.startTime;
state.expreEndTime = data.endTime;
sessionStorage.setItem('expreStartTime', data.startTime);
sessionStorage.setItem('expreEndTime', data.endTime);
setIsFullScreen(state, data) {
state.isFullScreen = data;
},
};

11
src/store/tall/layout/state.js

@ -3,16 +3,13 @@ const state = {
display: {
left: true, // 是否显示左栏
},
isShowListStatus: false, // 是否显示任务详情列表
startTime: '',
endTime: '',
refreshProjects: false, // 刷新项目
planStartTime: '', // 课题开始时间
planEndTime: '', // 课题结束时间
subStartTime: '', // 子课题开始时间
subEndTime: '', // 子课题结束时间
expreStartTime: '', // 实验开始时间
expreEndTime: '', // 实验结束时间
deviceId: '', // 唯一标识
businessPlugin: '', // 所有服务
allPlugin: '', // 所有插件
isFullScreen: false, // 流水账插件详情页是否全屏
};
export default state;

9
src/store/tall/projects/index.js

@ -6,7 +6,6 @@ export default {
projects: [], // 项目列表
dotList: [], // 小红点
newProject: {}, // 新建项目信息
expreStatus: 0, // 实验状态
},
getters: {
@ -94,14 +93,6 @@ export default {
setDotList(state, data) {
state.dotList = data;
},
/**
* 设置实验状态
*/
setExpreimentStatus(state, data) {
state.expreStatus = data || 0;
sessionStorage.setItem('expreStatus', state.expreStatus);
},
},
actions: {},

11
src/store/tall/role/getters.js

@ -1,12 +1,11 @@
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;
if (!visibleRoles || !visibleRoles.length) return false;
const visibleData = visibleRoles.find(visible => visible.id === roleId);
if (visibleData) return visibleData.mine;
const invisibleData = invisibleRoles.find(invisible => invisible.id === roleId);
if (invisibleData) return invisibleData.mine;
return false;
},
};

12
src/store/tall/role/mutations.js

@ -15,7 +15,16 @@ const mutations = {
*/
setVisibleRoles(state, data) {
state.visibleRoles = data || [];
sessionStorage.setItem('roles', JSON.stringify(data));
// sessionStorage.setItem('roles', JSON.stringify(data));
},
/**
* 设置当前角色信息
* @param {Object} state
* @param {string} roleId 当前正在展示的角色的id
*/
setRoleInfo(state, data) {
state.roleInfo = data;
},
/**
@ -25,6 +34,7 @@ const mutations = {
*/
setRoleId(state, roleId) {
state.roleId = roleId;
sessionStorage.setItem('roleId', roleId);
},
/**

1
src/store/tall/role/state.js

@ -2,6 +2,7 @@ const state = {
invisibleRoles: [], // 不展示的角色信息
visibleRoles: [], // 展示的角色信息
roleId: '', // 当前展示查看的角色id
roleInfo: null, // 当前展示查看的角色
members: [], // 项目下所有成员
};

291
src/store/tall/task/index.js

@ -1,5 +1,5 @@
import { getByCode, submitAnswer, getIntellectual, getIntellectualList, submitIntellectual } from 'apis';
import { message } from 'ant-design-vue';
// import { message } from 'ant-design-vue';
import timeConfig from 'utils/time';
export default {
namespaced: true,
@ -26,19 +26,21 @@ export default {
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,
// regularTasks: [], // 定期任务
realTasks: [], // 真实定期任务
currLocationTaskId: '', // 前需要定位到的任务id
businessCode: '', // 当前打开的项目的所属服务
timeLineType: 1, // 时间轴模式
upNextPage: 1, // 向上下一页的值
downNextPage: 1, // 向下下一页的值
taskDetailUrl: '', // iframe详情页链接
taskDetailShow: '', // 内置组件详情页显示
deliverId: '', // 当前交付物id
deliverRecordId: '', // 当前交付物记录id
detailParams: '', // 设置详情页参数
},
getters: {
@ -47,20 +49,20 @@ export default {
return [...permanents, ...dailyTasks];
},
// unitConfig({ timeUnit }) {
// const target = uni.$t.timeConfig.timeUnits.find(item => item.id === timeUnit);
// return target;
// },
unitConfig({ timeUnit }) {
const target = timeConfig.timeUnits.find(item => item.id === timeUnit);
return target;
},
// 计算任务开始时间的格式
// startTimeFormat(state, { unitConfig }) {
// return unitConfig.format || 'D日 HH:mm';
// },
startTimeFormat(state, { unitConfig }) {
return unitConfig.format || 'D日 HH:mm';
},
// 计算颗粒度 对应的 dayjs add 的单位
// timeGranularity(state, { unitConfig }) {
// return unitConfig.granularity;
// },
timeGranularity(state, { unitConfig }) {
return unitConfig.granularity;
},
},
mutations: {
@ -231,17 +233,7 @@ export default {
*/
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));
// sessionStorage.setItem('permanents', JSON.stringify(tasks));
},
/**
@ -311,187 +303,154 @@ export default {
},
/**
* 设置当前点击的任务信息
* 定期任务
* @param {object} state
* @param {array} tasks 服务端查询到的定期任务书籍
*/
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');
}
},
// setRegularTasks(state, tasks) {
// state.regularTasks = tasks || [];
// // sessionStorage.setItem('regularTasks', JSON.stringify(tasks));
// },
/**
* 设置当前任务的code
* 真实定期任务
* @param {object} state
* @param {array} tasks 服务端查询到的定期任务书籍
*/
setLabel(state, data) {
state.label = data;
if (data) {
sessionStorage.setItem('label', data);
} else {
sessionStorage.removeItem('label');
}
},
setUpRealTasks(state, tasks) {
state.realTasks = [...tasks, ...state.realTasks];
/**
* 设置成员列表
*/
setMembers(state, data) {
state.members = data || [];
const arr = [];
state.realTasks.forEach(item => {
const index = arr.findIndex(v => v.id === item.id);
if (index === -1) {
arr.push(item);
}
});
state.realTasks = [...arr];
},
/**
* 设置日常任务面板高度
* 真实定期任务
* @param {object} state
* @param {array} tasks 服务端查询到的定期任务书籍
*/
setGlobalHeight(state, data) {
state.globalHeight = data;
sessionStorage.setItem('globalHeight', data);
setDownRealTasks(state, tasks) {
state.realTasks = [...state.realTasks, ...tasks];
const arr = [];
state.realTasks.forEach(item => {
const index = arr.findIndex(v => v.id === item.id);
if (index === -1) {
arr.push(item);
}
});
state.realTasks = [...arr];
},
/**
* 子课题Id
*/
sonDetailId(state, data) {
state.detailId = data;
// 清空真实任务数据
clearRealTasks(state) {
state.realTasks = [];
},
/**
* 设置当前子课题信息
* 当前需要定位到的任务id
* @param {Object} state
* @param {Object} data
*/
setSubProjectInfo(state, data) {
state.subProjectInfo = data;
setCurrLocationTaskId(state, data) {
state.currLocationTaskId = data;
},
/**
* 实验Id
* 当前打开的项目的所属服务
* @param {Object} state
* @param {Object} data
*/
sonExperimentationId(state, data) {
state.experimentationId = data;
setBusinessCode(state, data) {
state.businessCode = data;
},
/**
* 设置当前实验信息
* 时间轴模式
* @param {Object} state
* @param {Object} data
*/
setExperimentationInfo(state, data) {
state.experimentationInfo = data;
setTimeLineType(state, data) {
state.timeLineType = data;
},
/**
* 知识产权ID
* 向上下一页页数
* @param {Object} state
* @param {Object} data
*/
setIntellectualId(state, data) {
state.intellectualId = data;
setUpNextPage(state, data) {
state.upNextPage = data;
},
/**
* 会议ID
* 下一页页数
* @param {Object} state
* @param {Object} data
*/
setMeetId(state, data) {
state.meetId = data;
setDownNextPage(state, data) {
state.downNextPage = data;
},
/**
* 子课题会议ID
* 设置iframe详情页链接
* @param {Object} state
* @param {Object} data
*/
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);
}
setTaskDetailUrl(state, data) {
state.taskDetailUrl = data;
sessionStorage.setItem('targetUrl', '');
sessionStorage.setItem('taskDetailUrl', data);
},
/**
* 答案
* @param {string} param.code
* @param {integer} param.projectId
* @param {array} param.questionAndAnswerList
* 设置内置组件详情页显示
* @param {Object} state
* @param {Object} data
*/
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);
}
setTaskDetailShow(state, data) {
state.taskDetailShow = data;
sessionStorage.setItem('taskDetailShow', data);
},
/**
* 查询知识产权题目论文专利软著
* @param {string} param.code
* @param {integer} param.projectId
* @param {integer} param.intellectualId
* 设置当前交付物id
* @param {Object} state
* @param {Object} data
*/
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);
}
setDeliverId(state, data) {
state.deliverId = data;
sessionStorage.setItem('deliverId', data);
},
/**
* 查询知识产权列表 论文专利软著
* @param {string} param.code
* @param {integer} param.projectId
* @param {integer} param.intellectualId
* 设置交付物记录id
* @param {Object} state
* @param {Object} data
*/
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);
}
setDeliverRecordId(state, data) {
state.deliverRecordId = data;
sessionStorage.setItem('deliverRecordId', data);
},
/**
* 提交知识产权信息论文专利软著
* @param {string} param.code
* @param {integer} param.projectId
* @param {integer} param.intellectualId
* @param {array} param.questionAndAnswerList
* 设置详情页参数
* @param {Object} state
* @param {Object} data
*/
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);
}
setTaskDetailParams(state, data) {
state.detailParams = data;
sessionStorage.setItem('detailParams', JSON.stringify(data));
},
},
actions: {},
};

35
src/store/tall/user/index.js

@ -1,10 +1,11 @@
import { getToken } from 'apis';
import { getNewToken, getToken } from 'apis';
import { message } from 'ant-design-vue';
export default {
namespaced: true,
state: { user: null },
state: { user: null, token: '', count: 3 },
getters: {
token({ user }) {
@ -37,6 +38,22 @@ export default {
sessionStorage.removeItem('user');
}
},
/**
* 设置token值
* @param {*} state
* @param {*} data
*/
setToken(state, data) {
state.token = data;
},
/**
* 设置token过期重新请求次数
*/
setCount(state, data) {
state.count = data;
},
},
actions: {
@ -55,5 +72,19 @@ export default {
throw new Error(error);
}
},
/**
* 根据refreshToken重新获取token
* @param {string} refreshToken
*/
async getTokenByRefreshToken({ commit }, refreshToken) {
try {
const data = await getNewToken(refreshToken);
return data;
} catch (error) {
message.error(error);
throw new Error(error);
}
},
},
};

49
src/utils/axios.js

@ -1,6 +1,7 @@
import Axios from 'axios';
import { message } from 'ant-design-vue';
import store from 'store';
import router from '../routers/index';
const baseUrl = '/gateway';
@ -12,10 +13,12 @@ const instance = Axios.create({
// request
instance.interceptors.request.use(
config => {
const token = store.getters['user/token'] || localStorage.getItem('token');
const token = store.state.user.token || sessionStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
config.headers.appType = 0;
config.headers.deviceId = store.state.layout.deviceId;
return config;
},
error => {
@ -25,17 +28,55 @@ instance.interceptors.request.use(
// response
instance.interceptors.response.use(
response => {
async response => {
if (response.status !== 200 || !response.data) {
return Promise.reject(response.statusText);
}
const { code, data, msg } = response.data;
const { code, msg, tokenObj } = response.data;
const resData = response.data.data;
if (code === 200) {
return data;
if (tokenObj.token) {
store.commit('user/setToken', tokenObj.token);
sessionStorage.setItem('token', tokenObj.token);
sessionStorage.setItem('refreshToken', tokenObj.refreshToken);
}
return resData || 1;
}
if (code === 49) {
// token过期
store.commit('user/setCount', store.state.user.count - 1);
// 2、刷新token
const refreshToken = sessionStorage.getItem('refreshToken');
const data = await store.dispatch('user/getTokenByRefreshToken', refreshToken);
// 3、重新请求当前api
const { config } = response;
config.headers.Authorization = `Bearer ${data.token}`;
const res = await Axios.request(config);
if (res.data.code === 200) {
store.commit('user/setCount', 3);
}
return res.data.data;
}
if (code === 401) {
// refreshToken过期
sessionStorage.removeItem('token');
sessionStorage.removeItem('refreshToken');
store.commit('user/setUser', null);
store.commit('user/setToken', '');
message.error(msg);
setTimeout(() => {
router.push({ path: '/tall/pc/user/signin' });
}, 1500);
return Promise.reject(msg);
}
return Promise.reject(msg);
},
error => {
console.log('error', error);
if (error.response && error.response.data) {
const msg = error.response.data.message;
message.error(msg);

9
src/utils/deliver.js

@ -0,0 +1,9 @@
// 上传文件的扩展名
export const UPLOAD_EXTENSION = ['.xls', '.xlsx', '.zip', '.exe', '.pdf', '.doc', '.docx', '.ppt', '.pptx'];
// 审核的快捷用语
export const quickWords = {
// RESOLVE: ['加油,再接再厉!', '很棒!', '不错,很详细!', '不详细', '还有需要改进的地方', '驳回审批']
RESOLVE: ['加油,再接再厉!', '很棒!', '不错,很详细!', '加油,再接再厉'], // 审核通过常用的审批语
REJECT: ['不详细', '还有需要改进的地方', '驳回审批1', '驳回审批2'], // 审核驳回常用的审批语
};

36
src/utils/storage.js

@ -0,0 +1,36 @@
// 用闭包实现局部对象storage(注意Storage的方法都重写一遍,不然调用其对象原型方法会报错。)
const sessionStorageMock = (function (win) {
const storage = win.sessionStorage;
return {
setItem(key, value) {
const setItemEvent = new Event('setItemEvent');
const oldValue = storage[key];
setItemEvent.key = key;
// 新旧值深度判断,派发监听事件
if (oldValue !== value) {
setItemEvent.newValue = value;
setItemEvent.oldValue = oldValue;
win.dispatchEvent(setItemEvent);
storage[key] = value;
return true;
}
return false;
},
getItem(key) {
return storage[key];
},
removeItem(key) {
storage[key] = null;
return true;
},
clear: () => {
storage.clear();
return true;
},
key(index) {
return storage.key(index);
},
};
})(window);
Object.defineProperty(window, 'sessionStorage', { value: sessionStorageMock, writable: true });

65
src/utils/task.js

@ -0,0 +1,65 @@
import dayjs from 'dayjs';
const pageTaskCount = 15;
/**
* 设置时间轴空数据
* @param {number} startTime
* @param {boolean} isUp true 向上加载,false 向下加载
* @param {string} timeGranularity 颗粒度
* @param {number} pageCount 加载的颗粒度数量 默认值是10
*/
const setPlaceholderTasks = (startTime, isUp, timeGranularity, pageCount) => {
const result = [];
pageCount = pageCount || pageTaskCount;
for (let i = 0; i < pageCount; i++) {
const delta = isUp ? `-${i + 1}` - 0 : i;
const item = {
id: Math.random() * 1000000000000000000,
panel: {},
plugins: [],
process: 4,
planStart: dayjs(+startTime)
.add(+delta, timeGranularity)
.valueOf(),
};
// console.log('isup: ', isUp, 'result:', new Date(item.planStart).toLocaleDateString());
isUp ? result.unshift(item) : result.push(item);
}
return result;
};
/**
* 超出旧数据上下限 补齐时间刻度到新数据的起始时间颗粒度
* @param {object} option
* @param {array} option.tasks 旧的已有的任务书籍
* @param {array} option.data 新拿到的任务数据 空值已经过滤过了
* @param {string} option.timeGranularity 颗粒度
*/
const computeFillPlaceholderTaskCount = obj => {
const { tasks, data, timeGranularity } = obj;
const result = { prev: 0, next: 0 };
// 新数据的开始时间 < 旧数据的开始时间
// 超出了上限 补上限的时间刻度
// 补上 新数据开始时间 到 旧数据开始时间 的刻度
if (+data[0].planStart < +tasks[0].planStart) {
// 找出来需要补几组颗粒度
result.prev = dayjs(+tasks[0].planStart).diff(+data[0].planStart, timeGranularity) + 1;
}
// 新数据的结束时间 > 旧数据的结束时间
// 超出了下线 补下限的时间刻度
// 补上 旧数据截止时间 到 新数据截止时间 的刻度
if (+data[data.length - 1].planStart > +tasks[tasks.length - 1].planStart) {
result.next = dayjs(+data[data.length - 1].planStart).diff(+tasks[tasks.length - 1].planStart, timeGranularity) + 1;
}
return result;
};
export default {
setPlaceholderTasks,
computeFillPlaceholderTaskCount,
pageTaskCount,
};

17
src/utils/time.js

@ -0,0 +1,17 @@
export default {
timeUnits: [
// 时间颗粒度
{ id: 0, value: '毫秒', format: 'x', cycle: 'YY-M-D HH:mm:ss', granularity: 'millisecond' },
{ id: 1, value: '秒', format: 'x', cycle: 'YY-M-D HH:mm:ss', granularity: 'second' },
{ id: 2, value: '分', format: 'ss', cycle: 'YY-M-D HH:mm', granularity: 'minute' },
{ id: 3, value: '时', format: 'mm', cycle: 'YY-M-D HH时', granularity: 'hour' },
{ id: 4, value: '天', format: 'M月D日 HH:mm', cycle: 'YY-M-D', granularity: 'day' },
{ id: 5, value: '周', format: 'M月D日 HH:mm', cycle: '', granularity: 'week' },
{ id: 6, value: '月', format: 'M月D日 H:m', cycle: 'YYYY年', granularity: 'month' },
{ id: 7, value: '季度', format: '', cycle: 'YYYY年', granularity: 'quarter' },
{ id: 8, value: '年', format: 'YYYY', cycle: '', granularity: 'year' },
{ id: 9, value: '年代', format: '', cycle: '', granularity: '' },
{ id: 10, value: '世纪', format: '', cycle: '', granularity: '' },
{ id: 11, value: '千年', format: '', cycle: '', granularity: '' },
],
};

311
src/views/detail/Test.vue

@ -1,306 +1,65 @@
<template>
<div class="task-detail">
<div class="task-con flex">
<!-- {{ taskInfo.name }} -->
<div class="task-con-box">
<!-- <div>{{ label }}</div> -->
<!-- 课题 -->
<!-- 查看课题进展 -->
<CheckSubjectProgress v-if="label === 'KT_KTJZ'" />
<DetailWebview v-if="taskDetailUrl"></DetailWebview>
<!-- 科研会议管理 -->
<MeetingManagement v-if="label === 'KT_KYHY'" />
<!-- 工作台 -->
<workbench v-if="taskDetailShow === 'workbench'"></workbench>
<!-- 合同管理 -->
<ContractManagement v-if="label === 'KT_HTGL'" />
<!-- 流水账详情页 -->
<p-daily-account-detail v-if="taskDetailShow === 'dailyAccount'"></p-daily-account-detail>
<!-- 数据追溯解锁 -->
<DataUnlock v-if="label === 'KT_SJZSJS'" />
<!-- 交付物历史记录 -->
<p-deliver-history v-if="taskDetailShow === 'deliverHistory'"></p-deliver-history>
<!-- 成员管理 -->
<MemberManagement v-if="label === 'KT_CYGL' || label === 'ZKT_CYGL'" />
<!-- 交付物审核记录 -->
<p-audit-records v-if="taskDetailShow === 'auditRecords'"></p-audit-records>
<!-- 计划任务书 -->
<PlanAssignment v-if="label === 'KT_JHRWS'" />
<!-- 交付物2详情页 -->
<p-deliver-second-detail v-if="taskDetailShow === 'deliverSecDetail'"></p-deliver-second-detail>
<!-- 分配课题 -->
<AssignmentSubject v-if="label === 'KT_FPKT'" />
<!-- 项目版本管理详情页 -->
<p-project-version-management-detail v-if="taskDetailShow === 'projectVersion'"></p-project-version-management-detail>
<!-- 提交中期检查报告 -->
<InterimInspection v-if="label === 'KT_ZQJC'" />
<!-- 域资源管理详情页 -->
<p-domain-source-manage-detail v-if="taskDetailShow === 'exarresources'"></p-domain-source-manage-detail>
<!-- 项目结题报告 -->
<Conclusion v-if="label === 'KT_JTBG'" />
<!-- 账号管理详情页 -->
<p-account-management-audit v-if="taskDetailShow === 'personal'"></p-account-management-audit>
<!-- 项目验收报告 -->
<Result v-if="label === 'KT_YSZS'" />
<!-- 子课题 -->
<!-- 子课题进展 -->
<SubSubjectProgress v-if="label === 'ZKT_KTJZ'" />
<!-- 子课题科研会议管理 -->
<SubMeetingManagement v-if="label === 'ZKT_HYGL'" />
<!-- 发表论文 -->
<PublishThesis v-if="label === 'ZKT_LW'" />
<!-- 申请专利 -->
<PublishPatent v-if="label === 'ZKT_ZL'" />
<!-- 发表软件著作权 -->
<PublishWork v-if="label === 'ZKT_RZ'" />
<!-- 科研成果管理 -->
<ScientificPayoffs v-if="label === 'ZKT_KYCG'" />
<!-- 成员管理 -->
<!-- <SubMemberManagement v-if="label === 'ZKT_CYGL'" /> -->
<!-- 分配实验 -->
<AssignmentExperiment v-if="label === 'ZKT_FPSY'" />
<!-- 子课题中期检查报告 -->
<SubInterimInspection v-if="label === 'ZKT_ZQJC'" />
<!-- 子课题结题报告 -->
<SubConclusion v-if="label === 'ZKT_JTBG'" />
<!-- 子课题验收报告 -->
<SubResult v-if="label === 'ZKT_YSZS'" />
<!-- 实验 -->
<!-- 提交实验报告 -->
<LabReport v-if="label === 'SY_BG'" />
<!-- 记录实验过程 -->
<Procedure v-if="label === 'SY_GC'" />
<!-- 记录实验数据 -->
<ExperimentalData v-if="label === 'SY_SJ'" />
<!-- 实验程序代码 -->
<ExperimentalCode v-if="label === 'SY_DM'" />
<!-- 撰写实验结果 -->
<ExperimentalResult v-if="label === 'SY_JG'" />
</div>
<div class="task-con-list" v-if="isShowList">
<TaskConList />
</div>
</div>
<!-- UI配置详情页 -->
<p-account-management-uidispose v-if="taskDetailShow === 'uidispose'"></p-account-management-uidispose>
</div>
</template>
<script setup>
import { computed, watch, ref } from 'vue';
import { computed } from 'vue';
import { useStore } from 'vuex';
import CheckSubjectProgress from 'components/tall/task/CheckSubjectProgress.vue'; //
import MeetingManagement from 'components/tall/task/MeetingManagement.vue'; //
import ContractManagement from 'components/tall/task/ContractManagement.vue'; //
import DataUnlock from 'components/tall/task/DataUnlock.vue'; //
import MemberManagement from 'components/tall/task/MemberManagement.vue'; //
import PlanAssignment from 'components/tall/task/PlanAssignment.vue'; //
import AssignmentSubject from 'components/tall/task/AssignmentSubject.vue'; //
import InterimInspection from 'components/tall/task/InterimInspection.vue'; //
import Conclusion from 'components/tall/task/Conclusion.vue'; //
import Result from 'components/tall/task/Result.vue'; //
import SubSubjectProgress from 'components/tall/task/SubSubjectProgress.vue'; //
import SubMeetingManagement from 'components/tall/task/SubMeetingManagement.vue'; //
import PublishThesis from 'components/tall/task/PublishThesis.vue'; //
import PublishPatent from 'components/tall/task/PublishPatent.vue'; //
import PublishWork from 'components/tall/task/PublishWork.vue'; //
import ScientificPayoffs from 'components/tall/task/ScientificPayoffs.vue'; //
// import SubMemberManagement from 'components/tall/task/SubMemberManagement.vue'; //
import AssignmentExperiment from 'components/tall/task/AssignmentExperiment.vue'; //
import SubInterimInspection from 'components/tall/task/SubInterimInspection.vue'; //
import SubConclusion from 'components/tall/task/SubConclusion.vue'; //
import SubResult from 'components/tall/task/SubResult.vue'; //
import LabReport from 'components/tall/task/LabReport.vue'; //
import Procedure from 'components/tall/task/Procedure.vue'; //
import ExperimentalData from 'components/tall/task/ExperimentalData.vue'; //
import ExperimentalCode from 'components/tall/task/ExperimentalCode.vue'; //
import ExperimentalResult from 'components/tall/task/ExperimentalResult.vue'; //
import TaskConList from 'components/tall/task/TaskConList.vue'; //
import DetailWebview from '@/components/tall/Right/DetailWebview.vue';
import pDailyAccountDetail from '@/plugins/p-daily-account/p-daily-account-detail.vue';
import pDeliverHistory from '@/plugins/p-deliver/p-deliver-history.vue';
import pAuditRecords from '@/plugins/p-deliver/p-audit-records.vue';
import pDeliverSecondDetail from '@/plugins/p-deliver-second/p-deliver-second-detail.vue';
import pProjectVersionManagementDetail from '@/plugins/p-project-version-management/p-project-version-management-detail.vue';
import pDomainSourceManageDetail from '@/plugins/p-domain-source-manage/p-domain-source-manage-detail.vue';
import pAccountManagementAudit from '@/plugins/p-account-management/p-account-management-audit.vue';
import pAccountManagementUidispose from '@/plugins/p-account-management/p-account-management-uidispose.vue';
import workbench from '@/plugins/workbench/workbench.vue';
const store = useStore();
const taskDetail = computed(() => store.state.task.taskDetail); //
const label = ref(null);
const sessionTaskDetail = sessionStorage.getItem('taskDetail');
const isShowList = computed(() => store.state.layout.isShowListStatus); //
if (sessionTaskDetail) {
const taskInfo = JSON.parse(sessionTaskDetail);
if (taskInfo.plugins && taskInfo.plugins.length > 0) {
taskInfo.plugins.forEach(item => {
if (Number(item[0].pluginId) === 1) {
label.value = item[0].param;
store.commit('task/setLabel', label.value);
}
});
}
}
//
watch([taskDetail], () => {
if (!taskDetail.value) return;
const taskInfo = taskDetail.value;
if (taskInfo.plugins && taskInfo.plugins.length > 0) {
taskInfo.plugins.forEach(item => {
if (Number(item[0].pluginId) === 1) {
label.value = item[0].param;
store.commit('task/setLabel', label.value);
}
});
}
});
const taskDetailUrl = computed(() => store.state.task.taskDetailUrl); // iframe
const taskDetailShow = computed(() => store.state.task.taskDetailShow); //
</script>
<style scoped>
.task-detail {
width: 100%;
height: 100%;
padding: 16px;
/* padding: 16px; */
overflow: auto;
}
.task-con {
min-width: 760px;
min-height: 500px;
}
.task-con-box {
max-width: 100%;
flex: 1;
}
.task-form {
padding: 24px;
display: flex;
justify-content: center;
}
.task-form :deep(.ant-form) {
width: 100%;
max-width: 680px;
}
.task-con-list {
min-width: 320px;
max-width: 460px;
background: #fff;
border-radius: 10px;
box-shadow: -5px 0px 5px 0px rgba(3, 27, 49, 0.1);
}
:deep(.ant-input) {
height: 38px;
border: 1px solid #cccccc;
border-radius: 4px;
}
:deep(.ant-btn-primary) {
width: 180px;
height: 38px;
font-size: 16px;
border-radius: 6px;
letter-spacing: 2px;
}
:deep(.ant-space) {
width: 100%;
}
:deep(.ant-picker) {
height: 38px;
width: 100%;
border: 1px solid #cccccc;
border-radius: 4px;
}
:deep(.ant-select-single:not(.ant-select-customize-input) .ant-select-selector) {
height: 38px;
}
:deep(.ant-select-single:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input) {
height: 38px;
}
:deep(.ant-select-single .ant-select-selector .ant-select-selection-item),
:deep(.ant-select-single .ant-select-selector .ant-select-selection-placeholder) {
line-height: 36px;
}
:deep(.ant-upload) {
width: 100%;
}
/* 上传样式 */
:deep(.ant-upload) .upload-box {
padding: 20px 0;
border: 1px dashed #cccccc;
background: #fafafa;
border-radius: 4px;
}
:deep(.ant-upload) .upload-box img {
width: 32px;
}
:deep(.ant-upload) .upload-box p:first-of-type {
margin-top: 24px;
line-height: 1;
}
:deep(.ant-upload) .upload-box p:last-of-type {
margin-top: 16px;
line-height: 1;
}
:deep(.form-item-dad) {
margin-bottom: 0 !important;
}
.form-item-son {
padding-left: 16px;
}
/* 表格 */
:deep(.ant-table-thead > tr > th),
:deep(.ant-table-tbody > tr > td) {
height: 60px;
}
:deep(.ant-table-thead > tr > th) {
background: #fff;
font-size: 16px;
color: #333;
}
:deep(.ant-table-thead
> tr
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before) {
width: 0;
}
:deep(.ant-checkbox-group) {
width: 100%;
}
/* 单选 */
:deep(.ant-radio-group) {
width: 100%;
display: flex;
align-items: center;
}
:deep(.ant-radio-wrapper) {
display: flex !important;
.task-detail::-webkit-scrollbar {
width: 0 !important;
}
</style>

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

Loading…
Cancel
Save