AmountOfWork.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. <template>
  2. <div class="WorkHoursSelect">
  3. <div class="select-title">
  4. <span>工作量分配</span>
  5. </div>
  6. <div class="select-list">
  7. <div class="title">
  8. <div class="left">
  9. {{ '工作项目' + (defaultTotalTime ? `(总耗时${defaultTotalTime}小时)` : '') }}
  10. </div>
  11. <div class="right">
  12. <el-input
  13. ref="searchInputRef"
  14. placeholder="搜索项目"
  15. v-model="searchValue"
  16. @input="searchOnchange"
  17. clearable
  18. />
  19. </div>
  20. </div>
  21. <ul class="list-ul">
  22. <li v-for="(item, index) in dataList" :key="index" @click="handleClick(item)">
  23. <div class="project-write">
  24. <el-popover
  25. ref="popperRef"
  26. popper-class="popover-panel"
  27. placement="top-end"
  28. :width="400"
  29. trigger="click"
  30. :visible="item.visible"
  31. >
  32. <template #reference>
  33. <div class="time-select">
  34. <span>{{ item.count ? `(${item.count}小时)` : '' }}</span>
  35. {{ item['name'] }}
  36. </div>
  37. </template>
  38. <template #default>
  39. <div class="work-hours-select-panel">
  40. <p class="title">{{ item['name'] }} 工时 (小时)</p>
  41. <div class="panel-input-number">
  42. 输入时间:
  43. <el-input-number
  44. v-model="item.count"
  45. class="input-number"
  46. :min="0"
  47. :max="80"
  48. controls-position="right"
  49. @change="(val) => changeCount(val, index)"
  50. />
  51. </div>
  52. <div class="panel-numeric-key">
  53. <ul>
  54. <li
  55. v-for="(num, i) in 40"
  56. :style="{
  57. background: i + 1 === item.count ? '#1b80eb' : '#f7f8fa',
  58. color: i + 1 === item.count ? '#fff' : '#000000'
  59. }"
  60. :key="i"
  61. @click="changeCount(num, index, true)"
  62. >
  63. <span>{{ num }}</span>
  64. </li>
  65. </ul>
  66. </div>
  67. <div class="panel-submit">
  68. <el-button type="primary" @click="panelSubmit(index)">确定</el-button>
  69. <el-button type="danger" @click="changeCount(0, index)">清零</el-button>
  70. <el-button type="info" @click="panelCancel(index)"> 取消 </el-button>
  71. </div>
  72. </div>
  73. </template>
  74. </el-popover>
  75. </div>
  76. </li>
  77. </ul>
  78. </div>
  79. </div>
  80. </template>
  81. <script setup lang="ts">
  82. /** 周日报相同文件 */
  83. import request from '@/config/axios'
  84. import { getUserInfo } from '@/utils/tool'
  85. // 获取用户信息
  86. const userInfo = getUserInfo()
  87. defineOptions({ name: 'WorkHoursSelect' })
  88. interface IProp {
  89. onChange: (any) => any
  90. initialData?: any[] // 初始数据
  91. nowDate: string
  92. }
  93. const props = defineProps<IProp>()
  94. const { onChange } = props
  95. const { initialData, nowDate } = toRefs(props)
  96. // 默认工作项目总时长
  97. const defaultTotalTime = ref(0)
  98. // 工时填写弹框的ref
  99. const popperRef = ref<any>(null)
  100. // 初始化数据
  101. const dataList = ref<any[]>([])
  102. const setInitData = () => {
  103. // 如果不是切换天数,则不重置
  104. if (defaultTotalTime.value > 0) return
  105. if (initialData?.value?.length == 0) {
  106. dataList.value = dataList.value.map((item) => {
  107. return {
  108. ...item,
  109. count: 0
  110. }
  111. })
  112. return
  113. }
  114. if (initialData.value && initialData.value.length > 0) {
  115. const initialObj = {}
  116. initialData.value.forEach((item) => {
  117. initialObj[item.projectId] = item.workTime
  118. })
  119. const timer = setTimeout(() => {
  120. dataList.value = dataList.value.map((item) => {
  121. let count = 0
  122. if (initialObj[item.id]) {
  123. count = initialObj[item.id]
  124. }
  125. return {
  126. ...item,
  127. count
  128. }
  129. })
  130. dataList.value.sort((x, y) => y['count'] - x['count'])
  131. clearTimeout(timer)
  132. }, 0)
  133. } else {
  134. getAllProject()
  135. }
  136. getTotalTime()
  137. }
  138. watch(
  139. () => initialData.value,
  140. () => {
  141. setInitData()
  142. }
  143. )
  144. watch(
  145. () => nowDate.value,
  146. () => {
  147. // 切换日期,重置数据
  148. defaultTotalTime.value = 0
  149. setInitData()
  150. }
  151. )
  152. onMounted(async () => {
  153. await getAllProject()
  154. await setInitData()
  155. })
  156. // 获取任务列表
  157. const getAllProject = async () => {
  158. const { records } = await request.get(
  159. {
  160. url: `/project/page`,
  161. params: {
  162. pageSize: -1,
  163. userId: userInfo.id ?? '',
  164. xmzList: [1, 4] // 仅可对进行中和已验收的项目进行填报
  165. }
  166. },
  167. '/business'
  168. )
  169. dataList.value = records.map((item) => {
  170. return {
  171. visible: false,
  172. count: 0,
  173. name: item.xmmc,
  174. id: item.id
  175. }
  176. })
  177. }
  178. // 临时存储数量
  179. const tempCount = ref(0)
  180. // 工时数量
  181. const changeCount = (val, index, submit?) => {
  182. // 获取临时数量
  183. tempCount.value = dataList.value[index].count
  184. dataList.value[index].count = val
  185. if (submit) {
  186. panelSubmit(index)
  187. }
  188. }
  189. // 计算总工时
  190. const getTotalTime = () => {
  191. defaultTotalTime.value = dataList.value.reduce((total, item) => {
  192. return total + item.count
  193. }, 0)
  194. }
  195. // 提交工时
  196. const panelSubmit = (index: number) => {
  197. dataList.value[index].visible = false
  198. getTotalTime()
  199. tempCount.value = 0
  200. // 提交工时后清空搜索框
  201. if (tempSearchList.value.length > 0) {
  202. searchInputRef.value?.clear()
  203. } else {
  204. // 提交后进行一下数据排序
  205. dataList.value.sort((x, y) => y['count'] - x['count'])
  206. // 提交工时后将已填数据传出去
  207. const countList = dataList.value
  208. .filter((item) => item.count > 0)
  209. .map((item) => ({
  210. workTime: item.count,
  211. projectId: item.id
  212. }))
  213. onChange(countList)
  214. }
  215. }
  216. // 取消选择
  217. const panelCancel = (index: number) => {
  218. dataList.value[index] = {
  219. ...dataList.value[index],
  220. visible: false,
  221. count: tempCount.value
  222. }
  223. getTotalTime()
  224. }
  225. // 搜索内容的临时存储
  226. const searchInputRef = ref<any>(null)
  227. const tempSearchList = ref<any[]>([])
  228. const searchValue = ref('')
  229. // 搜索内容改变时触发
  230. const searchOnchange = (val: string) => {
  231. // 搜索框第一次搜索时触发
  232. if (tempSearchList.value.length < dataList.value.length) {
  233. tempSearchList.value = dataList.value
  234. }
  235. // 搜索内容为空时,清空搜索列表,并显示全部数据
  236. if (val.length == 0) {
  237. clearSearch()
  238. return
  239. }
  240. // 使用 filter() 方法进行模糊查询
  241. const filteredArray = tempSearchList.value.filter((item) => {
  242. // 创建正则表达式,忽略大小写,并且匹配关键词
  243. const regex = new RegExp(val, 'i')
  244. // 使用正则表达式测试数组中的每个元素
  245. return regex.test(item.name)
  246. })
  247. dataList.value = filteredArray
  248. }
  249. // 清除内容时触发
  250. const clearSearch = () => {
  251. const countObj = {}
  252. dataList.value.forEach((item) => {
  253. countObj[item.id] = item.count
  254. })
  255. tempSearchList.value.forEach((item) => {
  256. if (countObj[item.id]) {
  257. // 如果有值,则赋值
  258. item.count = countObj[item.id]
  259. }
  260. })
  261. // 顺便进行一下数据排序
  262. tempSearchList.value.sort((x, y) => y['count'] - x['count'])
  263. // 还原全部数据
  264. dataList.value = tempSearchList.value
  265. // 提交工时后将已填数据传出去
  266. const countList = dataList.value
  267. .filter((item) => item.count > 0)
  268. .map((item) => ({
  269. workTime: item.count,
  270. projectId: item.id
  271. }))
  272. onChange(countList)
  273. tempSearchList.value = []
  274. getTotalTime()
  275. }
  276. interface IProject {
  277. name: string
  278. id: string
  279. }
  280. const emit = defineEmits<{
  281. (e: 'select', val: IProject): void
  282. }>()
  283. const handleClick = (val: IProject): void => {
  284. emit('select', val)
  285. openDialog(val.id)
  286. }
  287. // 打开弹窗
  288. const openDialog = (id) => {
  289. // 并且关闭所有其他弹窗
  290. dataList.value.forEach((item) => {
  291. item.visible = item.id == id
  292. })
  293. }
  294. </script>
  295. <style lang="scss" scoped>
  296. .WorkHoursSelect {
  297. width: 100%;
  298. height: calc(100% - 140px);
  299. display: flex;
  300. .select-title {
  301. font-family:
  302. Microsoft YaHei,
  303. Microsoft YaHei;
  304. width: 100px;
  305. font-size: 14px;
  306. font-weight: 400;
  307. height: 36px;
  308. line-height: 36px;
  309. text-align: right;
  310. padding-right: 12px;
  311. :before {
  312. content: '*';
  313. color: var(--el-color-danger);
  314. margin-right: 4px;
  315. }
  316. }
  317. .select-list {
  318. flex: 1;
  319. height: 100%;
  320. border: 1px solid #dee0e3;
  321. .title {
  322. padding: 8px 20px;
  323. background-color: #f7f8fa;
  324. color: #121518;
  325. display: flex;
  326. justify-content: space-between;
  327. align-items: center;
  328. }
  329. .list-ul {
  330. width: 100%;
  331. height: calc(100% - 50px);
  332. overflow-y: scroll;
  333. text-align: center;
  334. background-color: #fff;
  335. width: 100%;
  336. li {
  337. text-align: left;
  338. color: #000000;
  339. padding: 6px 10px;
  340. cursor: pointer;
  341. justify-content: space-between;
  342. &:hover {
  343. background-color: #d3e5ff;
  344. }
  345. .project-write {
  346. width: 100%;
  347. padding-left: 10px;
  348. }
  349. }
  350. .time-select {
  351. span {
  352. color: #1b80eb;
  353. }
  354. }
  355. }
  356. }
  357. }
  358. .work-hours-select-panel {
  359. .title {
  360. padding: 10px;
  361. text-align: center;
  362. background-color: #f7f8fa;
  363. color: #121518;
  364. font-size: 16px;
  365. font-weight: 400;
  366. }
  367. .popover-panel {
  368. padding: 0 !important;
  369. }
  370. .panel-input-number {
  371. height: 21px;
  372. margin: 10px 0;
  373. font-size: 16px;
  374. font-weight: 400;
  375. color: #404956;
  376. .input-number {
  377. width: calc(100% - 86px);
  378. }
  379. }
  380. .panel-numeric-key {
  381. text-align: center;
  382. overflow-y: auto;
  383. height: calc(100% - 42px);
  384. ul {
  385. padding: 20px 0;
  386. display: grid;
  387. grid-template-columns: repeat(8, 36px);
  388. gap: 10px;
  389. justify-content: space-between; /* 水平居中 */
  390. align-items: center; /* 垂直居中 */
  391. li {
  392. text-align: center;
  393. height: 35px;
  394. line-height: 35px;
  395. border-radius: 2px;
  396. color: #000000;
  397. cursor: pointer;
  398. }
  399. li:hover {
  400. border: 2px solid #1b80eb;
  401. }
  402. }
  403. }
  404. }
  405. </style>