util.js 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  1. import { getData } from '@/api/platform/desktop/column'
  2. import { getFile } from '@/utils/avatar'
  3. import { mapState } from 'vuex'
  4. import { taskTypeOptions, dashboardStatus, genderOptions, favoritesOptions, noticeOptions, unreadMessageOptions, imgOptionsData } from '@/business/platform/bpmn/constants'
  5. import ActionUtils from '@/utils/action'
  6. import Utils from '@/utils/util'
  7. import { findAllByCurrUserId, saveCalendarInfos, removeCalendarInfos, delNavigation, addNavigation, getNavigation, sortNavigation } from '@/api/detection/newHomeApi'
  8. import { isEqual, now } from 'lodash'
  9. import Bus from '@/utils/EventBus'
  10. import newPng from '@/assets/images/homepage/new.png'
  11. import { BASE_URL } from '@/constant'
  12. import dayjs from 'dayjs'
  13. import { scheduleType } from '@/views/constants/schedule'
  14. import { lifeTimeData } from '@/views/business/deviceManagement/constants/simulated'
  15. /**
  16. * 创建组件
  17. */
  18. export function buildComponent (name, column, preview, vm) {
  19. try {
  20. return {
  21. name,
  22. components: {
  23. VueDraggable: () => import('vuedraggable')
  24. },
  25. props: {
  26. params: {
  27. type: Object,
  28. default: () => {}
  29. },
  30. height: {
  31. type: Number,
  32. default: column.height || 300
  33. },
  34. visible: {
  35. type: Boolean,
  36. default: false
  37. },
  38. fullScreen: {
  39. type: Boolean,
  40. default: false
  41. }
  42. },
  43. filters: {
  44. filterStatus (val, type) {
  45. if (Utils.isEmpty(val)) {
  46. return ''
  47. }
  48. const typeMap = {
  49. pending: taskTypeOptions,
  50. already: dashboardStatus,
  51. myRequest: dashboardStatus,
  52. gender: genderOptions,
  53. favorites: favoritesOptions,
  54. notice: noticeOptions,
  55. unreadMessage: unreadMessageOptions
  56. }
  57. if (!typeMap[type]) {
  58. return val
  59. }
  60. return typeMap[type].find(x => x['value'] === val) ? typeMap[type].find(x => x['value'] === val).label : val
  61. }
  62. },
  63. data () {
  64. const { first = '', second = '' } = this.$store.getters.level
  65. const { userId, userList = [], deptList = [], menus, userInfo } = this.$store.getters
  66. const t1 = deptList.find(i => i.positionId === first) || {}
  67. const t2 = deptList.find(i => i.positionId === second) || {}
  68. const locationName = second ? t1.positionName + t2.positionName : t1.positionName
  69. return {
  70. userId,
  71. userList,
  72. deptList,
  73. menus,
  74. locationName,
  75. newPng,
  76. imgOptionsData,
  77. positions: userInfo.positions,
  78. loading: false,
  79. title: `${column.name}`,
  80. alias: `${column.alias}`,
  81. attrs: this.getAttrs(),
  82. variables: {}, // 一些变量,比如分页信息
  83. data: null,
  84. totalCount: 0,
  85. quickNavigationData: [],
  86. navigationList: [],
  87. stautusOptions: [],
  88. bpmnFormrenderDialogVisible: false, // 表单
  89. editId: '',
  90. bodyShow: true,
  91. show: false,
  92. showHeight: '',
  93. cardHeight: '100%',
  94. activeName: 'innerMessage',
  95. unreadMessageOption: {},
  96. formName: 'quickNavform',
  97. dialogFormVisible: false,
  98. formLabelWidth: '120px',
  99. quickNavform: {
  100. urlName: '',
  101. urlAddr: '',
  102. display: '_blank',
  103. diDian: second || first
  104. },
  105. defaultForm: {},
  106. pendingTabActiveName: 'user-type',
  107. bodyParams: ActionUtils.formatParams({}, {}, {}),
  108. pendingBusinessOption: {},
  109. bpmn: [],
  110. rules: {
  111. urlName: [
  112. {
  113. required: true,
  114. message: this.$t('validate.required')
  115. }
  116. ],
  117. urlAddr: [
  118. {
  119. required: true,
  120. message: this.$t('validate.required')
  121. }
  122. ],
  123. display: [
  124. {
  125. required: true,
  126. message: this.$t('validate.required')
  127. }
  128. ]
  129. },
  130. calendarDialogForm: {
  131. id: '',
  132. biaoTi: '',
  133. neiRong: '',
  134. kaiShiShiJian: '',
  135. jieShuShiJian: '',
  136. formDate: []
  137. },
  138. colorStatus: ['#e7505a', '#f3c200', '#578ebe', '#1BBC9B'],
  139. // const status = ['急', '重', '轻','缓']
  140. isDragging: false,
  141. draggableOptions: {
  142. handle: '.draggable',
  143. ghostClass: 'sortable-ghost',
  144. distance: 1,
  145. disabled: false,
  146. animation: 200,
  147. axis: 'y'
  148. },
  149. calendarToolbar: this.fullScreen ? [{ key: 'refresh' }] : [{ key: 'refresh' }, { key: 'fullscreen' }, { key: 'collapse' }],
  150. isFirstAlert: true, // 是否首次日程提醒
  151. scheduleData: [],
  152. hasMounted: false,
  153. attendanceData: [],
  154. scheduleShift: [],
  155. todaySchedule: [],
  156. tempSelectedValue: ''
  157. }
  158. },
  159. computed: {
  160. ...mapState({
  161. userInfo: state => state.ibps.user.info
  162. })
  163. },
  164. mounted () {
  165. this.defaultForm = JSON.parse(JSON.stringify(this.quickNavform))
  166. this.$nextTick(async () => {
  167. this.fetchData()
  168. // this.attendanceData = await this.getAttendanceData()
  169. // this.scheduleData = await this.getScheduleData()
  170. this.todaySchedule = await this.getTodaySchedule()
  171. })
  172. },
  173. methods: {
  174. fetchData (columns, params = {}) {
  175. this.loading = true
  176. this.data = []
  177. this.showHeight = this.getHeight()
  178. const param = Utils.isNotEmpty(columns) && (column.alias === 'unreadMessage' || column.alias === 'pendingBusiness')
  179. ? { dataMode: column.dataMode, dataFrom: column.dataFrom }
  180. : column
  181. if (param.alias === 'myCalendar') {
  182. const { getFormatDate, getDate } = this.$common
  183. findAllByCurrUserId().then(res => {
  184. const { data = [] } = res || {}
  185. if (this.isFirstAlert) {
  186. this.isFirstAlert = false
  187. this.showAlert(data)
  188. }
  189. this.data = data.map(i => ({
  190. id: i.id,
  191. title: i.title,
  192. content: i.content,
  193. start: i.startTime,
  194. // 日期组件日程显示里,end的时间需要多加一天,end:2024-01-02时,日程条只到2024-01-01的日期位置上
  195. end: getFormatDate('string', 10, getDate('day', 1, i.endTime)),
  196. jieShuShiJian: i.endTime,
  197. zhuangTai: i.emergencyState,
  198. color: this.colorStatus[Number(i.emergencyState) - 1]
  199. }))
  200. }).catch(() => {
  201. this.$message.error('获取日历日程失败!')
  202. })
  203. } else if (param.alias === 'quickNavigation') {
  204. getNavigation().then(res => {
  205. const { data = [] } = res || {}
  206. data.forEach(item => {
  207. if (!item.userId) {
  208. item.urlAddr = `${BASE_URL}#${item.urlAddr}`
  209. }
  210. })
  211. this.quickNavigationData = data
  212. })
  213. } else {
  214. getData(param, params).then(res => {
  215. let { data } = res || {}
  216. if (Utils.isNotEmpty(data) && Utils.isString(data)) {
  217. data = Utils.parseData(res.data)
  218. }
  219. this.data = data && data.dataResult ? data.dataResult : data
  220. this.totalCount = data && data.pageResult ? data.pageResult.totalCount : 0
  221. // 更新小铃铛消息数量
  222. if (param.alias === 'unreadMessage') {
  223. Bus.$emit('getMessageCount', this.totalCount)
  224. }
  225. this.variables = res.variables
  226. this.loading = false
  227. }).catch((e) => {
  228. this.loading = false
  229. })
  230. }
  231. },
  232. // 过滤日程提醒数据
  233. filterAlertData (data, dayNumber = 3) {
  234. if (dayNumber <= 0) return
  235. const today = dayjs()
  236. const tempCalendarAlertData = data.filter(day => {
  237. const startTime = dayjs(day.startTime)
  238. const endTime = dayjs(day.endTime)
  239. if (day.popUp) {
  240. if ((startTime.diff(today, 'day') <= 0 && endTime.diff(today, 'day') >= 0) || (startTime.diff(today, 'day') <= dayNumber - 2 && startTime.diff(today, 'day') >= 0)) {
  241. return true
  242. }
  243. }
  244. })
  245. tempCalendarAlertData.sort((a, b) => new Date(a.startTime) - new Date(b.startTime))
  246. const calendarIds = tempCalendarAlertData.map(item => item.id)
  247. const calendarAlertData = {}
  248. tempCalendarAlertData.forEach(item => {
  249. if (calendarAlertData[item.startTime]) {
  250. calendarAlertData[item.startTime].push(item)
  251. } else {
  252. calendarAlertData[item.startTime] = []
  253. calendarAlertData[item.startTime].push(item)
  254. }
  255. })
  256. return { calendarAlertData, calendarIds }
  257. },
  258. // 日程提醒
  259. showAlert (data) {
  260. const calendarAlertData = this.filterAlertData(data)
  261. this.$emit('action-event', 'calendarAlert', calendarAlertData)
  262. },
  263. getPhoto (photo) {
  264. return getFile(photo)
  265. },
  266. getTaskDesc (v) {
  267. if (!v.includes('#')) {
  268. return ''
  269. }
  270. return v.split('#')[1] || ''
  271. },
  272. getTaskInfo (val, arg) {
  273. const arr = val.split('#')
  274. if (!arr[2]) {
  275. return ''
  276. }
  277. // 中文冒号转英文冒号 防止流程定义时输入的冒号格式错误导致转换报错
  278. arr[2] = arr[2].replace(':', ':')
  279. const result = JSON.parse(`{${arr[2]}}`)
  280. if (!result.dept) {
  281. return ''
  282. }
  283. const depts = result.dept.split(',')
  284. const deptNames = []
  285. depts.forEach(item => {
  286. const t = this.deptList.find(i => i.positionId === item)
  287. deptNames.push(t ? t.positionName : result.dept)
  288. })
  289. result.deptName = deptNames.join(',')
  290. return result[arg]
  291. },
  292. transformData (val, dataset, from, to) {
  293. if (!val) {
  294. return ''
  295. }
  296. const temp = this[dataset].find(u => u[from] === val)
  297. return temp ? temp[to] : ''
  298. },
  299. getHeightNoUnit () {
  300. // 高度 - header - 边框
  301. if (!this.visible) {
  302. return this.height ? (this.height - 60 - 20) : 150
  303. } else {
  304. return 150
  305. }
  306. },
  307. getHeight (h = 20) {
  308. // 高度 - header - 边框
  309. if (!this.visible) {
  310. return this.height ? `${(this.height - 60 - h)}px` : '150px'
  311. } else {
  312. return '100%'
  313. }
  314. },
  315. getDashboardHeight () {
  316. return this.height ? `${this.height + 20}px` : '150px'
  317. },
  318. getAttrs () {
  319. const item = JSON.parse(JSON.stringify(column))
  320. item.templateHtml = null
  321. return item
  322. },
  323. /**
  324. * 构建首页日期组件的参数
  325. * @param {*} data
  326. * @returns
  327. */
  328. getFullCalendarConfig (data) {
  329. const events = data === null ? [] : Utils.parseJSON(data)
  330. const config = {
  331. height: preview ? '100%' : (this.height ? this.height : 180),
  332. // editable: true, // 允许拖动缩放,不写默认就是false
  333. selectable: true,
  334. dayMaxEvents: 1, // 最多显示3个日程
  335. locale: this.$i18n.locale ? this.$i18n.locale.toLowerCase() : 'zh-cn',
  336. events: events,
  337. buttonText: {
  338. today: '今天',
  339. dayGridMonth: '月',
  340. listMonth: '日程'
  341. // week: '周视图',
  342. // day: '日视图',
  343. // prev: '<i class="icon-chevron-left">后退</i>',
  344. // next: '<i class="icon-chevron-right">前进</i>'
  345. },
  346. dateClick: this.handleDateClick, // 日期点击
  347. eventClick: this.handleEventClick,
  348. moreLinkClick: this.handleMoreLinkClick
  349. }
  350. if (preview) {
  351. config.headerToolbar = {
  352. start: '',
  353. center: 'title',
  354. end: 'prev,next,today'
  355. // end: 'prev,next,today,month,agendaWeek,agendaDay,listWeek'
  356. }
  357. delete config['dayMaxEvents']
  358. }
  359. return config
  360. },
  361. handleDateClick (param) {
  362. this.$emit(
  363. 'open',
  364. 'calendar',
  365. [param.dateStr, param.dateStr],
  366. this.data
  367. )
  368. },
  369. handleEventClick (param) {
  370. this.$emit(
  371. 'open',
  372. 'calendar',
  373. [param.event.startStr, param.event._def.extendedProps.jieShuShiJian],
  374. this.data,
  375. param.event.id
  376. )
  377. },
  378. handleMoreLinkClick (date) {
  379. this.$emit(
  380. 'open',
  381. 'calendar',
  382. [date.allSegs[0].event.startStr, date.allSegs[0].event._def.extendedProps.jieShuShiJian],
  383. this.data,
  384. date.allSegs[0].event.id
  385. )
  386. },
  387. refreshData () {
  388. this.fetchData()
  389. },
  390. /**
  391. * 处理按钮事件
  392. * @param {*} command
  393. * @param {*} position
  394. * @param {*} data
  395. * @param {*} actions
  396. */
  397. handleActionEvent (command, position, data, actions) {
  398. switch (command) {
  399. case 'refresh': // 刷新
  400. this.refreshData()
  401. break
  402. case 'fullscreen': // 全屏
  403. this.handleFullscreen()
  404. break
  405. case 'more': // 更多
  406. this.handleMore()
  407. break
  408. case 'collapse': // 收缩
  409. case 'expansion': // 展开
  410. this.handleCollapseExpand(command, data, actions)
  411. break
  412. case 'add': // 新增
  413. this.getFormData()
  414. break
  415. default:
  416. break
  417. }
  418. },
  419. emitActionEventHandler (command) {
  420. this.$emit('action-event', command, ...Array.from(arguments).slice(1))
  421. },
  422. // 公告栏目点击事件
  423. handleApprove (id, title) {
  424. this.$emit('action-event', 'approve', {
  425. id, title
  426. })
  427. },
  428. handleUnreadMessage (id, tableId, tableName) {
  429. this.$emit('action-event', 'unRead', { id, tableId, tableName })
  430. },
  431. // 处理全屏
  432. handleFullscreen () {
  433. this.emitActionEventHandler('fullscreen', { id: this.attrs.id })
  434. },
  435. openPlate (url) {
  436. const menuMap = {
  437. myTraining: 'rygl/rypx/wdpx',
  438. myTesting: 'rygl/kszx/wdks',
  439. myDevices: 'sbgls/mywh',
  440. notice: 'tygl/tzgg',
  441. myFacility: 'sshjgl/sshjjk/sshjkzzl',
  442. quickNavigation: 'xtgl/xtsjpz/tyglpz/kjdhpz'
  443. }
  444. if (menuMap[url]) {
  445. const alias = menuMap[url].split('/')[0]
  446. const resInfo = this.menus.find(i => i.alias === alias)
  447. this.$store.dispatch('ibps/menu/activeHeaderSet', { activeHeader: resInfo.id, vm: this })
  448. }
  449. this.$router.push(`/${menuMap[url] || url}`)
  450. },
  451. /**
  452. * 处理更多
  453. */
  454. handleMore () {
  455. if (this.attrs.alias === 'quickNavigation') {
  456. return this.openPlate('quickNavigation')
  457. }
  458. if (!this.attrs.colUrl) {
  459. return this.$message.warning('未设置更多路径的url')
  460. }
  461. this.openPlate(this.attrs.colUrl)
  462. },
  463. // 未读消息
  464. handleClick (option) {
  465. this.unreadMessageOption = option
  466. option[this.activeName].dataMode = column.dataMode
  467. this.fetchData(option[this.activeName])
  468. },
  469. // 待办事务
  470. handleTabClick (option) {
  471. this.pendingBusinessOption = option
  472. option[this.pendingTabActiveName].dataMode = column.dataMode
  473. this.fetchData(option[this.pendingTabActiveName])
  474. },
  475. handleFlowClick (params) {
  476. params.$alias = this.alias
  477. this.emitActionEventHandler('flow', params)
  478. },
  479. handleCollapseExpand (command, data, actions) {
  480. this.bodyShow = !this.bodyShow
  481. const index = actions.findIndex((action) => action.key === data.key)
  482. actions[index].key = this.bodyShow ? 'collapse' : 'expansion'
  483. if (!this.visible) {
  484. this.emitActionEventHandler(command, {})
  485. return
  486. }
  487. this.showHeight = this.bodyShow ? this.getHeight() : 0
  488. this.$refs['toolbar'].callback(actions)
  489. },
  490. formValidate (formName) {
  491. this.$nextTick(() => {
  492. this.$refs[formName].validate(() => {})
  493. })
  494. },
  495. getFormData () {
  496. this.quickNavform = JSON.parse(JSON.stringify(this.defaultForm))
  497. this.formValidate('quickNavform')
  498. this.dialogFormVisible = true
  499. },
  500. handleNavRemove (navId, i) {
  501. this.$confirm('是否确认删除该快捷导航?', '提示', {
  502. confirmButtonText: '确认',
  503. cancelButtonText: '取消',
  504. type: 'warning',
  505. showClose: false,
  506. closeOnClickModal: false
  507. }).then(() => {
  508. delNavigation({ navigateIds: navId }).then(() => {
  509. this.$message.success('删除成功')
  510. this.quickNavigationData.splice(i, 1)
  511. })
  512. })
  513. },
  514. // 错误头像的照片
  515. errorAvatarHandler (data) {
  516. // data.photo = require('https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png')
  517. return true
  518. },
  519. close () {
  520. this.$refs[this.formName].resetFields()
  521. this.dialogFormVisible = false
  522. },
  523. saveQuickNav () {
  524. this.$refs[this.formName].validate(valid => {
  525. if (valid) {
  526. addNavigation({ id: '', ...this.quickNavform }).then(res => {
  527. this.$message.success('添加成功!')
  528. const { id } = res.variables || {}
  529. this.quickNavigationData.unshift({ id, ...this.quickNavform, userId: this.userId })
  530. this.dialogFormVisible = false
  531. })
  532. } else {
  533. ActionUtils.saveErrorMessage()
  534. }
  535. })
  536. },
  537. handleSortChange () {
  538. this.isDragging = false
  539. const newSort = this.quickNavigationData.map(i => i.id)
  540. if (isEqual(this.navigationList, newSort)) {
  541. return
  542. }
  543. this.navigationList = newSort
  544. sortNavigation({ orders: this.navigationList.join(',') }).then(() => {
  545. this.$message.success('排序成功!')
  546. })
  547. },
  548. // 父组件调用该方法给日程添加数据
  549. async setCalendarEvents (param) {
  550. const { name = '' } = this.$store.getters
  551. const form = param.form
  552. const paramObject = {
  553. id: form.id ? form.id : '11111',
  554. // diDian: "string", // 地点
  555. userId: this.userId, // 用户id
  556. userName: name, // 用户名
  557. title: form.biaoTi, // 标题
  558. content: form.neiRong, // 内容
  559. startTime: form.formDate[0], // 开始时间
  560. endTime: form.formDate[1], // 结束时间
  561. emergencyState: form.zhuangTai // 紧急状态
  562. }
  563. await saveCalendarInfos(paramObject)
  564. this.refreshData()
  565. },
  566. async hanldeCalendardel (param) {
  567. if (param.form.id) {
  568. await removeCalendarInfos({
  569. calendarIds: param.form.id
  570. })
  571. }
  572. this.refreshData()
  573. },
  574. // 公告栏是否显示new图标
  575. showNewIcon (date, days) {
  576. const nowDate = new Date().getTime()
  577. const targetDate = new Date(date).getTime()
  578. return targetDate + days * 24 * 60 * 60 * 1000 > nowDate
  579. },
  580. handleOverflow (val, length) {
  581. if (val.length > length) {
  582. return val.slice(0, length - 2) + '...'
  583. }
  584. return val
  585. },
  586. getDays (start, end) {
  587. if (!start || !end) {
  588. return 0
  589. }
  590. return Math.ceil((new Date(end) - new Date(start)) / (1000 * 60 * 60 * 24))
  591. },
  592. getTodaySchedule () { // 获取今日班次
  593. const { first, second } = this.$store.getters.level || {}
  594. const today = this.$common.getDateNow()
  595. const sql = `select a.*, b.start_date_, b.end_date_ from t_schedule_detail a, t_schedule b where a.parent_id_ = b.id_ and b.di_dian_ = '${second || first}' and a.user_id_ = '${this.userId}' and b.status_ = '已发布'`
  596. return new Promise((resolve, reject) => {
  597. this.$common.request('sql', sql).then((res) => {
  598. const { data = [] } = res.variables || {}
  599. let todaySchedule = []
  600. data.forEach(item => {
  601. const days = this.getDays(item.start_date_, today)
  602. const shift = item[`d${days}_`]
  603. if (shift) {
  604. const shiftList = shift.split(',')
  605. todaySchedule = shiftList // 返回今日班次
  606. }
  607. })
  608. console.log(todaySchedule)
  609. resolve(todaySchedule)
  610. }).catch(error => {
  611. reject(error)
  612. })
  613. })
  614. },
  615. getAttendanceData () {
  616. const { first, second } = this.$store.getters.level || {}
  617. const today = this.$common.getDateNow()
  618. const sql = `select a.id_, a.kao_qin_zhuang_ta,a.ri_qi_, a.pai_ban_id_, a.pai_ban_ji_lu_id_, a.ban_ci_bie_ming_, a.ban_ci_kai_shi_, a.ban_ci_jie_shu_, a.ban_ci_ming_, a.da_ka_shi_jian_1_, a.zhuang_tai_1_, a.da_ka_shi_jian_2_, a.zhuang_tai_2_, a.chi_dao_shi_chang FROM t_attendance_detail a JOIN t_schedule b ON a.pai_ban_id_ = b.id_ AND b.status_ = '已发布' WHERE a.di_dian_ = '${second || first}' AND a.ri_qi_ <= '${today}' AND a.yong_hu_id_ = '${this.userId}' `
  619. return new Promise((resolve, reject) => {
  620. this.$common.request('sql', sql).then(res => {
  621. const { data = [] } = res.variables || {}
  622. const resultMap = new Map()
  623. data.forEach(item => {
  624. const { ri_qi_, pai_ban_id_, ban_ci_bie_ming_, ...rest } = item
  625. // 第一级:按日期分组
  626. if (!resultMap.has(ri_qi_)) {
  627. resultMap.set(ri_qi_, new Map())
  628. }
  629. const dateMap = resultMap.get(ri_qi_)
  630. // 第二级:按排班ID分组
  631. if (!dateMap.has(pai_ban_id_)) {
  632. dateMap.set(pai_ban_id_, new Map())
  633. }
  634. const scheduleMap = dateMap.get(pai_ban_id_)
  635. // 第三级:按班次别名存储完整数据
  636. scheduleMap.set(ban_ci_bie_ming_, rest)
  637. })
  638. // return resultMap
  639. resolve(resultMap)
  640. }).catch(error => {
  641. reject(error)
  642. })
  643. })
  644. },
  645. getScheduleData () {
  646. const { first, second } = this.$store.getters.level || {}
  647. const sql = `select a.*, b.title_,b.type_, b.start_date_, b.end_date_, b.config_, b.overview_, b.id_ as pai_ban_id_ from t_schedule_detail a, t_schedule b where a.parent_id_ = b.id_ and b.di_dian_ = '${second || first}' and a.user_id_ = '${this.userId}' and b.status_ = '已发布'`
  648. return new Promise((resolve, reject) => {
  649. this.$common.request('sql', sql).then((res) => {
  650. const { data = [] } = res.variables || {}
  651. const eventList = []
  652. const self = this
  653. data.forEach(item => {
  654. const days = this.getDays(item.start_date_, item.end_date_)
  655. const config = item.config_ ? JSON.parse(item.config_) : {}
  656. const scheduleTypeLabel = scheduleType.filter(obj => obj.value === item.type_)[0]?.label || ''
  657. const scheduleCreateBy = self.userList.filter(obj => obj.userId === item.create_by_)[0]?.userName || ''
  658. const { scheduleShift } = config
  659. this.scheduleShift = scheduleShift
  660. for (let i = 1; i <= days; i++) {
  661. const shift = item[`d${i}_`]
  662. if (shift) {
  663. const date = this.$common.getFormatDate('string', 10, this.$common.getDate('day', i - 1, item.start_date_))
  664. const shiftList = shift.split(',')
  665. shiftList.forEach(s => {
  666. const t = scheduleShift.find(i => i.alias === s)
  667. const attendance = self.attendanceData
  668. .get(date)
  669. ?.get(item.pai_ban_id_)
  670. ?.get(s)
  671. eventList.push({
  672. scheduleName: item.title_ ? item.title_ : '', // 排班表名字
  673. scheduleTypeLabel: scheduleTypeLabel || '', // 排班类型
  674. scheduleCreateBy: scheduleCreateBy || '', // 排班创建人
  675. content: t.dateRange.map(d => {
  676. return d.type === 'allday' ? '全天' : (`当天 ${d.startTime}` + ' 至 ' + `${d.isSecondDay === 'Y' ? '第二天' : '当天'} ${d.endTime}`)
  677. }).join('\n'),
  678. title: s,
  679. start: date,
  680. end: date,
  681. jieShuShiJian: date,
  682. zhuangTai: '',
  683. id: i,
  684. bcolor: t.color,
  685. attendance: attendance || {}, // 考勤状态
  686. ...t
  687. })
  688. })
  689. }
  690. }
  691. })
  692. // const today = this.$common.getDateNow()
  693. // this.todaySchedule = eventList.filter(i => i.start === today).map(i => i.title)
  694. // console.log(this.todaySchedule)
  695. resolve(eventList)
  696. }).catch(error => {
  697. reject(error)
  698. })
  699. })
  700. },
  701. handleScheduleEventClick (param) { // 排班点击事件
  702. this.$emit(
  703. 'open',
  704. 'banci',
  705. param.event._def.extendedProps,
  706. this.scheduleShift,
  707. param.event._def.title
  708. )
  709. },
  710. showDaKaBtn (targetDay) { // 判断是否展示打卡按钮,当前日期则展示
  711. const today = this.$common.getDateNow()
  712. if (targetDay == today) {
  713. return true
  714. } else {
  715. return false
  716. }
  717. },
  718. // 打卡处理逻辑
  719. handleClock (attendance) {
  720. if (Object.keys(attendance).length === 0) {
  721. this.$message.warning('考勤数据异常!')
  722. return
  723. }
  724. // 获取当前时间
  725. const currentDate = new Date()
  726. const hours = currentDate.getHours()
  727. const minutes = currentDate.getMinutes()
  728. const dakashijian = `${hours}:${minutes}`
  729. const time = this.$common.getDateNow() + ' ' + dakashijian
  730. let str = '打卡成功!'
  731. // 在班次结束时间前初次点击打卡按钮,视作上班打卡,自动判定状态为正常或迟到(迟到需记录迟到时长);再次点击打卡按钮提示已打卡
  732. if (time < attendance.ban_ci_jie_shu_) { // 上班打卡
  733. if (!attendance.da_ka_shi_jian_1_) {
  734. attendance.da_ka_shi_jian_1_ = dakashijian
  735. attendance.zhuang_tai_1_ = time < attendance.ban_ci_kai_shi_ ? '正常' : '迟到'
  736. if (attendance.zhuang_tai_1_ == '迟到') {
  737. attendance.chi_dao_shi_chang = this.getTimeDifferenceInMinutes(attendance.ban_ci_kai_shi_, time)
  738. attendance.kao_qin_zhuang_ta = '异常' // 总考勤状态设置为异常
  739. }
  740. } else {
  741. this.$message.warning('该班次上班已打卡!')
  742. return
  743. }
  744. } else { // 下班打卡
  745. // 在班次结束时间后初始点击打卡按钮,视作下班打卡,再次点击打卡按钮则更新下班打卡时间并提示更新打卡时间成功
  746. if (attendance.da_ka_shi_jian_2_) {
  747. str = '已更新下班打卡!'
  748. }
  749. attendance.da_ka_shi_jian_2_ = dakashijian
  750. attendance.zhuang_tai_2_ = '正常'
  751. }
  752. // 更新打卡请求
  753. const tableName = ' t_attendance_detail'
  754. const updateParams = {
  755. tableName,
  756. updList: [
  757. {
  758. where: {
  759. id_: attendance.id_
  760. },
  761. param: {
  762. da_ka_shi_jian_1_: attendance.da_ka_shi_jian_1_,
  763. zhuang_tai_1_: attendance.zhuang_tai_1_,
  764. da_ka_shi_jian_2_: attendance.da_ka_shi_jian_2_,
  765. zhuang_tai_2_: attendance.zhuang_tai_2_,
  766. kao_qin_zhuang_ta: attendance.kao_qin_zhuang_ta,
  767. chi_dao_shi_chang: attendance.chi_dao_shi_chang
  768. }
  769. }
  770. ]
  771. }
  772. this.$common.request('update', updateParams).then(() => {
  773. this.$message.success(str)
  774. })
  775. },
  776. getTimeDifferenceInMinutes (startTimeStr, endTimeStr) { // 时间相减分钟数
  777. const startTime = new Date(startTimeStr)
  778. const endTime = new Date(endTimeStr)
  779. const timeDifference = endTime - startTime
  780. return timeDifference / (1000 * 60)
  781. },
  782. // 首页打卡处理逻辑
  783. handleClockFromTab (todaySchedule) {
  784. const today = this.$common.getDateNow()
  785. // 当天仅有一个班次
  786. if (todaySchedule.length == 1) {
  787. const scheduleObj = this.scheduleData.filter(item => item.start == today && item.alias == todaySchedule[0])
  788. const attendance = scheduleObj.attendance
  789. this.handleClock(attendance)
  790. } else {
  791. let scheduleArr = []
  792. for (let i = 0; i < todaySchedule.length; i++) {
  793. const currentSchedule = todaySchedule[i]
  794. const filtered = this.scheduleData.filter(item => item.start === today && item.alias === currentSchedule)
  795. scheduleArr = scheduleArr.concat(filtered)
  796. }
  797. const h = this.$createElement
  798. const self = this
  799. this.$msgbox({
  800. title: '选择打卡班次',
  801. message: h('div', null, [
  802. h('p', { style: 'margin-bottom: 15px;' }, '请选择一个班次打卡'),
  803. h('el-radio-group', {
  804. model: {
  805. value: self.tempSelectedValue,
  806. callback: (value) => {
  807. self.tempSelectedValue = value
  808. }
  809. }
  810. }, scheduleArr.map(schedule =>
  811. h('el-radio', {
  812. props: {
  813. label: schedule.alias,
  814. key: schedule.alias
  815. },
  816. style: 'display: block; margin: 10px 0;'
  817. }, schedule.alias))
  818. )
  819. ]),
  820. showCancelButton: true,
  821. confirmButtonText: '确定',
  822. cancelButtonText: '取消',
  823. beforeClose: (action, instance, done) => {
  824. if (action === 'confirm') {
  825. if (!this.tempSelectedValue) {
  826. this.$message.warning('请选择一个班次')
  827. return false
  828. }
  829. done()
  830. } else {
  831. done()
  832. }
  833. }
  834. }).then(() => {
  835. const scheduleObj = this.scheduleData.find(item =>
  836. item.start === today && item.alias === this.tempSelectedValue
  837. )
  838. if (scheduleObj?.attendance) {
  839. this.handleClock(scheduleObj.attendance)
  840. }
  841. }).catch(() => {
  842. // 用户取消操作
  843. })
  844. }
  845. },
  846. async showMySchedule () {
  847. this.attendanceData = await this.getAttendanceData()
  848. this.scheduleData = await this.getScheduleData()
  849. const scheduleConfig = {
  850. height: '100%',
  851. locale: 'zh-cn', // 语言
  852. selectable: true, // 是否可以选中日历格
  853. buttonText: { // 日历头部按钮中文转换
  854. today: '今天',
  855. dayGridMonth: '月',
  856. listMonth: '',
  857. month: '月',
  858. week: '周视图',
  859. day: '日视图',
  860. list: '表'
  861. // prev: '<i class="icon-chevron-left">后退</i>',
  862. // next: '<i class="icon-chevron-right">前进</i>'
  863. },
  864. headerToolbar: { // 日历头部按钮位置
  865. // left: 'prev,next today',
  866. // start: '',
  867. right: 'customButton prev,next today',
  868. left: '',
  869. center: 'title'
  870. // right: 'dayGridMonth,timeGridWeek,timeGridDay'
  871. // end: 'prev,next,today,month,agendaWeek,agendaDay,listWeek'
  872. },
  873. customButtons: {
  874. customButton: { // 定义自定义按钮
  875. text: '补卡',
  876. click: (param) => {
  877. // 打开补卡申请弹窗
  878. this.$emit(
  879. 'open',
  880. 'buka'
  881. )
  882. }
  883. }
  884. },
  885. // events: this.scheduleData, // 排班数组
  886. eventClick: this.handleScheduleEventClick, // 排班点击信息展示
  887. scheduleShift: this.scheduleShift,
  888. eventColor: '#ffffff',
  889. events: this.scheduleData,
  890. // 添加自定义事件渲染
  891. eventContent: (arg) => {
  892. const event = arg.event
  893. const now = new Date()
  894. const dayStart = new Date(event.start)
  895. dayStart.setHours(0, 0, 0, 0)
  896. // 组合DOM内容
  897. const fragment = document.createDocumentFragment()
  898. const content = document.createElement('div')
  899. const titleStr = event.extendedProps.name + '/' + event.extendedProps.alias
  900. const timeStr = event.extendedProps.dateRange[0].startTime + '至' + event.extendedProps.dateRange[0].endTime
  901. // 考勤是否正常
  902. const status = event.extendedProps.attendance.kao_qin_zhuang_ta || ''
  903. content.innerHTML = `
  904. <div class="event-header">
  905. <div>
  906. <div style="background:${event.extendedProps.bcolor};height:10px;width:10px;display:inline-block;"> </div>
  907. ${titleStr}
  908. </div>
  909. <div class="button-placeholder">
  910. ${ event.extendedProps.jieShuShiJian <= this.$common.getDateNow() ?
  911. (
  912. (status === "正常")? '<i class="el-icon-check" style="color:#409EFF"></i>'
  913. : '<i class="el-icon-warning-outline" style="color:#F5222D"></i>'
  914. ) : ''
  915. }
  916. </div>
  917. </div>
  918. `
  919. // 打卡按钮显示
  920. if (this.showDaKaBtn(event.extendedProps.jieShuShiJian)) {
  921. const button = document.createElement('button')
  922. button.className = 'clock-btn'
  923. // 根据打卡状态显示不同文本
  924. button.innerHTML = '打卡'
  925. // 绑定点击事件(通过闭包传递当前事件)
  926. button.onclick = (e) => {
  927. e.stopPropagation()
  928. this.handleClock(event.extendedProps.attendance)
  929. }
  930. const placeholder = content.querySelector('.button-placeholder')
  931. placeholder.replaceWith(button)
  932. }
  933. fragment.appendChild(content)
  934. return { domNodes: [fragment] }
  935. }
  936. }
  937. this.$emit('action-event', 'mySchedule', scheduleConfig)
  938. }
  939. },
  940. template: column.templateHtml !== '' ? `${column.templateHtml}` : `<div></div>`
  941. }
  942. } catch (error) {
  943. console.error(error)
  944. }
  945. }