feat(cnas,business): 客户满意度模板化表单与年度汇总导出、费用统计增强、路由加载自愈
- 客户满意度:新增/编辑表单按客户 15-1 模板重构(10 项评分、自动计算总分与满意度评定),列表补充地址、联系人、填表日期、总分、评定等字段
- 客户满意度:新增 15-2 年度汇总 Word 导出,每页固定 17 行明细,满页自动续页,统计只在文末
- 费用统计:新增送样时间列、月份区间筛选与委托单优惠字段维护
- 人员管理:精简人员能力标签页
- 路由:动态导入失败自动重试,chunk 加载失败自动刷新
| | |
| | | params: query |
| | | }) |
| | | } |
| | | // æ´æ°è´¹ç¨ä¼æ ä¸å¤æ³¨ |
| | | export function updateOrderFee(data) { |
| | | return request({ |
| | | url: '/insOrderRates/updateFee', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // 导åºå®¢æ·æ»¡æåº¦å¹´åº¦æ±æ» |
| | | export function exportAnnualSummary(query) { |
| | | return request({ |
| | | url: "/clientSatisfaction/exportAnnualSummary", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | |
| | | import { getToken } from '@/utils/auth' |
| | | import { isHttp, isPathMatch } from '@/utils/validate' |
| | | import { isRelogin } from '@/utils/request' |
| | | import { isChunkLoadError, autoReloadForChunkError } from '@/utils/routeLoad' |
| | | import useUserStore from '@/store/modules/user' |
| | | import useLockStore from '@/store/modules/lock' |
| | | import useSettingsStore from '@/store/modules/settings' |
| | |
| | | |
| | | router.onError(error => { |
| | | NProgress.done() |
| | | // 页é¢ä»£ç 模åå 载失败ï¼Vite éå¯/éç¼è¯ãåçåæ§åå
ï¼èªå¨å·æ°æ¢å¤ |
| | | if (isChunkLoadError(error) && autoReloadForChunkError()) { |
| | | console.warn('[router] 页颿¨¡åå 载失败ï¼èªå¨å·æ°éè¯ï¼', error) |
| | | return |
| | | } |
| | | console.error('[router] 页é¢å 载失败ï¼', error) |
| | | ElMessage.error('页é¢å 载失败ï¼è¯·å·æ°é¡µé¢åéè¯') |
| | | }) |
| | |
| | | import Layout from '@/layout/index' |
| | | import ParentView from '@/components/ParentView' |
| | | import InnerLink from '@/layout/components/InnerLink' |
| | | import { importWithRetry } from '@/utils/routeLoad' |
| | | |
| | | // å¹é
viewsé颿æç.vueæä»¶ |
| | | const modules = import.meta.glob('./../../views/**/*.vue') |
| | |
| | | for (const path in modules) { |
| | | const dir = path.split('views/')[1].split('.vue')[0] |
| | | if (dir === view || dir === `${view}/index`) { |
| | | res = () => modules[path]() |
| | | res = () => importWithRetry(modules[path]) |
| | | } |
| | | } |
| | | return res |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // è·¯ç±é¡µé¢ä»£ç 模åå 载失败çèªæå¤çã |
| | | // Vite éå¯/ä¾èµé¢æå»ºå·æ°ãåå¸åæ§åå
å¤±ææ¶ï¼é¡µé¢é¦æ¬¡ import ä¼å¤±è´¥ï¼ |
| | | // ç¨åéè¯ææ´é¡µå·æ°å³å¯æ¢å¤ï¼ä¸éè¦ç¨æ·æå¨æ F5ã |
| | | const CHUNK_LOAD_ERROR = /Failed to fetch dynamically imported module|Importing a module script failed|Loading chunk .* failed|Outdated Optimize Dep/i |
| | | |
| | | export function isChunkLoadError(error) { |
| | | if (!error) return false |
| | | return CHUNK_LOAD_ERROR.test(error.message || String(error)) |
| | | } |
| | | |
| | | export async function importWithRetry(loader, retries = 2, delay = 500) { |
| | | for (let attempt = 0; ; attempt++) { |
| | | try { |
| | | return await loader() |
| | | } catch (error) { |
| | | if (attempt >= retries || !isChunkLoadError(error)) throw error |
| | | await new Promise(resolve => setTimeout(resolve, delay * 2 ** attempt)) |
| | | } |
| | | } |
| | | } |
| | | |
| | | const RELOAD_STATE_KEY = 'lims-chunk-load-reload' |
| | | const RELOAD_WINDOW = 60 * 1000 |
| | | const RELOAD_LIMIT = 3 |
| | | |
| | | // éè¯åä»å¤±è´¥åèªå¨æ´é¡µå·æ°ï¼60 ç§å
æå¤ 3 次ï¼é¿å
çæ£æ
éæ¶æ éå·æ°ã |
| | | export function autoReloadForChunkError() { |
| | | const now = Date.now() |
| | | let state = null |
| | | try { |
| | | state = JSON.parse(sessionStorage.getItem(RELOAD_STATE_KEY) || 'null') |
| | | } catch (e) { |
| | | state = null |
| | | } |
| | | if (!state || now - state.first > RELOAD_WINDOW) { |
| | | state = { first: now, count: 0 } |
| | | } |
| | | if (state.count >= RELOAD_LIMIT) return false |
| | | state.count += 1 |
| | | try { |
| | | sessionStorage.setItem(RELOAD_STATE_KEY, JSON.stringify(state)) |
| | | } catch (e) { |
| | | // éç§æ¨¡å¼çåå¨ä¸å¯ç¨æ¶å¿½ç¥ï¼ä¸å½±åå·æ° |
| | | } |
| | | setTimeout(() => window.location.reload(), 500) |
| | | return true |
| | | } |
| | |
| | | :departId="departId" |
| | | :isDepartment="isDepartment"></job-responsibilities> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="奿©è®°å½" name="奿©è®°å½"> |
| | | <rewardPunishmentRecord v-if="activeName === '奿©è®°å½'" |
| | | :departId="departId" :isDepartment="isDepartment"></rewardPunishmentRecord> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="å¹è®è®°å½" name="å¹è®è®°å½"> |
| | | <training-record v-if="activeName === 'å¹è®è®°å½'" ref="trainingRecord" |
| | | :departId="departId" |
| | |
| | | <personnel-capacity v-if="activeName === '人åè½å'" ref="personnelCapacity" |
| | | :departId="departId" |
| | | :isDepartment="isDepartment"></personnel-capacity> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="æ²éè®°å½" name="æ²éè®°å½"> |
| | | <Communicate v-if="activeName === 'æ²éè®°å½'" ref="communicateRef" :departId="departId" :isDepartment="isDepartment"></Communicate> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | |
| | | import personnelInformation from './tabs/personnel-information.vue' |
| | | import PersonnelTraining from './tabs/personnelTraining'; |
| | | import JobResponsibilities from './tabs/job-responsibilities.vue'; |
| | | import rewardPunishmentRecord from "./tabs/reward-punishment-record.vue"; |
| | | import TrainingRecord from './tabs/training-record.vue'; |
| | | import Mandate from './tabs/mandate.vue'; |
| | | import PersonnelCapacity from './tabs/personnel-capacity.vue'; |
| | | import Communicate from './tabs/communicate.vue' |
| | | import {addDepartmentLims, delDepartmentLims, selectCNSAPersonTree} from "@/api/cnas/personal/personalList"; |
| | | export default { |
| | | name: 'PersonnelInfo', |
| | | components: { |
| | | PersonnelList, personnelInformation, PersonnelTraining, JobResponsibilities, rewardPunishmentRecord, TrainingRecord, Mandate, PersonnelCapacity, Communicate |
| | | PersonnelList, personnelInformation, PersonnelTraining, JobResponsibilities, TrainingRecord, Mandate, PersonnelCapacity |
| | | }, |
| | | data() { |
| | | return { |
| | |
| | | <lims-table :tableData="tableData" :column="yearColumnData" |
| | | @pagination="pagination" :height="'calc(100vh - 20em)'" |
| | | :page="search" :tableLoading="yearLoading"> |
| | | <template #jobResponsibilities="scope"><div> |
| | | <div v-html="changeLine(scope.row.responsibilities)"></div> |
| | | </div></template> |
| | | <template #placeWorkSlot="scope"><div> |
| | | <div v-html="changeLine(scope.row.placeWork)"></div> |
| | | </div></template> |
| | |
| | | <el-input v-model="form.workLicenseRemarks" :disabled="operationType === 'view' || operationType === 'confirm'" size="small"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="å²ä½èè´£ï¼" label-width="110px" prop="jobResponsibilitiesTem" style="height: 450px"> |
| | | <el-checkbox-group v-model="form.jobResponsibilitiesTem" :disabled="operationType === 'view' || operationType === 'confirm'" |
| | | @change="selectResponsibilities"> |
| | | <el-checkbox v-for="city in dict.type.responsibilities_list" :key="city.value" :label="city.label" |
| | | :value="city.value"></el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item class="radio-group" prop="jobResponsibilitiesConformNot" |
| | | style="text-align: center;height: 420px"> |
| | | <el-radio-group v-model="form.jobResponsibilitiesConformNot" :disabled="operationType === 'view' || operationType === 'confirm'"> |
| | | <el-radio :value="1">符å</el-radio> |
| | | <el-radio :value="2">ä¸ç¬¦å</el-radio> |
| | | <el-radio :value="3">ä¸éç¨</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item prop="jobResponsibilitiesRemarks" style="height: 420px"> |
| | | <el-input v-model="form.jobResponsibilitiesRemarks" :disabled="operationType === 'view' || operationType === 'confirm'" |
| | | size="small"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="24"> |
| | | <span style="padding-left: 110px;font-size: 14px">夿³¨ï¼å²ä½è责达å°5项å5项以ä¸ä¸ºç¬¦å</span> |
| | | </el-col> |
| | | <el-col :span="16"> |
| | | <el-form-item label="综åè¯ä»·ï¼" label-width="110px" prop="comprehensiveAssessment"> |
| | | <el-radio-group v-model="form.comprehensiveAssessment" :disabled="operationType === 'view' || operationType === 'confirm'"> |
| | |
| | | computed: { |
| | | ...mapGetters(["userId"]), |
| | | }, |
| | | dicts: ['responsibilities_list'], |
| | | data() { |
| | | return { |
| | | userName: '', |
| | |
| | | label: 'èç§°', |
| | | prop: 'professionalTitle', |
| | | width: '100' |
| | | }, { |
| | | dataType: 'slot', |
| | | label: 'å²ä½èè´£', |
| | | prop: 'jobResponsibilities', |
| | | width: '400', |
| | | slot: 'jobResponsibilities' |
| | | }, { |
| | | dataType: 'tag', |
| | | label: '综åè¯ä»·', |
| | |
| | | yearLoading: false, |
| | | dialogVisible: false, |
| | | verifyGetId: '', |
| | | form: { |
| | | jobResponsibilitiesTem: [] |
| | | }, |
| | | form: {}, |
| | | responsibleOptions: [], |
| | | rules: { |
| | | confirmOperatingPersonnelId: [{ required: true, message: 'è¯·éæ©ç¡®è®¤äºº', trigger: 'change' }], |
| | |
| | | relevantExperienceConformNot: [{ required: true, message: 'è¯·éæ©ç¸å
³ç»éªç¬¦åä¸å¦', trigger: 'change' }], |
| | | workLicense: [{ required: true, message: '请è¾å
¥ä¸å²è¯æ¥æ ¸ç»æ', trigger: 'blur' }], |
| | | workLicenseConformNot: [{ required: true, message: 'è¯·éæ©ä¸å²è¯ç¬¦åä¸å¦', trigger: 'change' }], |
| | | jobResponsibilitiesTem: [{ required: true, message: 'è¯·éæ©å²ä½èè´£æ¥æ ¸ç»æ', trigger: 'change' }], |
| | | jobResponsibilitiesConformNot: [{ required: true, message: 'è¯·éæ©å²ä½è责符åä¸å¦', trigger: 'change' }], |
| | | comprehensiveAssessment: [{ required: true, message: 'è¯·éæ©ç»¼åè¯ä»·', trigger: 'change' }] |
| | | }, |
| | | responsibilities: [] |
| | |
| | | this.search.size = page.limit |
| | | this.refreshTable() |
| | | }, |
| | | selectResponsibilities(arr) { |
| | | let arrTem = []; |
| | | arr.map(val => { |
| | | const index = this.dict.type.responsibilities_list.findIndex(item => item.label === val); |
| | | if (index > -1) { |
| | | arrTem.push(this.dict.type.responsibilities_list[index].value); |
| | | } |
| | | }); |
| | | this.form.jobResponsibilities = arrTem.join(','); |
| | | }, |
| | | changeLine (val) { |
| | | if(val) { |
| | | return val.replace(/,/g, '<br>') |
| | |
| | | this.operationType = type; |
| | | this.title = 'æ°å¢è½å认å®' |
| | | this.dialogVisible = true; |
| | | this.form = { |
| | | jobResponsibilitiesTem: [] |
| | | } |
| | | this.form = {} |
| | | this.form.userId = this.departId |
| | | this.getUserList(); |
| | | |
| | |
| | | this.title = this.operationType === 'edit' ? 'ä¿®æ¹è½å认å®' : 'æ¥çè½å认å®' |
| | | this.dialogVisible = true; |
| | | this.form = {...row} |
| | | if(this.form.responsibilities) { |
| | | this.form['jobResponsibilitiesTem'] = this.form.responsibilities.split(",") |
| | | } else { |
| | | this.form['jobResponsibilitiesTem'] = [] |
| | | } |
| | | this.getUserList(); |
| | | }, |
| | | // ä¿å |
| | |
| | | <div> |
| | | <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="formDia" title="å®¢æ·æ»¡æåº¦è°æ¥è¡¨" |
| | | width="70%" @close="closeFormDia"> |
| | | <div class="survey-intro"> |
| | | å°æ¬ç客æ·ï¼ä¸ºäºä½¿æ¬å®éªå®¤çæå¡æ°´å¹³å¾ä»¥ä¸ææé«ï¼ç¹è®¾æ¤å®¢æ·æ»¡æåº¦è°æ¥è¡¨ï¼ç¦è¯·æ¨æ¨åå¡«åãæä»¬ååæ³¨éæ¨çåé¦ï¼æ¨çæè§å建议ï¼å°ä¸ºæ¬å®éªå®¤çæç»æ¹è¿æä¾éè¦åèï¼ä»èè¾¾å°æ¨ææçæ°´å¹³ãè¯·ä»¥ä¼ çæé®ä»¶æ¹å¼å°æ¤è¡¨å夿¬å®éªå®¤ï¼å¤è°¢åä½ï¼å¨æ¤é常æè°¢æ¨å¯¹æä»¬å·¥ä½çæ¯æï¼ |
| | | </div> |
| | | <table border="1" cellspacing="10" class="tables"> |
| | | <colgroup> |
| | | <col style="width: 12%" /> |
| | | <col style="width: 12%" /> |
| | | <col style="width: 12%" /> |
| | | <col style="width: 10.66%" /> |
| | | <col style="width: 10.66%" /> |
| | | <col style="width: 10.66%" /> |
| | | <col style="width: 10.66%" /> |
| | | <col style="width: 10.66%" /> |
| | | <col style="width: 10.66%" /> |
| | | </colgroup> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>åä½åç§°ï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-input v-model="form.unitName" placeholder="请è¾å
¥å
容" size="small"> |
| | | </el-input> |
| | | </td> |
| | | <td class="td-title"> |
| | | <p>æ¥æï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-date-picker v-model="form.fillDate" format="YYYY-MM-DD" placeholder="éæ©æ¥æ" size="small" type="date" |
| | | value-format="YYYY-MM-DD"> |
| | | </el-date-picker> |
| | | <td class="section-title" colspan="9"> |
| | | <p>客 æ· å¡« å</p> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>客æ·ï¼</p> |
| | | </td> |
| | | <td class="td-info"> |
| | | <td class="td-info" colspan="8"> |
| | | <el-select v-model="form.orderId" filterable clearable size="small" style="width: 100%" |
| | | popper-class="customer-select-popper" placeholder="è¯·éæ©å®¢æ·ï¼å·²å®æå§æåï¼" @change="onCustomerChange"> |
| | | <el-option v-for="opt in customerOptions" :key="opt.id" :label="opt.company" :value="opt.id"> |
| | |
| | | </el-option> |
| | | </el-select> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>é¨é¨ï¼</p> |
| | | <p>å°åï¼</p> |
| | | </td> |
| | | <td class="td-info"> |
| | | <el-input v-model="form.department" placeholder="请è¾å
¥å
容" size="small"> |
| | | </el-input> |
| | | </td> |
| | | <td class="td-title"> |
| | | <p>èç³»çµè¯ï¼</p> |
| | | </td> |
| | | <td class="td-info"> |
| | | <el-input v-model="form.contactNumber" placeholder="请è¾å
¥å
容" size="small"> |
| | | <td class="td-info" colspan="8"> |
| | | <el-input v-model="form.address" placeholder="请è¾å
¥å
容" size="small"> |
| | | </el-input> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>æå¡æåº¦ï¼</p> |
| | | <p>è系人ï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-radio-group v-model="form.serviceAttitude"> |
| | | <el-radio :value="0">满æ</el-radio> |
| | | <el-radio :value="1">ä¸è¬</el-radio> |
| | | <el-radio :value="2">䏿»¡æ</el-radio> |
| | | </el-radio-group> |
| | | <td class="td-info" colspan="3"> |
| | | <el-input v-model="form.contactPerson" placeholder="请è¾å
¥å
容" size="small"> |
| | | </el-input> |
| | | </td> |
| | | <td class="td-title"> |
| | | <p>建议ï¼</p> |
| | | <p>å¡«è¡¨æ¥æï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-input v-model="form.serviceAttitudeSuggestion" placeholder="请è¾å
¥å
容" size="small"> |
| | | </el-input> |
| | | <td class="td-info" colspan="4"> |
| | | <el-date-picker v-model="form.fillDate" format="YYYY-MM-DD" placeholder="éæ©æ¥æ" size="small" type="date" |
| | | value-format="YYYY-MM-DD" style="width: 100%"> |
| | | </el-date-picker> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>ææ¯è½åï¼</p> |
| | | <td class="td-title" colspan="3"> |
| | | <p>è¯å项</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-radio-group v-model="form.technicalCompetence"> |
| | | <el-radio :value="0">满æ</el-radio> |
| | | <el-radio :value="1">ä¸è¬</el-radio> |
| | | <el-radio :value="2">䏿»¡æ</el-radio> |
| | | </el-radio-group> |
| | | <td class="score-head" colspan="2">ä¼ç§</td> |
| | | <td class="score-head" colspan="2">ä¸è¬</td> |
| | | <td class="score-head" colspan="2">å·®</td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title" colspan="3"> |
| | | <p>ï¼åå¼ï¼10â5åï¼</p> |
| | | </td> |
| | | <td class="td-title"> |
| | | <p>建议ï¼</p> |
| | | <td class="score-head" v-for="s in scoreOptions" :key="'head-' + s">{{ s }}</td> |
| | | </tr> |
| | | <tr v-for="(q, qi) in questions" :key="q.key"> |
| | | <td class="td-title question-title" colspan="3"> |
| | | <p>{{ q.text }}</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-input v-model="form.technicalCompetenceSuggestion" placeholder="请è¾å
¥å
容" size="small"> |
| | | </el-input> |
| | | <td class="score-cell" v-for="s in scoreOptions" :key="s"> |
| | | <el-radio v-model="form[q.key]" :value="s" class="score-radio"></el-radio> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>æ£æµå·¥ä½ï¼</p> |
| | | <td class="td-title" colspan="3"> |
| | | <p>*æ¨è®¤ä¸ºæ¬å®éªå®¤è¿æåªäºä¸è¶³ï¼æå¯¹æä»¬çå·¥ä½è¿æåªäºæè§å建议ï¼è¯·è¯´æï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-radio-group v-model="form.inspectionWork"> |
| | | <el-radio :value="0">满æ</el-radio> |
| | | <el-radio :value="1">ä¸è¬</el-radio> |
| | | <el-radio :value="2">䏿»¡æ</el-radio> |
| | | </el-radio-group> |
| | | </td> |
| | | <td class="td-title"> |
| | | <p>建议ï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-input v-model="form.inspectionWorkSuggestion" placeholder="请è¾å
¥å
容" size="small"> |
| | | </el-input> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>æ¶è´¹åçæ§ï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-radio-group v-model="form.reasonableFees"> |
| | | <el-radio :value="0">满æ</el-radio> |
| | | <el-radio :value="1">ä¸è¬</el-radio> |
| | | <el-radio :value="2">䏿»¡æ</el-radio> |
| | | </el-radio-group> |
| | | </td> |
| | | <td class="td-title"> |
| | | <p>建议ï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="2"> |
| | | <el-input v-model="form.reasonableFeesSuggestion" placeholder="请è¾å
¥å
容" size="small"> |
| | | </el-input> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>æ¨å¯¹æä»¬ç叿ï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="5"> |
| | | <td class="td-info" colspan="6"> |
| | | <el-input v-model="form.remark" :rows="4" placeholder="请è¾å
¥å
容" size="small" type="textarea"> |
| | | </el-input> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="section-title" colspan="9"> |
| | | <p>å® éª å®¤ å¡« å</p> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>æ»åï¼</p> |
| | | </td> |
| | | <td class="td-info"> |
| | | <span class="score-result">{{ scoreComplete ? totalScore : '--' }}</span> å |
| | | </td> |
| | | <td class="td-title" colspan="2"> |
| | | <p>满æåº¦è¯å®ï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="5"> |
| | | {{ scoreComplete ? satisfactionText : 'å¡«åè¯ååèªå¨è®¡ç®' }} |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>æè§ï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="8"> |
| | | <el-input v-model="form.labOpinion" :rows="3" placeholder="请è¾å
¥å
容" size="small" type="textarea"> |
| | | </el-input> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>è´¨éè´è´£äººï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="3"> |
| | | <el-input v-model="form.qualityManager" placeholder="请è¾å
¥å
容" size="small"> |
| | | </el-input> |
| | | </td> |
| | | <td class="td-title"> |
| | | <p>æ¥æï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="4"> |
| | | <el-date-picker v-model="form.labDate" format="YYYY-MM-DD" placeholder="éæ©æ¥æ" size="small" type="date" |
| | | value-format="YYYY-MM-DD" style="width: 100%"> |
| | | </el-date-picker> |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td class="td-title"> |
| | | <p>ç
§çï¼</p> |
| | | </td> |
| | | <td class="td-info" colspan="5"> |
| | | <td class="td-info" colspan="8"> |
| | | <lims-attachment ref="photoAttachment" inline defer-upload accept=".jpg,.jpeg,.png,.gif" |
| | | :main-id="form.clientSatisfactionId || ''" upload-url="/clientSatisfaction/uploadAttachment" |
| | | select-url="/clientSatisfaction/selectAttachments" delete-url="/clientSatisfaction/deleteAttachment" |
| | |
| | | } from '@/api/cnas/systemManagement/customerSatisfaction.js' |
| | | import { selectCompletedOrders } from '@/api/business/productOrder' |
| | | import limsAttachment from '@/components/Attachment/lims-attachment.vue' |
| | | export default { |
| | | name: 'formDialog', |
| | | // import å¼å
¥çç»ä»¶éè¦æ³¨å
¥å°å¯¹è±¡ä¸æè½ä½¿ç¨ |
| | | components: { limsAttachment }, |
| | | data() { |
| | | // è¿éåæ¾æ°æ® |
| | | return { |
| | | formDia: false, |
| | | form: { |
| | | unitName: '', |
| | | fillDate: '', |
| | | userName: '', |
| | | department: '', |
| | | contactNumber: '', |
| | | serviceAttitude: '', |
| | | serviceAttitudeSuggestion: '', |
| | | technicalCompetence: '', |
| | | technicalCompetenceSuggestion: '', |
| | | inspectionWork: '', |
| | | inspectionWorkSuggestion: '', |
| | | reasonableFees: '', |
| | | reasonableFeesSuggestion: '', |
| | | remark: '', |
| | | |
| | | const QUESTIONS = [ |
| | | { key: 'score1', text: '1.ä¸å¡/æ¥å¾
人åçæå¡æåº¦' }, |
| | | { key: 'score2', text: '2.ä¸å¡/æ¥å¾
人åçæ²éè½å' }, |
| | | { key: 'score3', text: '3.ä¸å¡/æ¥å¾
人åçååºé度æåå¤åæ¶æ§' }, |
| | | { key: 'score4', text: '4.åºå
·æ£æµæ¥åçåæ¶æ§' }, |
| | | { key: 'score5', text: '5.æ£æµç»æçåç¡®æ§' }, |
| | | { key: 'score6', text: '6.æ£æµæ¥åå
容表达æ¸
æ°ï¼ç¼æåçå¹¶æäºçè§£' }, |
| | | { key: 'score7', text: '7.å¤çæè¯æ±æ¨çåæ¶æ§' }, |
| | | { key: 'score8', text: '8.对æè¯æ±æ¨å¤ççç»æ' }, |
| | | { key: 'score9', text: '9.对æå¸æå¡çä¸ä¸æ§è¯ä»·' }, |
| | | { key: 'score10', text: '10.对æå¸æå¡çæ»ä½æ»¡æç¨åº¦è¯ä»·' } |
| | | ] |
| | | |
| | | function createEmptyForm() { |
| | | const form = { |
| | | clientSatisfactionId: '', |
| | | orderId: '', |
| | | unitName: '', |
| | | userName: '', |
| | | address: '', |
| | | contactPerson: '', |
| | | fillDate: '', |
| | | remark: '', |
| | | labOpinion: '', |
| | | qualityManager: '', |
| | | labDate: null, |
| | | entrustCode: '', |
| | | sampleName: '' |
| | | }, |
| | | // 客æ·ä¸æé项ï¼åªå
å«"å·²ä¸è¿å§æåä¸å·²å®æ"çæ°æ® |
| | | } |
| | | QUESTIONS.forEach(q => { |
| | | form[q.key] = '' |
| | | }) |
| | | return form |
| | | } |
| | | |
| | | export default { |
| | | name: 'formDialog', |
| | | components: { limsAttachment }, |
| | | data() { |
| | | return { |
| | | formDia: false, |
| | | form: createEmptyForm(), |
| | | questions: QUESTIONS, |
| | | scoreOptions: [10, 9, 8, 7, 6, 5], |
| | | customerOptions: [], |
| | | operationType: '', |
| | | editLoad: false, |
| | | |
| | | }; |
| | | }, |
| | | // æ¹æ³éå |
| | | computed: { |
| | | scoreComplete() { |
| | | return this.questions.every(q => this.form[q.key] !== '' && this.form[q.key] !== null && this.form[q.key] !== undefined) |
| | | }, |
| | | totalScore() { |
| | | return this.questions.reduce((sum, q) => sum + (Number(this.form[q.key]) || 0), 0) |
| | | }, |
| | | satisfactionText() { |
| | | if (!this.scoreComplete) { |
| | | return '' |
| | | } |
| | | return this.totalScore >= 80 ? '满æï¼80åï¼å«ï¼ä»¥ä¸ï¼' : '䏿»¡æï¼80å以ä¸ï¼' |
| | | } |
| | | }, |
| | | methods: { |
| | | async openDia(type, row) { |
| | | this.formDia = true; |
| | | this.operationType = type |
| | | await this.loadCustomerOptions() |
| | | if (this.operationType === 'edit') { |
| | | this.form = { ...row } |
| | | this.form = { ...createEmptyForm(), ...row } |
| | | if (!this.form.unitName) { |
| | | this.form.unitName = this.form.userName || '' |
| | | } |
| | | // èæ°æ®æ²¡æå
³èå§æåï¼åæä¸ä¸ªé项æåæ¥çå¼åæ¾åºæ¥ï¼ä¸å¼ºå¶éé |
| | | if (row.orderId == null && row.userName) { |
| | | this.customerOptions.unshift({ |
| | |
| | | } |
| | | }, |
| | | resetForm() { |
| | | this.form = { |
| | | unitName: '', |
| | | fillDate: '', |
| | | userName: '', |
| | | department: '', |
| | | contactNumber: '', |
| | | serviceAttitude: '', |
| | | serviceAttitudeSuggestion: '', |
| | | technicalCompetence: '', |
| | | technicalCompetenceSuggestion: '', |
| | | inspectionWork: '', |
| | | inspectionWorkSuggestion: '', |
| | | reasonableFees: '', |
| | | reasonableFeesSuggestion: '', |
| | | remark: '', |
| | | clientSatisfactionId: '', |
| | | orderId: '', |
| | | entrustCode: '', |
| | | sampleName: '' |
| | | } |
| | | this.form = createEmptyForm() |
| | | }, |
| | | // ä¸ææ°æ®ï¼å端åªè¿å"å·²æ£éª"çå§æåï¼state=4ï¼ |
| | | loadCustomerOptions() { |
| | |
| | | this.$message.warning('è¯·éæ©å®¢æ·') |
| | | return |
| | | } |
| | | if (!this.form.unitName) { |
| | | this.$message.warning('请填ååä½åç§°') |
| | | if (!this.form.fillDate) { |
| | | this.$message.warning('è¯·éæ©å¡«è¡¨æ¥æ') |
| | | return |
| | | } |
| | | if (!this.form.department) { |
| | | this.$message.warning('请填åé¨é¨') |
| | | for (let i = 0; i < this.questions.length; i++) { |
| | | const score = this.form[this.questions[i].key] |
| | | if (score === '' || score === null || score === undefined) { |
| | | this.$message.warning('è¯·å®æç¬¬' + (i + 1) + 'é¢è¯å') |
| | | return |
| | | } |
| | | } |
| | | this.editLoad = true |
| | | const payload = { ...this.form } |
| | |
| | | margin: 10vh auto 50px !important; |
| | | } |
| | | |
| | | .survey-intro { |
| | | margin-top: 10px; |
| | | font-size: 13px; |
| | | line-height: 22px; |
| | | color: #303133; |
| | | } |
| | | |
| | | .tables { |
| | | table-layout: fixed; |
| | | width: 100%; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .section-title { |
| | | height: 34px; |
| | | text-align: center; |
| | | font-size: 14px; |
| | | font-weight: bold; |
| | | background: #f5f7fa; |
| | | padding: 6px; |
| | | } |
| | | |
| | | .td-title { |
| | | height: 40px; |
| | | width: 170px; |
| | | text-align: center; |
| | | font-size: 14px; |
| | | word-wrap: break-word; |
| | |
| | | padding: 6px; |
| | | } |
| | | |
| | | .question-title { |
| | | text-align: left; |
| | | } |
| | | |
| | | .td-info { |
| | | padding: 6px; |
| | | } |
| | | |
| | | .td-info1 { |
| | | display: inline-block; |
| | | width: 100%; |
| | | text-align: left; |
| | | .score-head { |
| | | text-align: center; |
| | | font-size: 14px; |
| | | word-wrap: break-word; |
| | | white-space: normal; |
| | | padding: 6px; |
| | | } |
| | | |
| | | .score-cell { |
| | | text-align: center; |
| | | height: 38px; |
| | | } |
| | | |
| | | .score-cell :deep(.el-radio) { |
| | | margin-right: 0; |
| | | } |
| | | |
| | | .score-result { |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .customer-card-sub { |
| | |
| | | <div class="search"> |
| | | <div> |
| | | <el-form :model="searchForm" ref="searchForm" size="small" :inline="true"> |
| | | <el-form-item label="åä½åç§°" prop="unitName" v-if="tabIndex === '0'"> |
| | | <el-form-item label="客æ·åç§°" prop="unitName" v-if="tabIndex === '0'"> |
| | | <el-input v-model="searchForm.unitName" clearable size="small"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="æä»¶åç§°" prop="fileName" v-if="tabIndex === '1'"> |
| | |
| | | </div> |
| | | <div> |
| | | <el-button v-if="tabIndex === '0'" size="small" type="primary" @click="openFormDia('add')">æ° å¢</el-button> |
| | | <el-button v-if="tabIndex === '0'" size="small" type="primary" @click="openExportDialog">导 åº</el-button> |
| | | <el-upload v-if="tabIndex === '1'" ref='upload' :action="action" :before-upload="beforeUpload" |
| | | :headers="uploadHeader" :on-error="onError" :on-success="handleSuccessUp" :show-file-list="false" |
| | | accept='.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'> |
| | |
| | | <filePreview v-if="lookDialogVisible" :currentFile="{}" :fileUrl="javaApi + '/word/' + currentInfo.fileUrl" |
| | | style="height: 90vh;overflow-y: auto;" /> |
| | | </el-dialog> |
| | | <el-dialog v-model="exportDialogVisible" title="导åºå¹´åº¦æ±æ»" width="360px" :close-on-click-modal="false"> |
| | | <el-form label-width="80px"> |
| | | <el-form-item label="æ±æ»å¹´åº¦"> |
| | | <el-date-picker v-model="exportYear" type="year" format="YYYYå¹´" value-format="YYYY" placeholder="éæ©å¹´åº¦" |
| | | style="width: 100%"></el-date-picker> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="exportDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" :loading="exportLoading" @click="handleExport">导 åº</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | <FormDialog v-if="formDialog" ref="formDialog" @closeFormDia="closeFormDia"></FormDialog> |
| | | <lims-attachment ref="attachmentDialog" title="满æåº¦éä»¶" name-field="unitName" id-param="clientSatisfactionId" |
| | | upload-url="/clientSatisfaction/uploadAttachment" select-url="/clientSatisfaction/selectAttachments" |
| | |
| | | pageAnalyseFile, |
| | | delClientSatisfaction, |
| | | delAnalyseFile, |
| | | exportAnnualSummary, |
| | | } from '@/api/cnas/systemManagement/customerSatisfaction.js' |
| | | |
| | | export default { |
| | |
| | | tabIndex: '0', |
| | | tableColumn: [ |
| | | { |
| | | label: 'åä½åç§°', |
| | | label: '客æ·åç§°', |
| | | prop: 'unitName', |
| | | minWidth: '140' |
| | | }, |
| | | { |
| | | label: 'å°å', |
| | | prop: 'address', |
| | | minWidth: '150' |
| | | }, |
| | | { |
| | | label: 'è系人', |
| | | prop: 'contactPerson', |
| | | minWidth: '100' |
| | | }, |
| | | { |
| | | label: 'æ¥æ', |
| | | label: 'å¡«è¡¨æ¥æ', |
| | | prop: 'fillDate', |
| | | minWidth: '100' |
| | | }, |
| | | { |
| | | label: '客æ·', |
| | | prop: 'userName', |
| | | minWidth: '100' |
| | | label: 'æ»å', |
| | | prop: 'totalScore', |
| | | minWidth: '80', |
| | | formatData: (val) => (val === null || val === undefined || val === '') ? '--' : val |
| | | }, |
| | | { |
| | | label: '满æåº¦è¯å®', |
| | | prop: 'satisfactionResult', |
| | | minWidth: '110', |
| | | dataType: 'tag', |
| | | formatData: (val) => { |
| | | if (val === null || val === undefined || val === '') { |
| | | return '--' |
| | | } |
| | | return Number(val) === 0 ? '满æ' : '䏿»¡æ' |
| | | }, |
| | | formatType: (val) => { |
| | | if (val === null || val === undefined || val === '') { |
| | | return 'info' |
| | | } |
| | | return Number(val) === 0 ? 'success' : 'danger' |
| | | } |
| | | }, |
| | | { |
| | | label: 'å§æåç¼å·', |
| | | prop: 'entrustCode', |
| | | minWidth: '100' |
| | | minWidth: '110' |
| | | }, |
| | | { |
| | | label: 'æ ·ååç§°', |
| | | prop: 'sampleName', |
| | | minWidth: '100' |
| | | }, |
| | | { |
| | | label: 'é¨é¨', |
| | | prop: 'department', |
| | | minWidth: '100' |
| | | }, |
| | | { |
| | | label: 'èç³»çµè¯', |
| | | prop: 'contactNumber', |
| | | minWidth: '100' |
| | | minWidth: '120' |
| | | }, |
| | | { |
| | | label: 'åå»ºæ¥æ', |
| | | prop: 'createTime', |
| | | minWidth: '100', |
| | | minWidth: '160', |
| | | }, |
| | | { |
| | | dataType: 'action', |
| | | minWidth: '80', |
| | | label: 'æä½', |
| | | operation: [ |
| | | { |
| | |
| | | }, |
| | | { |
| | | dataType: 'action', |
| | | minWidth: '50', |
| | | label: 'æä½', |
| | | operation: [ |
| | | { |
| | |
| | | upLoading: false, |
| | | currentInfo: {}, |
| | | lookDialogVisible: false, |
| | | exportDialogVisible: false, |
| | | exportYear: String(new Date().getFullYear()), |
| | | exportLoading: false, |
| | | }; |
| | | }, |
| | | mounted() { |
| | |
| | | this.formDialog = false |
| | | this.searchList() |
| | | }, |
| | | // æå¼å¹´åº¦æ±æ»å¯¼åºå¼¹çª |
| | | openExportDialog() { |
| | | this.exportYear = String(new Date().getFullYear()) |
| | | this.exportDialogVisible = true |
| | | }, |
| | | // 导åºå¹´åº¦æ±æ» Word |
| | | handleExport() { |
| | | if (!this.exportYear) { |
| | | this.$message.warning('è¯·éæ©æ±æ»å¹´åº¦') |
| | | return |
| | | } |
| | | this.exportLoading = true |
| | | exportAnnualSummary({ year: this.exportYear }).then(async data => { |
| | | // åç«¯æ æ°æ®æ¶è¿åçæ¯ JSONï¼éè½¬æææ¬æç¤ºï¼é¿å
åæåæä»¶ |
| | | if (data && data.type && data.type.indexOf('json') !== -1) { |
| | | let msg = '导åºå¤±è´¥' |
| | | try { |
| | | const resText = await data.text() |
| | | const rspObj = JSON.parse(resText) |
| | | msg = rspObj.msg || msg |
| | | } catch (e) { |
| | | console.log('err---', e) |
| | | } |
| | | this.exportLoading = false |
| | | this.$message.error(msg) |
| | | return |
| | | } |
| | | const blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }) |
| | | this.$download.saveAs(blob, 'å®¢æ·æ»¡æåº¦å¹´åº¦æ±æ»è¡¨_' + this.exportYear + 'å¹´.docx') |
| | | this.exportLoading = false |
| | | this.exportDialogVisible = false |
| | | }).catch(err => { |
| | | console.log('err---', err) |
| | | this.exportLoading = false |
| | | }) |
| | | }, |
| | | // éç½®æ¥è¯¢æ¡ä»¶ |
| | | resetSearchForm() { |
| | | this.searchForm.unitName = ''; |
| | |
| | | <div class="search"> |
| | | <div> |
| | | <el-form :model="entity" ref="entity" size="small" :inline="true"> |
| | | <el-form-item label="éæ ·æ¶é´"> |
| | | <el-date-picker v-model="monthRange" type="monthrange" size="small" |
| | | value-format="YYYY-MM" range-separator="è³" |
| | | start-placeholder="å¼å§æä»½" end-placeholder="ç»ææä»½" |
| | | style="width: 240px" @change="refreshTable()"></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="å§æç¼å·" prop="entrustCode"> |
| | | <el-input size="small" placeholder="请è¾å
¥" clearable v-model="entity.entrustCode" |
| | | @keyup.enter="refreshTable()"></el-input> |
| | |
| | | </div> |
| | | <div> |
| | | <lims-table :tableData="tableData" :column="column" |
| | | :height="'calc(100vh - 250px)'" @pagination="pagination" |
| | | :page="page" :tableLoading="tableLoading"></lims-table> |
| | | :height="'calc(100vh - 300px)'" @pagination="pagination" |
| | | :isSelection="true" :handleSelectionChange="handleSelectionChange" |
| | | :page="page" :tableLoading="tableLoading"> |
| | | <template #isDiscount="{ row }"> |
| | | <el-select v-model="row.isDiscount" size="small" style="width: 80px" |
| | | @change="handleDiscountChange(row)"> |
| | | <el-option label="æ " :value="0"></el-option> |
| | | <el-option label="æ" :value="1"></el-option> |
| | | </el-select> |
| | | </template> |
| | | <template #discountAmount="{ row }"> |
| | | <el-input-number v-model="row.discountAmount" size="small" style="width: 100%" |
| | | :controls="false" :precision="2" :min="0" |
| | | :disabled="row.isDiscount !== 1" |
| | | placeholder="请è¾å
¥" @change="handleAmountChange(row)"></el-input-number> |
| | | </template> |
| | | <template #feeRemark="{ row }"> |
| | | <el-input v-model="row.feeRemark" size="small" maxlength="500" |
| | | @change="handleFeeRemarkChange(row)"></el-input> |
| | | </template> |
| | | </lims-table> |
| | | </div> |
| | | <div class="sum-bar"> |
| | | <span>å·²å¾é <b class="sum-count">{{ selectedRows.length }}</b> æ¡</span> |
| | | <span>æ»éé¢ï¼<b class="sum-amount">Â¥{{ selectedTotal }}</b></span> |
| | | </div> |
| | | <viewInfoDia ref="viewInfoDia" v-if="viewInfoDia"></viewInfoDia> |
| | | </div> |
| | |
| | | <script> |
| | | import limsTable from "@/components/Table/lims-table.vue"; |
| | | import viewInfoDia from "./components/viewInfoDia.vue"; |
| | | import {selectRatesPage} from "@/api/business/insOrderRates"; |
| | | import {selectRatesPage, updateOrderFee} from "@/api/business/insOrderRates"; |
| | | import {selectCustomPageList} from "@/api/system/customer"; |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | export default { |
| | | components: {limsTable,viewInfoDia}, |
| | |
| | | sample: null, |
| | | company: null, |
| | | }, |
| | | monthRange: [], |
| | | tableData: [], |
| | | tableLoading: false, |
| | | column: [ |
| | | { label: 'ä¸åæ¶é´', prop: 'createTime', width: 150 }, |
| | | { label: 'éæ ·æ¶é´', prop: 'sampleDate', width: 120 }, |
| | | { label: 'å§æç¼å·', prop: 'entrustCode', width: 160 }, |
| | | { label: 'æ ·ååç§°', prop: 'sample', width: 160 }, |
| | | { label: 'å§æåä½', prop: 'company' }, |
| | |
| | | }, |
| | | }, |
| | | { label: 'å§æäºº', prop: 'prepareUser' }, |
| | | { label: 'ç产åä½', prop: 'production' }, |
| | | { label: 'æ»ä»·', prop: 'totalPrice' }, |
| | | { label: 'æ»ä»·', prop: 'totalPrice', width: 110 }, |
| | | { |
| | | label: 'æ¯å¦ä¼æ ', |
| | | prop: 'isDiscount', |
| | | width: 100, |
| | | dataType: 'slot', |
| | | slot: 'isDiscount' |
| | | }, |
| | | { |
| | | label: '伿 åéé¢', |
| | | prop: 'discountAmount', |
| | | width: 130, |
| | | dataType: 'slot', |
| | | slot: 'discountAmount' |
| | | }, |
| | | { |
| | | label: '夿³¨', |
| | | prop: 'feeRemark', |
| | | minWidth: 160, |
| | | dataType: 'slot', |
| | | slot: 'feeRemark' |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | fixed: "right", |
| | |
| | | current: 1 |
| | | }, |
| | | companyOptions: [], |
| | | viewInfoDia: false |
| | | viewInfoDia: false, |
| | | selectedRows: [] |
| | | } |
| | | }, |
| | | computed: { |
| | | // å¾éåè®¡ï¼æä¼æ ä¸å¡«äºä¼æ åéé¢çæä¼æ åéé¢ç®ï¼å¦åææ»ä»·ç® |
| | | selectedTotal() { |
| | | const total = this.selectedRows.reduce((sum, row) => { |
| | | const useDiscount = row.isDiscount === 1 && row.discountAmount !== null && row.discountAmount !== undefined |
| | | const amount = Number(useDiscount ? row.discountAmount : row.totalPrice) |
| | | return sum + (isFinite(amount) ? amount : 0) |
| | | }, 0) |
| | | return total.toFixed(2) |
| | | } |
| | | }, |
| | | mounted() { |
| | |
| | | methods :{ |
| | | refreshTable() { |
| | | this.tableLoading = true |
| | | selectRatesPage({ ...this.page, ...this.entity }).then(res => { |
| | | const [startMonth, endMonth] = this.monthRange || [] |
| | | selectRatesPage({ ...this.page, ...this.entity, startMonth, endMonth }).then(res => { |
| | | this.tableLoading = false |
| | | this.tableData = res.data.records |
| | | this.tableData = (res.data.records || []).map(item => { |
| | | item.isDiscount = item.isDiscount === 1 ? 1 : 0 |
| | | return item |
| | | }) |
| | | this.page.total = res.data.total |
| | | this.selectedRows = [] |
| | | }).catch(err => { |
| | | this.tableLoading = false |
| | | }) |
| | |
| | | // éç½® |
| | | refresh() { |
| | | this.resetForm('entity') |
| | | this.monthRange = [] |
| | | this.refreshTable() |
| | | }, |
| | | // æ¥è¯¢å§æåä½ä¸ææ¡æ°æ® |
| | |
| | | this.page.size = page.limit |
| | | this.refreshTable() |
| | | }, |
| | | // å¾éè¡åå |
| | | handleSelectionChange(selection) { |
| | | this.selectedRows = selection |
| | | }, |
| | | // 忢æ¯å¦ä¼æ |
| | | handleDiscountChange(row) { |
| | | if (row.isDiscount === 1) { |
| | | // éâæâæ¶å¸¦åºæ»ä»·ï¼ç¨æ·åæ¹æå®é
伿 åéé¢ |
| | | if (row.discountAmount === null || row.discountAmount === undefined || row.discountAmount === '') { |
| | | row.discountAmount = this.amountOf(row.totalPrice) |
| | | } |
| | | } else { |
| | | row.discountAmount = null |
| | | } |
| | | this.saveFee(row) |
| | | }, |
| | | // 伿 åé颿¹å®ä¿å |
| | | handleAmountChange(row) { |
| | | if (row.isDiscount !== 1) { |
| | | row.discountAmount = null |
| | | return |
| | | } |
| | | if (row.discountAmount === null || row.discountAmount === undefined || row.discountAmount === '') { |
| | | ElMessage.warning('æä¼æ æ¶è¯·å¡«å伿 åéé¢') |
| | | return |
| | | } |
| | | this.saveFee(row) |
| | | }, |
| | | // 夿³¨æ¹å®ä¿å |
| | | handleFeeRemarkChange(row) { |
| | | this.saveFee(row) |
| | | }, |
| | | amountOf(value) { |
| | | const num = Number(value) |
| | | if (!isFinite(num)) return 0 |
| | | return Math.round(num * 100) / 100 |
| | | }, |
| | | saveFee(row) { |
| | | updateOrderFee({ |
| | | id: row.id, |
| | | isDiscount: row.isDiscount, |
| | | discountAmount: row.isDiscount === 1 ? row.discountAmount : null, |
| | | feeRemark: row.feeRemark || null |
| | | }).catch(() => { |
| | | this.refreshTable() |
| | | }) |
| | | }, |
| | | // æ¥ç详æ
|
| | | openDia (row) { |
| | | this.viewInfoDia = true |
| | |
| | | </script> |
| | | <style scoped> |
| | | .search { |
| | | height: 46px; |
| | | min-height: 46px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | .sum-bar { |
| | | position: sticky; |
| | | bottom: 0; |
| | | z-index: 10; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 24px; |
| | | margin-top: 8px; |
| | | padding: 10px 16px; |
| | | background: #fff; |
| | | border-top: 1px solid var(--el-border-color-light); |
| | | box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06); |
| | | } |
| | | .sum-count { |
| | | color: var(--el-color-primary); |
| | | } |
| | | .sum-amount { |
| | | color: var(--el-color-danger); |
| | | font-size: 16px; |
| | | } |
| | | </style> |