Merge remote-tracking branch 'origin/dev' into dev
| | |
| | | import { getToken, setToken, removeToken } from '@/utils/auth'
|
| | | import { isHttp, isEmpty } from "@/utils/validate"
|
| | | import defAva from '@/assets/images/profile.jpg'
|
| | | import { defineStore } from 'pinia'
|
| | |
|
| | | const useUserStore = defineStore(
|
| | | 'user',
|
| | |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- æå¡è®°å½ --> |
| | | <el-tab-pane label="æå¡è®°å½" name="attendance"> |
| | | <div class="tab-content"> |
| | | <div style="margin-bottom: 20px;"> |
| | | <el-date-picker |
| | | v-model="attendanceDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="margin-right: 10px;" |
| | | @change="filterAttendanceData" |
| | | /> |
| | | <el-select |
| | | v-model="attendanceStatus" |
| | | placeholder="éæ©ç¶æ" |
| | | style="width: 120px; margin-right: 10px;" |
| | | @change="filterAttendanceData" |
| | | > |
| | | <el-option label="å
¨é¨" value="" /> |
| | | <el-option label="æ£å¸¸" value="normal" /> |
| | | <el-option label="è¿å°" value="late" /> |
| | | <el-option label="æ©é" value="early" /> |
| | | <el-option label="缺å¤" value="absent" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="exportAttendance">导åºè®°å½</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="filteredAttendanceData" border style="width: 100%;"> |
| | | <el-table-column prop="employeeName" label="åå·¥å§å" width="120" /> |
| | | <el-table-column prop="department" label="é¨é¨" width="120" /> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="clockInTime" label="ä¸çæå¡" width="120" /> |
| | | <el-table-column prop="clockOutTime" label="ä¸çæå¡" width="120" /> |
| | | <el-table-column prop="workHours" label="工使¶é¿" width="100" align="center" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getAttendanceTagType(scope.row.status)">{{ getAttendanceStatusLabel(scope.row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="æå¡å°ç¹" width="150" /> |
| | | <el-table-column prop="remark" label="夿³¨" min-width="150" /> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- éç¨å¼¹çª --> |
| | |
| | | const overtimeData = ref([]) |
| | | const worktimeData = ref([]) |
| | | |
| | | // æå¡è®°å½ç¸å
³æ°æ® |
| | | const attendanceData = ref([]) |
| | | const filteredAttendanceData = ref([]) |
| | | const attendanceDate = ref('') |
| | | const attendanceStatus = ref('') |
| | | |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | name: '', |
| | |
| | | regular: 'æ£å¼åå·¥', probation: 'è¯ç¨æåå·¥', intern: 'å®ä¹ ç' |
| | | } |
| | | return labelMap[type] || type |
| | | } |
| | | |
| | | // æå¡è®°å½ç¸å
³å·¥å
·å½æ° |
| | | const getAttendanceTagType = (status) => { |
| | | const tagMap = { |
| | | normal: 'success', |
| | | late: 'warning', |
| | | early: 'warning', |
| | | absent: 'danger' |
| | | } |
| | | return tagMap[status] || 'info' |
| | | } |
| | | |
| | | const getAttendanceStatusLabel = (status) => { |
| | | const labelMap = { |
| | | normal: 'æ£å¸¸', |
| | | late: 'è¿å°', |
| | | early: 'æ©é', |
| | | absent: '缺å¤' |
| | | } |
| | | return labelMap[status] || status |
| | | } |
| | | |
| | | const getTypeOptions = () => { |
| | |
| | | } |
| | | } |
| | | |
| | | // æå¡è®°å½è¿æ»¤åè½ |
| | | const filterAttendanceData = () => { |
| | | let filtered = attendanceData.value |
| | | |
| | | // ææ¥æè¿æ»¤ |
| | | if (attendanceDate.value) { |
| | | filtered = filtered.filter(item => item.date === attendanceDate.value) |
| | | } |
| | | |
| | | // æç¶æè¿æ»¤ |
| | | if (attendanceStatus.value) { |
| | | filtered = filtered.filter(item => item.status === attendanceStatus.value) |
| | | } |
| | | |
| | | filteredAttendanceData.value = filtered |
| | | } |
| | | |
| | | // å¯¼åºæå¡è®°å½ |
| | | const exportAttendance = () => { |
| | | ElMessage.success('导åºåè½å¼åä¸...') |
| | | } |
| | | |
| | | // åå§åæå¡è®°å½åæ°æ® |
| | | const initAttendanceData = () => { |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | employeeName: 'éå¿å¼º', |
| | | department: 'ææ¯é¨', |
| | | date: '2025-08-15', |
| | | clockInTime: '09:00:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '8.0h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 2, |
| | | employeeName: 'æéªæ¢
', |
| | | department: 'å¸åºé¨', |
| | | date: '2025-08-16', |
| | | clockInTime: '08:58:00', |
| | | clockOutTime: '18:05:00', |
| | | workHours: '8.12h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 3, |
| | | employeeName: 'ç建å', |
| | | department: '人äºé¨', |
| | | date: '2025-08-16', |
| | | clockInTime: '09:02:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.97h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 4, |
| | | employeeName: 'èµµæä¸½', |
| | | department: 'è´¢å¡é¨', |
| | | date: '2025-09-02', |
| | | clockInTime: '08:55:00', |
| | | clockOutTime: '18:10:00', |
| | | workHours: '8.25h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 5, |
| | | employeeName: 'å¼ å½åº', |
| | | department: 'ææ¯é¨', |
| | | date: '2025-09-02', |
| | | clockInTime: '09:00:00', |
| | | clockOutTime: '18:30:00', |
| | | workHours: '8.5h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: 'å ç' |
| | | }, |
| | | { |
| | | id: 6, |
| | | employeeName: 'åæè¾', |
| | | department: 'è¿è¥é¨', |
| | | date: '2025-09-03', |
| | | clockInTime: '09:05:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.92h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 7, |
| | | employeeName: 'å丽å', |
| | | department: '设计é¨', |
| | | date: '2025-09-03', |
| | | clockInTime: '08:59:00', |
| | | clockOutTime: '18:02:00', |
| | | workHours: '8.05h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 8, |
| | | employeeName: 'å¨å»ºå', |
| | | department: 'éå®é¨', |
| | | date: '2025-09-04', |
| | | clockInTime: '09:15:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.75h', |
| | | status: 'late', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '交éå µå¡' |
| | | }, |
| | | { |
| | | id: 9, |
| | | employeeName: 'å´å°è³', |
| | | department: '客æé¨', |
| | | date: '2025-09-04', |
| | | clockInTime: '09:01:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.98h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 10, |
| | | employeeName: 'é©¬ææ°', |
| | | department: 'ææ¯é¨', |
| | | date: '2025-09-05', |
| | | clockInTime: '08:57:00', |
| | | clockOutTime: '17:30:00', |
| | | workHours: '7.55h', |
| | | status: 'early', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: 'ææ¥äºæå离å¼' |
| | | }, |
| | | { |
| | | id: 11, |
| | | employeeName: 'ææä¸', |
| | | department: 'è¡æ¿é¨', |
| | | date: '2025-09-05', |
| | | clockInTime: '09:03:00', |
| | | clockOutTime: '18:08:00', |
| | | workHours: '8.08h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 12, |
| | | employeeName: 'é»ç¾ç²', |
| | | department: 'è´¢å¡é¨', |
| | | date: '2025-09-06', |
| | | clockInTime: '', |
| | | clockOutTime: '', |
| | | workHours: '0h', |
| | | status: 'absent', |
| | | location: '', |
| | | remark: '请ç
å' |
| | | }, |
| | | { |
| | | id: 13, |
| | | employeeName: 'éæµ·æ¶', |
| | | department: 'å¸åºé¨', |
| | | date: '2025-08-14', |
| | | clockInTime: '09:00:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '8.0h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 14, |
| | | employeeName: '谢丽å¨', |
| | | department: '人äºé¨', |
| | | date: '2025-08-20', |
| | | clockInTime: '08:58:00', |
| | | clockOutTime: '18:03:00', |
| | | workHours: '8.08h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 15, |
| | | employeeName: 'ä½å¿ä¼', |
| | | department: 'ææ¯é¨', |
| | | date: '2025-08-21', |
| | | clockInTime: '09:10:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.83h', |
| | | status: 'late', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 16, |
| | | employeeName: '许é
è³', |
| | | department: '设计é¨', |
| | | date: '2025-08-22', |
| | | clockInTime: '09:01:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.98h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 17, |
| | | employeeName: 'é建平', |
| | | department: 'è¿è¥é¨', |
| | | date: '2025-09-10', |
| | | clockInTime: '08:59:00', |
| | | clockOutTime: '18:05:00', |
| | | workHours: '8.1h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 18, |
| | | employeeName: 'æ¾å°çº¢', |
| | | department: '客æé¨', |
| | | date: '2025-09-11', |
| | | clockInTime: '09:02:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.97h', |
| | | status: 'normal', |
| | | location: 'å
¬å¸æ»é¨', |
| | | remark: '' |
| | | } |
| | | ] |
| | | |
| | | attendanceData.value = mockData |
| | | filteredAttendanceData.value = mockData |
| | | } |
| | | |
| | | // å é¤é¡¹ç® |
| | | const deleteItem = (type, row) => { |
| | | ElMessageBox.confirm('ç¡®å®è¦å é¤è¿ä¸ªé¡¹ç®åï¼', 'æç¤º', { |
| | |
| | | getAnnualLeaveSettingList() |
| | | getOvertimeSettingList() |
| | | getWorkingHoursSettingList() |
| | | initAttendanceData() |
| | | console.log('èå¤ç®¡ç页é¢å è½½å®æ') |
| | | }) |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog :title="operationType === 'add' ? 'æ°å¢å·¡æ£ä»»å¡' : 'ç¼è¾å·¡æ£ä»»å¡'" |
| | | v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä»»å¡åç§°" prop="taskName"> |
| | | <el-input v-model="form.taskName" placeholder="请è¾å
¥ä»»å¡åç§°" maxlength="30" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å°ç¹" prop="inspectionLocation"> |
| | | <el-input v-model="form.inspectionLocation" placeholder="请è¾å
¥å°ç¹" maxlength="30" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å·¡æ£äºº" prop="inspector"> |
| | | <el-select v-model="form.inspector" placeholder="è¯·éæ©" multiple clearable> |
| | | <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="夿³¨" prop="remarks"> |
| | | <el-input v-model="form.remarks" placeholder="请è¾å
¥å¤æ³¨" type="textarea" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä»»å¡é¢ç" prop="frequencyType"> |
| | | <el-select v-model="form.frequencyType" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="æ¯æ¥" value="DAILY"/> |
| | | <el-option label="æ¯å¨" value="WEEKLY"/> |
| | | <el-option label="æ¯æ" value="MONTHLY"/> |
| | | <el-option label="å£åº¦" value="QUARTERLY"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-time-picker v-model="form.frequencyDetail" placeholder="éæ©æ¶é´" format="HH:mm" |
| | | value-format="HH:mm" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-select v-model="form.week" placeholder="è¯·éæ©" clearable style="width: 50%"> |
| | | <el-option label="å¨ä¸" value="MON"/> |
| | | <el-option label="å¨äº" value="TUE"/> |
| | | <el-option label="å¨ä¸" value="WED"/> |
| | | <el-option label="å¨å" value="THU"/> |
| | | <el-option label="å¨äº" value="FRI"/> |
| | | <el-option label="å¨å
" value="SAT"/> |
| | | <el-option label="卿¥" value="SUN"/> |
| | | </el-select> |
| | | <el-time-picker v-model="form.time" placeholder="éæ©æ¶é´" format="HH:mm" |
| | | value-format="HH:mm" style="width: 50%"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-date-picker |
| | | v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="éæ©å¼å§æ¥æ" |
| | | format="DD,HH:mm" |
| | | value-format="DD,HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-date-picker |
| | | v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="éæ©å¼å§æ¥æ" |
| | | format="MM,DD,HH:mm" |
| | | value-format="MM,DD,HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="cancel">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ä¿å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {reactive, ref} from "vue"; |
| | | import useUserStore from '@/store/modules/user' |
| | | import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js"; |
| | | import {userListAll} from "@/api/publicApi/index.js"; |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits() |
| | | const userStore = useUserStore() |
| | | const dialogVisitable = ref(false); |
| | | const operationType = ref('add'); |
| | | const data = reactive({ |
| | | form: { |
| | | taskName: '', |
| | | inspectionLocation: '', |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | | }, |
| | | rules: { |
| | | taskName: [{ required: true, message: "请è¾å
¥ä»»å¡åç§°", trigger: "blur" },], |
| | | inspectionLocation: [{ required: true, message: "请è¾å
¥å°ç¹", trigger: "blur" },], |
| | | inspector: [{ required: true, message: "请è¾å
¥å·¡æ£äºº", trigger: "blur" },], |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | | const userList = ref([]) |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | dialogVisitable.value = true |
| | | userListAll().then(res => { |
| | | userList.value = res.data |
| | | }) |
| | | if (type === 'edit') { |
| | | form.value = {...row} |
| | | form.value.inspector = form.value.inspectorIds.split(',').map(Number) |
| | | } |
| | | } |
| | | |
| | | // å
³éå并表å |
| | | const cancel = () => { |
| | | proxy.resetForm("formRef") |
| | | dialogVisitable.value = false |
| | | emit('closeDia') |
| | | } |
| | | |
| | | // æäº¤å并表å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(async valid => { |
| | | if (valid) { |
| | | form.value.inspectorIds = form.value.inspector.join(',') |
| | | delete form.value.inspector |
| | | if (form.value.frequencyType === 'WEEKLY') { |
| | | let frequencyDetail = '' |
| | | frequencyDetail = form.value.week + ',' + form.value.time |
| | | form.value.frequencyDetail = frequencyDetail |
| | | } |
| | | let res = await userStore.getInfo() |
| | | form.value.registrantId = res.user.userId |
| | | addOrEditTimingTask(form.value).then(() => { |
| | | cancel() |
| | | proxy.$modal.msgSuccess('æäº¤æå') |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | defineExpose({ openDialog }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog :title="operationType === 'add' ? 'æ°å¢äºç»´ç ' : 'ç¼è¾äºç»´ç '" |
| | | v-model="dialogVisitable" width="500px" @close="cancel"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="设å¤åç§°" prop="deviceName"> |
| | | <el-input v-model="form.deviceName" placeholder="请è¾å
¥è®¾å¤åç§°" maxlength="30" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æå¨ä½ç½®æè¿°" prop="location"> |
| | | <el-input v-model="form.location" placeholder="请è¾å
¥æå¨ä½ç½®æè¿°" maxlength="30"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <div> |
| | | <el-button type="primary" @click="submitForm">çæå¹¶æå°äºç»´ç </el-button> |
| | | </div> |
| | | <div v-if="isShowQrCode" class="print-section" ref="qrCodeContainer" id="qrCodeContainer"> |
| | | <vue-qrcode :value="qrCodeValue" :width="qrCodeSize"></vue-qrcode> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {reactive, ref} from "vue"; |
| | | import printJS from 'print-js'; |
| | | import {addOrEditQrCode} from "@/api/inspectionUpload/index.js"; |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits() |
| | | const userStore = useUserStore() |
| | | const dialogVisitable = ref(false); |
| | | const isShowQrCode = ref(false); |
| | | const operationType = ref('add'); |
| | | |
| | | const qrCodeValue = ref(''); |
| | | const qrCodeSize = ref(100); |
| | | const data = reactive({ |
| | | form: { |
| | | deviceName: '', |
| | | location: '', |
| | | qrCodeId: '', |
| | | id: '' |
| | | }, |
| | | rules: { |
| | | deviceName: [{ required: true, message: '请è¾å
¥è®¾å¤åç§°', trigger: 'blur' }], |
| | | location: [{ required: true, message: '请è¾å
¥å°ç¹', trigger: 'blur' }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | | |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | dialogVisitable.value = true |
| | | qrCodeValue.value = '' |
| | | isShowQrCode.value = false; |
| | | if (type === 'edit') { |
| | | form.value.id = row.id |
| | | form.value.qrCodeId = row.id |
| | | form.value.deviceName = row.deviceName |
| | | form.value.location = row.location |
| | | // å°è¡¨åæ°æ®è½¬ä¸º JSON å符串ä½ä¸ºäºç»´ç å
容 |
| | | qrCodeValue.value = JSON.stringify(form.value); |
| | | isShowQrCode.value = true; |
| | | } |
| | | } |
| | | // æäº¤å并表å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | addOrEditQrCode(form.value).then((res) => { |
| | | form.value.qrCodeId = res.data |
| | | }) |
| | | // å°è¡¨åæ°æ®è½¬ä¸º JSON å符串ä½ä¸ºäºç»´ç å
容 |
| | | qrCodeValue.value = JSON.stringify(form.value); |
| | | isShowQrCode.value = true; |
| | | showQrCode() |
| | | } |
| | | }) |
| | | } |
| | | const showQrCode = () => { |
| | | // å»¶è¿æ§è¡æå°ï¼é¿å
DOM æ´æ°åå°±è°ç¨æå° |
| | | setTimeout(() => { |
| | | printJS({ |
| | | printable: 'qrCodeContainer',//é¡µé¢ |
| | | type: "html",//ææ¡£ç±»å |
| | | maxWidth: 360, |
| | | style: `@page { |
| | | margin:0; |
| | | size: 400px 75px collapse; |
| | | margin-top:3px; |
| | | &:first-of-type{ |
| | | margin-top:0 !important; |
| | | } |
| | | } |
| | | html{ |
| | | zoom:100%; |
| | | } |
| | | @media print{ |
| | | width: 400px; |
| | | height: 75px; |
| | | margin:0; |
| | | }`, |
| | | targetStyles: ["*"], // 使ç¨domçæææ ·å¼ï¼å¾éè¦ |
| | | font_size: '0.20cm', |
| | | }); |
| | | }, 300); |
| | | } |
| | | // å
³éå并表å |
| | | const cancel = () => { |
| | | proxy.resetForm("formRef") |
| | | dialogVisitable.value = false |
| | | emit('closeDia') |
| | | } |
| | | defineExpose({ openDialog }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .print-section { |
| | | text-align: center; |
| | | margin-top: 30px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog title="æ¥çéä»¶" |
| | | v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | <div class="upload-container"> |
| | | <!-- ç产å --> |
| | | <div class="form-container"> |
| | | <div class="title">ç产å</div> |
| | | |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in beforeProductionImgs" :key="index" |
| | | @click="showMedia(beforeProductionImgs, index, 'image')" |
| | | :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt=""> |
| | | </div> |
| | | |
| | | <!-- è§é¢å表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(videoUrl, index) in beforeProductionVideos" |
| | | :key="index" |
| | | @click="showMedia(beforeProductionVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | > |
| | | <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" style="width: 30px; height: 30px; opacity: 0.8;" /> |
| | | </div> |
| | | <div style="text-align: center; font-size: 12px; color: #666;">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç产å --> |
| | | <div class="form-container"> |
| | | <div class="title">ç产å</div> |
| | | |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in afterProductionImgs" :key="index" |
| | | @click="showMedia(afterProductionImgs, index, 'image')" |
| | | :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt=""> |
| | | </div> |
| | | |
| | | <!-- è§é¢å表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(videoUrl, index) in afterProductionVideos" |
| | | :key="index" |
| | | @click="showMedia(afterProductionVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | > |
| | | <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" style="width: 30px; height: 30px; opacity: 0.8;" /> |
| | | </div> |
| | | <div style="text-align: center; font-size: 12px; color: #666;">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç产é®é¢ --> |
| | | <div class="form-container"> |
| | | <div class="title">ç产é®é¢</div> |
| | | |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in productionIssuesImgs" :key="index" |
| | | @click="showMedia(productionIssuesImgs, index, 'image')" |
| | | :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt=""> |
| | | </div> |
| | | |
| | | <!-- è§é¢å表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(videoUrl, index) in productionIssuesVideos" |
| | | :key="index" |
| | | @click="showMedia(productionIssuesVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | > |
| | | <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" style="width: 30px; height: 30px; opacity: 0.8;" /> |
| | | </div> |
| | | <div style="text-align: center; font-size: 12px; color: #666;">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- ç»ä¸åªä½æ¥çå¨ --> |
| | | <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer"> |
| | | <div class="media-viewer-content" @click.stop> |
| | | <!-- å¾ç --> |
| | | <vue-easy-lightbox |
| | | v-if="mediaType === 'image'" |
| | | :visible="isMediaViewerVisible" |
| | | :imgs="mediaList" |
| | | :index="currentMediaIndex" |
| | | @hide="closeMediaViewer" |
| | | ></vue-easy-lightbox> |
| | | |
| | | <!-- è§é¢ --> |
| | | <div v-else-if="mediaType === 'video'" style="position: relative;"> |
| | | <Video |
| | | :src="mediaList[currentMediaIndex]" |
| | | autoplay |
| | | controls |
| | | style="max-width: 90vw; max-height: 80vh;" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import { ref } from 'vue'; |
| | | import VueEasyLightbox from 'vue-easy-lightbox'; |
| | | |
| | | // æ§å¶å¼¹çªæ¾ç¤º |
| | | const dialogVisitable = ref(false); |
| | | |
| | | // å¾çæ°ç» |
| | | const beforeProductionImgs = ref([]); |
| | | const afterProductionImgs = ref([]); |
| | | const productionIssuesImgs = ref([]); |
| | | |
| | | // è§é¢æ°ç» |
| | | const beforeProductionVideos = ref([]); |
| | | const afterProductionVideos = ref([]); |
| | | const productionIssuesVideos = ref([]); |
| | | |
| | | // åªä½æ¥çå¨ç¶æ |
| | | const isMediaViewerVisible = ref(false); |
| | | const currentMediaIndex = ref(0); |
| | | const mediaList = ref([]); // åå¨å½åè¦æ¥ççåªä½å表ï¼å«å¾çåè§é¢å¯¹è±¡ï¼ |
| | | const mediaType = ref('image'); // image | video |
| | | |
| | | // å¤çæ¯ä¸ç±»æ°æ®ï¼å离å¾çåè§é¢ |
| | | function processItems(items) { |
| | | const images = []; |
| | | const videos = []; |
| | | items.forEach(item => { |
| | | if (item.contentType?.startsWith('image/')) { |
| | | images.push(item.url); |
| | | } else if (item.contentType?.startsWith('video/')) { |
| | | videos.push(item.url); |
| | | } |
| | | }); |
| | | return { images, videos }; |
| | | } |
| | | |
| | | // æå¼å¼¹çªå¹¶å è½½æ°æ® |
| | | const openDialog = async (row) => { |
| | | const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction); |
| | | const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction); |
| | | const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues); |
| | | |
| | | beforeProductionImgs.value = beforeImgs; |
| | | beforeProductionVideos.value = beforeVids; |
| | | |
| | | afterProductionImgs.value = afterImgs; |
| | | afterProductionVideos.value = afterVids; |
| | | |
| | | productionIssuesImgs.value = issueImgs; |
| | | productionIssuesVideos.value = issueVids; |
| | | |
| | | dialogVisitable.value = true; |
| | | }; |
| | | |
| | | // æ¾ç¤ºåªä½ï¼å¾ç or è§é¢ï¼ |
| | | function showMedia(mediaArray, index, type) { |
| | | mediaList.value = mediaArray; |
| | | currentMediaIndex.value = index; |
| | | mediaType.value = type; |
| | | isMediaViewerVisible.value = true; |
| | | } |
| | | |
| | | // å
³éåªä½æ¥çå¨ |
| | | function closeMediaViewer() { |
| | | isMediaViewerVisible.value = false; |
| | | mediaList.value = []; |
| | | mediaType.value = 'image'; |
| | | } |
| | | |
| | | // 表åå
³éæ¹æ³ |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | <style scoped lang="scss"> |
| | | .upload-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #dcdfe6; |
| | | box-sizing: border-box; |
| | | |
| | | .form-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | |
| | | .title { |
| | | font-size: 14px; |
| | | color: #165dff; |
| | | line-height: 20px; |
| | | font-weight: 600; |
| | | padding-left: 10px; |
| | | position: relative; |
| | | margin: 6px 0; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 3px; |
| | | width: 4px; |
| | | height: 14px; |
| | | background-color: #165dff; |
| | | } |
| | | } |
| | | |
| | | .media-viewer-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background-color: rgba(0, 0, 0, 0.8); |
| | | z-index: 9999; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .media-viewer-content { |
| | | position: relative; |
| | | max-width: 90vw; |
| | | max-height: 90vh; |
| | | overflow: hidden; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog title="æ¥çéä»¶" |
| | | v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | <div class="upload-container"> |
| | | <div class="form-container"> |
| | | <div class="title">å·¡æ£éä»¶</div> |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in beforeProductionImgs" :key="index" |
| | | @click="showMedia(beforeProductionImgs, index, 'image')" |
| | | :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt=""> |
| | | </div> |
| | | |
| | | <!-- è§é¢å表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(videoUrl, index) in beforeProductionVideos" |
| | | :key="index" |
| | | @click="showMedia(beforeProductionVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | > |
| | | <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" style="width: 30px; height: 30px; opacity: 0.8;" /> |
| | | </div> |
| | | <div style="text-align: center; font-size: 12px; color: #666;">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | <!-- ç»ä¸åªä½æ¥çå¨ --> |
| | | <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer"> |
| | | <div class="media-viewer-content" @click.stop> |
| | | <!-- å¾ç --> |
| | | <vue-easy-lightbox |
| | | v-if="mediaType === 'image'" |
| | | :visible="isMediaViewerVisible" |
| | | :imgs="mediaList" |
| | | :index="currentMediaIndex" |
| | | @hide="closeMediaViewer" |
| | | ></vue-easy-lightbox> |
| | | |
| | | <!-- è§é¢ --> |
| | | <div v-else-if="mediaType === 'video'" style="position: relative;"> |
| | | <Video |
| | | :src="mediaList[currentMediaIndex]" |
| | | autoplay |
| | | controls |
| | | style="max-width: 90vw; max-height: 80vh;" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | // æ§å¶å¼¹çªæ¾ç¤º |
| | | import VueEasyLightbox from "vue-easy-lightbox"; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | // å¾çæ°ç» |
| | | const beforeProductionImgs = ref([]); |
| | | // è§é¢æ°ç» |
| | | const beforeProductionVideos = ref([]); |
| | | // åªä½æ¥çå¨ç¶æ |
| | | const isMediaViewerVisible = ref(false); |
| | | const currentMediaIndex = ref(0); |
| | | const mediaList = ref([]); // åå¨å½åè¦æ¥ççåªä½å表ï¼å«å¾çåè§é¢å¯¹è±¡ï¼ |
| | | const mediaType = ref('image'); // image | video |
| | | |
| | | // æå¼å¼¹çªå¹¶å è½½æ°æ® |
| | | const openDialog = async (row) => { |
| | | const { images: beforeImgs, videos: beforeVids } = processItems(row.storageBlobDTO); |
| | | |
| | | beforeProductionImgs.value = beforeImgs; |
| | | beforeProductionVideos.value = beforeVids; |
| | | dialogVisitable.value = true; |
| | | }; |
| | | // æ¾ç¤ºåªä½ï¼å¾ç or è§é¢ï¼ |
| | | function showMedia(mediaArray, index, type) { |
| | | mediaList.value = mediaArray; |
| | | currentMediaIndex.value = index; |
| | | mediaType.value = type; |
| | | isMediaViewerVisible.value = true; |
| | | } |
| | | // å
³éåªä½æ¥çå¨ |
| | | function closeMediaViewer() { |
| | | isMediaViewerVisible.value = false; |
| | | mediaList.value = []; |
| | | mediaType.value = 'image'; |
| | | } |
| | | // 表åå
³éæ¹æ³ |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | // å¤çæ¯ä¸ç±»æ°æ®ï¼å离å¾çåè§é¢ |
| | | function processItems(items) { |
| | | const images = []; |
| | | const videos = []; |
| | | items.forEach(item => { |
| | | if (item.contentType?.startsWith('image/')) { |
| | | images.push(item.url); |
| | | } else if (item.contentType?.startsWith('video/')) { |
| | | videos.push(item.url); |
| | | } |
| | | }); |
| | | return { images, videos }; |
| | | } |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .upload-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #dcdfe6; |
| | | box-sizing: border-box; |
| | | |
| | | .form-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | |
| | | .title { |
| | | font-size: 14px; |
| | | color: #165dff; |
| | | line-height: 20px; |
| | | font-weight: 600; |
| | | padding-left: 10px; |
| | | position: relative; |
| | | margin: 6px 0; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 3px; |
| | | width: 4px; |
| | | height: 14px; |
| | | background-color: #165dff; |
| | | } |
| | | } |
| | | |
| | | .media-viewer-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background-color: rgba(0, 0, 0, 0.8); |
| | | z-index: 9999; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .media-viewer-content { |
| | | position: relative; |
| | | max-width: 90vw; |
| | | max-height: 90vh; |
| | | overflow: hidden; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :inline="true" :model="queryParams" class="search-form"> |
| | | <el-form-item label="æç´¢"> |
| | | <el-input |
| | | v-model="queryParams.searchAll" |
| | | placeholder="请è¾å
¥å
³é®å" |
| | | clearable |
| | | :style="{ width: '100%' }" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <el-card> |
| | | <!-- æ ç¾é¡µ --> |
| | | <el-tabs |
| | | v-model="activeTab" |
| | | class="info-tabs" |
| | | @tab-click="handleTabClick" |
| | | > |
| | | <el-tab-pane |
| | | v-for="tab in tabs" |
| | | :key="tab.name" |
| | | :label="tab.label" |
| | | :name="tab.name" |
| | | /> |
| | | </el-tabs> |
| | | <div style="display: flex;flex-direction: row;justify-content: space-between;" v-if="tabName === 'task'"> |
| | | <el-radio-group v-model="activeRadio" @change="radioChange"> |
| | | <el-radio-button v-for="tab in radios" |
| | | :key="tab.name" |
| | | :label="tab.label" |
| | | :value="tab.name"/> |
| | | </el-radio-group> |
| | | <!-- æä½æé®åº --> |
| | | <el-space v-if="activeRadio !== 'task'"> |
| | | <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">æ°å»º</el-button> |
| | | <el-button type="danger" :icon="Delete" @click="handleDelete">å é¤</el-button> |
| | | <!-- <el-button type="info" plain :icon="Download">导åº</el-button> --> |
| | | </el-space> |
| | | </div> |
| | | <div> |
| | | <div> |
| | | <ETable :loading="tableLoading" |
| | | :table-data="tableData" |
| | | :columns="tableColumns" |
| | | @selection-change="handleSelectionChange" |
| | | :show-selection="true" |
| | | :border="true" |
| | | style="width: 100%;height: calc(100vh - 30em)" |
| | | operationsWidth="130" |
| | | :operations="operationsArr" |
| | | @edit="handleAdd" |
| | | @viewFile="viewFile" |
| | | v-if="tabName === 'task'" |
| | | > |
| | | <template #inspector="{ row }"> |
| | | <div class="person-tags"> |
| | | <!-- è°è¯ä¿¡æ¯ï¼ä¸çº¿æ¶å é¤ --> |
| | | <!-- {{ console.log('inspector data:', row.inspector) }} --> |
| | | <template v-if="row.inspector && row.inspector.length > 0"> |
| | | <el-tag |
| | | v-for="(person, index) in row.inspector" |
| | | :key="index" |
| | | size="small" |
| | | type="primary" |
| | | class="person-tag" |
| | | > |
| | | {{ person }} |
| | | </el-tag> |
| | | </template> |
| | | <span v-else class="no-data">--</span> |
| | | </div> |
| | | </template> |
| | | </ETable> |
| | | <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" border v-else style="width: 100%;height: calc(100vh - 25em)"> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column prop="deviceName" label="设å¤åç§°" :show-overflow-tooltip="true"> |
| | | <template #default="scope"> |
| | | {{scope.row.qrCode.deviceName}} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="æå¨ä½ç½®æè¿°" :show-overflow-tooltip="true"> |
| | | <template #default="scope"> |
| | | {{scope.row.qrCode.location}} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="scanner" label="å·¡æ£äºº"></el-table-column> |
| | | <el-table-column prop="scanTime" label="å·¡æ£æ¶é´"></el-table-column> |
| | | <el-table-column fixed="right" label="æä½"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleAdd(scope.row)">æ¥çéä»¶</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <pagination |
| | | v-if="total>0" |
| | | :page="pageNum" |
| | | :limit="pageSize" |
| | | :total="total" |
| | | @pagination="handlePagination" |
| | | :layout="'total, prev, pager, next, jumper'" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | <form-dia ref="formDia" @closeDia="handleQuery"></form-dia> |
| | | <qr-code-dia ref="qrCodeDia" @closeDia="handleQuery"></qr-code-dia> |
| | | <view-files ref="viewFiles"></view-files> |
| | | <view-qr-code-files ref="viewQrCodeFiles"></view-qr-code-files> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Delete, Plus } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue"; |
| | | |
| | | // ç»ä»¶å¼å
¥ |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | import ETable from "@/components/Table/ETable.vue"; |
| | | import FormDia from "@/views/inspectionManagement/components/formDia.vue"; |
| | | import QrCodeDia from "@/views/inspectionManagement/components/qrCodeDia.vue"; |
| | | import ViewFiles from "@/views/inspectionManagement/components/viewFiles.vue"; |
| | | import ViewQrCodeFiles from "@/views/inspectionManagement/components/viewQrCodeFiles.vue"; |
| | | |
| | | // æ¥å£å¼å
¥ |
| | | import { |
| | | delTimingTask, |
| | | inspectionTaskList, |
| | | timingTaskList |
| | | } from "@/api/inspectionManagement/index.js"; |
| | | import { |
| | | delQrCode, |
| | | qrCodeList, |
| | | qrCodeScanRecordList |
| | | } from "@/api/inspectionUpload/index.js"; |
| | | |
| | | // å
¨å±åé |
| | | const { proxy } = getCurrentInstance(); |
| | | const formDia = ref(); |
| | | const qrCodeDia = ref(); |
| | | const viewFiles = ref(); |
| | | const viewQrCodeFiles = ref(); |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | searchAll: "", |
| | | }); |
| | | |
| | | // æ ç¾é¡µé
ç½® |
| | | const activeTab = ref("task"); |
| | | const tabName = ref("task"); |
| | | const tabs = reactive([ |
| | | { name: "task", label: "ç产巡æ£" }, |
| | | { name: "qrCodeScanRecord", label: "ç°åºå·¡æ£è®°å½" }, |
| | | ]); |
| | | |
| | | // åéæ¡é
ç½® |
| | | const activeRadio = ref("taskManage"); |
| | | const radios = reactive([ |
| | | { name: "taskManage", label: "宿¶ä»»å¡ç®¡ç" }, |
| | | { name: "task", label: "宿¶ä»»å¡è®°å½" }, |
| | | { name: "qrCode", label: "äºç»´ç 管ç" }, |
| | | ]); |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | const selectedRows = ref([]); |
| | | const tableData = ref([]); |
| | | const operationsArr = ref([]); |
| | | const tableColumns = ref([]); |
| | | const tableLoading = ref(false); |
| | | const total = ref(0); |
| | | const pageNum = ref(1); |
| | | const pageSize = ref(10); |
| | | |
| | | // åé
ç½® |
| | | const columns = ref([ |
| | | { prop: "taskName", label: "å·¡æ£ä»»å¡åç§°", minWidth: 160 }, |
| | | { prop: "inspectionLocation", label: "å°ç¹", minWidth: 120 }, |
| | | { prop: "remarks", label: "夿³¨", minWidth: 150 }, |
| | | { prop: "inspector", label: "æ§è¡å·¡æ£äºº", minWidth: 150, slot: "inspector" }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 150, |
| | | formatter: (_, __, val) => ({ |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦" |
| | | }[val] || "") |
| | | }, |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "å¼å§æ¥æä¸æ¶é´", |
| | | minWidth: 150, |
| | | formatter: (row, column, cellValue) => { |
| | | // å
夿æ¯å¦æ¯å符串 |
| | | if (typeof cellValue !== 'string') return ''; |
| | | let val = cellValue; |
| | | const replacements = { |
| | | MON: 'å¨ä¸', |
| | | TUE: 'å¨äº', |
| | | WED: 'å¨ä¸', |
| | | THU: 'å¨å', |
| | | FRI: 'å¨äº', |
| | | SAT: 'å¨å
', |
| | | SUN: '卿¥' |
| | | }; |
| | | // ä½¿ç¨æ£å䏿¬¡æ§æ¿æ¢ææå¹é
项 |
| | | return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]); |
| | | } |
| | | }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { prop: "createTime", label: "ç»è®°æ¥æ", minWidth: 100 }, |
| | | ]); |
| | | |
| | | const columns1 = ref([ |
| | | { prop: "deviceName", label: "设å¤åç§°", minWidth: 160 }, |
| | | { prop: "location", label: "æå¨ä½ç½®æè¿°", minWidth: 120 }, |
| | | { prop: "createBy", label: "å建è
", minWidth: 100 }, |
| | | { prop: "createTime", label: "å建æ¶é´", minWidth: 100 }, |
| | | ]); |
| | | |
| | | onMounted(() => { |
| | | radioChange('taskManage'); |
| | | }); |
| | | |
| | | // æ ç¾é¡µç¹å»äºä»¶ |
| | | const handleTabClick = (tab) => { |
| | | tabName.value = tab.props.name; |
| | | tableData.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | // åéåå |
| | | const radioChange = (value) => { |
| | | if (value === "taskManage") { |
| | | tableColumns.value = columns.value; |
| | | operationsArr.value = ['edit']; |
| | | } else if (value === "task") { |
| | | tableColumns.value = columns.value; |
| | | operationsArr.value = ['viewFile']; |
| | | } else { |
| | | tableColumns.value = columns1.value; |
| | | operationsArr.value = ['edit']; |
| | | } |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | |
| | | // æ¥è¯¢æä½ |
| | | const handleQuery = () => { |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | // å页å¤ç |
| | | const handlePagination = (val) => { |
| | | pageNum.value = val.page; |
| | | pageSize.value = val.limit; |
| | | getList(); |
| | | }; |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | const params = { ...queryParams, size: pageSize.value, current: pageNum.value }; |
| | | |
| | | let apiCall; |
| | | if (tabName.value === 'task') { |
| | | switch (activeRadio.value) { |
| | | case "task": |
| | | apiCall = inspectionTaskList(params); |
| | | break; |
| | | case "qrCode": |
| | | apiCall = qrCodeList(params); |
| | | break; |
| | | default: |
| | | apiCall = timingTaskList(params); |
| | | } |
| | | } else { |
| | | apiCall = qrCodeScanRecordList(params); |
| | | } |
| | | |
| | | apiCall.then(res => { |
| | | const rawData = res.data.records || []; |
| | | // å¤ç inspector åæ®µï¼å°å符串转æ¢ä¸ºæ°ç»ï¼éç¨äºæææ
åµï¼ |
| | | tableData.value = rawData.map(item => { |
| | | const processedItem = { ...item }; |
| | | |
| | | // å¤ç inspector åæ®µ |
| | | if (processedItem.inspector) { |
| | | if (typeof processedItem.inspector === 'string') { |
| | | // å符串æéå·åå² |
| | | processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s); |
| | | } else if (!Array.isArray(processedItem.inspector)) { |
| | | // éæ°ç»è½¬ä¸ºæ°ç» |
| | | processedItem.inspector = [processedItem.inspector]; |
| | | } |
| | | } else { |
| | | // 空å¼è®¾ä¸ºç©ºæ°ç» |
| | | processedItem.inspector = []; |
| | | } |
| | | |
| | | return processedItem; |
| | | }); |
| | | total.value = res.data.total || 0; |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // éç½®æ¥è¯¢ |
| | | const resetQuery = () => { |
| | | for (const key in queryParams) { |
| | | if (!["pageNum", "pageSize"].includes(key)) { |
| | | queryParams[key] = ""; |
| | | } |
| | | } |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // æ°å¢ / ç¼è¾ |
| | | const handleAdd = (row) => { |
| | | const type = row ? 'edit' : 'add'; |
| | | nextTick(() => { |
| | | if (tabName.value === 'task') { |
| | | if (activeRadio.value === "taskManage") { |
| | | formDia.value?.openDialog(type, row); |
| | | } else if (activeRadio.value === "qrCode") { |
| | | qrCodeDia.value?.openDialog(type, row); |
| | | } |
| | | } else { |
| | | viewQrCodeFiles.value?.openDialog(row); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æ¥çéä»¶ |
| | | const viewFile = (row) => { |
| | | nextTick(() => { |
| | | viewFiles.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤æä½ |
| | | const handleDelete = () => { |
| | | if (!selectedRows.value.length) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦å é¤çæ°æ®"); |
| | | return; |
| | | } |
| | | |
| | | const deleteIds = selectedRows.value.map(item => item.id); |
| | | const api = activeRadio.value === "taskManage" ? delTimingTask : delQrCode; |
| | | |
| | | proxy.$modal.confirm('æ¯å¦ç¡®è®¤å 餿鿰æ®é¡¹ï¼').then(() => { |
| | | return api(deleteIds); |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | handleQuery(); |
| | | }).catch(() => {}); |
| | | }; |
| | | |
| | | // å¤éåæ´ |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .person-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .person-tag { |
| | | margin-right: 4px; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | | |
| | | <!-- 顶鍿 颿 --> |
| | | <div class="dashboard-header"> |
| | | <div class="factory-name">{{ userStore.currentFactoryName }}</div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import useUserStore from '@/store/modules/user' |
| | | import { |
| | | analysisCustomerContractAmounts, getAmountHalfYear, |
| | | homeTodos, |
| | |
| | | |
| | | // å
¨å±ç¸å
³ç¶æ |
| | | const isFullscreen = ref(false); |
| | | |
| | | // ç¨æ·store |
| | | const userStore = useUserStore() |
| | | |
| | | // ååºå¼æ°æ® |
| | | const currentTime = ref('') |
| | |
| | | // 使ç¨nextTickç¡®ä¿DOMå®å
¨æ¸²æåååå§åå¾è¡¨ |
| | | nextTick(() => { |
| | | // åå§åautofitèªéåº |
| | | autofit.init({ dh: 1440, dw: 2560, el: '.data-dashboard', resize: true }, false) |
| | | autofit.init({ dh: 1080, dw: 1920, el: '.data-dashboard', resize: true }, false) |
| | | |
| | | // æ·»å èªå¨æ»å¨å¨ç»ææ - 客æ·ä¿¡æ¯å表 |
| | | const contractList = refContractList.value |
| | |
| | | position: relative; |
| | | width: 100%; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 32px; |
| | | position: absolute; |
| | | } |
| | | |
| | | .fullscreen-btn { |