mine.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. <template>
  2. <div class="entry-container">
  3. <div class="list-container">
  4. <div
  5. class="search-wrap"
  6. ref="searchWrapRef"
  7. >
  8. <el-form
  9. :model="state.queryParams"
  10. ref="queryRef"
  11. :inline="true"
  12. >
  13. <el-form-item prop="serialNo">
  14. <el-select
  15. v-model="state.queryParams.platformId"
  16. style="width: 100%"
  17. placeholder="申请平台"
  18. clearable
  19. @change="search"
  20. >
  21. <el-option
  22. v-for="item in platformList"
  23. :key="item.id"
  24. :label="item.platformName"
  25. :value="item.id"
  26. ></el-option>
  27. </el-select>
  28. </el-form-item>
  29. <el-form-item prop="dateRange">
  30. <el-input
  31. v-model="dateRangeText"
  32. placeholder="请选择入室时间范围"
  33. readonly
  34. style="width: 100%"
  35. @click="showCalendar = true"
  36. >
  37. <template #suffix>
  38. <el-icon
  39. v-if="dateRangeText"
  40. style="cursor: pointer; margin-right: 8px"
  41. @click.stop="clearDateRange"
  42. >
  43. <Close />
  44. </el-icon>
  45. <el-icon
  46. style="cursor: pointer"
  47. @click.stop="showCalendar = true"
  48. >
  49. <Calendar />
  50. </el-icon>
  51. </template>
  52. </el-input>
  53. </el-form-item>
  54. <el-form-item prop="serialNo">
  55. <el-input
  56. v-model="state.queryParams.resName"
  57. style="width: 100%"
  58. placeholder="资源名称"
  59. clearable
  60. @keyup.enter="search"
  61. @input="handleResNameInput"
  62. />
  63. </el-form-item>
  64. </el-form>
  65. </div>
  66. <van-list
  67. v-model:loading="state.loading"
  68. :finished="state.finished"
  69. finished-text="没有更多了"
  70. @load="onLoad"
  71. >
  72. <van-cell
  73. v-for="item in state.list"
  74. :key="item"
  75. >
  76. <template #default>
  77. <div class="list">
  78. <header class="flex justify-between">
  79. <strong class="title">{{ item.userName }}的{{ item.platformName }}入室申请</strong>
  80. <van-tag
  81. v-if="item.assignStatus == EntryAssignStatus.TO_BE_ENTERED"
  82. type="primary"
  83. >
  84. 待入室
  85. </van-tag>
  86. <van-tag
  87. v-else-if="item.assignStatus == EntryAssignStatus.NEXT_MONTH_ENTERED"
  88. type="primary"
  89. >
  90. 次月入室
  91. </van-tag>
  92. <van-tag
  93. v-else-if="item.assignStatus == EntryAssignStatus.ENTERED"
  94. type="success"
  95. >
  96. 已入室
  97. </van-tag>
  98. <van-tag
  99. v-else-if="item.assignStatus == EntryAssignStatus.OUT_ROOM"
  100. type="primary"
  101. >
  102. 已出室
  103. </van-tag>
  104. <van-tag
  105. v-else-if="item.assignStatus == EntryAssignStatus.NEXT_MONTH_OUT_ROOM"
  106. type="primary"
  107. >
  108. 次月出室
  109. </van-tag>
  110. </header>
  111. <p class="inst-title">
  112. <span>申请平台</span>
  113. <span class="title ml8">
  114. {{ item.platformName }}
  115. </span>
  116. </p>
  117. <p class="inst-title">
  118. <span>入室时间</span>
  119. <span class="title ml8">
  120. {{ item?.startTime ? formatDate(new Date(item?.startTime), 'YYYY-mm-dd') : '' }}
  121. ~
  122. {{ item?.endTime ? formatDate(new Date(item?.endTime), 'YYYY-mm-dd') : '' }}
  123. </span>
  124. </p>
  125. <p class="inst-title">
  126. <span>资源名称</span>
  127. <span class="title ml8">
  128. {{ item.resName }}
  129. </span>
  130. </p>
  131. <p class="inst-title">
  132. <span>位置信息</span>
  133. <span class="title ml8">
  134. {{ item.resLocation }}
  135. </span>
  136. </p>
  137. <footer class="flex justify-between mt16">
  138. <div
  139. v-if="item.assignStatus === '20' && item.userId === userInfos.id"
  140. style="text-align: right; width: 100%"
  141. >
  142. <van-button
  143. type="primary"
  144. style="height: 30px"
  145. @click="handleAppointment(item)"
  146. >
  147. 使用预约
  148. </van-button>
  149. </div>
  150. <div
  151. v-if="
  152. item.assignStatus === '20' &&
  153. item.userId === userInfos.id &&
  154. item.outStatus !== 20 &&
  155. item.outStatus !== 30 &&
  156. isEndTimeInFirstTenDays(item.startTime,item.endTime)
  157. "
  158. style="text-align: right; width: 100%"
  159. >
  160. <van-button
  161. type="primary"
  162. style="height: 30px"
  163. @click="handleRenewal(item)"
  164. >
  165. 申请续期
  166. </van-button>
  167. </div>
  168. <div
  169. v-if="item.assignStatus === EntryAssignStatus.ENTERED && item.userId === userInfos.id"
  170. style="text-align: right; width: 100%"
  171. >
  172. <van-button
  173. type="primary"
  174. style="height: 30px"
  175. @click="handleOutRoom(item)"
  176. >
  177. 申请出室
  178. </van-button>
  179. </div>
  180. </footer>
  181. </div>
  182. </template>
  183. </van-cell>
  184. </van-list>
  185. </div>
  186. <el-dialog
  187. v-model="showOutDialog"
  188. title="申请出室"
  189. width="85%"
  190. :before-close="
  191. () => {
  192. showOutDialog = false
  193. }
  194. "
  195. >
  196. <el-form
  197. ref="outRoomFormRef"
  198. :model="outRoomForm"
  199. :rules="rules"
  200. label-position="top"
  201. >
  202. <el-form-item
  203. style="display: block"
  204. label="申请出室日期"
  205. prop="outRoomDate"
  206. >
  207. <el-date-picker
  208. :disabled-date="disabledTime"
  209. v-model="outRoomForm.outRoomDate"
  210. type="month"
  211. style="width: 100%"
  212. placeholder="请选择出室月份"
  213. value-format="YYYY-MM-DD"
  214. />
  215. </el-form-item>
  216. <el-form-item>
  217. <div style="text-align: right; width: 100%">
  218. <el-button
  219. @click="
  220. () => {
  221. showOutDialog = false
  222. }
  223. "
  224. >
  225. 取消
  226. </el-button>
  227. <el-button
  228. type="primary"
  229. @click="handleSubmitOutRoom"
  230. >
  231. 申请
  232. </el-button>
  233. </div>
  234. </el-form-item>
  235. </el-form>
  236. </el-dialog>
  237. <Appoint ref="appointRef" />
  238. <RenewalEdit ref="renewalEditRef" />
  239. <!-- 日期范围选择日历 -->
  240. <van-popup
  241. v-model:show="showCalendar"
  242. position="bottom"
  243. :style="{ height: '80vh' }"
  244. round
  245. >
  246. <van-calendar
  247. v-model:show="showCalendar"
  248. type="range"
  249. :min-date="new Date(1900, 0, 1)"
  250. @confirm="onDateRangeConfirm"
  251. />
  252. </van-popup>
  253. </div>
  254. </template>
  255. <script name="entryMine" lang="ts" setup>
  256. import { reactive, onMounted, ref, computed } from 'vue'
  257. import { useRouter } from 'vue-router'
  258. import to from 'await-to-js'
  259. import { storeToRefs } from 'pinia'
  260. import { ElMessage } from 'element-plus'
  261. import { Calendar, Close } from '@element-plus/icons-vue'
  262. import dayjs from 'dayjs'
  263. import { usePlatformAppointApi } from '/@/api/platform/appoint'
  264. import { useCellAssignApi } from '/@/api/platform/home/assign'
  265. import { usePlatformApi } from '/@/api/platform/home'
  266. import { formatDate } from '/@/utils/formatTime'
  267. import { useUserInfo } from '/@/stores/userInfo'
  268. import { debounce } from '/@/utils/other'
  269. import Appoint from './components/appoint.vue'
  270. import RenewalEdit from './components/renewalEdit.vue'
  271. enum EntryAssignStatus {
  272. TO_BE_ENTERED = '10', // 待入室
  273. NEXT_MONTH_ENTERED = '15', // 次月入室
  274. ENTERED = '20', // 已入室
  275. OUT_ROOM = '30', // 已出室
  276. NEXT_MONTH_OUT_ROOM = '40', // 次月出室
  277. }
  278. const stores = useUserInfo()
  279. const { userInfos } = storeToRefs(stores)
  280. const platformAppointApi = usePlatformAppointApi()
  281. const platformApi = usePlatformApi()
  282. const cellAssignApi = useCellAssignApi()
  283. const router = useRouter()
  284. const outRoomFormRef = ref()
  285. const renewalEditRef = ref()
  286. const appointRef = ref()
  287. const showOutDialog = ref<boolean>(false)
  288. const dateRange = ref<string[]>([])
  289. const showCalendar = ref(false)
  290. const selectedDateRange = ref<[Date, Date] | null>(null)
  291. const platformList = ref<any[]>([])
  292. const dateRangeText = computed(() => {
  293. if (selectedDateRange.value && selectedDateRange.value.length === 2) {
  294. const start = dayjs(selectedDateRange.value[0]).format('YYYY-MM-DD')
  295. const end = dayjs(selectedDateRange.value[1]).format('YYYY-MM-DD')
  296. return `${start} 至 ${end}`
  297. }
  298. return ''
  299. })
  300. const state = reactive({
  301. queryParams: {
  302. platformId: null,
  303. dateTime: [],
  304. resName: '',
  305. pageNum: 1,
  306. orderBy: '',
  307. pageSize: 20,
  308. },
  309. finished: false,
  310. loading: true,
  311. list: [] as any[],
  312. })
  313. const currentSelectEntryItem = ref({
  314. id: null,
  315. endTime: '',
  316. startTime: '',
  317. })
  318. const outRoomForm = reactive({
  319. outRoomDate: '',
  320. })
  321. const rules = {
  322. outRoomDate: { required: true, message: '不能为空', trigger: 'change' },
  323. }
  324. const disabledTime = (date: Date) => {
  325. let startTime = formatDate(
  326. new Date(dayjs(currentSelectEntryItem.value.startTime).add(1, 'month').format('YYYY-MM-DD')),
  327. 'YYYY-mm-dd',
  328. )
  329. let endTime = formatDate(
  330. new Date(dayjs(currentSelectEntryItem.value.endTime).subtract(1, 'month').format('YYYY-MM-DD')),
  331. 'YYYY-mm-dd',
  332. )
  333. let now = formatDate(new Date(), 'YYYY-mm-dd')
  334. let time = formatDate(new Date(date), 'YYYY-mm-dd')
  335. return time < now || time > endTime || time < startTime
  336. }
  337. const onLoad = async () => {
  338. state.loading = true
  339. const payload: any = {
  340. ...state.queryParams,
  341. }
  342. Object.keys(payload).map((item) => {
  343. if (payload[item] === '' || payload[item] === null) {
  344. delete payload[item]
  345. }
  346. })
  347. const [err, res]: ToResponse = await to(cellAssignApi.getUserAssignResources(payload))
  348. if (err) return
  349. const list = res?.data?.list || []
  350. for (const item of list) {
  351. state.list.push(item)
  352. }
  353. state.loading = false
  354. state.queryParams.pageNum++
  355. if (list.length < state.queryParams.pageSize) {
  356. state.finished = true
  357. }
  358. }
  359. const getDicts = () => {
  360. Promise.all([platformApi.getPlatformList()]).then(([plat]) => {
  361. platformList.value = plat?.data?.list || []
  362. })
  363. }
  364. const resetQuery = () => {
  365. state.queryParams = {
  366. ...state.queryParams,
  367. pageNum: 1,
  368. pageSize: 20,
  369. }
  370. state.list = []
  371. state.finished = false
  372. state.loading = true
  373. }
  374. const onDateRangeConfirm = (values: Date[]) => {
  375. if (values && values.length === 2) {
  376. selectedDateRange.value = [values[0], values[1]]
  377. dateRange.value = [dayjs(values[0]).format('YYYY-MM-DD'), dayjs(values[1]).format('YYYY-MM-DD')]
  378. showCalendar.value = false
  379. search()
  380. }
  381. }
  382. const clearDateRange = () => {
  383. selectedDateRange.value = null
  384. dateRange.value = []
  385. search()
  386. }
  387. // 防抖后的搜索函数
  388. const debouncedSearch = debounce(() => {
  389. search()
  390. }, 500)
  391. // 处理资源名称输入
  392. const handleResNameInput = () => {
  393. debouncedSearch()
  394. }
  395. const search = () => {
  396. if (dateRange.value && dateRange.value.length === 2) {
  397. state.queryParams.dateTime[0] = dateRange.value[0] + ' 00:00:00'
  398. state.queryParams.dateTime[1] = dateRange.value[1] + ' 23:59:59'
  399. } else {
  400. state.queryParams.dateTime[0] = ''
  401. state.queryParams.dateTime[1] = ''
  402. }
  403. resetQuery()
  404. onLoad()
  405. }
  406. const handleOutRoom = (item: any) => {
  407. showOutDialog.value = true
  408. currentSelectEntryItem.value = item
  409. }
  410. const handleSubmitOutRoom = () => {
  411. outRoomFormRef.value.validate(async (valid: boolean) => {
  412. if (!valid) return
  413. const { projectGroup } = userInfos.value
  414. const [err, res]: ToResponse = await to(
  415. platformAppointApi.createCommonAppoint({
  416. tranId: currentSelectEntryItem.value.id,
  417. tranType: 1,
  418. outApplyTime: dayjs(outRoomForm.outRoomDate).endOf('month').format('YYYY-MM-DD HH:mm:ss'),
  419. memberId: userInfos.value.id,
  420. memberName: userInfos.value.nickName,
  421. memberType: userInfos.value.userType,
  422. memberPhone: userInfos.value.phone,
  423. deptId: userInfos.value.deptId,
  424. deptName: userInfos.value.deptName,
  425. pgId: projectGroup.id,
  426. pgName: projectGroup.pgName,
  427. mentorId: projectGroup.pgLeaderId,
  428. mentorName: projectGroup.pgLeaderName,
  429. mentorDeptName: projectGroup.pgOrgPaths,
  430. mentorPhone: projectGroup.pgLeaderContact,
  431. isTemporary: '20', // 是否暂存,10是,20否
  432. }),
  433. )
  434. if (err) return
  435. showOutDialog.value = false
  436. ElMessage.success('操作成功')
  437. state.list = []
  438. state.queryParams.pageNum = 1
  439. onLoad()
  440. })
  441. }
  442. const handleAppointment = (item: any) => {
  443. appointRef.value.openDialog(item)
  444. }
  445. const handleRenewal = (row) => {
  446. renewalEditRef.value.openDialog('add', row)
  447. }
  448. // 判断endtime是否在当前月份的前10天内
  449. const isEndTimeInFirstTenDays = (startTime: string,endTime: string) => {
  450. if (!startTime || !endTime) return false;
  451. const startDate = dayjs(startTime).startOf('day');
  452. const endDate10 = dayjs(endTime).date(10).endOf('day');
  453. const today = dayjs();
  454. const todayStart = today.startOf('day');
  455. return todayStart.valueOf() >= startDate.valueOf() && todayStart.valueOf() <= endDate10.valueOf();
  456. }
  457. onMounted(() => {
  458. getDicts()
  459. onLoad()
  460. })
  461. </script>
  462. <style lang="scss" scoped>
  463. .entry-container {
  464. position: relative;
  465. display: flex;
  466. flex-direction: column;
  467. .list-container {
  468. overflow-y: auto;
  469. padding: 10px;
  470. border-radius: 4px;
  471. flex: 1;
  472. }
  473. .van-list {
  474. .van-cell {
  475. background-color: #fff;
  476. + .van-cell {
  477. margin-top: 10px;
  478. }
  479. header,
  480. footer {
  481. color: #333;
  482. }
  483. .title {
  484. flex: 1;
  485. white-space: nowrap;
  486. overflow: hidden;
  487. text-overflow: ellipsis;
  488. text-align: left;
  489. }
  490. .inst-title {
  491. color: #333;
  492. text-align: left;
  493. flex: 1;
  494. overflow: hidden;
  495. white-space: nowrap;
  496. text-overflow: ellipsis;
  497. margin-top: 4px;
  498. span:first-child {
  499. color: rgb(120, 120, 120);
  500. }
  501. }
  502. .time {
  503. color: #f69a4d;
  504. }
  505. }
  506. }
  507. .asterisk-left {
  508. display: flex;
  509. margin-right: 0;
  510. }
  511. .search-wrap {
  512. background: #fff;
  513. margin-bottom: 10px;
  514. padding: 15px;
  515. }
  516. }
  517. </style>