| | |
| | | |
| | | /** |
| | | * å é¤è´§æ¶ |
| | | * @description æ ¹æ®è´§æ¶IDå 餿å®çè´§æ¶è®°å½ |
| | | * @param {string|number} id è´§æ¶ID |
| | | * @description æ ¹æ®è´§æ¶IDå 餿å®çè´§æ¶è®°å½ï¼åç«¯è¦æ±ä¼ å
¥ ID æ°ç»ï¼æ¯ææ¹éï¼ |
| | | * @param {Array<string|number>} data è´§æ¶IDæ°ç» |
| | | * @returns {Promise} è¿åå é¤ç»æ |
| | | */ |
| | | export function deleteShelf(id) { |
| | | export function deleteShelf(data) { |
| | | return request({ |
| | | url: `/warehouse/goodsShelves/delete/${id}`, |
| | | url: `/warehouse/goodsShelves/delete/`, |
| | | method: "delete", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | |
| | | method: 'delete', |
| | | data: ids |
| | | }) |
| | | } |
| | | } |
| | | // ç¾å° |
| | | export function safeTrainingSign(query) { |
| | | return request({ |
| | | url: '/safeTraining/sign', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // æ¥è¯¢è¯¦æ
|
| | | export function safeTrainingGet(query) { |
| | | return request({ |
| | | url: '/safeTraining/getSafeTraining', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // æäº¤ |
| | | export function safeTrainingSave(query) { |
| | | return request({ |
| | | url: '/safeTraining/saveSafeTraining', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | |
| | | export function safeTrainingDetailListPage(query) { |
| | | return request({ |
| | | url: "/safeTrainingDetails/page", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // å¯¼åº |
| | | export function safeTrainingDetailExport(query) { |
| | | return request({ |
| | | url: '/safeTrainingDetails/export', |
| | | method: 'post', |
| | | data: query, |
| | | responseType: 'blob' |
| | | }) |
| | | } |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, watchEffect } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | const emit = defineEmits(['finished']) |
| | | |
| | | // Props |
| | | const props = defineProps({ |
| | |
| | | // Refs |
| | | const chartRef = ref(null) |
| | | let chartInstance = null |
| | | let finishedHandler = null |
| | | let initTimer = null |
| | | let initAttempts = 0 |
| | | |
| | | function clearInitTimer() { |
| | | if (initTimer) { |
| | | clearTimeout(initTimer) |
| | | initTimer = null |
| | | } |
| | | } |
| | | |
| | | function isContainerReady() { |
| | | const el = chartRef.value |
| | | if (!el) return false |
| | | // offsetWidth/offsetHeight æ´è´´è¿çå®å¸å±ï¼ä¸º 0 å¾å¾ä»£è¡¨è¿æ²¡å¸å±/ä¸å¯è§ï¼ |
| | | return el.offsetWidth > 0 && el.offsetHeight > 0 |
| | | } |
| | | |
| | | function initChartWhenReady() { |
| | | clearInitTimer() |
| | | initAttempts += 1 |
| | | |
| | | if (!isContainerReady()) { |
| | | // ç容å¨çæ£æå°ºå¯¸ï¼é¿å
é¦å±åå§ååç§»/空ç½ï¼çæ´æ°åææ£å¸¸çæ
åµï¼ |
| | | // æå¤éè¯çº¦ 3 ç§ï¼é¿å
æ éå¾ªç¯ |
| | | if (initAttempts < 60) { |
| | | initTimer = setTimeout(initChartWhenReady, 50) |
| | | } |
| | | return |
| | | } |
| | | |
| | | if (chartInstance) return |
| | | chartInstance = echarts.init(chartRef.value) |
| | | finishedHandler = () => emit('finished') |
| | | chartInstance.on('finished', finishedHandler) |
| | | renderChart() |
| | | // setOption åè¡¥ä¸æ¬¡ resizeï¼ç¡®ä¿é¦å±å°ºå¯¸æ£ç¡® |
| | | nextTick(() => { |
| | | if (chartInstance) chartInstance.resize() |
| | | }) |
| | | } |
| | | |
| | | // Methods |
| | | function generateChart(option) { |
| | |
| | | |
| | | // Lifecycle hooks |
| | | onMounted(() => { |
| | | chartInstance = echarts.init(chartRef.value) |
| | | renderChart() |
| | | initAttempts = 0 |
| | | initChartWhenReady() |
| | | window.addEventListener('resize', windowResizeListener) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (chartInstance) { |
| | | window.removeEventListener('resize', windowResizeListener) |
| | | if (finishedHandler) { |
| | | chartInstance.off('finished', finishedHandler) |
| | | finishedHandler = null |
| | | } |
| | | chartInstance.dispose() |
| | | chartInstance = null |
| | | } |
| | | clearInitTimer() |
| | | }) |
| | | |
| | | // Watch all reactive props that affect the chart |
| | | watch( |
| | | () => [props.xAxis, props.yAxis, props.series, props.legend, props.tooltip, props.visualMap], |
| | | () => { |
| | | if (chartInstance) { |
| | | renderChart() |
| | | // 妿é¦å±è¿æ²¡åå§åæåï¼çå¾
å®¹å¨ ready å忏²æ |
| | | if (!chartInstance) { |
| | | initChartWhenReady() |
| | | return |
| | | } |
| | | renderChart() |
| | | // æ°æ®åååè¡¥ä¸æ¬¡ resizeï¼é¿å
å¸å±åå导è´çåç§» |
| | | nextTick(() => { |
| | | if (chartInstance) chartInstance.resize() |
| | | }) |
| | | }, |
| | | { deep: true, immediate: true } |
| | | ) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue' |
| | | |
| | | /** |
| | | * å¾è¡¨èæ¯ä½ç½®è°æ´ composable |
| | | * @param {Object} options é
ç½®é项 |
| | | * @param {Ref} options.wrapperRef - å¾è¡¨å®¹å¨ç ref |
| | | * @param {Ref} options.backgroundRef - èæ¯å
ç´ ç ref |
| | | * @param {String} options.left - èæ¯ left ä½ç½®ï¼å¦ '25%' æ '50%'ï¼é»è®¤ '50%' |
| | | * @param {String} options.top - èæ¯ top ä½ç½®ï¼å¦ '50%'ï¼é»è®¤ '50%' |
| | | * @param {String} options.offsetX - X è½´åç§»å¼ï¼å¦ '-51.5%' æ '-50%'ï¼é»è®¤ '-50%' |
| | | * @param {String} options.offsetY - Y è½´åç§»å¼ï¼å¦ '-39%' æ '-50%'ï¼é»è®¤ '-50%' |
| | | * @param {Ref} options.watchData - å¯éï¼çå¬çæ°æ®ååï¼æ°æ®ååæ¶éæ°è°æ´ä½ç½® |
| | | * @returns {Function} adjustBackgroundPosition - æå¨è°æ´èæ¯ä½ç½®çæ¹æ³ |
| | | */ |
| | | export function useChartBackground(options = {}) { |
| | | const { |
| | | wrapperRef, |
| | | backgroundRef, |
| | | left = '50%', |
| | | top = '50%', |
| | | offsetX = '-50%', |
| | | offsetY = '-50%', |
| | | watchData = null |
| | | } = options |
| | | |
| | | let resizeObserver = null |
| | | let intersectionObserver = null |
| | | let retryTimers = [] |
| | | |
| | | const clearRetryTimers = () => { |
| | | if (!retryTimers.length) return |
| | | retryTimers.forEach((t) => clearTimeout(t)) |
| | | retryTimers = [] |
| | | } |
| | | |
| | | // è°æ´èæ¯ä½ç½® |
| | | const adjustBackgroundPosition = () => { |
| | | nextTick(() => { |
| | | if (!wrapperRef?.value || !backgroundRef?.value) { |
| | | return |
| | | } |
| | | |
| | | // åå§åé¶æ®µç»å¸¸åºç°ï¼å®¹å¨å°æªå¯è§/尺寸为 0ï¼éå
¨å±ãtabãå¨ç»çï¼ |
| | | // è¿ç§æ
åµä¸å
ä¸å¯¹é½ï¼ç ResizeObserver / IntersectionObserver å触å |
| | | const rect = wrapperRef.value.getBoundingClientRect() |
| | | if (!rect.width || !rect.height) return |
| | | |
| | | const background = backgroundRef.value |
| | | |
| | | // 使ç¨ç¾åæ¯å®ä½ + transform å¾®è°ï¼è¿æ¯æå¯é çæ¹å¼ï¼ |
| | | background.style.left = left |
| | | background.style.top = top |
| | | background.style.transform = `translate(${offsetX}, ${offsetY})` |
| | | }) |
| | | } |
| | | |
| | | // åå§åé¶æ®µå¤æ¬¡âè¡¥å¿å¯¹é½âï¼è¦ç Echarts 馿¬¡æ¸²æ/å¨ç»é æçå»¶è¿å¸å± |
| | | const scheduleKickAlign = () => { |
| | | clearRetryTimers() |
| | | ;[0, 60, 180, 360, 800].forEach((ms) => { |
| | | retryTimers.push( |
| | | setTimeout(() => { |
| | | adjustBackgroundPosition() |
| | | }, ms) |
| | | ) |
| | | }) |
| | | } |
| | | |
| | | // çªå£ resize å¤ç |
| | | const resizeHandler = () => { |
| | | adjustBackgroundPosition() |
| | | } |
| | | |
| | | // 妿æä¾äº watchDataï¼ç嬿°æ®ååï¼éè¦å¨ setup é¶æ®µåå»ºï¼ |
| | | if (watchData) { |
| | | watch(watchData, () => { |
| | | adjustBackgroundPosition() |
| | | }, { deep: true }) |
| | | } |
| | | |
| | | // åå§å |
| | | const init = () => { |
| | | // çå¬çªå£ resize |
| | | window.addEventListener('resize', resizeHandler) |
| | | |
| | | // ä½¿ç¨ ResizeObserver çå¬å®¹å¨å°ºå¯¸åå |
| | | nextTick(() => { |
| | | if (wrapperRef?.value && window.ResizeObserver) { |
| | | resizeObserver = new ResizeObserver(() => { |
| | | adjustBackgroundPosition() |
| | | }) |
| | | resizeObserver.observe(wrapperRef.value) |
| | | } |
| | | |
| | | // çå¬âä»ä¸å¯è§å°å¯è§âï¼è§£å³åå§åæ¶æªå¯¹é½ä½çæ´æ°åæ£å¸¸çé®é¢ |
| | | if (wrapperRef?.value && window.IntersectionObserver) { |
| | | intersectionObserver = new IntersectionObserver( |
| | | (entries) => { |
| | | const entry = entries?.[0] |
| | | if (entry?.isIntersecting) { |
| | | scheduleKickAlign() |
| | | } |
| | | }, |
| | | { threshold: 0.01 } |
| | | ) |
| | | intersectionObserver.observe(wrapperRef.value) |
| | | } |
| | | |
| | | // åå§å夿¬¡è¡¥å¿å¯¹é½ï¼ç¡®ä¿å¾è¡¨æ¸²æå®æ |
| | | scheduleKickAlign() |
| | | }) |
| | | } |
| | | |
| | | // æ¸
ç |
| | | const cleanup = () => { |
| | | window.removeEventListener('resize', resizeHandler) |
| | | clearRetryTimers() |
| | | if (resizeObserver) { |
| | | resizeObserver.disconnect() |
| | | resizeObserver = null |
| | | } |
| | | if (intersectionObserver) { |
| | | intersectionObserver.disconnect() |
| | | intersectionObserver = null |
| | | } |
| | | } |
| | | |
| | | return { |
| | | adjustBackgroundPosition, |
| | | init, |
| | | cleanup |
| | | } |
| | | } |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="page.total > 0" :total="page.total" layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | </div> |
| | | </el-card> |
| | | <!-- ç¨å°ç³è¯·å¯¹è¯æ¡ï¼å·²ç§»é¤ï¼ --> |
| | |
| | | :delete-method="handleAttachmentDelete" |
| | | :rules-regulations-management-id="currentFileRuleId" |
| | | :name-column-label="'éä»¶åç§°'" |
| | | @upload="handleAttachmentUpload" /> |
| | | @upload="handleAttachmentUpload"/> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | const currentFileRuleId = ref(null); |
| | | const filePage = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | size: 1000, |
| | | total: 0, |
| | | }); |
| | | // è§ç« å¶åº¦ç¸å
³ |
| | |
| | | regulations.value = res.data.records; |
| | | // è¿æ»¤æå·²åºå¼çå¶åº¦ |
| | | // regulations.value = res.data.records.filter(item => item.status !== 'repealed') |
| | | page.value.total = res.data.total; |
| | | page.total = res.data.total; |
| | | tableLoading.value = false; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // å页ååå¤ç |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getRegulationList(); |
| | | }; |
| | | onMounted(() => { |
| | | // åå§å |
| | | getRegulationList(); |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" |
| | | <pagination v-show="page.total > 0" :total="page.total" layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | </div> |
| | | </el-card> |
| | |
| | | form.taxRate = data.taxRate; |
| | | form.unTaxIncludingPriceTotal = data.unTaxIncludingPriceTotal; |
| | | form.createTime = data.createTime; |
| | | // é¢è®¡è¿è¡æ¶é´ï¼å端è¿åå转为 YYYY-MM-DD ä»¥ä¾¿æ¥æéæ©å¨æ£ç¡®å±ç¤º |
| | | if (data.planRuntimeTime) { |
| | | form.planRuntimeTime = dayjs(data.planRuntimeTime).format('YYYY-MM-DD'); |
| | | } else { |
| | | form.planRuntimeTime = undefined; |
| | | } |
| | | } |
| | | }; |
| | | |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææï¼" prop="valid"> |
| | | <el-form-item label="æææ¥æ(天)ï¼" prop="valid"> |
| | | <el-input |
| | | v-model="form.valid" |
| | | placeholder="请è¾å
¥" |
| | | type="number" |
| | | placeholder="请è¾å
¥æææå¤©æ°" |
| | | clearable |
| | | :min="1" |
| | | @input="handleValidInput" |
| | | > |
| | | <template #append>æ¥</template> |
| | | </el-input> |
| | |
| | | rules: { |
| | | code: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | name: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | valid: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | valid: [ |
| | | {required: true, message: "请è¾å
¥", trigger: "blur"}, |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (value === '' || value === null || value === undefined) { |
| | | callback(); |
| | | return; |
| | | } |
| | | const numValue = Number(value); |
| | | if (isNaN(numValue)) { |
| | | callback(new Error('请è¾å
¥ææçæ°å')); |
| | | return; |
| | | } |
| | | if (numValue <= 0) { |
| | | callback(new Error('åªè½è¾å
¥æ£æ°')); |
| | | return; |
| | | } |
| | | if (!Number.isInteger(numValue)) { |
| | | callback(new Error('请è¾å
¥æ´æ°')); |
| | | return; |
| | | } |
| | | callback(); |
| | | }, |
| | | trigger: 'blur' |
| | | } |
| | | ], |
| | | recordDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | userId: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | entryDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | |
| | | } |
| | | } |
| | | |
| | | // å¤çæææ¥æè¾å
¥ï¼åªå
è®¸æ£æ´æ° |
| | | const handleValidInput = (value) => { |
| | | if (value === '' || value === null || value === undefined) { |
| | | form.value.valid = ''; |
| | | return; |
| | | } |
| | | // 转æ¢ä¸ºåç¬¦ä¸²å¹¶ç§»é¤ææéæ°åå符ï¼å
æ¬è´å·ãå°æ°ç¹çï¼ |
| | | const numStr = String(value).replace(/[^0-9]/g, ''); |
| | | if (numStr === '') { |
| | | form.value.valid = ''; |
| | | return; |
| | | } |
| | | const numValue = parseInt(numStr, 10); |
| | | // ç¡®ä¿æ¯æ£æ´æ°ï¼å¤§äº0ï¼ |
| | | if (numValue > 0 && !isNaN(numValue)) { |
| | | form.value.valid = numValue; |
| | | } else { |
| | | form.value.valid = ''; |
| | | } |
| | | } |
| | | |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | |
| | | ref="formRef" |
| | | > |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåç¼å·ï¼" prop="code"> |
| | | <el-input |
| | | v-model="form.code" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="计éå¨å
·åç§°ï¼" prop="name"> |
| | | <el-input |
| | | v-model="form.name" |
| | | placeholder="请è¾å
¥è®¡éå¨å
·åç§°" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | |
| | | <el-form-item label="æææ¥æ(天)ï¼" prop="valid"> |
| | | <el-input |
| | | v-model="form.valid" |
| | | type="number" |
| | | placeholder="请è¾å
¥æææå¤©æ°" |
| | | clearable |
| | | :min="1" |
| | | @input="handleValidInput" |
| | | > |
| | | <template #append>æ¥</template> |
| | | </el-input> |
| | |
| | | const data = reactive({ |
| | | form: { |
| | | code: "", |
| | | name: "", |
| | | instationLocation: "", |
| | | mostDate:"", |
| | | model: "", |
| | |
| | | }, |
| | | rules: { |
| | | code: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | name: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | model: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | validDate: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | nextDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | |
| | | instationLocation: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | mostDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | cycle: [{required: true, message: "è¯·éæ©", trigger: "blur"}], |
| | | valid: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | valid: [ |
| | | {required: true, message: "请è¾å
¥", trigger: "blur"}, |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (value === '' || value === null || value === undefined) { |
| | | callback(); |
| | | return; |
| | | } |
| | | const numValue = Number(value); |
| | | if (isNaN(numValue)) { |
| | | callback(new Error('请è¾å
¥ææçæ°å')); |
| | | return; |
| | | } |
| | | if (numValue <= 0) { |
| | | callback(new Error('åªè½è¾å
¥æ£æ°')); |
| | | return; |
| | | } |
| | | if (!Number.isInteger(numValue)) { |
| | | callback(new Error('请è¾å
¥æ´æ°')); |
| | | return; |
| | | } |
| | | callback(); |
| | | }, |
| | | trigger: 'blur' |
| | | } |
| | | ], |
| | | unit: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | } |
| | | }) |
| | |
| | | } |
| | | } |
| | | |
| | | // å¤çæææ¥æè¾å
¥ï¼åªå
è®¸æ£æ´æ° |
| | | const handleValidInput = (value) => { |
| | | if (value === '' || value === null || value === undefined) { |
| | | form.value.valid = ''; |
| | | return; |
| | | } |
| | | // 转æ¢ä¸ºåç¬¦ä¸²å¹¶ç§»é¤ææéæ°åå符ï¼å
æ¬è´å·ãå°æ°ç¹çï¼ |
| | | const numStr = String(value).replace(/[^0-9]/g, ''); |
| | | if (numStr === '') { |
| | | form.value.valid = ''; |
| | | return; |
| | | } |
| | | const numValue = parseInt(numStr, 10); |
| | | // ç¡®ä¿æ¯æ£æ´æ°ï¼å¤§äº0ï¼ |
| | | if (numValue > 0 && !isNaN(numValue)) { |
| | | form.value.valid = numValue; |
| | | } else { |
| | | form.value.valid = ''; |
| | | } |
| | | } |
| | | |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | |
| | | minWidth:150, |
| | | align:"center" |
| | | }, |
| | | { |
| | | label: "计éå¨å
·åç§°", |
| | | prop: "name", |
| | | width: '160px', |
| | | align: "center", |
| | | }, |
| | | { |
| | | label: "å®è£
ä½ç½®", |
| | | prop: "instationLocation", |
| | |
| | | </el-table-column> |
| | | <el-table-column prop="price" label="ä»·æ ¼" width="140"></el-table-column> |
| | | <el-table-column prop="quantity" label="æ°é" width="140"></el-table-column> |
| | | <el-table-column prop="description" label="æè¿°" width="150"></el-table-column> |
| | | <el-table-column prop="description" label="æè¿°"></el-table-column> |
| | | <el-table-column label="æä½" width="150" fixed="right" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <!-- å页ç»ä»¶ --> |
| | | <div class="pagination-container"> |
| | | <el-pagination |
| | | v-model:current-page="pagination.current" |
| | | v-model:page-size="pagination.size" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | <el-dialog title="å类管ç" v-model="dialogVisible" width="60%"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | |
| | | const queryParams = reactive({ |
| | | name: '' |
| | | }); |
| | | // å页忰 |
| | | const pagination = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0 |
| | | }); |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | id:'', |
| | |
| | | const fetchListData = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const params = {}; |
| | | const params = { |
| | | current: pagination.current, |
| | | size: pagination.size |
| | | }; |
| | | if (queryParams.name) { |
| | | params.name = queryParams.name; |
| | | } |
| | |
| | | if (res.code === 200) { |
| | | renderTableData.value = res.data.records || []; |
| | | categories.value = res.data.records || []; |
| | | pagination.total = res.data.total || 0; |
| | | } |
| | | } catch (error) { |
| | | loading.value = false; |
| | |
| | | |
| | | // æ¥è¯¢ |
| | | const handleQuery = () => { |
| | | pagination.current = 1; |
| | | fetchListData(); |
| | | } |
| | | |
| | | // éç½®æ¥è¯¢ |
| | | const resetQuery = () => { |
| | | queryParams.name = ''; |
| | | pagination.current = 1; |
| | | fetchListData(); |
| | | } |
| | | |
| | | // å页大尿¹å |
| | | const handleSizeChange = (size) => { |
| | | pagination.size = size; |
| | | pagination.current = 1; |
| | | fetchListData(); |
| | | } |
| | | |
| | | // å½å页æ¹å |
| | | const handleCurrentChange = (current) => { |
| | | pagination.current = current; |
| | | fetchListData(); |
| | | } |
| | | |
| | |
| | | margin-top: unset; |
| | | } |
| | | |
| | | .pagination-container { |
| | | margin-top: 20px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | padding: 16px 0; |
| | | } |
| | | |
| | | .el-table__header-wrapper th { |
| | | background-color: #f5f7fa; |
| | | font-weight: 600; |
| | |
| | | <el-col class="search_thing" :span="24"> |
| | | <div class="search_label"><span class="required-span">* </span>è´§æ¶å±æ°ï¼</div> |
| | | <div class="search_input"> |
| | | <el-input v-model="shelves.row" size="small"></el-input> |
| | | <el-input-number v-model="shelves.row" size="small" :min="1" :max="10" :precision="0" :step="1" controls-position="right" style="width: 100%"></el-input-number> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-col class="search_thing" :span="24"> |
| | | <div class="search_label"><span class="required-span">* </span>è´§æ¶åæ°ï¼</div> |
| | | <div class="search_input"> |
| | | <el-input v-model="shelves.col" size="small"></el-input> |
| | | <el-input-number v-model="shelves.col" size="small" :min="1" :max="10" :precision="0" :step="1" controls-position="right" style="width: 100%"></el-input-number> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | ElMessage.error('请填åè´§æ¶åæ°') |
| | | return |
| | | } |
| | | const rowNum = Number(shelves.row) |
| | | const colNum = Number(shelves.col) |
| | | if (rowNum < 1 || colNum < 1 || rowNum > 10 || colNum > 10) { |
| | | ElMessage.error('è´§æ¶å±æ°ååæ°é为1-10çæ´æ°') |
| | | return |
| | | } |
| | | if (!Number.isInteger(rowNum) || !Number.isInteger(colNum)) { |
| | | ElMessage.error('è´§æ¶å±æ°ååæ°ä¸è½ä¸ºå°æ°') |
| | | return |
| | | } |
| | | upLoadShelves.value = true |
| | | |
| | | if (currentEdit.value && currentEdit.value.id) { |
| | |
| | | updateShelf({ |
| | | id: currentEdit.value.id, |
| | | name: shelves.name, |
| | | row: Number(shelves.row), |
| | | col: Number(shelves.col), |
| | | row: rowNum, |
| | | col: colNum, |
| | | warehouseId: entity.warehouseId |
| | | }).then(res => { |
| | | upLoadShelves.value = false |
| | |
| | | |
| | | } else { |
| | | // æ°å¢ |
| | | // è¿ééè¦æ¿æ¢ä¸ºå®é
çAPIè°ç¨ |
| | | addShelf({ |
| | | addShelf({ |
| | | name: shelves.name, |
| | | row: Number(shelves.row), |
| | | col: Number(shelves.col), |
| | | row: rowNum, |
| | | col: colNum, |
| | | warehouseId: entity.warehouseId |
| | | }).then(res => { |
| | | upLoadShelves.value = false |
| | |
| | | type: "warning" |
| | | }).then(() => { |
| | | if (level == 1) { |
| | | // å é¤ä»åº |
| | | // å é¤ä»åºï¼æ¥å£è¦æ±ä¼ ID æ°ç»ï¼ |
| | | deleteWarehouse([row.id]).then(res => { |
| | | ElMessage.success('å 餿å') |
| | | selectList() |
| | | }) |
| | | } else { |
| | | // å é¤è´§æ¶ |
| | | deleteShelf({ |
| | | id: row.id |
| | | }).then(res => { |
| | | // å é¤è´§æ¶ï¼æ¥å£åæ ·è¦æ±ä¼ ID æ°ç»ï¼ |
| | | deleteShelf([row.id]).then(res => { |
| | | ElMessage.success('å 餿å') |
| | | selectList() |
| | | }) |
| | |
| | | :on-remove="handleRemove" |
| | | :file-list="fileList" |
| | | multiple |
| | | :limit="10" |
| | | :show-file-list="false" |
| | | :data="{documentId: currentDocumentId}" |
| | | accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.xml,.jpg,.jpeg,.png,.gif,.bmp,.rar,.zip,.7z" |
| | |
| | | |
| | | <!-- éä»¶å表 --> |
| | | <div class="attachment-list"> |
| | | <el-table :data="fileList" border height="400px" v-loading="loading"> |
| | | <el-table :data="fileList" border max-height="400px" v-loading="loading"> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column label="éä»¶åç§°" prop="name" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column label="æä»¶å¤§å°" prop="size" width="100" align="center"> |
| | |
| | | <template> |
| | | <div style="padding: 20px;"> |
| | | <!-- 页颿 é¢åç鿡件 --> |
| | | <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;"> |
| | | <div class="w-full md:w-auto flex items-center gap-3"> |
| | | <el-form :inline="true"> |
| | | <el-form-item label="年份"> |
| | | <el-date-picker |
| | |
| | | |
| | | <main class="container mx-auto px-4 pb-10"> |
| | | <!-- åºå®èµäº§ææ å¡ç --> |
| | | <div class="grid-container"> |
| | | <div class="kpi-grid"> |
| | | <!-- è®¾å¤æ»æ° --> |
| | | <el-card class="bg2"> |
| | | <p>è®¾å¤æ»æ°</p> |
| | | <h3> |
| | | {{ assetInfo.totalEquipment }} |
| | | </h3> |
| | | </el-card> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-left"> |
| | | <span class="kpi-dot kpi-dot-blue"></span> |
| | | <span class="kpi-title">è®¾å¤æ»æ°</span> |
| | | <div class="kpi-value">{{ assetInfo.totalEquipment }}个</div> |
| | | </div> |
| | | <div class="kpi-icon-wrap kpi-icon-blue"> |
| | | <img :src="iconBlue" alt="" class="kpi-icon" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- èµäº§åå¼ --> |
| | | <el-card class="bg3"> |
| | | <p>èµäº§åå¼</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.totalOriginalValue) }} |
| | | </h3> |
| | | </el-card> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-left"> |
| | | <span class="kpi-dot kpi-dot-orange"></span> |
| | | <span class="kpi-title">èµäº§åå¼</span> |
| | | <div class="kpi-value">Â¥{{ formatCurrency(assetInfo.totalOriginalValue) }}</div> |
| | | </div> |
| | | <div class="kpi-icon-wrap kpi-icon-orange"> |
| | | <img :src="iconWalletOrange" alt="" class="kpi-icon" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç´¯è®¡ææ§ --> |
| | | <el-card class="bg4"> |
| | | <p>ç´¯è®¡ææ§</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.totalDepreciation) }} |
| | | </h3> |
| | | </el-card> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-left"> |
| | | <span class="kpi-dot kpi-dot-green"></span> |
| | | <span class="kpi-title">ç´¯è®¡ææ§</span> |
| | | <div class="kpi-value">Â¥{{ formatCurrency(assetInfo.totalDepreciation) }}</div> |
| | | </div> |
| | | <div class="kpi-icon-wrap kpi-icon-green"> |
| | | <img :src="iconGreen" alt="" class="kpi-icon" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åºåèµäº§ --> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-left"> |
| | | <span class="kpi-dot kpi-dot-pink"></span> |
| | | <span class="kpi-title">åºåèµäº§</span> |
| | | <div class="kpi-value">Â¥{{ formatCurrency(assetInfo.inventoryValue) }}</div> |
| | | </div> |
| | | <div class="kpi-icon-wrap kpi-icon-pink"> |
| | | <img :src="iconPink" alt="" class="kpi-icon" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åå¼ --> |
| | | <el-card class="bg5"> |
| | | <p>åå¼</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.totalNetValue) }} |
| | | </h3> |
| | | </el-card> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-left"> |
| | | <span class="kpi-dot kpi-dot-yellow"></span> |
| | | <span class="kpi-title">åå¼</span> |
| | | <div class="kpi-value">Â¥{{ formatCurrency(assetInfo.totalNetValue) }}</div> |
| | | </div> |
| | | <div class="kpi-icon-wrap kpi-icon-yellow"> |
| | | <img :src="iconYellow" alt="" class="kpi-icon" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è´åº --> |
| | | <el-card class="bg2"> |
| | | <p>è´åº</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.debt) }} |
| | | </h3> |
| | | </el-card> |
| | | <!-- åºåèµäº§ --> |
| | | <el-card class="bg3"> |
| | | <p>åºåèµäº§</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.inventoryValue) }} |
| | | </h3> |
| | | </el-card> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-left"> |
| | | <span class="kpi-dot kpi-dot-red"></span> |
| | | <span class="kpi-title">è´åº</span> |
| | | <div class="kpi-value">Â¥{{ formatCurrency(assetInfo.debt) }}</div> |
| | | </div> |
| | | <div class="kpi-icon-wrap kpi-icon-red"> |
| | | <img :src="iconWalletRed" alt="" class="kpi-icon" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åºå®èµäº§ç»è®¡å¾è¡¨ --> |
| | | <div class="grid-layout"> |
| | | <!-- æè®¾å¤ç±»åç»è®¡ --> |
| | | <el-card style="margin-bottom: 20px;"> |
| | | <div class="chart-row"> |
| | | <!-- 设å¤ç±»ååå¸ --> |
| | | <el-card class="chart-card"> |
| | | <h2 class="section-title">设å¤ç±»ååå¸</h2> |
| | | <div class="echarts"> |
| | | <Echarts |
| | | <div class="chart-content"> |
| | | <div class="pie-wrap"> |
| | | <Echarts |
| | | :legend="typeDistributionLegend" |
| | | :chartStyle="chartStylePie" |
| | | :series="typeDistributionSeries" |
| | | :tooltip="pieTooltip" |
| | | style="height: 260px; width: 35%;"> |
| | | <div class="chart-num"> |
| | | <span style="font-size: 22px;">设å¤ç±»å</span> |
| | | <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ deviceTypeTotalCount }}</span> |
| | | style="height: 260px; width: 100%;" |
| | | /> |
| | | </div> |
| | | <div class="type-cards"> |
| | | <div class="type-card" v-for="(item, index) in typeDistributionData" :key="index"> |
| | | <span class="type-name">{{ item.name }}</span> |
| | | <span class="type-count">{{ item.count }}</span> |
| | | </div> |
| | | </Echarts> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | <!-- 设å¤éé¢åæ --> |
| | | <el-card class="chart-card"> |
| | | <h2 class="section-title">设å¤éé¢åæ</h2> |
| | | <div class="bar-chart-wrap"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="lineLegend" |
| | | :series="typeDistributionLineSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | style="height: 260px; width: 64%;"></Echarts> |
| | | ref="barChart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="lineLegend" |
| | | :series="typeDistributionBarSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxisBar" |
| | | style="height: 260px; width: 100%;" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | <!-- 设å¤å°è´¦è¡¨æ ¼ --> |
| | | <el-card style="margin-bottom: 20px;"> |
| | | <!-- è®¾å¤æ°æ®è¡¨ --> |
| | | <el-card class="table-card"> |
| | | <h2 class="section-title">è®¾å¤æ°æ®è¡¨</h2> |
| | | <el-table |
| | | :data="equipmentList" |
| | | stripe |
| | | style="width: 100%" |
| | | :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" :index="(index) => (pagination.currentPage - 1) * pagination.pageSize + index + 1" /> |
| | | <el-table-column prop="deviceName" label="设å¤åç§°" width="250" /> |
| | | <el-table-column prop="deviceModel" label="åå·è§æ ¼" min-width="150" /> |
| | | <el-table-column prop="deviceModel" label="è§æ ¼åå·" min-width="150" /> |
| | | <el-table-column prop="supplierName" label="ä¾åºå" min-width="120" /> |
| | | <el-table-column prop="unit" label="åä½" width="120" /> |
| | | <el-table-column prop="number" label="æ°é" width="120" /> |
| | |
| | | import { ref, computed, onMounted, reactive } from 'vue'; |
| | | import 'element-plus/dist/index.css'; |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import { getLedgerPage } from "@/api/equipmentManagement/ledger"; |
| | | import { getAccountingTotal, getDeviceTypeDistribution, getCalculateDepreciation } from "@/api/financialManagement/accounting"; |
| | | import dayjs from "dayjs"; |
| | | import iconBlue from '@/assets/icons/png/blue@2x.png'; |
| | | import iconWalletOrange from '@/assets/icons/png/walletOrange@2x.png'; |
| | | import iconGreen from '@/assets/icons/png/green@2x.png'; |
| | | import iconPink from '@/assets/icons/png/pink@2x.png'; |
| | | import iconYellow from '@/assets/icons/png/yellow@2x.png'; |
| | | import iconWalletRed from '@/assets/icons/png/walletRed@2x.png'; |
| | | |
| | | // ç鿡件 |
| | | const dateRange = ref(null); |
| | |
| | | height: '100%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | }; |
| | | |
| | | const pieColors = ['#F04864', '#FACC14', '#8543E0', '#1890FF', '#13C2C2', '#2FC25B']; // 坿 ¹æ®å®é
è°æ´ |
| | | const pieColors = ['#165DFF', '#14C9C9', '#8543E0', '#1890FF', '#13C2C2', '#2FC25B']; // 坿 ¹æ®å®é
è°æ´ |
| | | |
| | | // 饼徿°æ® |
| | | const typeDistributionData = ref([]); |
| | | const departmentDistributionData = ref([]); |
| | | |
| | | // 饼å¾å¾ä¾ |
| | | // 饼å¾å¾ä¾ï¼æ¬åæ¾ç¤ºåç§°+å æ¯ï¼å¾ä¾æ¾ä¸æ¹å¡çå±ç¤ºï¼ |
| | | const typeDistributionLegend = computed(() => ({ |
| | | show: true, |
| | | top: 'center', |
| | | left: '60%', |
| | | orient: 'vertical', |
| | | icon: 'circle', |
| | | data: typeDistributionData.value.map(item => item.name), |
| | | formatter: function(name) { |
| | | const item = typeDistributionData.value.find(i => i.name === name); |
| | | if (!item) return name; |
| | | return `${name} | ${item.count} å° | ${item.amount}`; |
| | | }, |
| | | textStyle: { |
| | | color: '#333', |
| | | fontSize: 14, |
| | | lineHeight: 26, |
| | | } |
| | | show: false, |
| | | data: typeDistributionData.value.map(item => item.name) |
| | | })); |
| | | |
| | | |
| | |
| | | const typeDistributionSeries = computed(() => [ |
| | | { |
| | | type: 'pie', |
| | | radius: ['50%', '65%'], |
| | | center: ['25%', '50%'], |
| | | radius: ['0%', '65%'], |
| | | center: ['50%', '45%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | label: { show: false }, |
| | | data: typeDistributionData.value, |
| | | color: pieColors |
| | | } |
| | |
| | | |
| | | // æçº¿å¾æ°æ® |
| | | const typeDistributionLineSeries = ref([]); |
| | | // æ±ç¶å¾æ°æ®ï¼è®¾å¤éé¢åæï¼ |
| | | const typeDistributionBarSeries = computed(() => [ |
| | | { |
| | | name: 'éå®é¢', |
| | | type: 'bar', |
| | | data: typeDistributionData.value.map(item => (item.amountNum != null ? item.amountNum : 0)), |
| | | itemStyle: { color: '#13C2C2' } |
| | | } |
| | | ]); |
| | | // æ±ç¶å¾ Y è½´ |
| | | const yAxisBar = [ |
| | | { |
| | | type: 'value', |
| | | name: 'éå®é¢(ä¸å
)', |
| | | position: 'left', |
| | | min: 0, |
| | | nameTextStyle: { color: '#000', fontSize: 14 }, |
| | | splitLine: { lineStyle: { color: '#f0f0f0' } } |
| | | } |
| | | ]; |
| | | |
| | | |
| | | // é¥¼å¾æç¤ºæ¡ |
| | | // é¥¼å¾æç¤ºæ¡ï¼å¾å
æ ·å¼ï¼åç§° + å æ¯ï¼ |
| | | const pieTooltip = reactive({ |
| | | trigger: 'item', |
| | | formatter: function(params) { |
| | | // æ£æ¥æ°æ®æ¯å¦åå¨ |
| | | if (!params.data) return params.name; |
| | | // æ¼æ¥å®æ´å
容 |
| | | return ` |
| | | <div> |
| | | <div style="color:${params.color};font-size:16px;">â</div> |
| | | <div>${params.name}</div> |
| | | <div>æ°éï¼${params.data.count} å°</div> |
| | | <div>éé¢ï¼${params.data.amount}</div> |
| | | </div> |
| | | `; |
| | | const pct = params.percent != null ? params.percent.toFixed(0) : 0; |
| | | return `${params.name} ${pct}%`; |
| | | } |
| | | }); |
| | | |
| | |
| | | name: item.type || '', |
| | | value: Number(item.count || 0), |
| | | count: Number(item.count || 0), |
| | | amount: `Â¥${formatCurrency(item.amount || 0)}` |
| | | amount: `Â¥${formatCurrency(item.amount || 0)}`, |
| | | amountNum: Number(item.amount || 0) |
| | | })); |
| | | } else if (data.categories && data.categories.length > 0) { |
| | | // å¦ææ²¡æ detailsï¼ä½¿ç¨ categoriesãcountData å amountData æå»º |
| | |
| | | name: category, |
| | | value: Number(data.countData[index] || 0), |
| | | count: Number(data.countData[index] || 0), |
| | | amount: `Â¥${formatCurrency(data.amountData[index] || 0)}` |
| | | amount: `Â¥${formatCurrency(data.amountData[index] || 0)}`, |
| | | amountNum: Number(data.amountData[index] || 0) |
| | | })); |
| | | } else { |
| | | typeDistributionData.value = []; |
| | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | /* åºç¡æ ·å¼è¡¥å
*/ |
| | | :root { |
| | | --el-color-primary: #4f46e5; |
| | | --el-color-primary: #1890ff; |
| | | } |
| | | |
| | | .el-card { |
| | | position: relative; |
| | | border-radius: 12px; |
| | | padding: 14px 10px 10px 10px; |
| | | box-shadow: 0 2px 8px #eee; |
| | | /* 页é¢èæ¯ */ |
| | | main { |
| | | background: #f5f5f5; |
| | | padding: 0; |
| | | margin: 0 -20px; |
| | | padding: 0 20px 20px; |
| | | } |
| | | |
| | | /* KPI å¡çç½æ ¼ */ |
| | | .kpi-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(3, 1fr); |
| | | gap: 16px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | @media (max-width: 1024px) { |
| | | .kpi-grid { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 640px) { |
| | | .kpi-grid { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | } |
| | | |
| | | /* KPI å¡ç - ç½åºãåè§ãé´å½± */ |
| | | .kpi-card { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | padding: 16px 20px; |
| | | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); |
| | | min-height: 100px; |
| | | } |
| | | |
| | | .kpi-left { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .kpi-dot { |
| | | display: inline-block; |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | margin-right: 6px; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .kpi-dot-blue { background: #1890ff; } |
| | | .kpi-dot-orange { background: #fa8c16; } |
| | | .kpi-dot-green { background: #52c41a; } |
| | | .kpi-dot-pink { background: #eb2f96; } |
| | | .kpi-dot-yellow { background: #facc14; } |
| | | .kpi-dot-red { background: #f5222d; } |
| | | |
| | | .kpi-title { |
| | | font-size: 14px; |
| | | color: #333; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .kpi-value { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-top: 8px; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | /* å³ä¾§å¾æ æ¹å */ |
| | | .kpi-icon-wrap { |
| | | width: 48px; |
| | | height: 48px; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .kpi-icon-blue { background: #e6f7ff; } |
| | | .kpi-icon-orange { background: #fff7e6; } |
| | | .kpi-icon-green { background: #f6ffed; } |
| | | .kpi-icon-pink { background: #fff0f6; } |
| | | .kpi-icon-yellow { background: #fffbe6; } |
| | | .kpi-icon-red { background: #fff1f0; } |
| | | |
| | | .kpi-icon { |
| | | width: 28px; |
| | | height: 28px; |
| | | object-fit: contain; |
| | | } |
| | | |
| | | /* å¾è¡¨åºå两å */ |
| | | .chart-row { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1fr; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | @media (max-width: 1024px) { |
| | | .chart-row { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | } |
| | | |
| | | .chart-card { |
| | | border-radius: 8px; |
| | | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 10px 20px !important; |
| | | } |
| | | |
| | | &.bg1 { |
| | | background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important; |
| | | } |
| | | |
| | | &.bg2 { |
| | | background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important; |
| | | } |
| | | |
| | | &.bg3 { |
| | | background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important; |
| | | } |
| | | |
| | | &.bg4 { |
| | | background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important; |
| | | } |
| | | |
| | | &.bg5 { |
| | | background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important; |
| | | padding: 16px 20px; |
| | | } |
| | | } |
| | | |
| | | .grid-container { |
| | | /* grid 容å¨åºç¡æ ·å¼ */ |
| | | display: grid; |
| | | gap: 1rem; /* gap-4 å¯¹åº 1rem (16px) */ |
| | | margin-bottom: 2rem; /* mb-8 å¯¹åº 2rem (32px) */ |
| | | |
| | | p { |
| | | font-size: 22px; |
| | | margin-top: 0px; |
| | | color: #fff; |
| | | } |
| | | |
| | | h3 { |
| | | font-size: 36px; |
| | | font-weight: 500; |
| | | font-family: 'MyCustomFont', sans-serif; |
| | | margin: 10px 0; |
| | | color: #fff; |
| | | } |
| | | } |
| | | |
| | | /* ç§»å¨ç«¯é»è®¤æ ·å¼ (grid-cols-1) */ |
| | | .grid-container { |
| | | grid-template-columns: repeat(1, minmax(0, 1fr)); |
| | | } |
| | | |
| | | /* å°å±å¹åä»¥ä¸ (sm:grid-cols-2) */ |
| | | @media (min-width: 640px) { |
| | | .grid-container { |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | } |
| | | } |
| | | |
| | | /* 大å±å¹åä»¥ä¸ (lg:grid-cols-6) */ |
| | | @media (min-width: 1024px) { |
| | | .grid-container { |
| | | grid-template-columns: repeat(6, minmax(0, 1fr)); |
| | | } |
| | | } |
| | | |
| | | /* å¡çæ¬åææå¢å¼º */ |
| | | .el-card:hover { |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .echarts { |
| | | .chart-content { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | |
| | | /* å¾è¡¨å®¹å¨æ ·å¼ */ |
| | | .el-chart { |
| | | .pie-wrap { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 100%; |
| | | height: 260px; |
| | | } |
| | | |
| | | .type-cards { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 12px; |
| | | justify-content: center; |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .type-card { |
| | | background: #fafafa; |
| | | border-radius: 6px; |
| | | padding: 8px 16px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | min-width: 80px; |
| | | } |
| | | |
| | | .type-name { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | .type-count { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .bar-chart-wrap { |
| | | width: 100%; |
| | | height: 260px; |
| | | } |
| | | |
| | | /* åºåæ é¢ - 左侧èè²ç«çº¿ */ |
| | | .section-title { |
| | | position: relative; |
| | | font-size: 18px; |
| | | color: #333; |
| | | padding-left: 10px; |
| | | margin-bottom: 10px; |
| | | padding-left: 12px; |
| | | margin-bottom: 16px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .section-title::before { |
| | | position: absolute; |
| | | left: 0; |
| | | top: 0px; |
| | | top: 2px; |
| | | content: ''; |
| | | width: 4px; |
| | | height: 18px; |
| | | background-color: #002FA7; |
| | | background: #1890ff; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .chart-num { |
| | | position: absolute; |
| | | z-index: 3; |
| | | top: 92px; |
| | | left: 92px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | /* è¡¨æ ¼å¡ç */ |
| | | .table-card { |
| | | border-radius: 8px; |
| | | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 16px 20px; |
| | | } |
| | | } |
| | | |
| | | .pagination-container { |
| | | margin-top: 20px; |
| | | display: flex; |
| | | justify-content: center; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | } |
| | | |
| | | :deep(.el-pagination) { |
| | | --el-pagination-button-bg-color: #fff; |
| | | } |
| | | |
| | | :deep(.el-pager li.is-active) { |
| | | background: #1890ff; |
| | | } |
| | | </style> |
| | |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isSelection="true" |
| | | :page="page" |
| | | @selection-change="handleSelectionChange" |
| | | height="500" |
| | | @pagination="paginationSearch" |
| | |
| | | const getList = () => { |
| | | fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => { |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | page.total = res.data.total; |
| | | }) |
| | | } |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | |
| | | <el-table-column label="åä½" prop="unit" width="100" /> |
| | | <el-table-column label="æä½" align="center" fixed="right" width="150"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link size="small" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | <el-button type="primary" link size="small" @click="handleEdit(scope.row)" :disabled="scope.row.isComplete">ç¼è¾</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(scope.row)" :disabled="scope.row.isComplete">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | |
| | | <!-- æä½æé® --> |
| | | <div class="card-footer"> |
| | | <el-button type="primary" link size="small" @click="handleEdit(item)">ç¼è¾</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(item)">å é¤</el-button> |
| | | <el-button type="primary" link size="small" @click="handleEdit(item)" :disabled="item.isComplete">ç¼è¾</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(item)" :disabled="item.isComplete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <el-input v-model="row.unit" |
| | | placeholder="请è¾å
¥åä½" |
| | | clearable |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | const productData = row[0]; |
| | | |
| | | // æå¤å±ç»ä»¶ä¸ï¼ä¸å½å产åç¸åç产ååªè½æä¸ä¸ª |
| | | const isTopLevel = dataValue.dataList.some(item => (item as any).tempId === dataValue.currentRowName); |
| | | const isTopLevel = dataValue.dataList.some( |
| | | item => (item as any).tempId === dataValue.currentRowName |
| | | ); |
| | | if (isTopLevel) { |
| | | if (productData.productName === tableData[0].productName && |
| | | productData.model === tableData[0].model) { |
| | | if ( |
| | | productData.productName === tableData[0].productName && |
| | | productData.model === tableData[0].model |
| | | ) { |
| | | // æ¥æ¾æ¯å¦å·²ç»æå
¶ä»é¡¶å±è¡å·²ç»æ¯è¿ä¸ªäº§å |
| | | const hasOther = dataValue.dataList.some(item => |
| | | (item as any).tempId !== dataValue.currentRowName && |
| | | (item as any).productName === tableData[0].productName && |
| | | (item as any).model === tableData[0].model |
| | | const hasOther = dataValue.dataList.some( |
| | | item => |
| | | (item as any).tempId !== dataValue.currentRowName && |
| | | (item as any).productName === tableData[0].productName && |
| | | (item as any).model === tableData[0].model |
| | | ); |
| | | if (hasOther) { |
| | | ElMessage.warning("æå¤å±åå½å产å䏿 ·çä¸çº§åªè½æä¸ä¸ª"); |
| | |
| | | } |
| | | }; |
| | | |
| | | const removeItem = (tempId:string) => { |
| | | const removeItem = (tempId: string) => { |
| | | // å
å°è¯ä»é¡¶å±å é¤ |
| | | const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId); |
| | | if (topIndex !== -1) { |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="content-layout"> |
| | | <el-row :gutter="16" class="content-row"> |
| | | <!-- 左侧å°è´¦ + é¡¶é¨çé --> |
| | | <div class="left-panel"> |
| | | <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8" class="left-col"> |
| | | <div class="left-panel"> |
| | | <div class="left-header"> |
| | | <!-- <div class="left-title">ç产å°è´¦</div> --> |
| | | <el-radio-group v-model="dateType" size="small" @change="handleDateTypeChange"> |
| | | <el-radio-button label="day">æ¥</el-radio-button> |
| | | <el-radio-button label="month">æ</el-radio-button> |
| | | </el-radio-group> |
| | | |
| | | <el-form :model="searchForm" inline> |
| | | <el-form-item prop="dateType"> |
| | | <el-radio-group v-model="searchForm.dateType" size="small" @change="handleDateTypeChange"> |
| | | <el-radio-button label="day">æ¥</el-radio-button> |
| | | <el-radio-button label="month">æ</el-radio-button> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æ¥æï¼" prop="dateRange"> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | :type="searchForm.dateType === 'day' ? 'date' : 'daterange'" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 200px" |
| | | @change="handleDateRangeChange" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | |
| | | :tableData="leftTableData" |
| | | :tableLoading="tableLoading" |
| | | :page="page" |
| | | :height="200" |
| | | @row-click="handleLeftRowClick" |
| | | @pagination="pagination" |
| | | ></PIMTable> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | |
| | | <!-- å³ä¾§æç»ï¼åæå
å®¹ï¼ --> |
| | | <div class="right-panel"> |
| | | <div class="header-filters"> |
| | | <el-button @click="handleOut" class="ml10">导åº</el-button> |
| | | </div> |
| | | <!-- å³ä¾§æç» --> |
| | | <el-col :xs="24" :sm="24" :md="24" :lg="16" :xl="16" class="right-col"> |
| | | <div class="right-panel"> |
| | | |
| | | <el-form inline> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleOut">导åº</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page1" |
| | | :tableLoading="tableLoading" |
| | | :tableLoading="tableLoading1" |
| | | style="margin-right: 20px;" |
| | | @pagination="pagination1" |
| | | ></PIMTable> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "çäº§æ¥æ", |
| | | prop: "scheduleDate", |
| | | prop: "schedulingDate", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | |
| | | label: "åæ ¼ç", |
| | | prop: "outputRate", |
| | | minWidth: 100, |
| | | |
| | | formatData: (val) => { |
| | | if (val == null || val === '') return '-' |
| | | return parseFloat(val).toFixed(2) |
| | | }, |
| | | }, |
| | | ]); |
| | | |
| | |
| | | const tableLoading1 = ref(false); |
| | | const leftTableData = ref([]); |
| | | // æ¥ / æ 忢ï¼é»è®¤ææ¥ï¼ |
| | | const dateType = ref("day"); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | |
| | | searchForm: { |
| | | schedulingUserName: "", |
| | | salesContractNo: "", |
| | | entryDate: [ |
| | | dayjs().format("YYYY-MM-DD"), |
| | | dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | ], // å½å
¥æ¥æ |
| | | entryDateStart: dayjs().format("YYYY-MM-DD"), |
| | | entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | dateType: "day", |
| | | dateRange: dayjs().format("YYYY-MM-DD"), |
| | | entryDate: dayjs().format("YYYY-MM-DD"), |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | |
| | | getList1(); |
| | | }; |
| | | |
| | | const changeDaterange = (value) => { |
| | | const handleDateRangeChange = (value) => { |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | if (searchForm.value.dateType === "day") { |
| | | searchForm.value.entryDate = value; |
| | | } else { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | |
| | | } else { |
| | | searchForm.value.entryDate = undefined; |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | } |
| | | handleQuery(); |
| | | reloadData() |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.dateType = dateType.value; |
| | | params.entryDate = undefined |
| | | |
| | | salesLedgerProductionAccountingList(params).then((res) => { |
| | | tableLoading.value = false; |
| | | const records = res.data.records || []; |
| | | leftTableData.value = records; |
| | | page.total = res.data.total || 0; |
| | | }); |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | }) |
| | | |
| | | |
| | | |
| | | }; |
| | | |
| | |
| | | tableLoading1.value = true; |
| | | const params = { ...page1, ...searchForm.value }; |
| | | salesLedgerProductionAccountingListProductionDetails(params).then((res) => { |
| | | tableLoading1.value = false; |
| | | tableData.value = res.data.records || [];; |
| | | page1.total = res.data.total || 0; |
| | | }); |
| | | }).finally(() => { |
| | | tableLoading1.value = false; |
| | | }) |
| | | }; |
| | | |
| | | // æå»ºå·¦ä¾§æ±æ»å°è´¦ï¼æçäº§äººæ±æ»äº§éãå·¥èµçï¼ |
| | |
| | | }; |
| | | |
| | | // 左侧æ¥/æåæ¢ |
| | | const handleDateTypeChange = () => { |
| | | const handleDateTypeChange = (value) => { |
| | | // è¿éåªä½ä¸ºç鿡件çä¸é¨åï¼ç´æ¥éæ°æ¥è¯¢å表 |
| | | page.current = 1; |
| | | getList(); |
| | | handleQuery() |
| | | if (value === "day") { |
| | | searchForm.value.entryDate = dayjs().format("YYYY-MM-DD"); |
| | | searchForm.value.dateRange = searchForm.value.entryDate |
| | | } else { |
| | | searchForm.value.entryDateStart = dayjs().startOf("month").format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs().endOf("month").format("YYYY-MM-DD"); |
| | | searchForm.value.dateRange = [searchForm.value.entryDateStart, searchForm.value.entryDateEnd] |
| | | } |
| | | |
| | | reloadData() |
| | | }; |
| | | |
| | | const reloadData = () => { |
| | | page.current = 1; |
| | | page1.current = 1; |
| | | getList(); |
| | | tableData.value = [] |
| | | } |
| | | |
| | | // ç¹å»å·¦ä¾§è¡ï¼å·å³ä¾§æç»ï¼æçäº§äººè¿æ»¤ï¼ |
| | | const handleLeftRowClick = (row) => { |
| | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .content-layout { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | .content-row { |
| | | width: 100%; |
| | | } |
| | | |
| | | .left-panel { |
| | | flex: 0 0 50%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | .content-row .left-col, |
| | | .content-row .right-col { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | flex: 0 0 50%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .left-header { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .left-title { |
| | |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "å·¥åç±»å", |
| | | prop: "workOrderType", |
| | | width: "80", |
| | | }, |
| | | { |
| | | label: "å·¥åç¼å·", |
| | | prop: "workOrderNo", |
| | | width: "140", |
| | |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import InspectionFormDia from "@/views/qualityManagement/finalInspection/components/inspectionFormDia.vue"; |
| | | import FormDia from "@/views/qualityManagement/finalInspection/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | |
| | | import FilesDia from "@/views/qualityManagement/finalInspection/components/filesDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½ç¼è¾ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | { |
| | |
| | | submit(row.id); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½æäº¤ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | { |
| | |
| | | const filesDia = ref() |
| | | const inspectionFormDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | const userStore = useUserStore() |
| | | const userList = ref([]); |
| | | const form = ref({ |
| | | checkName: "" |
| | |
| | | <template> |
| | | <div class="app-container metric-binding"> |
| | | <!-- å·¦ä¾§ï¼æ£æµæ åå表ï¼åªè¯»ï¼ --> |
| | | <div class="left-panel"> |
| | | <el-row :gutter="16" class="metric-binding-row"> |
| | | <!-- å·¦ä¾§ï¼æ£æµæ åå表 --> |
| | | <el-col :xs="24" :sm="24" :md="12" :lg="14" :xl="14" class="left-col"> |
| | | <div class="panel left-panel"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="standardColumns" |
| | |
| | | </el-select> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | |
| | | <!-- å³ä¾§ï¼ç»å®å表 --> |
| | | <div class="right-panel"> |
| | | <!-- å³ä¾§ï¼ç»å®å表 --> |
| | | <el-col :xs="24" :sm="24" :md="12" :lg="10" :xl="10" class="right-col"> |
| | | <div class="panel right-panel"> |
| | | <div class="right-header"> |
| | | <div class="title">ç»å®å
³ç³»</div> |
| | | <div class="desc" v-if="currentStandard"> |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æ·»å ç»å®å¼¹æ¡ --> |
| | | <el-dialog |
| | |
| | | } |
| | | |
| | | const handleUnbind = async (row) => { |
| | | if (!row?.id) return |
| | | const id = row?.id ?? row?.qualityTestStandardBindingId |
| | | if (id == null || id === '') return |
| | | try { |
| | | await ElMessageBox.confirm('确认å é¤è¯¥ç»å®ï¼', 'æç¤º', { type: 'warning' }) |
| | | } catch { |
| | | return |
| | | } |
| | | await qualityTestStandardBindingDel([row.qualityTestStandardBindingId]) |
| | | proxy.$message.success('å 餿å') |
| | | loadBindingList() |
| | | try { |
| | | await qualityTestStandardBindingDel([id]) |
| | | proxy.$message.success('å 餿å') |
| | | loadBindingList() |
| | | } catch (err) { |
| | | console.error('å é¤ç»å®å¤±è´¥:', err) |
| | | proxy.$message?.error(err?.message || 'å é¤å¤±è´¥') |
| | | } |
| | | } |
| | | |
| | | const handleBatchUnbind = async () => { |
| | |
| | | proxy.$message.warning('è¯·éæ©æ°æ®') |
| | | return |
| | | } |
| | | const ids = bindingSelectedRows.value.map((i) => i.qualityTestStandardBindingId) |
| | | const ids = bindingSelectedRows.value |
| | | .map((i) => i?.id ?? i?.qualityTestStandardBindingId) |
| | | .filter((id) => id != null && id !== '') |
| | | if (!ids.length) { |
| | | proxy.$message.warning('é䏿°æ®ç¼ºå°ææ id') |
| | | return |
| | | } |
| | | try { |
| | | await ElMessageBox.confirm('éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼', 'å é¤æç¤º', { type: 'warning' }) |
| | | } catch { |
| | | return |
| | | } |
| | | await qualityTestStandardBindingDel(ids) |
| | | proxy.$message.success('å 餿å') |
| | | loadBindingList() |
| | | try { |
| | | await qualityTestStandardBindingDel(ids) |
| | | proxy.$message.success('å 餿å') |
| | | loadBindingList() |
| | | } catch (err) { |
| | | console.error('æ¹éå é¤ç»å®å¤±è´¥:', err) |
| | | proxy.$message?.error(err?.message || 'å é¤å¤±è´¥') |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | |
| | | |
| | | <style scoped> |
| | | .metric-binding { |
| | | display: flex; |
| | | gap: 16px; |
| | | padding: 0; |
| | | } |
| | | |
| | | .metric-binding-row { |
| | | width: 100%; |
| | | } |
| | | |
| | | .metric-binding-row .left-col, |
| | | .metric-binding-row .right-col { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .metric-binding-row .panel { |
| | | background: #ffffff; |
| | | padding: 16px; |
| | | box-sizing: border-box; |
| | | height: 100%; |
| | | min-height: 400px; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | flex: 1; |
| | | background: #ffffff; |
| | | padding: 16px; |
| | | box-sizing: border-box; |
| | | height: 100%; |
| | | } |
| | | |
| | | .toolbar { |
| | |
| | | <template> |
| | | <div class="app-container metric-maintenance"> |
| | | <!-- å·¦ä¾§ï¼æ£æµæ åå表 --> |
| | | <div class="left-panel"> |
| | | <el-row :gutter="16" class="metric-maintenance-row"> |
| | | <!-- å·¦ä¾§ï¼æ£æµæ åå表 --> |
| | | <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" class="left-col"> |
| | | <div class="left-panel"> |
| | | <div class="toolbar"> |
| | | <div class="toolbar-left"></div> |
| | | <div class="toolbar-right"> |
| | |
| | | </el-select> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | |
| | | <!-- å³ä¾§ï¼æ ååæ°å表 --> |
| | | <div class="right-panel"> |
| | | <!-- å³ä¾§ï¼æ ååæ°å表 --> |
| | | <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" class="right-col"> |
| | | <div class="right-panel"> |
| | | <div class="right-header"> |
| | | <div class="title">æ ååæ°</div> |
| | | <div class="desc" v-if="currentStandard"> |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æ°å¢ / ç¼è¾æ£æµæ å --> |
| | | <StandardFormDialog |
| | |
| | | |
| | | <style scoped> |
| | | .metric-maintenance { |
| | | display: flex; |
| | | gap: 16px; |
| | | min-width: 0; /* å
许 flex åå
ç´ æ¶ç¼© */ |
| | | padding: 0; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .metric-maintenance-row { |
| | | width: 100%; |
| | | } |
| | | |
| | | .metric-maintenance-row .left-col, |
| | | .metric-maintenance-row .right-col { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | flex: 1; |
| | | min-width: 0; /* å
许 flex åå
ç´ æ¶ç¼© */ |
| | | min-width: 0; |
| | | background: #ffffff; |
| | | padding: 16px; |
| | | box-sizing: border-box; |
| | | overflow: hidden; /* 鲿¢å
å®¹æº¢åº */ |
| | | } |
| | | |
| | | /* ä½å辨çéé
*/ |
| | | @media (max-width: 1400px) { |
| | | .metric-maintenance { |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | width: 100%; |
| | | min-width: 0; |
| | | } |
| | | overflow: hidden; |
| | | height: 100%; |
| | | min-height: 400px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .metric-maintenance { |
| | | gap: 12px; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | padding: 12px; |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 120, |
| | | width: 100, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => row.inspectState === 1, |
| | | }, |
| | | { |
| | | name: "å¤ç", |
| | | type: "text", |
| | |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import InspectionFormDia from "@/views/qualityManagement/processInspection/components/inspectionFormDia.vue"; |
| | | import FormDia from "@/views/qualityManagement/processInspection/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | |
| | | import FilesDia from "@/views/qualityManagement/processInspection/components/filesDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½ç¼è¾ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | { |
| | |
| | | submit(row.id); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½æäº¤ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | { |
| | |
| | | const filesDia = ref() |
| | | const inspectionFormDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | const userStore = useUserStore() |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | |
| | | |
| | | <script setup> |
| | | import {Search} from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import InspectionFormDia from "@/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue"; |
| | | import FormDia from "@/views/qualityManagement/rawMaterialInspection/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | |
| | | import FilesDia from "@/views/qualityManagement/rawMaterialInspection/components/filesDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½ç¼è¾ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | { |
| | |
| | | submit(row.id); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | // å·²æäº¤åç¦ç¨ |
| | | if (row.inspectState == 1) return true; |
| | | // 妿æ£éªåæå¼ï¼åªæå½åç»å½ç¨æ·è½æäº¤ |
| | | if (row.checkName) { |
| | | return row.checkName !== userStore.nickName; |
| | | } |
| | | return false; |
| | | } |
| | | }, |
| | | { |
| | |
| | | const filesDia = ref() |
| | | const inspectionFormDia = ref() |
| | | const {proxy} = getCurrentInstance() |
| | | const userStore = useUserStore() |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | |
| | | <PanelHeader title="éè´ååå¸" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background"></div> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import { rawMaterialPurchaseAmountRatio } from '@/api/viewIndex.js' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | |
| | | /** |
| | | * @introduction ææ°ç»ä¸keyå¼ç¸åçé£ä¸é¡¹æååºæ¥ï¼ç»æä¸ä¸ªå¯¹è±¡ |
| | |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ |
| | | // å¾è¡¨ä¸å¿æ¯ ['25%', '50%']ï¼èæ¯éè¦å¯¹é½å°è¿ä¸ªä½ç½® |
| | | const { init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | left: '25%', // å¾è¡¨ä¸å¿ X æ¯ 25% |
| | | top: '50%', // å¾è¡¨ä¸å¿ Y æ¯ 50% |
| | | offsetX: '-51.5%', // X è½´åç§» |
| | | offsetY: '-50%', // Y è½´åç§» |
| | | watchData: dataList // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | |
| | | const fetchData = () => { |
| | | rawMaterialPurchaseAmountRatio() |
| | | .then((res) => { |
| | |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | width: 310px; |
| | | height: 310px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | /* ä½ç½®ç± JS å¨æè®¾ç½®ï¼é»è®¤å±
ä¸ */ |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | } |
| | | </style> |
| | |
| | | <PanelHeader title="éå®ååå¸" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background"></div> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { productSalesAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | |
| | | /** |
| | | * @introduction ææ°ç»ä¸keyå¼ç¸åçé£ä¸é¡¹æååºæ¥ï¼ç»æä¸ä¸ªå¯¹è±¡ |
| | |
| | | |
| | | const cardItems = ref([]) |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ï¼ä¸å
¶ä»æä»¶ä¿æä¸è´ï¼ |
| | | const { init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | left: '25%', // å¾è¡¨ä¸å¿ X æ¯ 25% |
| | | top: '50%', // å¾è¡¨ä¸å¿ Y æ¯ 50% |
| | | offsetX: '-51.5%', // X è½´åç§» |
| | | offsetY: '-50%', // Y è½´åç§» |
| | | watchData: pieDatas // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | |
| | | const fetchData = () => { |
| | | productSalesAnalysis() |
| | | .then((res) => { |
| | |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | <div> |
| | | <PanelHeader title="产å大类" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background"></div> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '../PanelHeader.vue' |
| | | import { productCategoryDistribution } from '@/api/viewIndex.js' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | const chart = ref(null) |
| | | |
| | | // æ°æ®åè¡¨ï¼æ¥èªæ¥å£ï¼ |
| | | const dataList = ref([]) |
| | |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ï¼å¯èªå®ä¹åç§»å¼ |
| | | const { adjustBackgroundPosition, init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | offsetX: '-51.5%', // X è½´åç§»ï¼å¯å¨æè°æ´ |
| | | offsetY: '-39%', // Y è½´åç§»ï¼å¯å¨æè°æ´ |
| | | watchData: dataList // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | |
| | | const loadData = async () => { |
| | | try { |
| | | const res = await productCategoryDistribution() |
| | |
| | | })) |
| | | landLegend.data = dataList.value.map((d) => d.name) |
| | | landSeries.value[0].data = dataList.value |
| | | // æ°æ®å è½½å®æåè°æ´èæ¯ä½ç½® |
| | | adjustBackgroundPosition() |
| | | } catch (e) { |
| | | console.error('è·å产å大类åå¸å¤±è´¥:', e) |
| | | dataList.value = [] |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -39%); |
| | | width: 360px; |
| | | height: 360px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | /* é»è®¤å±
ä¸ï¼ä¼å¨ JS ä¸å¨æè°æ´ */ |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -39%); |
| | | } |
| | | </style> |
| | |
| | | /> |
| | | </div> |
| | | <!-- <CarouselCards :items="cardItems" :visible-count="3" /> --> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background"></div> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import ProductTypeSwitch from './ProductTypeSwitch.vue' |
| | | import { expenseCompositionAnalysis } from '@/api/viewIndex.js' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | |
| | | /** |
| | | * @introduction ææ°ç»ä¸keyå¼ç¸åçé£ä¸é¡¹æååºæ¥ï¼ç»æä¸ä¸ªå¯¹è±¡ |
| | |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ |
| | | // å¾è¡¨ä¸å¿æ¯ ['25%', '50%']ï¼èæ¯éè¦å¯¹é½å°è¿ä¸ªä½ç½® |
| | | const { init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | left: '25%', // å¾è¡¨ä¸å¿ X æ¯ 25% |
| | | top: '50%', // å¾è¡¨ä¸å¿ Y æ¯ 50% |
| | | offsetX: '-51.5%', // X è½´åç§» |
| | | offsetY: '-50%', // Y è½´åç§» |
| | | watchData: dataList // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | |
| | | const fetchData = () => { |
| | | expenseCompositionAnalysis({ type: amountType.value }) |
| | | .then((res) => { |
| | |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | width: 310px; |
| | | height: 310px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | /* ä½ç½®ç± JS å¨æè®¾ç½®ï¼é»è®¤å±
ä¸ */ |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="carousel-cards"> |
| | | <button |
| | | v-if="canScrollLeft" |
| | | class="nav-button nav-button-left" |
| | | @click="scrollLeftFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å·¦ç®å¤´" /> |
| | | </button> |
| | | <div |
| | | class="cards-container" |
| | | :style="{ '--visible-count': visibleCount }" |
| | | ref="cardsContainerRef" |
| | | > |
| | | <div |
| | | v-for="(item, index) in items" |
| | | :key="index" |
| | | class="card-item" |
| | | > |
| | | <div v-if="item.icon" class="card-icon" :style="{ backgroundImage: `url(${item.icon})` }"></div> |
| | | <div class="card-title"> |
| | | <div class="card-label">{{ item.label }}</div> |
| | | <div class="card-value"> |
| | | <span class="value-number">{{ item.value }}</span> |
| | | <span class="value-unit">{{ item.unit }}</span> |
| | | </div> |
| | | <div v-if="item.rate ?? item.ratio ?? item.percent" class="card-rate"> |
| | | <span class="rate-value">{{ item.rate ?? item.ratio ?? item.percent }}%</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <button |
| | | v-if="canScrollRight" |
| | | class="nav-button nav-button-right" |
| | | @click="scrollRightFn" |
| | | > |
| | | <img src="@/assets/BI/jiantou.png" alt="å³ç®å¤´" /> |
| | | </button> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, watch, computed } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | items: { |
| | | type: Array, |
| | | default: () => [], |
| | | validator: (value) => { |
| | | return value.every(item => |
| | | item && typeof item.label !== 'undefined' && |
| | | typeof item.value !== 'undefined' && |
| | | typeof item.unit !== 'undefined' |
| | | ) |
| | | } |
| | | }, |
| | | visibleCount: { |
| | | type: Number, |
| | | default: 3 |
| | | } |
| | | }) |
| | | |
| | | const cardsContainerRef = ref(null) |
| | | const currentScrollLeft = ref(0) |
| | | const maxScrollLeft = ref(0) |
| | | |
| | | // æ£æ¥æ¯å¦å¯ä»¥åå·¦æ»å¨ |
| | | const canScrollLeft = computed(() => { |
| | | return currentScrollLeft.value > 0 |
| | | }) |
| | | |
| | | // æ£æ¥æ¯å¦å¯ä»¥å峿»å¨ |
| | | const canScrollRight = computed(() => { |
| | | return currentScrollLeft.value < maxScrollLeft.value |
| | | }) |
| | | |
| | | // æ´æ°æ»å¨ç¶æ |
| | | const updateScrollState = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | currentScrollLeft.value = container.scrollLeft |
| | | maxScrollLeft.value = container.scrollWidth - container.clientWidth |
| | | } |
| | | |
| | | // åå·¦æ»å¨ |
| | | const scrollLeftFn = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | const scrollItems = Array.from(container.querySelectorAll('.card-item')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | const itemWidth = scrollItems[0]?.offsetWidth || 0 |
| | | const gap = 12 |
| | | const scrollDistance = itemWidth + gap |
| | | |
| | | container.scrollBy({ |
| | | left: -scrollDistance, |
| | | behavior: 'smooth' |
| | | }) |
| | | |
| | | // å»¶è¿æ´æ°ç¶æï¼çå¾
æ»å¨å¨ç»å®æ |
| | | setTimeout(() => { |
| | | updateScrollState() |
| | | }, 300) |
| | | } |
| | | |
| | | // å峿»å¨ |
| | | const scrollRightFn = () => { |
| | | const container = cardsContainerRef.value |
| | | if (!container) return |
| | | |
| | | const scrollItems = Array.from(container.querySelectorAll('.card-item')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | const itemWidth = scrollItems[0]?.offsetWidth || 0 |
| | | const gap = 12 |
| | | const scrollDistance = itemWidth + gap |
| | | |
| | | container.scrollBy({ |
| | | left: scrollDistance, |
| | | behavior: 'smooth' |
| | | }) |
| | | |
| | | // å»¶è¿æ´æ°ç¶æï¼çå¾
æ»å¨å¨ç»å®æ |
| | | setTimeout(() => { |
| | | updateScrollState() |
| | | }, 300) |
| | | } |
| | | |
| | | // çå¬ items ååï¼æ´æ°æ»å¨ç¶æ |
| | | watch(() => props.items, () => { |
| | | nextTick(() => { |
| | | updateScrollState() |
| | | }) |
| | | }, { deep: true }) |
| | | |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | updateScrollState() |
| | | // ç嬿»å¨äºä»¶ |
| | | const container = cardsContainerRef.value |
| | | if (container) { |
| | | container.addEventListener('scroll', updateScrollState) |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | // æ¸
çæ»å¨äºä»¶çå¬å¨ |
| | | const container = cardsContainerRef.value |
| | | if (container) { |
| | | container.removeEventListener('scroll', updateScrollState) |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .carousel-cards { |
| | | width: 100%; |
| | | overflow: hidden; |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .cards-container { |
| | | display: flex; |
| | | gap: 12px; |
| | | width: 100%; |
| | | overflow-x: auto; |
| | | overflow-y: hidden; |
| | | scrollbar-width: none; /* Firefox */ |
| | | -ms-overflow-style: none; /* IE and Edge */ |
| | | padding-bottom: 4px; |
| | | scroll-behavior: smooth; |
| | | } |
| | | |
| | | .cards-container::-webkit-scrollbar { |
| | | display: none; /* Chrome, Safari, Opera */ |
| | | } |
| | | |
| | | .nav-button { |
| | | position: absolute; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | background: rgba(26, 88, 176, 0.6); |
| | | border: 1px solid rgba(26, 88, 176, 0.8); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | cursor: pointer; |
| | | z-index: 10; |
| | | transition: all 0.3s ease; |
| | | padding: 0; |
| | | } |
| | | |
| | | .nav-button:hover { |
| | | background: rgba(26, 88, 176, 0.8); |
| | | transform: translateY(-50%) scale(1.1); |
| | | } |
| | | |
| | | .nav-button-left { |
| | | left: -16px; |
| | | } |
| | | |
| | | .nav-button-left img { |
| | | width: 16px; |
| | | height: 16px; |
| | | transform: rotate(180deg); |
| | | } |
| | | |
| | | .nav-button-right { |
| | | right: -16px; |
| | | } |
| | | |
| | | .nav-button-right img { |
| | | width: 16px; |
| | | height: 16px; |
| | | } |
| | | |
| | | .card-item { |
| | | flex: 0 0 calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count)); |
| | | min-width: calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count)); |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(269deg, rgba(27,57,126,0.13) 0%, rgba(33,137,206,0.33) 98.13%, #24AFF4 100%); |
| | | border-radius: 8px 8px 8px 8px; |
| | | padding: 12px 16px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .card-item:hover { |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 80px; |
| | | height: 60px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | flex-shrink: 0; |
| | | margin-right: 12px; |
| | | } |
| | | |
| | | .card-title { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | margin-bottom: 4px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | width: 100%; |
| | | } |
| | | |
| | | .card-value { |
| | | display: flex; |
| | | align-items: baseline; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .card-rate { |
| | | margin-top: 4px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: rgba(255, 255, 255, 0.85); |
| | | } |
| | | |
| | | .rate-label { |
| | | opacity: 0.85; |
| | | } |
| | | |
| | | .rate-value { |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .value-number { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .value-unit { |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | font-weight: 400; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-radio-group |
| | | v-model="currentValue" |
| | | class="date-type-switch" |
| | | @change="handleChange" |
| | | > |
| | | <el-radio-button :label="1">å¨</el-radio-button> |
| | | <el-radio-button :label="2">æ</el-radio-button> |
| | | <el-radio-button :label="3">å£åº¦</el-radio-button> |
| | | </el-radio-group> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Number, |
| | | default: 1, // é»è®¤éä¸"å¨" |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:modelValue', 'change']) |
| | | |
| | | const currentValue = ref(props.modelValue) |
| | | |
| | | // çå¬å¤é¨å¼åå |
| | | watch( |
| | | () => props.modelValue, |
| | | (newVal) => { |
| | | currentValue.value = newVal |
| | | } |
| | | ) |
| | | |
| | | // å¤çå¼åå |
| | | const handleChange = (value) => { |
| | | emit('update:modelValue', value) |
| | | emit('change', value) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .date-type-switch { |
| | | display: inline-flex; |
| | | } |
| | | |
| | | /* æªéä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | /* 第ä¸ä¸ªæé®å·¦ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | } |
| | | |
| | | /* æåä¸ä¸ªæé®å³ä¾§åè§ */ |
| | | .date-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | } |
| | | |
| | | /* æé®ä¹é´çåé线 */ |
| | | .date-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | } |
| | | |
| | | /* éä¸ç¶æçæ ·å¼ */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | box-shadow: none; |
| | | } |
| | | |
| | | /* æ¬åææ */ |
| | | .date-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | } |
| | | |
| | | /* éä¸ç¶ææ¬å */ |
| | | .date-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">{{ title }}</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | defineProps({ |
| | | title: { |
| | | type: String, |
| | | required: true, |
| | | default: '' |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-header { |
| | | background-image: url("@/assets/BI/kehuhetongback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .panel-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #D9ECFF; |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-radio-group |
| | | v-model="currentValue" |
| | | class="product-type-switch" |
| | | @change="handleChange" |
| | | > |
| | | <el-radio-button :label="1">åææ</el-radio-button> |
| | | <el-radio-button :label="3">åæå</el-radio-button> |
| | | <el-radio-button :label="2">æå</el-radio-button> |
| | | </el-radio-group> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Number, |
| | | default: 1, // é»è®¤éä¸"åææ" |
| | | }, |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:modelValue', 'change']) |
| | | |
| | | const currentValue = ref(props.modelValue) |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | (newVal) => { |
| | | currentValue.value = newVal |
| | | } |
| | | ) |
| | | |
| | | const handleChange = (value) => { |
| | | emit('update:modelValue', value) |
| | | emit('change', value) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .product-type-switch { |
| | | display: inline-flex; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner) { |
| | | background-color: rgba(26, 88, 176, 0.3); |
| | | color: rgba(184, 200, 224, 0.8); |
| | | border-color: rgba(255, 255, 255, 0.2); |
| | | border-radius: 0; |
| | | padding: 6px 20px; |
| | | font-size: 14px; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:first-child .el-radio-button__inner) { |
| | | border-top-left-radius: 4px; |
| | | border-bottom-left-radius: 4px; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:last-child .el-radio-button__inner) { |
| | | border-top-right-radius: 4px; |
| | | border-bottom-right-radius: 4px; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button:not(:last-child) .el-radio-button__inner) { |
| | | border-right: 1px solid rgba(255, 255, 255, 0.2); |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { |
| | | background: linear-gradient(180deg, #3378ff 0%, #00a4ed 100%); |
| | | color: #ffffff; |
| | | border-color: rgba(51, 120, 255, 0.8); |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__inner:hover) { |
| | | color: rgba(184, 200, 224, 1); |
| | | border-color: rgba(255, 255, 255, 0.3); |
| | | } |
| | | |
| | | .product-type-switch :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) { |
| | | background: linear-gradient(180deg, #4e8aff 0%, #4ee4ff 100%); |
| | | color: #ffffff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="ç产订å宿è¿åº¦" /> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="4" /> |
| | | <div |
| | | class="progress-table-container" |
| | | ref="progressTableRef" |
| | | style="margin-top: 0px;" |
| | | @scroll="handleTableScroll" |
| | | > |
| | | <table class="progress-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>ç产订åå·</th> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼</th> |
| | | <th>éæ±æ°é</th> |
| | | <th>宿æ°é</th> |
| | | <th>宿è¿åº¦</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr |
| | | v-for="(item, index) in progressTableData" |
| | | :key="index" |
| | | :ref="(el) => setRowRef(el, index)" |
| | | :class="{ 'row-under-header': isRowUnderHeader(index) }" |
| | | > |
| | | <td>{{ item.npsNo || '-' }}</td> |
| | | <td>{{ item.productCategory || '-' }}</td> |
| | | <td>{{ item.specificationModel || '-' }}</td> |
| | | <td>{{ item.quantity || 0 }}</td> |
| | | <td>{{ item.completeQuantity || 0 }}</td> |
| | | <td> |
| | | <el-progress |
| | | :percentage="calculateProgress(item)" |
| | | :color="progressColor(calculateProgress(item))" |
| | | :status="calculateProgress(item) >= 100 ? 'success' : ''" |
| | | :stroke-width="8" |
| | | /> |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { getProgressStatistics } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | |
| | | const progressTableRef = ref(null) |
| | | const progressTableScrollTimer = ref(null) |
| | | const tableScrollTimeout = ref(null) |
| | | const tableRowRefs = ref([]) |
| | | const rowsUnderHeader = ref(new Set()) |
| | | |
| | | // 订åç»è®¡å¯¹è±¡ |
| | | const orderStatisticsObject = ref({ |
| | | totalOrderCount: 0, |
| | | uncompletedOrderCount: 0, |
| | | partialCompletedOrderCount: 0, |
| | | completedOrderCount: 0, |
| | | }) |
| | | |
| | | // è½®æå¡çæ°æ®ï¼ç± orderStatisticsObject åæ¥ï¼ |
| | | const cardItems = ref([]) |
| | | |
| | | // ç产订å宿è¿åº¦è¡¨æ ¼æ°æ® |
| | | const progressTableData = ref([]) |
| | | |
| | | // 计ç®å®æè¿åº¦ç¾åæ¯ |
| | | const calculateProgress = (item) => { |
| | | if (!item) return 0 |
| | | if (item.completionStatus !== undefined && item.completionStatus !== null) { |
| | | const percentage = Number(item.completionStatus) |
| | | if (isNaN(percentage)) return 0 |
| | | return Math.min(Math.max(Math.round(percentage), 0), 100) |
| | | } |
| | | if (!item.quantity || item.quantity === 0) return 0 |
| | | const percentage = ((item.completeQuantity || 0) / item.quantity) * 100 |
| | | return Math.min(Math.max(Math.round(percentage), 0), 100) |
| | | } |
| | | |
| | | // æ ¹æ®è¿åº¦ç¾åæ¯è¿åé¢è² |
| | | const progressColor = (percentage) => { |
| | | const p = percentage || 0 |
| | | if (p < 30) return '#f56c6c' |
| | | if (p < 50) return '#e6a23c' |
| | | if (p < 80) return '#409eff' |
| | | return '#67c23a' |
| | | } |
| | | |
| | | const setRowRef = (el, index) => { |
| | | if (el) { |
| | | tableRowRefs.value[index] = el |
| | | } |
| | | } |
| | | |
| | | const isRowUnderHeader = (index) => rowsUnderHeader.value.has(index) |
| | | |
| | | const handleTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | const thead = tableContainer.querySelector('thead') |
| | | if (!thead) return |
| | | const theadHeight = thead.offsetHeight |
| | | const containerRect = tableContainer.getBoundingClientRect() |
| | | const containerTop = containerRect.top |
| | | const theadBottom = containerTop + theadHeight |
| | | rowsUnderHeader.value.clear() |
| | | tableRowRefs.value.forEach((row, index) => { |
| | | if (row) { |
| | | const rowRect = row.getBoundingClientRect() |
| | | const rowTop = rowRect.top |
| | | const rowBottom = rowRect.bottom |
| | | if (rowTop < theadBottom && rowBottom > containerTop) { |
| | | rowsUnderHeader.value.add(index) |
| | | } |
| | | } |
| | | }) |
| | | if (tableScrollTimeout.value) clearTimeout(tableScrollTimeout.value) |
| | | tableScrollTimeout.value = setTimeout(() => { |
| | | rowsUnderHeader.value.clear() |
| | | }, 150) |
| | | } |
| | | |
| | | const initProgressTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | progressTableScrollTimer.value = null |
| | | } |
| | | if (tableContainer._pauseTimer) { |
| | | clearInterval(tableContainer._pauseTimer) |
| | | tableContainer._pauseTimer = null |
| | | } |
| | | const tbody = tableContainer.querySelector('tbody') |
| | | if (!tbody) return |
| | | const originalCount = progressTableData.value.length |
| | | const allRows = Array.from(tbody.querySelectorAll('tr')) |
| | | if (allRows.length > originalCount) { |
| | | for (let i = originalCount; i < allRows.length; i++) { |
| | | allRows[i].remove() |
| | | } |
| | | } |
| | | const scrollItems = Array.from(tbody.querySelectorAll('tr')) |
| | | if (scrollItems.length === 0) return |
| | | const originalItemCount = scrollItems.length |
| | | const thead = tableContainer.querySelector('thead') |
| | | const theadHeight = thead ? thead.offsetHeight : 40 |
| | | const containerHeight = tableContainer.clientHeight |
| | | const visibleHeight = containerHeight - theadHeight |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 40 |
| | | const totalContentHeight = itemHeight * originalItemCount |
| | | if (totalContentHeight <= visibleHeight) return |
| | | const cloneCount = Math.ceil(visibleHeight / itemHeight) + 2 |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % originalItemCount].cloneNode(true) |
| | | tbody.appendChild(clone) |
| | | } |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 |
| | | const pauseTime = 3000 |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) |
| | | const maxScroll = itemHeight * originalItemCount |
| | | if (scrollPosition >= maxScroll) { |
| | | scrollPosition = 0 |
| | | tableContainer.scrollTop = 0 |
| | | } else { |
| | | tableContainer.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation) |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | tableContainer._pauseTimer = pauseTimer |
| | | } |
| | | |
| | | const progressStatisticsInfo = () => { |
| | | getProgressStatistics() |
| | | .then((res) => { |
| | | if (!res || !res.data) return |
| | | const obj = { |
| | | totalOrderCount: res.data.totalOrderCount || 0, |
| | | uncompletedOrderCount: res.data.uncompletedOrderCount || 0, |
| | | partialCompletedOrderCount: res.data.partialCompletedOrderCount || 0, |
| | | completedOrderCount: res.data.completedOrderCount || 0, |
| | | } |
| | | orderStatisticsObject.value = obj |
| | | cardItems.value = [ |
| | | { label: 'æ»è®¢åæ°', value: obj.totalOrderCount, unit: 'ä»¶' }, |
| | | { label: 'æªå®æè®¢åæ°', value: obj.uncompletedOrderCount, unit: 'ä»¶' }, |
| | | { label: 'é¨åå®æè®¢åæ°', value: obj.partialCompletedOrderCount, unit: 'ä»¶' }, |
| | | { label: 'å·²å®æè®¢åæ°', value: obj.completedOrderCount, unit: 'ä»¶' }, |
| | | ] |
| | | progressTableData.value = res.data.completedOrderDetails || [] |
| | | tableRowRefs.value = [] |
| | | rowsUnderHeader.value.clear() |
| | | nextTick(() => { |
| | | initProgressTableScroll() |
| | | }) |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åç产订å宿è¿åº¦ç»è®¡å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | progressStatisticsInfo() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | } |
| | | if (tableScrollTimeout.value) clearTimeout(tableScrollTimeout.value) |
| | | const tableContainer = progressTableRef.value |
| | | if (tableContainer?._pauseTimer) { |
| | | clearInterval(tableContainer._pauseTimer) |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 428px; |
| | | } |
| | | |
| | | .progress-table-container { |
| | | height: 320px; |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | margin-top: 10px; |
| | | scrollbar-width: none; |
| | | -ms-overflow-style: none; |
| | | } |
| | | |
| | | .progress-table-container::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | | |
| | | .progress-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | color: #b8c8e0; |
| | | font-size: 12px; |
| | | table-layout: fixed; |
| | | } |
| | | |
| | | .progress-table thead { |
| | | position: sticky; |
| | | top: 0; |
| | | background-color: rgba(26, 88, 176, 0.9); |
| | | z-index: 10; |
| | | } |
| | | |
| | | .progress-table th { |
| | | padding: 8px 6px; |
| | | text-align: left; |
| | | font-weight: 500; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.3); |
| | | color: #b8c8e0; |
| | | font-size: 12px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .progress-table th:nth-child(1) { |
| | | width: 15%; |
| | | } |
| | | |
| | | .progress-table th:nth-child(2) { |
| | | width: 15%; |
| | | } |
| | | |
| | | .progress-table th:nth-child(3) { |
| | | width: 15%; |
| | | } |
| | | |
| | | .progress-table th:nth-child(4) { |
| | | width: 12%; |
| | | } |
| | | |
| | | .progress-table th:nth-child(5) { |
| | | width: 12%; |
| | | } |
| | | |
| | | .progress-table th:nth-child(6) { |
| | | width: 31%; |
| | | } |
| | | |
| | | .progress-table td { |
| | | padding: 8px 6px; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.1); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | font-size: 12px; |
| | | transition: opacity 0.3s ease; |
| | | } |
| | | |
| | | .progress-table tbody tr:hover { |
| | | background-color: rgba(184, 200, 224, 0.1); |
| | | } |
| | | |
| | | .progress-table tbody tr.row-under-header { |
| | | opacity: 0.5; |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress) { |
| | | width: 100%; |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress-bar__outer) { |
| | | background-color: rgba(184, 200, 224, 0.2); |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress__text) { |
| | | color: #b8c8e0; |
| | | font-size: 11px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <!-- 设å¤ç»è®¡ --> |
| | | <div class="equipment-stats"> |
| | | <div class="equipment-header"> |
| | | <img |
| | | src="@/assets/BI/shujutongjiicon@2x.png" |
| | | alt="徿 " |
| | | class="equipment-icon" |
| | | /> |
| | | <span class="equipment-title">æå
¥äº§åºåæ</span> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="lineLegend" |
| | | :series="lineSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { productInOutAnalysis } from '@/api/viewIndex.js' |
| | | |
| | | const chartStyle = { width: '100%', height: '100%' } |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | top: '16%', |
| | | containLabel: true, |
| | | } |
| | | |
| | | const lineLegend = { |
| | | show: true, |
| | | top: '2%', |
| | | left: 'center', |
| | | itemGap: 24, |
| | | itemWidth: 12, |
| | | itemHeight: 12, |
| | | textStyle: { color: '#B8C8E0', fontSize: 14 }, |
| | | data: [ |
| | | { name: 'åºåº', itemStyle: { color: 'rgba(11, 137, 254, 1)' } }, |
| | | { name: 'å
¥åº', itemStyle: { color: 'rgba(11, 249, 254, 1)' } }, |
| | | ], |
| | | } |
| | | |
| | | const xAxis1 = ref([ |
| | | { |
| | | type: 'category', |
| | | data: [], |
| | | axisTick: { show: false }, |
| | | axisLine: { show: false, lineStyle: { color: 'rgba(184, 200, 224, 0.3)' } }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | splitLine: { show: false, lineStyle: { type: 'dashed', color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | }, |
| | | ]) |
| | | |
| | | const yAxis1 = [ |
| | | { |
| | | type: 'value', |
| | | name: 'åä½: ä»¶', |
| | | nameTextStyle: { color: '#B8C8E0', fontSize: 12, padding: [0, 0, 0, 0] }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0', fontSize: 12 }, |
| | | splitLine: { lineStyle: { color: '#B8C8E0' } }, |
| | | }, |
| | | ] |
| | | |
| | | const lineSeries = ref([ |
| | | { |
| | | name: 'åºåº', |
| | | type: 'line', |
| | | smooth: false, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(11, 137, 254, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 137, 254, 1)', borderWidth: 0 }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(11, 137, 254, 0.40)' }, |
| | | { offset: 1, color: 'rgba(11, 137, 254, 0.05)' }, |
| | | ]), |
| | | }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | { |
| | | name: 'å
¥åº', |
| | | type: 'line', |
| | | smooth: false, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(11, 249, 254, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(11, 249, 254, 1)', borderWidth: 0 }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: 'rgba(11, 249, 254, 0.5)' }, |
| | | { offset: 1, color: 'rgba(11, 249, 254, 0.05)' }, |
| | | ]), |
| | | }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'line' }, |
| | | borderWidth: 1, |
| | | textStyle: { fontSize: 12 }, |
| | | formatter(params) { |
| | | let result = params[0].axisValue + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `${item.marker} ${item.seriesName}: ${item.value} ä»¶<br/>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | productInOutAnalysis({ type: 1 }) |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | const list = res.data |
| | | xAxis1.value[0].data = list.map((d) => d.date) |
| | | lineSeries.value[0].data = list.map((d) => Number(d.outCount) || 0) |
| | | lineSeries.value[1].data = list.map((d) => Number(d.inCount) || 0) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åæå
¥äº§åºåæå¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .equipment-stats { |
| | | border: 1px solid #1a58b0; |
| | | padding: 0 18px 18px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .equipment-header { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | display: flex; |
| | | border-bottom: 1px solid; |
| | | border-image: linear-gradient( |
| | | 270deg, |
| | | rgba(0, 126, 255, 0) 0%, |
| | | rgba(0, 126, 255, 0.4549) 35%, |
| | | #007eff 78%, |
| | | #007eff 100% |
| | | ) |
| | | 1; |
| | | padding-bottom: 2px; |
| | | } |
| | | |
| | | .equipment-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | | background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | line-height: 50px; |
| | | } |
| | | |
| | | .equipment-icon { |
| | | width: 50px; |
| | | height: 50px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <!-- é¡¶é¨ç»è®¡å¡ç --> |
| | | <div class="stats-cards"> |
| | | <div |
| | | v-for="item in statItems" |
| | | :key="item.name" |
| | | class="stat-card" |
| | | > |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">{{ item.name }}</span> |
| | | <span class="card-value">{{ item.value }}</span> |
| | | <div class="card-compare" :class="compareClass(Number(item.rate))"> |
| | | <span>忝</span> |
| | | <span class="compare-value">{{ formatPercent(item.rate) }}</span> |
| | | <span class="compare-icon">{{ Number(item.rate) >= 0 ? 'â' : 'â' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js' |
| | | |
| | | const statItems = ref([]) |
| | | |
| | | const formatPercent = (val) => { |
| | | const num = Number(val) || 0 |
| | | return `${num.toFixed(2)}%` |
| | | } |
| | | |
| | | const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down') |
| | | |
| | | const fetchData = () => { |
| | | salesPurchaseStorageProductCount() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | statItems.value = res.data.map((item) => ({ |
| | | name: item.name, |
| | | value: item.value, |
| | | rate: item.rate, |
| | | })) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åéå®/éè´/å¨åäº§åæ°å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .stats-cards { |
| | | display: flex; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .stat-card { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | background-image: url('@/assets/BI/border@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | height: 142px; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin: 20px 20px 0 10px; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .card-value { |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 19px; |
| | | color: rgba(208, 231, 255, 0.7); |
| | | } |
| | | |
| | | .card-compare { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-size: 15px; |
| | | color: #d0e7ff; |
| | | } |
| | | |
| | | .card-compare > span:first-child { |
| | | font-size: 13px; |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | .compare-value { |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .compare-icon { |
| | | font-size: 14px; |
| | | position: relative; |
| | | top: -1px; /* 轻微ä¸ç§»ï¼è®©ç®å¤´ä¸æååç´å±
ä¸å¯¹é½ */ |
| | | } |
| | | |
| | | .compare-up .compare-value, |
| | | .compare-up .compare-icon { |
| | | color: #00c853; |
| | | } |
| | | |
| | | .compare-down .compare-value, |
| | | .compare-down .compare-icon { |
| | | color: #ff5252; |
| | | } |
| | | |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å¨å¶åç»è®¡åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <CarouselCards :items="cardItems" :visible-count="3" /> |
| | | <div class="chart-wrapper"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="workInProcessBarLegend" |
| | | :series="workInProcessBarSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="workInProcessXAxis" |
| | | :yAxis="workInProcessYAxis" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 100%" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import CarouselCards from './CarouselCards.vue' |
| | | import { getWorkInProcessTurnover } from '@/api/viewIndex.js' |
| | | |
| | | // å¨å¶åå¨è½¬ç»è®¡å¯¹è±¡ |
| | | const workInProcessStatistics = ref({ |
| | | totalQuantity: 0, |
| | | avgTurnoverDays: 0, |
| | | turnoverEfficiency: 0, |
| | | }) |
| | | |
| | | // è½®æå¡çæ°æ®ï¼ç± workInProcessStatistics åæ¥ï¼ |
| | | const cardItems = ref([]) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true, |
| | | } |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' }, |
| | | formatter: function (params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | |
| | | // å¨å¶åå·¥åºæ±ç¶å¾é
ç½® |
| | | const workInProcessXAxis = ref([ |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | const workInProcessYAxis = [ |
| | | { |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | name: '', |
| | | }, |
| | | ] |
| | | const workInProcessBarLegend = { |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: [], |
| | | } |
| | | const workInProcessBarSeries = ref([ |
| | | { |
| | | name: 'å¨å¶åæ°é', |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | barGap: 0, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(0,164,237,0)' }, |
| | | { offset: 0, color: '#4EE4FF' }, |
| | | ], |
| | | }, |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | color: '#B8C8E0', |
| | | }, |
| | | data: [], |
| | | }, |
| | | ]) |
| | | |
| | | const workInProcessTurnoverInfo = () => { |
| | | getWorkInProcessTurnover() |
| | | .then((res) => { |
| | | if (!res || !res.data) return |
| | | const stats = { |
| | | totalQuantity: res.data.totalOrderCount || 0, |
| | | avgTurnoverDays: res.data.averageTurnoverDays || 0, |
| | | turnoverEfficiency: res.data.turnoverEfficiency || 0, |
| | | } |
| | | workInProcessStatistics.value = stats |
| | | cardItems.value = [ |
| | | { label: 'æ»å¨å¶æ°é', value: stats.totalQuantity, unit: 'ä»¶' }, |
| | | { label: 'å¹³åå¨è½¬å¤©æ°', value: stats.avgTurnoverDays, unit: '天' }, |
| | | { label: 'å¨è½¬æç', value: stats.turnoverEfficiency, unit: '%' }, |
| | | ] |
| | | if (res.data.processDetails && Array.isArray(res.data.processDetails)) { |
| | | workInProcessXAxis.value[0].data = res.data.processDetails |
| | | } else { |
| | | workInProcessXAxis.value[0].data = [] |
| | | } |
| | | if (res.data.processQuantityDetails && Array.isArray(res.data.processQuantityDetails)) { |
| | | workInProcessBarSeries.value[0].data = res.data.processQuantityDetails |
| | | } else { |
| | | workInProcessBarSeries.value[0].data = [] |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åå¨å¶åå¨è½¬ç»è®¡å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | workInProcessTurnoverInfo() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | |
| | | .chart-wrapper { |
| | | height: 70%; |
| | | flex: 1; |
| | | min-height: 200px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å·¥åºäº§åºåæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | | </div> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | | :legend="pieLegend" |
| | | :series="pieSeries" |
| | | :tooltip="pieTooltip" |
| | | :color="pieColors" |
| | | :options="pieOptions" |
| | | style="height: 320px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { productSalesAnalysis } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import DateTypeSwitch from '@/views/reportAnalysis/financialAnalysis/components/DateTypeSwitch.vue' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | |
| | | const dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | |
| | | function array2obj(array, key) { |
| | | const resObj = {} |
| | | for (let i = 0; i < array.length; i++) { |
| | | resObj[array[i][key]] = array[i] |
| | | } |
| | | return resObj |
| | | } |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | } |
| | | |
| | | const echartsRef = ref(null) |
| | | const pieDatas = ref([]) |
| | | const pieColors = ['#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF', '#43e8fc', '#27EBE7'] |
| | | |
| | | const pieObjData = computed(() => array2obj(pieDatas.value, 'name')) |
| | | |
| | | const pieLegend = computed(() => { |
| | | const data = pieDatas.value.map((d, idx) => ({ |
| | | name: d.name, |
| | | icon: 'circle', |
| | | textStyle: { |
| | | fontSize: 18, |
| | | color: pieColors[idx % pieColors.length], |
| | | }, |
| | | })) |
| | | |
| | | return { |
| | | orient: 'vertical', |
| | | top: 'center', |
| | | left: '52%', |
| | | itemGap: 30, |
| | | data: data, |
| | | formatter: function (name) { |
| | | const item = pieObjData.value[name] |
| | | if (!item) return name |
| | | return `{title|${name}}{value|${item.value}}{unit|å
}{percent|${item.rate}}{unit|%}` |
| | | }, |
| | | textStyle: { |
| | | rich: { |
| | | value: { |
| | | color: '#43e8fc', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 10], |
| | | }, |
| | | unit: { |
| | | color: '#82baff', |
| | | fontSize: 12, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 0], |
| | | }, |
| | | percent: { |
| | | color: '#43e8fc', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | title: { |
| | | fontSize: 12, |
| | | padding: [0, 0, 0, 0], |
| | | }, |
| | | }, |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | const pieTooltip = { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c}å
({d}%)', |
| | | } |
| | | |
| | | const pieSeries = computed(() => [ |
| | | { |
| | | name: '产åéå®éé¢åæ', |
| | | type: 'pie', |
| | | radius: '60%', |
| | | center: ['25%', '50%'], |
| | | itemStyle: { |
| | | borderColor: '#0a1c3a', |
| | | borderWidth: 2, |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | minAngle: 15, |
| | | data: pieDatas.value, |
| | | animationType: 'scale', |
| | | animationEasing: 'elasticOut', |
| | | animationDelay: function () { |
| | | return Math.random() * 200 |
| | | }, |
| | | }, |
| | | ]) |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' }, |
| | | } |
| | | |
| | | // 使ç¨å°è£
çèæ¯ä½ç½®è°æ´æ¹æ³ |
| | | // å¾è¡¨ä¸å¿æ¯ ['25%', '50%']ï¼èæ¯éè¦å¯¹é½å°è¿ä¸ªä½ç½® |
| | | const { init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | left: '25%', // å¾è¡¨ä¸å¿ X æ¯ 25% |
| | | top: '50%', // å¾è¡¨ä¸å¿ Y æ¯ 50% |
| | | offsetX: '-51.5%', // X è½´åç§» |
| | | offsetY: '-50%', // Y è½´åç§» |
| | | watchData: pieDatas // ç嬿°æ®ååï¼èªå¨è°æ´ä½ç½® |
| | | }) |
| | | |
| | | const fetchData = () => { |
| | | productSalesAnalysis() |
| | | .then((res) => { |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | const items = res.data |
| | | pieDatas.value = items.map((item) => ({ |
| | | name: item.name, |
| | | value: parseFloat(item.value) || 0, |
| | | rate: item.rate, |
| | | })) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·å产åéå®éé¢åæå¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | const handleDateTypeChange = () => { |
| | | fetchData() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | |
| | | .filters-row { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | background: transparent; |
| | | } |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | width: 310px; |
| | | height: 310px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | /* ä½ç½®ç± JS å¨æè®¾ç½®ï¼é»è®¤å±
ä¸ */ |
| | | left: 25%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -50%); |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="çäº§æ ¸ç®åæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="filters-row"> |
| | | <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" /> |
| | | </div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend" |
| | | :series="chartSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { qualityStatistics } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import DateTypeSwitch from './DateTypeSwitch.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | |
| | | const dateType = ref(1) // 1=å¨ 2=æ 3=å£åº¦ |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '140%', |
| | | } |
| | | |
| | | const grid = { left: '10%', right: '4%', bottom: '3%', top: '10%', containLabel: true } |
| | | |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['产é', 'å·¥èµ', 'åæ ¼ç'], |
| | | } |
| | | |
| | | // æ±ç¶å¾ï¼äº§éãå·¥èµï¼æçº¿å¾ï¼åæ ¼çï¼ç»¿è²ï¼ |
| | | const chartSeries = ref([ |
| | | { |
| | | name: '产é', |
| | | type: 'bar', |
| | | barWidth: 20, |
| | | barGap: '40%', |
| | | yAxisIndex: 0, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(0, 164, 237, 0)' }, |
| | | { offset: 0, color: 'rgba(78, 228, 255, 1)' }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | { |
| | | name: 'å·¥èµ', |
| | | type: 'bar', |
| | | barGap: '40%', |
| | | barWidth: 20, |
| | | yAxisIndex: 1, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(83, 126, 245, 0.19)' }, |
| | | { offset: 0, color: 'rgba(144, 97, 248, 1)' }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | { |
| | | name: 'åæ ¼ç', |
| | | type: 'line', |
| | | yAxisIndex: 2, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(90, 216, 166, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(90, 216, 166, 1)' }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' }, |
| | | formatter(params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | let unit = 'ä»¶' |
| | | if (item.seriesName === 'åæ ¼ç') unit = '%' |
| | | else if (item.seriesName === 'å·¥èµ') unit = 'å
' |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value}${unit}</div>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | |
| | | const xAxis1 = ref([ |
| | | { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }, |
| | | ]) |
| | | const yAxis1 = [ |
| | | { type: 'value', name: '产é(ä»¶)', position: 'left', axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } }, |
| | | { type: 'value', name: 'å·¥èµ(å
)', position: 'left', offset: 50, axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } }, |
| | | { |
| | | type: 'value', |
| | | name: 'åæ ¼ç(%)', |
| | | position: 'right', |
| | | min: 0, |
| | | max: 100, |
| | | axisLabel: { color: '#B8C8E0', formatter: '{value}%' }, |
| | | nameTextStyle: { color: '#B8C8E0' }, |
| | | splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | }, |
| | | ] |
| | | |
| | | const handleDateTypeChange = () => { |
| | | fetchData() |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | qualityStatistics() |
| | | .then((res) => { |
| | | if (!res?.data?.item || !Array.isArray(res.data.item)) return |
| | | const items = res.data.item |
| | | xAxis1.value[0].data = items.map((d) => d.date) |
| | | // 产éï¼åºåæ° |
| | | chartSeries.value[0].data = items.map((d) => Number(d.factoryNum) || 0) |
| | | // å·¥èµï¼ææ åç¬æ¥å£ï¼ç¨ 0 å ä½ï¼åç»å¯æ¥å·¥èµæ¥å£ |
| | | chartSeries.value[1].data = items.map(() => 0) |
| | | // åæ ¼çï¼åºåæ°/è¿ç¨æ°*100ï¼æ åç¬æ¥å£æ¶ç¨æ¤å ä½ï¼ |
| | | chartSeries.value[2].data = items.map((d) => { |
| | | const processNum = Number(d.processNum) || 0 |
| | | const factoryNum = Number(d.factoryNum) || 0 |
| | | if (processNum <= 0) return 0 |
| | | return Math.min(100, Math.round((factoryNum / processNum) * 100)) |
| | | }) |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·å产éãå·¥èµä¸åæ ¼çæ°æ®å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .filters-row { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å·¥åæ§è¡æçåæ" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend" |
| | | :series="chartSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#B8C8E0' } }" |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { qualityStatistics } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '160%', |
| | | } |
| | | |
| | | const grid = { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true } |
| | | |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['å¼å·¥', '宿', 'è¯åç'], |
| | | } |
| | | |
| | | // æ±ç¶å¾ï¼å¼å·¥ãå®æï¼æçº¿å¾ï¼è¯åçï¼é¢è² rgba(90, 216, 166, 1)ï¼ |
| | | const chartSeries = ref([ |
| | | { |
| | | name: 'å¼å·¥', |
| | | type: 'bar', |
| | | barWidth: 20, |
| | | barGap: '40%', |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(0, 164, 237, 0)' }, |
| | | { offset: 0, color: 'rgba(78, 228, 255, 1)' }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | { |
| | | name: '宿', |
| | | type: 'bar', |
| | | barGap: '40%', |
| | | barWidth: 20, |
| | | emphasis: { focus: 'series' }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: 'rgba(83, 126, 245, 0.19)' }, |
| | | { offset: 0, color: 'rgba(144, 97, 248, 1)' }, |
| | | ], |
| | | }, |
| | | }, |
| | | data: [], |
| | | }, |
| | | { |
| | | name: 'è¯åç', |
| | | type: 'line', |
| | | yAxisIndex: 1, |
| | | showSymbol: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { color: 'rgba(90, 216, 166, 1)', width: 2 }, |
| | | itemStyle: { color: 'rgba(90, 216, 166, 1)' }, |
| | | data: [], |
| | | emphasis: { focus: 'series' }, |
| | | }, |
| | | ]) |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' }, |
| | | formatter(params) { |
| | | let result = params[0].axisValueLabel + '<br/>' |
| | | params.forEach((item) => { |
| | | const unit = item.seriesName === 'è¯åç' ? '%' : 'ä»¶' |
| | | result += `<div>${item.marker} ${item.seriesName}: ${item.value}${unit}</div>` |
| | | }) |
| | | return result |
| | | }, |
| | | } |
| | | |
| | | const xAxis1 = ref([ |
| | | { type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }, |
| | | ]) |
| | | const yAxis1 = [ |
| | | { type: 'value', name: 'ä»¶', axisLabel: { color: '#B8C8E0' }, nameTextStyle: { color: '#B8C8E0' } }, |
| | | { |
| | | type: 'value', |
| | | name: 'è¯åç(%)', |
| | | min: 0, |
| | | max: 100, |
| | | axisLabel: { color: '#B8C8E0', formatter: '{value}%' }, |
| | | nameTextStyle: { color: '#B8C8E0' }, |
| | | splitLine: { lineStyle: { color: 'rgba(184, 200, 224, 0.2)' } }, |
| | | }, |
| | | ] |
| | | |
| | | const fetchData = () => { |
| | | qualityStatistics() |
| | | .then((res) => { |
| | | if (!res?.data?.item || !Array.isArray(res.data.item)) return |
| | | const items = res.data.item |
| | | xAxis1.value[0].data = items.map((d) => d.date) |
| | | // å¼å·¥ï¼è¿ç¨æ£éªæ° |
| | | chartSeries.value[0].data = items.map((d) => Number(d.processNum) || 0) |
| | | // 宿ï¼åºåæ° |
| | | chartSeries.value[1].data = items.map((d) => Number(d.factoryNum) || 0) |
| | | // è¯åçï¼åºåæ°/è¿ç¨æ°*100ï¼æ åç¬æ¥å£æ¶ç¨æ¤å ä½ï¼ |
| | | chartSeries.value[2].data = items.map((d) => { |
| | | const processNum = Number(d.processNum) || 0 |
| | | const factoryNum = Number(d.factoryNum) || 0 |
| | | if (processNum <= 0) return 0 |
| | | return Math.min(100, Math.round((factoryNum / processNum) * 100)) |
| | | }) |
| | | }) |
| | | .catch((err) => { |
| | | console.error('è·åå¼å·¥ä¸è¯åçæ°æ®å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="scale-container"> |
| | | <div class="data-dashboard" :style="{ transform: `scale(${scaleRatio})` }"> |
| | | <!-- å
¨å±æé® - ç§»å¨å°å·¦ä¸è§ --> |
| | | <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? 'éåºå
¨å±' : 'å
¨å±æ¾ç¤º'"> |
| | | <svg v-if="!isFullscreen" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/> |
| | | </svg> |
| | | <svg v-else width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/> |
| | | </svg> |
| | | </button> |
| | | |
| | | <!-- 顶鍿 颿 --> |
| | | <div class="dashboard-header"> |
| | | <div class="factory-name">çäº§æ°æ®åæ</div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <div class="dashboard-content"> |
| | | <!-- 左侧åºå --> |
| | | <div class="left-panel"> |
| | | <LeftTop /> |
| | | |
| | | <LeftBottom /> |
| | | </div> |
| | | |
| | | <!-- ä¸é´åºå --> |
| | | <div class="center-panel"> |
| | | <CenterTop /> |
| | | <CenterCenter/> |
| | | <CenterBottom /> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§åºå --> |
| | | <div class="right-panel"> |
| | | |
| | | <RightTop /> |
| | | <RightBottom /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import LeftBottom from './components/left-bottom.vue' |
| | | import CenterCenter from './components/center-center.vue' |
| | | import RightTop from './components/right-top.vue' |
| | | import RightBottom from './components/right-bottom.vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import LeftTop from './components/left-top.vue' |
| | | import CenterTop from './components/center-top.vue' |
| | | import CenterBottom from './components/center-bottom.vue' |
| | | |
| | | // å
¨å±ç¸å
³ç¶æ |
| | | const isFullscreen = ref(false); |
| | | |
| | | // ç¼©æ¾æ¯ä¾ |
| | | const scaleRatio = ref(1) |
| | | // 设计尺寸ï¼åºå尺寸ï¼- æ ¹æ®å®é
è®¾è®¡ç¨¿è°æ´ |
| | | const designWidth = 1920 |
| | | const designHeight = 1080 |
| | | |
| | | // ç¨æ·store |
| | | const userStore = useUserStore() |
| | | |
| | | // 计ç®ç¼©æ¾æ¯ä¾ |
| | | const calculateScale = () => { |
| | | const container = document.querySelector('.scale-container') |
| | | if (!container) return |
| | | |
| | | // è·å容å¨çå®é
尺寸 |
| | | const rect = container.getBoundingClientRect?.() |
| | | const containerWidth = container.clientWidth || rect?.width || window.innerWidth |
| | | const containerHeight = container.clientHeight || rect?.height || window.innerHeight |
| | | |
| | | // 计ç®å®½é«ç¼©æ¾æ¯ä¾ï¼åè¾å°å¼ä»¥ä¿è¯å
容宿´æ¾ç¤ºï¼çæ¯ç¼©æ¾ï¼ |
| | | const scaleX = containerWidth / designWidth |
| | | const scaleY = containerHeight / designHeight |
| | | scaleRatio.value = Math.min(scaleX, scaleY) |
| | | } |
| | | |
| | | // çªå£å¤§å°ååå¤ç |
| | | const handleResize = () => { |
| | | // å»¶è¿æ§è¡ï¼ç¡®ä¿DOMæ´æ°å®æ |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 100) |
| | | } |
| | | |
| | | // å
¨å±åè½å®ç° - é对scale-containerå
ç´ |
| | | const toggleFullscreen = () => { |
| | | const element = document.querySelector('.scale-container') |
| | | |
| | | if (!element) return |
| | | |
| | | if (!isFullscreen.value) { |
| | | if (element.requestFullscreen) { |
| | | element.requestFullscreen() |
| | | } else if (element.webkitRequestFullscreen) { |
| | | element.webkitRequestFullscreen() |
| | | } else if (element.msRequestFullscreen) { |
| | | element.msRequestFullscreen() |
| | | } |
| | | } else { |
| | | if (document.exitFullscreen) { |
| | | document.exitFullscreen() |
| | | } else if (document.webkitExitFullscreen) { |
| | | document.webkitExitFullscreen() |
| | | } else if (document.msExitFullscreen) { |
| | | document.msExitFullscreen() |
| | | } |
| | | } |
| | | } |
| | | |
| | | // çå¬å
¨å±ååäºä»¶ |
| | | const handleFullscreenChange = () => { |
| | | const fullscreenElement = document.fullscreenElement || |
| | | document.webkitFullscreenElement || |
| | | document.msFullscreenElement |
| | | isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('scale-container') |
| | | |
| | | // å
¨å±ç¶æååæ¶ï¼å»¶è¿éæ°è®¡ç®ç¼©æ¾æ¯ä¾ï¼ç¡®ä¿DOMæ´æ°å®æï¼ |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 200) |
| | | } |
| | | |
| | | // çå½å¨æé©å |
| | | onMounted(() => { |
| | | // 使ç¨nextTickç¡®ä¿DOMå®å
¨æ¸²æåååå§å |
| | | nextTick(() => { |
| | | // 计ç®åå§ç¼©æ¾æ¯ä¾ |
| | | calculateScale() |
| | | }) |
| | | |
| | | window.addEventListener('resize', handleResize) |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | window.removeEventListener('resize', handleResize) |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | // ç§»é¤æä»¬æ·»å çautofitå¨æè°æ´çå¬å¨ |
| | | if (window._autofitUpdateHandler) { |
| | | window.removeEventListener('resize', window._autofitUpdateHandler) |
| | | delete window._autofitUpdateHandler |
| | | } |
| | | // å
³éautofit |
| | | autofit.off() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* å¤é¨ç¼©æ¾å®¹å¨ - å æ®æ´ä¸ªè§å£ */ |
| | | .scale-container { |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | height: calc(100vh - 84px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: #000; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* å
é¨å
容åºå - åºå®è®¾è®¡å°ºå¯¸ */ |
| | | .data-dashboard { |
| | | position: relative; |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | transform-origin: center center; |
| | | } |
| | | |
| | | /* å
¨å±ç¶æçæ ·å¼ - ä½ç¨äºscale-container */ |
| | | .scale-container:fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* Webkitæµè§å¨åç¼ */ |
| | | .scale-container:-webkit-full-screen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* MSæµè§å¨åç¼ */ |
| | | .scale-container:-ms-fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | |
| | | .dashboard-header { |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 86px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | | position: absolute; |
| | | } |
| | | |
| | | .fullscreen-btn { |
| | | position: absolute; |
| | | top: 10px; |
| | | left: 20px; |
| | | width: 40px; |
| | | height: 40px; |
| | | background: rgba(0, 20, 60, 0.8); |
| | | border: 1px solid rgba(0, 212, 255, 0.3); |
| | | border-radius: 6px; |
| | | color: #00d4ff; |
| | | cursor: pointer; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s; |
| | | z-index: 10000; |
| | | } |
| | | |
| | | .fullscreen-btn:hover { |
| | | background: rgba(0, 30, 90, 0.9); |
| | | border-color: rgba(0, 212, 255, 0.5); |
| | | } |
| | | |
| | | .dashboard-content { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: flex; |
| | | gap: 30px; |
| | | padding: 0 30px; |
| | | height: calc(100% - 86px); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* ç¡®ä¿å颿¿è½å¤æ£ç¡®æ¾ç¤º */ |
| | | .left-panel, .center-panel, .right-panel { |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 24px; |
| | | width: 520px; |
| | | } |
| | | |
| | | .center-panel { |
| | | flex: 1.5; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | </style> |
| | |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="140px"> |
| | | label-position="top" |
| | | label-width="150px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="äºæ
ç¼å·" |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <div v-if="operationType === 'edit2' || operationType === 'edit3'" |
| | | class="classtitle">éæ£è¯¦æ
</div> |
| | | <el-descriptions :column="2" |
| | | style="margin-bottom: 20px;" |
| | | v-if="operationType === 'edit2' || operationType === 'edit3'" |
| | | title="éæ£è¯¦æ
" |
| | | border> |
| | | <el-descriptions-item label="鿣ç¼å·"> |
| | | <span class="detail-title">{{ form.hiddenCode }}</span> |
| | |
| | | <span class="detail-title">{{ form.rectifyTime }}</span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | <div class="classtitle" |
| | | v-if="operationType === 'edit3'" |
| | | style="margin-top: 40px;">æ´æ¹è¯¦æ
</div> |
| | | <el-descriptions :column="2" |
| | | style="margin-bottom: 20px;" |
| | | v-if="operationType === 'edit3'" |
| | | title="æ´æ¹è¯¦æ
" |
| | | border> |
| | | <el-descriptions-item label="æ´æ¹å
·ä½æªæ½" |
| | | :span="2"> |
| | |
| | | <span class="detail-title">{{ form2.rectifyActualTime }}</span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | <div class="classtitle" |
| | | v-if="operationType === 'edit2' || operationType === 'edit3'" |
| | | style="margin-top: 40px;margin-bottom: 30px;">éªæ¶æ
åµ</div> |
| | | <el-form :model="form2" |
| | | v-if="operationType === 'edit2'" |
| | | label-width="140px" |
| | |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | .classtitle { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | border-left: 4px solid #409eff; |
| | | padding-left: 12px; |
| | | margin-bottom: 12px; |
| | | } |
| | | </style> |
| | |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="120px"> |
| | | label-position="top" |
| | | label-width="150px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºæ¥é¢æ¡ç¼ç " |
| | | <el-form-item label="åºæ¥é¢æ¡ç¼ç ï¼" |
| | | prop="planCode"> |
| | | <el-input v-model="form.planCode" |
| | | placeholder="请è¾å
¥åºæ¥é¢æ¡ç¼ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºæ¥é¢æ¡åç§°" |
| | | <el-form-item label="åºæ¥é¢æ¡åç§°ï¼" |
| | | prop="planName"> |
| | | <el-input v-model="form.planName" |
| | | placeholder="请è¾å
¥åºæ¥é¢æ¡åç§°" /> |
| | |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="颿¡ç±»å" |
| | | <el-form-item label="颿¡ç±»åï¼" |
| | | prop="planType"> |
| | | <el-select v-model="form.planType" |
| | | placeholder="è¯·éæ©é¢æ¡ç±»å" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="夿³¨" |
| | | <el-form-item label="夿³¨ï¼" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="éç¨èå´" |
| | | <el-form-item label="éç¨èå´ï¼" |
| | | prop="applyScope"> |
| | | <el-checkbox-group v-model="form.applyScope"> |
| | | <el-checkbox label="all">å
¨ä½åå·¥</el-checkbox> |
| | |
| | | <el-checkbox label="tech">ææ¯é¨é¨</el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <el-form-item label="åºæ¥å¤ç½®æ¥éª¤" |
| | | <el-form-item label="åºæ¥å¤ç½®æ¥éª¤ï¼" |
| | | prop="execSteps"> |
| | | <div class="exec-steps-container" |
| | | style="width:100%"> |
| | |
| | | <div v-for="(step, index) in JSON.parse(currentKnowledge.execSteps)" |
| | | :key="index" |
| | | class="exec-step-view"> |
| | | <span class="step-number">{{ index + 1 }}.</span> |
| | | <!-- <span class="step-number">{{ index + 1 }}.</span> --> |
| | | <span class="step-title">{{ step.step }}ï¼</span> |
| | | <span>{{ step.description }}</span> |
| | | </div> |
| | |
| | | |
| | | .exec-steps-container { |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 4px; |
| | | padding: 15px; |
| | | border-radius: 8px; |
| | | padding: 20px; |
| | | background-color: #f9fafc; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .exec-step-item { |
| | | margin-bottom: 10px; |
| | | padding: 10px; |
| | | margin-bottom: 12px; |
| | | padding: 12px; |
| | | background-color: #ffffff; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 4px; |
| | | border-radius: 6px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .exec-step-item:hover { |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | border-color: #c6e2ff; |
| | | } |
| | | |
| | | .step-header { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .exec-step-view { |
| | | margin-bottom: 8px; |
| | | padding-left: 20px; |
| | | margin-bottom: 16px; |
| | | padding-left: 24px; |
| | | position: relative; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .exec-step-view::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 10px; |
| | | top: 20px; |
| | | bottom: -16px; |
| | | width: 2px; |
| | | background-color: #eaeaea; |
| | | } |
| | | |
| | | .exec-step-view:last-child::before { |
| | | display: none; |
| | | } |
| | | |
| | | .step-number { |
| | | position: absolute; |
| | | left: 0; |
| | | top: 0; |
| | | width: 20px; |
| | | height: 20px; |
| | | line-height: 20px; |
| | | text-align: center; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | color: #ffffff; |
| | | background-color: #409eff; |
| | | border-radius: 50%; |
| | | font-size: 12px; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .step-title { |
| | | font-weight: bold; |
| | | margin-right: 5px; |
| | | font-weight: 600; |
| | | margin-right: 8px; |
| | | color: #395a9c; |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | font-style: italic; |
| | | text-align: center; |
| | | padding: 20px; |
| | | background-color: #f8f9fa; |
| | | border-radius: 4px; |
| | | margin-top: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="å¹è®è®°å½"> |
| | | </PageHeader> |
| | | <div class="search_form"> |
| | | <div class="search_item"> |
| | | <span class="search_title">人ååç§°ï¼</span> |
| | | <el-input v-model="searchForm.searchText" |
| | | style="width: 240px" |
| | | placeholder="è¾å
¥å®¢æ·åç§°æç´¢" |
| | | @change="searchName" |
| | | clearable |
| | | prefix-icon="Search" /> |
| | | <el-button type="primary" |
| | | @click="searchName" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div class="search_item"> |
| | | <span class="search_title">年份ï¼</span> |
| | | <el-date-picker v-model="searchForm.invoiceDate" |
| | | type="year" |
| | | @change="searchDate" |
| | | placeholder="鿩年"> |
| | | </el-date-picker> |
| | | <el-button type="primary" |
| | | @click="searchDate" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | <el-button type="primary" |
| | | @click="exportData" |
| | | style="margin-left: 20px;margin-right: 20px">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <div style="display: flex"> |
| | | <div class="table_list"> |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | :row-key="(row) => row.id" |
| | | @row-click="rowClickMethod" |
| | | height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="åç§°" |
| | | prop="nickName" |
| | | show-overflow-tooltip |
| | | width="200" /> |
| | | <el-table-column label="æå±é¨é¨" |
| | | prop="deptNames" |
| | | show-overflow-tooltip |
| | | width="200" /> |
| | | <el-table-column label="èç³»æ¹å¼" |
| | | prop="phonenumber" |
| | | show-overflow-tooltip |
| | | width="200" /> |
| | | <!-- <el-table-column label="email" |
| | | prop="email" |
| | | show-overflow-tooltip |
| | | width="200" /> --> |
| | | <!-- <el-table-column label="åºæ¶éé¢(å
)" |
| | | prop="unReceiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | width="200"> |
| | | <template #default="{ row, column }"> |
| | | <el-text type="danger"> |
| | | {{ formattedNumber(row, column, row.unReceiptPaymentAmount) }} |
| | | </el-text> |
| | | </template> |
| | | </el-table-column> --> |
| | | </el-table> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" /> |
| | | </div> |
| | | <div class="table_list"> |
| | | <el-table :data="receiptRecord" |
| | | border |
| | | :row-key="(row) => row.id" |
| | | height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="å¹è®æ¥æ" |
| | | prop="trainingDate" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="å¹è®å
容" |
| | | prop="trainingContent" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="å¹è®è¯¾æ¶" |
| | | prop="classHour" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="èæ ¸ç»æ" |
| | | prop="examinationResults" |
| | | show-overflow-tooltip> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.examinationResults === 'åæ ¼' ? 'success' : 'danger'"> |
| | | {{ row.examinationResults }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref } from "vue"; |
| | | import { invoiceLedgerSalesAccount } from "@/api/salesManagement/invoiceLedger.js"; |
| | | import { customerInteractions } from "@/api/salesManagement/receiptPayment.js"; |
| | | import Pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { |
| | | safeTrainingDetailListPage, |
| | | safeTrainingDetailExport, |
| | | } from "@/api/safeProduction/safetyTrainingAssessment.js"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const receiptRecord = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const recordPage = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const recordTotal = ref(0); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | searchText: "", |
| | | invoiceDate: "", |
| | | }, |
| | | }); |
| | | const customerId = ref(""); |
| | | const { searchForm } = toRefs(data); |
| | | const originReceiptRecord = ref([]); |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const paginationChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const tableDataCopy = ref([]); |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | userListNoPage({}).then(res => { |
| | | console.log("res", res.data); |
| | | tableData.value = res.data; |
| | | tableDataCopy.value = res.data; |
| | | if (tableData.value.length > 0) { |
| | | customerId.value = tableData.value[0].userId; |
| | | receiptPaymentList(customerId.value); |
| | | tableLoading.value = false; |
| | | } |
| | | }); |
| | | }; |
| | | const exportData = () => { |
| | | safeTrainingDetailExport({ |
| | | userId: customerId.value, |
| | | }) |
| | | .then(res => { |
| | | // å建Blob对象 |
| | | const blob = new Blob([res], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | // å建ä¸è½½é¾æ¥ |
| | | const url = window.URL.createObjectURL(blob); |
| | | const link = document.createElement("a"); |
| | | link.href = url; |
| | | link.download = `è®°å½è¯¦æ
_${tableData.value[0].nickName}.docx`; |
| | | |
| | | // 模æç¹å»ä¸è½½ |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | |
| | | // æ¸
ç临æ¶å¯¹è±¡ |
| | | setTimeout(() => { |
| | | document.body.removeChild(link); |
| | | window.URL.revokeObjectURL(url); |
| | | }, 100); |
| | | |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }) |
| | | .catch(err => { |
| | | console.error("导åºå¤±è´¥:", err); |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·éè¯"); |
| | | }); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | ["invoiceTotal", "receiptPaymentAmount", "unReceiptPaymentAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeMainTable1 = param => { |
| | | var summarizeTable = proxy.summarizeTable( |
| | | param, |
| | | ["invoiceAmount", "receiptAmount", "unReceiptAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | // åæåä¸è¡æ°æ®; |
| | | if (receiptRecord.value?.length > 0) { |
| | | const index = tableData.value.findIndex( |
| | | item => item.id == customerId.value |
| | | ); |
| | | summarizeTable[summarizeTable.length - 1] = |
| | | tableData.value[index].unReceiptPaymentAmount.toFixed(2); |
| | | } else { |
| | | summarizeTable[summarizeTable.length - 1] = 0.0; |
| | | } |
| | | return summarizeTable; |
| | | }; |
| | | const goBack = () => { |
| | | proxy.$router.push({ |
| | | path: "/safeProduction/safetyTrainingAssessment", |
| | | }); |
| | | }; |
| | | const searchName = () => { |
| | | tableData.value = tableDataCopy.value; |
| | | if (searchForm.value.searchText) { |
| | | tableData.value = tableData.value.filter(item => |
| | | item.nickName.includes(searchForm.value.searchText) |
| | | ); |
| | | customerId.value = tableData.value[0].userId; |
| | | } |
| | | |
| | | receiptPaymentList(customerId.value); |
| | | }; |
| | | const dateFormat = (date, format = "yyyy-MM-dd") => { |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | return format.replace("yyyy", year).replace("MM", month).replace("dd", day); |
| | | }; |
| | | const searchDate = () => { |
| | | receiptRecord.value = originReceiptRecordCopy.value; |
| | | console.log("searchForm.value.invoiceDate", searchForm.value.invoiceDate); |
| | | if (searchForm.value.invoiceDate) { |
| | | const year = dateFormat(searchForm.value.invoiceDate, "yyyy"); |
| | | receiptRecord.value = receiptRecord.value.filter(item => { |
| | | console.log("item.trainingDate", item.trainingDate); |
| | | return item.trainingDate.includes(year); |
| | | }); |
| | | } |
| | | }; |
| | | const originReceiptRecordCopy = ref([]); |
| | | const receiptPaymentList = id => { |
| | | const param = { |
| | | userId: id, |
| | | }; |
| | | console.log("param", param); |
| | | safeTrainingDetailListPage(param).then(res => { |
| | | originReceiptRecord.value = res.data.records; |
| | | handlePagination({ page: 1, limit: recordPage.size }); |
| | | recordTotal.value = res.data.length; |
| | | }); |
| | | }; |
| | | |
| | | // æ±æ¬¾è®°å½å表å页 |
| | | const recordPaginationChange = pagination => { |
| | | handlePagination(pagination); |
| | | }; |
| | | |
| | | const rowClickMethod = row => { |
| | | customerId.value = row.userId; |
| | | receiptPaymentList(customerId.value); |
| | | }; |
| | | |
| | | const handlePagination = ({ page, limit }) => { |
| | | recordPage.current = page; |
| | | recordPage.size = limit; |
| | | |
| | | const start = (page - 1) * limit; |
| | | const end = start + limit; |
| | | |
| | | receiptRecord.value = originReceiptRecord.value.slice(start, end); |
| | | originReceiptRecordCopy.value = originReceiptRecord.value.slice(start, end); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .table_list { |
| | | width: 50%; |
| | | } |
| | | .search_back { |
| | | cursor: pointer; |
| | | color: #0f497e; |
| | | } |
| | | .search_item { |
| | | width: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-start; |
| | | margin-right: 20px; |
| | | } |
| | | </style> |
| | |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å¹è®åç§°ï¼</span> |
| | | <el-input v-model="searchForm.name" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å¹è®åç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span class="search_title ml10">å¹è®ç±»åï¼</span> |
| | | <el-select v-model="searchForm.type" |
| | | clearable |
| | | @change="handleQuery" |
| | | style="width: 240px"> |
| | | <el-option v-for="item in knowledgeTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | <span class="search_title">å¹è®æ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.trainingDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | @change="handleQuery" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable /> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px"> |
| | |
| | | <div> |
| | | <el-button type="primary" |
| | | @click="openForm('add')">æ°å¢å¹è®</el-button> |
| | | <el-button type="primary" |
| | | @click="opendetail">å¹è®è®°å½</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <el-tabs v-model="searchForm.state" |
| | | @tab-click="tabhandleQuery"> |
| | | <el-tab-pane label="æªå¼å§" |
| | | :name="0"></el-tab-pane> |
| | | <el-tab-pane label="è¿è¡ä¸" |
| | | :name="1"></el-tab-pane> |
| | | <el-tab-pane label="å·²ç»æ" |
| | | :name="2"></el-tab-pane> |
| | | </el-tabs> |
| | | <!-- state ç¶æ(0ï¼æªå¼å§1ï¼è¿è¡ä¸ï¼2ï¼å·²ç»æ) --> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="120px"> |
| | | label-position="top" |
| | | label-width="150px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¹è®æ¥æ" |
| | |
| | | prop="openingTime"> |
| | | <el-time-picker v-model="form.openingTime" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | value-format="HH:mm:ss" |
| | | format="HH:mm:ss" |
| | | clearable /> |
| | |
| | | prop="endTime"> |
| | | <el-time-picker v-model="form.endTime" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | value-format="HH:mm:ss" |
| | | format="HH:mm:ss" |
| | | clearable /> |
| | |
| | | <el-form-item label="课ç¨å¦å" |
| | | prop="projectCredits"> |
| | | <el-input v-model="form.projectCredits" |
| | | type="number" |
| | | min="0" |
| | | placeholder="请è¾å
¥è¯¾ç¨å¦å" /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </el-dialog> |
| | | <!-- æ¥çç¥è¯è¯¦æ
å¼¹çª --> |
| | | <el-dialog v-model="viewDialogVisible" |
| | | title="å¹è®è¯¦æ
" |
| | | title="ç»ææç»" |
| | | width="900px" |
| | | :close-on-click-modal="false"> |
| | | <div class="knowledge-detail"> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="å¹è®åç§°" |
| | | :span="2"> |
| | | <span class="detail-title">{{ currentKnowledge.name }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å¹è®ç¼ç "> |
| | | {{ currentKnowledge.code }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å¹è®ç±»å"> |
| | | <el-tag type="info"> |
| | | <!-- {{ getTypeLabel(currentKnowledge.type) }} --> |
| | | <div class="classtitle">课ç¨è¯¦æ
</div> |
| | | <el-descriptions size="mini" |
| | | border |
| | | :column="3"> |
| | | <el-descriptions-item label="课ç¨ç¼å·:">{{ currentKnowledge.courseCode }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¹è®å
容:">{{ currentKnowledge.trainingContent }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç¶æ:"> |
| | | <el-tag :type="currentKnowledge.status === 0 ? 'success' : (currentKnowledge.status === 1 ? 'success' : 'info')"> |
| | | {{ currentKnowledge.status === 0 ? 'æªå¼å§' : (currentKnowledge.status === 1 ? 'è¿è¡ä¸' : 'å·²ç»æ') }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æå¨ä½ç½®"> |
| | | {{ currentKnowledge.location }} |
| | | <el-descriptions-item label="å¹è®è®²å¸:"> |
| | | {{ currentKnowledge.trainingLecturer }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç®¡æ§æªæ½"> |
| | | {{ currentKnowledge.controlMeasures }} |
| | | <el-descriptions-item label="å¹è®å¼å§æ¶é´:"> |
| | | {{ currentKnowledge.trainingDate + ' ' + currentKnowledge.openingTime }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="åºåæ°é"> |
| | | {{ currentKnowledge.stockQty }} |
| | | <el-descriptions-item label="å¹è®ç»ææ¶é´:"> |
| | | {{ currentKnowledge.trainingDate + ' ' + currentKnowledge.endTime }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="管æ§è´£ä»»äºº"> |
| | | {{ currentKnowledge.principalUserId }} |
| | | <el-descriptions-item label="å¹è®ç®æ :"> |
| | | {{ currentKnowledge.trainingObjectives }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="责任人èç³»çµè¯"> |
| | | {{ currentKnowledge.principalMobile }} |
| | | <el-descriptions-item label="åå 对象:"> |
| | | {{ currentKnowledge.participants }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="é£é©ç级"> |
| | | <el-tag :type="getTypeTagType(currentKnowledge.riskLevel)"> |
| | | {{ currentKnowledge.riskLevel }} |
| | | <el-descriptions-item label="å¹è®æ¹å¼:"> |
| | | <el-tag type="primary"> |
| | | {{ getTrainingModeLabel(currentKnowledge.trainingMode) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="è§æ ¼ / é£é©æè¿°"> |
| | | {{ currentKnowledge.specInfo }} |
| | | <el-descriptions-item label="å¹è®å°ç¹:"> |
| | | {{ currentKnowledge.placeTraining }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="课æ¶:"> |
| | | {{ currentKnowledge.classHour }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="课ç¨å¦å:"> |
| | | {{ currentKnowledge.projectCredits }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æ¥å人æ°:"> |
| | | {{ currentKnowledge.nums }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="éä»¶å表:"> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="downLoadFile(endform)">éä»¶å表</el-button> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | <!-- <el-divider style="margin: 20px 0;" /> --> |
| | | <div class="classtitle" |
| | | style="margin-top: 40px;margin-bottom: 30px;">课ç¨è¯ä»·</div> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-position="top" |
| | | label-width="150px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¯ä»·äºº:" |
| | | prop="courseCode"> |
| | | <el-input v-model="endform.assessmentUserName" |
| | | disabled |
| | | placeholder="è¯·éæ©è¯ä»·äºº" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¯ä»·æ¶é´:" |
| | | prop="trainingDate"> |
| | | <el-date-picker style="width: 100%" |
| | | disabled |
| | | v-model="endform.assessmentDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èæ ¸æ¹å¼:" |
| | | prop="assessmentMethod"> |
| | | <el-input v-model="endform.assessmentMethod" |
| | | placeholder="è¯·éæ©èæ ¸æ¹å¼" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¬æ¬¡è¯¾ç¨ç»¼åè¯ä»·:" |
| | | prop="comprehensiveAssessment"> |
| | | <el-input v-model="endform.comprehensiveAssessment" |
| | | placeholder="请è¾å
¥æ¬æ¬¡è¯¾ç¨ç»¼åè¯ä»·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å¹è®æè¦:" |
| | | prop="trainingAbstract"> |
| | | <el-input v-model="endform.trainingAbstract" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="请è¾å
¥å¹è®æè¦" /> |
| | | </el-form-item> |
| | | <!-- <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" |
| | | prop="remark"> |
| | | <el-upload v-model:file-list="fileList" |
| | | :action="upload.url" |
| | | multiple |
| | | ref="fileUpload" |
| | | auto-upload |
| | | :headers="upload.headers" |
| | | :before-upload="handleBeforeUpload" |
| | | :on-error="handleUploadError" |
| | | :on-success="handleUploadSuccess" |
| | | :on-remove="handleRemove"> |
| | | <el-button type="primary" |
| | | v-if="operationType !== 'view'">ä¸ä¼ </el-button> |
| | | <template #tip |
| | | v-if="operationType !== 'view'"> |
| | | <div class="el-upload__tip"> |
| | | æä»¶æ ¼å¼æ¯æ |
| | | docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> --> |
| | | </el-form> |
| | | <div class="classtitle" |
| | | style="margin-top: 40px;">èæ ¸å表</div> |
| | | <el-table style="margin-top: 20px;" |
| | | :data="endform.safeTrainingDetailsDtoList" |
| | | border |
| | | fit |
| | | stripe |
| | | highlight-current-row> |
| | | <el-table-column prop="nickName" |
| | | label="å§å" /> |
| | | <el-table-column prop="phonenumber" |
| | | label="çµè¯å·ç " /> |
| | | <el-table-column prop="examinationResults" |
| | | label="èæ ¸ç»æ"> |
| | | <template #default="scope"> |
| | | <el-select v-model="scope.row.examinationResults" |
| | | placeholder="è¯·éæ©èæ ¸ç»æ"> |
| | | <el-option label="åæ ¼" |
| | | value="åæ ¼" /> |
| | | <el-option label="ä¸åæ ¼" |
| | | value="ä¸åæ ¼" /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm2">æäº¤</el-button> |
| | | <el-button @click="viewDialogVisible = false">å
³é</el-button> |
| | | </span> |
| | | </template> |
| | |
| | | safeTrainingFileListPage, |
| | | safeTrainingFileAdd, |
| | | safeTrainingFileDel, |
| | | safeTrainingSign, |
| | | safeTrainingGet, |
| | | safeTrainingSave, |
| | | } from "@/api/safeProduction/safetyTrainingAssessment.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | const userStore = useUserStore(); |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | |
| | | ], |
| | | classHour: [{ required: true, message: "请è¾å
¥è¯¾æ¶", trigger: "blur" }], |
| | | }; |
| | | |
| | | const upload = reactive({ |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | name: "", |
| | | type: "", |
| | | trainingDate: "", |
| | | state: 0, |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | |
| | | ); |
| | | return item ? item.label : val; |
| | | }; |
| | | // 忢tabæ¥è¯¢ |
| | | const tabhandleQuery = val => { |
| | | searchForm.value.state = val.paneName; |
| | | console.log(searchForm.value.state, "searchForm.value.state"); |
| | | |
| | | handleQuery(); |
| | | }; |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | const riskLevelOptions = ref([ |
| | |
| | | { value: "é大é£é©", label: "é大é£é©" }, |
| | | ]); |
| | | |
| | | const fileList = ref([]); |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "课ç¨ç¼å·", |
| | | prop: "courseCode", |
| | | width: 150, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å¹è®æ¥æ", |
| | | prop: "trainingDate", |
| | | width: 120, |
| | | |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å¼å§æ¶é´", |
| | | prop: "openingTime", |
| | | width: 120, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "ç»ææ¶é´", |
| | | prop: "endTime", |
| | | width: 120, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å¹è®ç®æ ", |
| | | prop: "trainingObjectives", |
| | | width: 200, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "åå 对象", |
| | | prop: "participants", |
| | | width: 200, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å¹è®å
容", |
| | | prop: "trainingContent", |
| | | width: 200, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å¹è®è®²å¸", |
| | | prop: "trainingLecturer", |
| | | width: 200, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "项ç®å¦å", |
| | | prop: "projectCredits", |
| | | width: 120, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å¹è®æ¹å¼", |
| | | prop: "trainingMode", |
| | | width: 120, |
| | | showOverflowTooltip: true, |
| | | formatData: params => { |
| | | return getTrainingModeLabel(params); |
| | |
| | | { |
| | | label: "å¹è®å°ç¹", |
| | | prop: "placeTraining", |
| | | width: 200, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "课æ¶", |
| | | prop: "classHour", |
| | | width: 120, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "æ¥å人æ°", |
| | | prop: "nums", |
| | | width: 120, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | width: 300, |
| | | operation: [ |
| | | { |
| | | name: "ç¾å°", |
| | | type: "text", |
| | | disabled: row => row.state !== 1, |
| | | clickFun: row => { |
| | | signIn(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | disabled: row => row.state !== 0, |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "导åº", |
| | | type: "danger", |
| | | type: "text", |
| | | clickFun: row => { |
| | | exportKnowledge(row); |
| | | }, |
| | | color: "#C49000", |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "danger", |
| | | type: "text", |
| | | clickFun: row => { |
| | | downLoadFile(row); |
| | | }, |
| | | color: "#007AFF", |
| | | }, |
| | | |
| | | { |
| | | name: "ç»ææç»", |
| | | type: "text", |
| | | disabled: row => row.state == 0, |
| | | clickFun: row => { |
| | | viewResultDetail(row); |
| | | }, |
| | | }, |
| | | // { |
| | |
| | | const userList = ref([]); |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | getCurrentFactoryName(); |
| | | getList(); |
| | | startAutoRefresh(); |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | }); |
| | | const endform = ref({ |
| | | assessmentUserId: "", //è¯ä»·äºº |
| | | assessmentUserName: "", //è¯ä»·äººå§å |
| | | assessmentMethod: "", //èæ ¸æ¹å¼ |
| | | assessmentDate: "", //è¯ä»·æ¶é´ |
| | | comprehensiveAssessment: "", //综åè¯ä»· |
| | | trainingAbstract: "", //å¹è®æè¦ |
| | | safeTrainingFileList: [], //å¹è®éä»¶ |
| | | safeTrainingDetailsDtoList: [], //èæ ¸ç»æè¯¦æ
|
| | | }); |
| | | const operationType = ref("edit"); |
| | | const viewResultDetail = row => { |
| | | // fileList.value = []; |
| | | operationType.value = "edit"; |
| | | safeTrainingGet({ id: row.id }).then(res => { |
| | | if (res.code === 200) { |
| | | console.log(res.data, "res.data"); |
| | | currentKnowledge.value = JSON.parse(JSON.stringify(res.data)); |
| | | currentKnowledge.value.nums = row.nums; |
| | | viewDialogVisible.value = true; |
| | | endform.value = { ...res.data }; |
| | | endform.value.assessmentUserName = endform.value.assessmentUserName |
| | | ? endform.value.assessmentUserName |
| | | : currentUserName.value; |
| | | endform.value.assessmentUserId = endform.value.assessmentUserId |
| | | ? endform.value.assessmentUserId |
| | | : currentUserId.value; |
| | | endform.value.assessmentDate = dayjs().format("YYYY-MM-DD"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "æ¥è¯¢è¯¦æ
失败"); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | | proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); |
| | | return true; |
| | | } |
| | | // ä¸ä¼ 失败 |
| | | function handleUploadError(err) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | // ä¸ä¼ æååè° |
| | | function handleUploadSuccess(res, file, uploadFiles) { |
| | | proxy.$modal.closeLoading(); |
| | | if (res.code === 200) { |
| | | // ç¡®ä¿ tempFileIds åå¨ä¸ä¸ºæ°ç» |
| | | if (!endform.value.safeTrainingFileList) { |
| | | endform.value.safeTrainingFileList = []; |
| | | } |
| | | endform.value.safeTrainingFileList.push({ |
| | | id: res.data.tempId, |
| | | fileName: res.data.originalName, |
| | | url: res.data.tempPath, |
| | | safeTrainingId: currentKnowledge.value.id, |
| | | }); |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | } |
| | | } |
| | | // ç§»é¤æä»¶ |
| | | function handleRemove(file) { |
| | | if (operationType.value === "edit") { |
| | | let index = endform.value.safeTrainingFileList.findIndex( |
| | | item => item.fileName === file.name |
| | | ); |
| | | if (index !== -1) { |
| | | endform.value.safeTrainingFileList.splice(index, 1); |
| | | } |
| | | } |
| | | } |
| | | const submitForm2 = () => { |
| | | endform.value.safeTrainingDetailsDtoList.forEach((item, index) => { |
| | | if (!item.examinationResults) { |
| | | proxy.$modal.msgError(`è¯·éæ©${item.nickName}çèæ ¸ç»æ`); |
| | | return; |
| | | } |
| | | }); |
| | | console.log(endform.value, "endform.value"); |
| | | proxy.$modal.loading("æ£å¨æäº¤ï¼è¯·ç¨å..."); |
| | | safeTrainingSave(endform.value).then(res => { |
| | | proxy.$modal.closeLoading(); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getList(); |
| | | viewDialogVisible.value = false; |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "æäº¤å¤±è´¥"); |
| | | } |
| | | }); |
| | | }; |
| | | const opendetail = row => { |
| | | proxy.$router.push({ |
| | | path: "/safeProduction/safetyTrainingAssessmentDetail", |
| | | }); |
| | | }; |
| | | |
| | | const signIn = row => { |
| | | ElMessageBox.confirm("确认ç¾å°åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | safeTrainingSign({ |
| | | safeTrainingId: row.id, |
| | | userId: currentUserId.value, |
| | | }).then(res => { |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("ç¾å°æå"); |
| | | getList(); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "ç¾å°å¤±è´¥"); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // å¤çç¨æ·éæ©åå |
| | | const handleUserChange = userId => { |
| | |
| | | const url = window.URL.createObjectURL(blob); |
| | | const link = document.createElement("a"); |
| | | link.href = url; |
| | | link.download = `å¹è®è®°å½_${row.courseCode}.xlsx`; |
| | | link.download = `å¹è®è®°å½_${row.courseCode}.docx`; |
| | | |
| | | // 模æç¹å»ä¸è½½ |
| | | document.body.appendChild(link); |
| | |
| | | const handleSelectionChange = selection => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | const currentUserId = ref(""); |
| | | const currentUserName = ref(""); |
| | | const getCurrentFactoryName = async () => { |
| | | let res = await userStore.getInfo(); |
| | | currentUserId.value = res.user.userId; |
| | | currentUserName.value = res.user.nickName; |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row = null) => { |
| | |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // æ¥çå¹è®è¯¦æ
|
| | | const viewKnowledge = row => { |
| | | currentKnowledge.value = { ...row }; |
| | | viewDialogVisible.value = true; |
| | | }; |
| | | |
| | | // è·åç±»åæ ç¾ç±»å |
| | |
| | | :deep(.danger-row td) { |
| | | color: #e95a66 !important; |
| | | } |
| | | .classtitle { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | border-left: 4px solid #409eff; |
| | | padding-left: 12px; |
| | | margin-bottom: 12px; |
| | | } |
| | | </style> |