feat: 综合业务模块页面重构与交互完善
- feat(sales): 完善招投标、诉讼管理、发货台账及收付款台账的前端页面支持
- feat(project): 新增项目配置页面,补充项目归档与合同列表弹窗交互
- fix(equipment): 优化设备台账、校准、维修(新增验收/维修弹窗)及保养计划的表单交互
- fix(office): 优化审批列表及费用报销的详情面板(DetailPanel)与相关工具函数
- fix(quality): 调整原材、过程、成品检验的表单视图(formDia)及指标维护页面
- fix(inventory): 优化车辆管理、车辆加油及发料管理的页面展示逻辑
| | |
| | | |
| | | package-lock.json |
| | | yarn.lock |
| | | |
| | | /graphify-out |
| | | build_graph.py |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | export function listStatusChangeRecord(query) { |
| | | return request({ |
| | | url: '/device/statusChangeRecord/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // 车è¾ä¿¡æ¯ |
| | | export function listVehicleInfo(query) { |
| | | return request({ |
| | | url: '/vehicle/info/listPage', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | export function getWarningList() { |
| | | return request({ |
| | | url: '/vehicle/info/warningList', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | export function addVehicleInfo(data) { |
| | | return request({ |
| | | url: '/vehicle/info', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | export function updateVehicleInfo(data) { |
| | | return request({ |
| | | url: '/vehicle/info', |
| | | method: 'put', |
| | | data: data |
| | | }) |
| | | } |
| | | export function delVehicleInfo(id) { |
| | | return request({ |
| | | url: '/vehicle/info/' + id, |
| | | method: 'delete' |
| | | }) |
| | | } |
| | | |
| | | // å æ²¹å¡ |
| | | export function listFuelCardPage(query) { |
| | | return request({ |
| | | url: '/vehicle/fuelCard/listPage', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | export function listAllFuelCard() { |
| | | return request({ |
| | | url: '/vehicle/fuelCard/listAll', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | export function addFuelCard(data) { |
| | | return request({ |
| | | url: '/vehicle/fuelCard', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | export function updateFuelCard(data) { |
| | | return request({ |
| | | url: '/vehicle/fuelCard', |
| | | method: 'put', |
| | | data: data |
| | | }) |
| | | } |
| | | export function delFuelCard(id) { |
| | | return request({ |
| | | url: '/vehicle/fuelCard/' + id, |
| | | method: 'delete' |
| | | }) |
| | | } |
| | | |
| | | // å æ²¹è®°å½ |
| | | export function listFuelRecord(query) { |
| | | return request({ |
| | | url: '/vehicle/fuelRecord/listPage', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | export function addFuelRecord(data) { |
| | | return request({ |
| | | url: '/vehicle/fuelRecord', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | export function updateFuelRecord(data) { |
| | | return request({ |
| | | url: '/vehicle/fuelRecord', |
| | | method: 'put', |
| | | data: data |
| | | }) |
| | | } |
| | | export function delFuelRecord(id) { |
| | | return request({ |
| | | url: '/vehicle/fuelRecord/' + id, |
| | | method: 'delete' |
| | | }) |
| | | } |
| | | |
| | | |
| | | // å页æ¥è¯¢å æ²¹å¡å
å¼è®°å½ |
| | | export function listRecharge(query) { |
| | | return request({ |
| | | url: '/vehicle/fuelCardRecharge/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // æ°å¢å æ²¹å¡å
å¼ |
| | | export function addRecharge(data) { |
| | | return request({ |
| | | url: '/vehicle/fuelCardRecharge/add', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | // å é¤å æ²¹å¡å
å¼ |
| | | export function delRecharge(id) { |
| | | return request({ |
| | | url: '/vehicle/fuelCardRecharge/' + id, |
| | | method: 'delete' |
| | | }) |
| | | } |
| | | |
| | | |
| | | // æäº¤å æ²¹è®°å½ |
| | | export function submitFuelRecord(id) { |
| | | return request({ |
| | | url: '/vehicle/fuelRecord/submit/' + id, |
| | | method: 'post' |
| | | }) |
| | | } |
| | | |
| | | // æ¤åå æ²¹è®°å½ |
| | | export function revokeFuelRecord(id) { |
| | | return request({ |
| | | url: '/vehicle/fuelRecord/revoke/' + id, |
| | | method: 'post' |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // æ¥è¯¢é¡¹ç®ç®¡çå
¨å±é
ç½® |
| | | export function getConfig() { |
| | | return request({ |
| | | url: '/projectManagement/config', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // æ´æ°é¡¹ç®ç®¡çå
¨å±é
ç½® |
| | | export function updateConfig(data) { |
| | | return request({ |
| | | url: '/projectManagement/config', |
| | | method: 'put', |
| | | data: data |
| | | }) |
| | | } |
| | |
| | | method: 'post' |
| | | }) |
| | | } |
| | | |
| | | // è·å项ç®å½æ¡£éä»¶ |
| | | export function getProjectAttachments(id) { |
| | | return request({ |
| | | url: `/projectManagement/info/attachments/${id}`, |
| | | method: 'get' |
| | | }) |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | // æäº¤åæææ£éª |
| | | // ä¸è½½åæææ£éªWord |
| | | export function downloadQualityInspect(data) { |
| | | return request({ |
| | | url: '/quality/qualityInspect/down', |
| | |
| | | }) |
| | | } |
| | | |
| | | // ä¸è½½åæææ£éªPDF |
| | | export function downloadQualityInspectPdf(data) { |
| | | return request({ |
| | | url: '/quality/qualityInspect/downPdf', |
| | | method: 'post', |
| | | data: data, |
| | | responseType: "blob", |
| | | }) |
| | | } |
| | | |
| | |
| | | }); |
| | | } |
| | | |
| | | // 项ç®å¾æ¥æ±æ»ï¼é¡¶å±ï¼ |
| | | export function projectTransactions(query) { |
| | | return request({ |
| | | url: "/metricStatistics/projectTransactions", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 客æ·å¾æ¥å表 |
| | | export function customewTransactions(query) { |
| | | return request({ |
| | |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å¤§çæ»è§ |
| | | export function transactionsSummary() { |
| | | return request({ |
| | | url: "/metricStatistics/transactionsSummary", |
| | | method: "get" |
| | | }); |
| | | } |
| | |
| | | })
|
| | | }
|
| | | // æ¥è¯¢ç¨æ·å表
|
| | | export function userListNoPageByTenantId() {
|
| | | export function userListNoPageByTenantId(params) {
|
| | | return request({
|
| | | url: '/system/user/userListNoPageByTenantId',
|
| | | method: 'get'
|
| | | method: 'get',
|
| | | params
|
| | | })
|
| | | }
|
| | |
|
| | |
| | | @click.stop> |
| | | <template v-for="(o, key) in item.operation" |
| | | :key="key"> |
| | | <el-button v-show="o.type != 'upload'" |
| | | <el-dropdown v-if="o.type == 'dropdown'" |
| | | v-show="o.showHide ? o.showHide(scope.row) : true" |
| | | trigger="click" |
| | | @command="(command) => o.commandFun(command, scope.row)" |
| | | style="margin-left: 12px; margin-right: 12px; vertical-align: middle;"> |
| | | <el-button :disabled="isOperationDisabled(o, scope.row)" |
| | | :plain="o.plain" |
| | | type="primary" |
| | | :style="{ |
| | | color: getOperationColor(o, scope.row), |
| | | fontWeight: 'bold', |
| | | }" |
| | | link> |
| | | {{ o.name }}<el-icon class="el-icon--right"><ArrowDown /></el-icon> |
| | | </el-button> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item v-for="(dItem, dIdx) in (typeof o.dropdownItems === 'function' ? o.dropdownItems(scope.row) : o.dropdownItems)" |
| | | :key="dIdx" |
| | | :command="dItem.command" |
| | | :disabled="dItem.disabled ? dItem.disabled(scope.row) : false"> |
| | | {{ dItem.label }} |
| | | </el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | <el-button v-show="o.type != 'upload' && o.type != 'dropdown'" |
| | | v-if="o.showHide ? o.showHide(scope.row) : true" |
| | | :disabled="isOperationDisabled(o, scope.row)" |
| | | :plain="o.plain" |
| | |
| | | import pagination from "./Pagination.vue"; |
| | | import { computed, ref, inject, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { ArrowDown } from "@element-plus/icons-vue"; |
| | | |
| | | // è·åå
¨å±ç uploadHeader |
| | | const { proxy } = getCurrentInstance(); |
| | |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | ></PIMTable> |
| | | > |
| | | <template #validUntilSlot="{ row }"> |
| | | <span>{{ row.validUntil ? row.validUntil.split(' ')[0] : '' }}</span> |
| | | <el-tag v-if="isWarning(row.validUntil)" type="warning" class="ml10">å³å°å°æ</el-tag> |
| | | <el-tag v-else-if="isOverdue(row.validUntil)" type="danger" class="ml10">已龿</el-tag> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <calibration-dia ref="calibrationDia" @close="handleQuery"></calibration-dia> |
| | | </div> |
| | |
| | | <script setup> |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import {ElMessageBox, ElMessage} from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import CalibrationDia from "@/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue"; |
| | | import {ledgerRecordListPage, ledgerRecordDelete} from "@/api/equipmentManagement/calibration.js"; |
| | |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "æææ", |
| | | label: "æææ(天)", |
| | | prop: "valid", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "æææè³", |
| | | prop: "validUntil", |
| | | dataType: "slot", |
| | | slot: "validUntilSlot", |
| | | width: 160, |
| | | }, |
| | | { |
| | | label: "溯æº/æ ¸æ¥ç±»å", |
| | | prop: "traceabilityType", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "宿½æºæ", |
| | | prop: "implementingAgency", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "è¯ä¹¦ç¼å·", |
| | | prop: "certificateNumber", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "æ ¸å¿ç»æ", |
| | | prop: "coreResult", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "ç»æç¡®è®¤", |
| | | prop: "resultConfirmation", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "确认人", |
| | | prop: "confirmerName", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "å½å
¥äºº", |
| | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | ledgerRecordListPage({ ...searchForm.value, ...page }).then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | tableLoading.value = false; |
| | | }).catch((err) => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }); |
| | | }; |
| | | |
| | | const isWarning = (validUntil) => { |
| | | if (!validUntil) return false; |
| | | const now = dayjs().startOf('day'); |
| | | const until = dayjs(validUntil).startOf('day'); |
| | | const diffDays = until.diff(now, 'day'); |
| | | return diffDays >= 0 && diffDays <= 30; |
| | | }; |
| | | |
| | | const isOverdue = (validUntil) => { |
| | | if (!validUntil) return false; |
| | | const now = dayjs().startOf('day'); |
| | | const until = dayjs(validUntil).startOf('day'); |
| | | return until.isBefore(now); |
| | | }; |
| | | |
| | | // æå¼æ£å®æ ¡åå¼¹æ¡ |
| | |
| | | <template> |
| | | <el-form :model="form" label-width="120px" :rules="formRules" ref="formRef"> |
| | | <el-divider content-position="left">åºç¡ä¿¡æ¯</el-divider> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤ç¼å·" prop="uniqueCode"> |
| | | <div style="display: flex; gap: 10px; width: 100%;"> |
| | | <el-checkbox |
| | | v-model="form.autoGenerateUniqueCode" |
| | | v-if="operationType === 'add'" |
| | | >èªå¨çæ |
| | | </el-checkbox> |
| | | <el-input |
| | | v-model="form.uniqueCode" |
| | | :placeholder="form.autoGenerateUniqueCode ? 'ä¿ååèªå¨çæ' : '请è¾å
¥è®¾å¤ç¼å·'" |
| | | :disabled="form.autoGenerateUniqueCode || operationType === 'view'" |
| | | clearable |
| | | /> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤åç§°" prop="deviceName"> |
| | | <el-input v-model="form.deviceName" placeholder="请è¾å
¥è®¾å¤åç§°" /> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierName"> |
| | | <el-input v-model="form.supplierName" placeholder="请è¾å
¥ä¾åºå" /> |
| | | <el-form-item label="åºåç¼å·" prop="factoryNumber"> |
| | | <el-input v-model="form.factoryNumber" placeholder="请è¾å
¥åºåç¼å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥åä½" /> |
| | | <el-form-item label="责任人" prop="responsiblePerson"> |
| | | <el-input v-model="form.responsiblePerson" placeholder="请è¾å
¥è´£ä»»äºº" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¯ç¨ææ§" prop="isDepr"> |
| | | <el-switch v-model="form.isDepr" :active-value="1" :inactive-value="2" /> |
| | | <el-form-item label="å»ºæ¡£æ¥æ" prop="filingDate"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.filingDate" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©å»ºæ¡£æ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.isDepr === 1"> |
| | | <el-form-item label="æ¯å¹´ææ§éé¢" prop="annualDepreciationAmount"> |
| | | <el-input-number |
| | | :step="0.01" |
| | | :min="0" |
| | | </el-row> |
| | | |
| | | <el-divider content-position="left">éè´ä¿¡æ¯</el-divider> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierName"> |
| | | <el-input v-model="form.supplierName" placeholder="请è¾å
¥ä¾åºå" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¶é åå" prop="manufacturer"> |
| | | <el-input v-model="form.manufacturer" placeholder="请è¾å
¥å¶é åå" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è´ç½®æ¥æ" prop="purchaseDate"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.annualDepreciationAmount" |
| | | placeholder="请è¾å
¥æ¯å¹´ææ§éé¢" |
| | | v-model="form.purchaseDate" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©è´ç½®æ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§åå¼" prop="originalAssetValue"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" |
| | | v-model="form.originalAssetValue" |
| | | placeholder="请è¾å
¥èµäº§åå¼" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥åä½" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ°é" prop="number"> |
| | | <el-input-number :min="1" style="width: 100%" |
| | | v-model="form.number" |
| | | disabled |
| | | disabled |
| | | placeholder="请è¾å
¥æ°é" |
| | | @change="mathNum" |
| | | /> |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <!-- <el-col :span="12"> |
| | | <el-form-item label="å½å
¥äºº" prop="createUser"> |
| | | <el-input v-model="form.createUser" placeholder="请è¾å
¥å½å
¥äºº" /> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="left">ææ¯åæ°</el-divider> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="ä¸»è¦ææ¯åæ°" prop="technicalParameters"> |
| | | <el-input type="textarea" :rows="3" v-model="form.technicalParameters" placeholder="请è¾å
¥ä¸»è¦ææ¯åæ°" /> |
| | | </el-form-item> |
| | | </el-col> --> |
| | | </el-col> |
| | | <el-col :span="24"> |
| | | <el-form-item label="主è¦ç¨é" prop="mainPurpose"> |
| | | <el-input type="textarea" :rows="3" v-model="form.mainPurpose" placeholder="请è¾å
¥ä¸»è¦ç¨é" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="left">è¿è¡ç®¡ç</el-divider> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥æ¥æ" prop="createTime"> |
| | | <el-form-item label="设å¤ç¶æ" prop="status"> |
| | | <el-select |
| | | v-model="form.status" |
| | | placeholder="è¯·éæ©" |
| | | :disabled="operationType === 'edit'" |
| | | style="width: 100%" |
| | | > |
| | | <el-option label="åç¨" :value="0" /> |
| | | <el-option label="å¯ç¨" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¯ç¨æ¥æ" prop="activationDate"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.createTime" |
| | | v-model="form.activationDate" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©å½å
¥æ¥æ" |
| | | placeholder="è¯·éæ©å¯ç¨æ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¢è®¡è¿è¡æ¶é´" prop="planRuntimeTime"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.planRuntimeTime" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©å½å
¥æ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¢è®¡è¿è¡æ¶é´" prop="planRuntimeTime"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.planRuntimeTime" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©é¢è®¡è¿è¡æ¶é´" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¯ç¨ææ§" prop="isDepr"> |
| | | <el-switch v-model="form.isDepr" :active-value="1" :inactive-value="2" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.isDepr === 1"> |
| | | <el-form-item label="æ¯å¹´ææ§éé¢" prop="annualDepreciationAmount"> |
| | | <el-input-number |
| | | :step="0.01" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="form.annualDepreciationAmount" |
| | | placeholder="请è¾å
¥æ¯å¹´ææ§éé¢" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="24"> |
| | | <el-form-item label="设å¤å¾ç" prop="storageBlobDTOs"> |
| | | <AttachmentUploadImage |
| | |
| | | |
| | | <script setup> |
| | | import useFormData from "@/hooks/useFormData"; |
| | | // import useUserStore from "@/store/modules/user"; |
| | | import { getLedgerById } from "@/api/equipmentManagement/ledger"; |
| | | import dayjs from "dayjs"; |
| | | import { |
| | |
| | | name: "设å¤å°è´¦è¡¨å", |
| | | }); |
| | | const formRef = ref(null); |
| | | const operationType = ref(''); |
| | | const operationType = ref('add'); |
| | | // 设å¤ç±»ååºå®é项 |
| | | const deviceTypeOptions = ref([ |
| | | 'ç产设å¤', |
| | |
| | | 'å
¶ä»è®¾å¤' |
| | | ]); |
| | | const formRules = { |
| | | deviceName: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | deviceModel: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | type: [{ required: true, trigger: "change", message: "è¯·éæ©æè¾å
¥è®¾å¤ç±»å" }], |
| | | supplierName: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | unit: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | number: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | taxIncludingPriceUnit: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | taxRate: [{ required: true, trigger: "change", message: "请è¾å
¥" }], |
| | | planRuntimeTime: [{ required: true, trigger: "change", message: "è¯·éæ©" }], |
| | | annualDepreciationAmount: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (form.isDepr === 1 && (value === undefined || value === null || value === '')) { |
| | | callback(new Error('å¯ç¨ææ§æ¶ï¼è¯·è¾å
¥æ¯å¹´ææ§éé¢')); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | trigger: "blur" |
| | | } |
| | | ], |
| | | uniqueCode: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (!form.autoGenerateUniqueCode && (value === undefined || value === null || value === '')) { |
| | | callback(new Error('请è¾å
¥è®¾å¤ç¼å·')); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | trigger: "blur" |
| | | } |
| | | ], |
| | | deviceName: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | deviceModel: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | type: [{ required: true, trigger: "change", message: "è¯·éæ©æè¾å
¥è®¾å¤ç±»å" }], |
| | | supplierName: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | unit: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | number: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | taxIncludingPriceUnit: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | taxRate: [{ required: true, trigger: "change", message: "请è¾å
¥" }], |
| | | planRuntimeTime: [{ required: true, trigger: "change", message: "è¯·éæ©" }], |
| | | status: [{ required: true, trigger: "change", message: "è¯·éæ©" }], |
| | | annualDepreciationAmount: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (form.isDepr === 1 && (value === undefined || value === null || value === '')) { |
| | | callback(new Error('å¯ç¨ææ§æ¶ï¼è¯·è¾å
¥æ¯å¹´ææ§éé¢')); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | trigger: "blur" |
| | | } |
| | | ], |
| | | } |
| | | |
| | | const { form, resetForm } = useFormData({ |
| | | // Basic Info |
| | | autoGenerateUniqueCode: true, |
| | | uniqueCode: undefined, // å¯ä¸æ§ç¼å· |
| | | deviceName: undefined, // 设å¤åç§° |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | deviceBrand: undefined, // 设å¤åç |
| | | type: undefined, // 设å¤ç±»å |
| | | supplierName: undefined, // ä¾åºå |
| | | factoryNumber: undefined, // åºåç¼å· |
| | | storageLocation: undefined, // åæ¾ä½ç½® |
| | | isDepr: 2, // æ¯å¦å¯ç¨ææ§ 1-æ¯ 2-å¦ |
| | | annualDepreciationAmount: undefined, // æ¯å¹´ææ§éé¢ |
| | | responsiblePerson: undefined, // 责任人 |
| | | filingDate: dayjs().format("YYYY-MM-DD"), // å»ºæ¡£æ¥æ |
| | | |
| | | // Purchase Info |
| | | supplierName: undefined, // ä¾åºå |
| | | manufacturer: undefined, // å¶é åå |
| | | purchaseDate: undefined, // è´ç½®æ¥æ |
| | | originalAssetValue: undefined, // èµäº§åå¼ |
| | | unit: undefined, // åä½ |
| | | number: 1, // æ°é |
| | | taxIncludingPriceUnit: undefined, // å«ç¨åä»· |
| | | taxIncludingPriceTotal: undefined, // å«ç¨æ»ä»· |
| | | taxRate: undefined, // ç¨ç |
| | | unTaxIncludingPriceTotal: undefined, // ä¸å«ç¨æ»ä»· |
| | | // createUser: useUserStore().nickName, // å½å
¥äºº |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // å½å
¥æ¥æ |
| | | planRuntimeTime: dayjs().format("YYYY-MM-DD"), // å½å
¥æ¥æ |
| | | |
| | | // Technical Specs |
| | | technicalParameters: undefined, // ä¸»è¦ææ¯åæ° |
| | | mainPurpose: undefined, // 主è¦ç¨é |
| | | |
| | | // Operation Info |
| | | status: 1, // 设å¤ç¶æ (0åç¨ 1å¯ç¨) |
| | | activationDate: undefined, // å¯ç¨æ¥æ |
| | | planRuntimeTime: dayjs().format("YYYY-MM-DD"), // é¢è®¡è¿è¡æ¶é´ |
| | | isDepr: 2, // æ¯å¦å¯ç¨ææ§ 1-æ¯ 2-å¦ |
| | | annualDepreciationAmount: undefined, // æ¯å¹´ææ§éé¢ |
| | | storageBlobDTOs: undefined, // 设å¤å¾çæäº¤ |
| | | storageBlobVOs: undefined, // 设å¤å¾çå±ç¤º |
| | | }); |
| | |
| | | }); |
| | | |
| | | const loadForm = async (id) => { |
| | | if (id) { |
| | | operationType.value = 'edit' |
| | | } |
| | | if (id) { |
| | | operationType.value = 'edit'; |
| | | } else { |
| | | operationType.value = 'add'; |
| | | } |
| | | const { code, data } = await getLedgerById(id); |
| | | if (code == 200) { |
| | | form.autoGenerateUniqueCode = false; |
| | | form.uniqueCode = data.uniqueCode; |
| | | form.deviceName = data.deviceName; |
| | | form.deviceModel = data.deviceModel; |
| | | form.deviceBrand = data.deviceBrand; |
| | | form.type = data.type; |
| | | form.supplierName = data.supplierName; |
| | | form.factoryNumber = data.factoryNumber; |
| | | form.storageLocation = data.storageLocation; |
| | | form.isDepr = data.isDepr; |
| | | form.annualDepreciationAmount = data.annualDepreciationAmount; |
| | | form.responsiblePerson = data.responsiblePerson; |
| | | form.filingDate = data.filingDate; |
| | | |
| | | form.supplierName = data.supplierName; |
| | | form.manufacturer = data.manufacturer; |
| | | if (data.purchaseDate) { |
| | | form.purchaseDate = dayjs(data.purchaseDate).format('YYYY-MM-DD'); |
| | | } |
| | | form.originalAssetValue = data.originalAssetValue; |
| | | form.unit = data.unit; |
| | | form.number = 1; |
| | | form.taxIncludingPriceUnit = data.taxIncludingPriceUnit; |
| | | form.taxIncludingPriceTotal = data.taxIncludingPriceTotal; |
| | | form.taxRate = data.taxRate; |
| | | form.unTaxIncludingPriceTotal = data.unTaxIncludingPriceTotal; |
| | | form.createTime = data.createTime; |
| | | |
| | | form.technicalParameters = data.technicalParameters; |
| | | form.mainPurpose = data.mainPurpose; |
| | | |
| | | form.status = data.status != null ? data.status : 1; |
| | | if (data.activationDate) { |
| | | form.activationDate = dayjs(data.activationDate).format('YYYY-MM-DD'); |
| | | } |
| | | form.isDepr = data.isDepr; |
| | | form.annualDepreciationAmount = data.annualDepreciationAmount; |
| | | |
| | | // é¢è®¡è¿è¡æ¶é´ï¼å端è¿åå转为 YYYY-MM-DD ä»¥ä¾¿æ¥æéæ©å¨æ£ç¡®å±ç¤º |
| | | if (data.planRuntimeTime) { |
| | | form.planRuntimeTime = dayjs(data.planRuntimeTime).format('YYYY-MM-DD'); |
| | |
| | | // éç½®è¡¨åæ°æ®åæ ¡éªç¶æ |
| | | const resetFormAndValidate = () => { |
| | | resetForm(); |
| | | operationType.value = 'add'; // é置为æ°å¢ |
| | | clearValidate(); |
| | | }; |
| | | |
| | |
| | | resetForm, |
| | | clearValidate, |
| | | resetFormAndValidate, |
| | | formRef, |
| | | formRef, |
| | | }); |
| | | </script> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="设å¤ç¼å·"> |
| | | <el-input |
| | | v-model="filters.uniqueCode" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥è®¾å¤ç¼å·" |
| | | clearable |
| | | @change="getTableData" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="设å¤åç§°"> |
| | | <el-input |
| | | v-model="filters.deviceName" |
| | |
| | | <!-- 详æ
å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="detailDialogVisible" title="设å¤å°è´¦è¯¦æ
" width="60%" draggable> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="设å¤ç¼å·">{{ detailData.uniqueCode }}</el-descriptions-item> |
| | | <el-descriptions-item label="设å¤åç§°">{{ detailData.deviceName }}</el-descriptions-item> |
| | | <el-descriptions-item label="è§æ ¼åå·">{{ detailData.deviceModel }}</el-descriptions-item> |
| | | <el-descriptions-item label="设å¤åç">{{ detailData.deviceBrand }}</el-descriptions-item> |
| | |
| | | <el-button @click="detailDialogVisible = false">å
³é</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- ç¶æåæ´å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="statusDialog.visible" title="设å¤ç¶æåæ´ç³è¯·" width="500px" draggable> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="å½åç¶æ"> |
| | | <el-tag :type="statusDialog.row?.status == 1 ? 'success' : 'danger'"> |
| | | {{ statusDialog.row?.status == 1 ? 'å¯ç¨' : 'åç¨' }} |
| | | </el-tag> |
| | | </el-form-item> |
| | | <el-form-item label="ç®æ ç¶æ" required> |
| | | <el-radio-group v-model="statusDialog.targetStatus"> |
| | | <el-radio :label="1">å¯ç¨</el-radio> |
| | | <el-radio :label="0">åç¨</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="åæ´åå " required> |
| | | <el-input type="textarea" :rows="3" v-model="statusDialog.reason" placeholder="请è¾å
¥ç¶æåæ´åå " /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="statusDialog.visible = false">åæ¶</el-button> |
| | | <el-button type="primary" :loading="statusDialog.loading" @click="submitStatusChange">æäº¤å®¡æ¹</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- ç¶æåæ´è®°å½å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="recordDialog.visible" title="设å¤ç¶æåæ´è®°å½" width="800px" draggable> |
| | | <el-table v-loading="recordDialog.loading" :data="recordDialog.list" border> |
| | | <el-table-column label="åæ´æ¶é´" prop="createTime" width="160" /> |
| | | <el-table-column label="åç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.originalStatus == 1 ? 'success' : 'danger'"> |
| | | {{ scope.row.originalStatus == 1 ? 'å¯ç¨' : 'åç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç®æ ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.targetStatus == 1 ? 'success' : 'danger'"> |
| | | {{ scope.row.targetStatus == 1 ? 'å¯ç¨' : 'åç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åæ´åå " prop="reason" show-overflow-tooltip /> |
| | | <el-table-column label="ç³è¯·äºº" prop="createUserName" width="100" /> |
| | | <el-table-column label="审æ¹ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.approvalStatus === 'PENDING'" type="warning">审æ¹ä¸</el-tag> |
| | | <el-tag v-else-if="scope.row.approvalStatus === 'APPROVED'" type="success">å·²éè¿</el-tag> |
| | | <el-tag v-else-if="scope.row.approvalStatus === 'REJECTED'" type="danger">已驳å</el-tag> |
| | | <span v-else>{{ scope.row.approvalStatus }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="驳ååå " prop="rejectReason" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span v-if="scope.row.approvalStatus === 'REJECTED'">{{ scope.row.rejectReason || '--' }}</span> |
| | | <span v-else>--</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div style="display: flex; justify-content: flex-end; margin-top: 15px;"> |
| | | <el-pagination |
| | | v-model:current-page="recordDialog.queryParams.pageNum" |
| | | v-model:page-size="recordDialog.queryParams.pageSize" |
| | | :total="recordDialog.total" |
| | | layout="total, prev, pager, next" |
| | | @current-change="getRecordList" |
| | | /> |
| | | </div> |
| | | <template #footer> |
| | | <el-button @click="recordDialog.visible = false">å
³é</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { usePaginationApi } from "@/hooks/usePaginationApi"; |
| | | // import { Search } from "@element-plus/icons-vue"; |
| | | import { getLedgerPage, delLedger, getLedgerById } from "@/api/equipmentManagement/ledger"; |
| | | import { listStatusChangeRecord } from "@/api/device/statusChangeRecord"; |
| | | import { saveApprovalInstance } from "@/api/officeProcessAutomation/approvalInstance"; |
| | | import { listApprovalTemplate, getApprovalTemplateDetail, TEMPLATE_TYPE_CUSTOM } from "@/api/officeProcessAutomation/approvalTemplate"; |
| | | import { onMounted, getCurrentInstance, ref, reactive } from "vue"; |
| | | import Modal from "./Modal.vue"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | |
| | | } = usePaginationApi( |
| | | getLedgerPage, |
| | | { |
| | | uniqueCode: undefined, |
| | | deviceName: undefined, |
| | | deviceModel: undefined, |
| | | supplierName: undefined, |
| | |
| | | entryDateEnd: undefined, |
| | | }, |
| | | [ |
| | | { |
| | | label: "设å¤ç¼å·", |
| | | prop: "uniqueCode", |
| | | }, |
| | | { |
| | | label: "设å¤åç§°", |
| | | prop: "deviceName", |
| | |
| | | prop: "number", |
| | | }, |
| | | { |
| | | label: "设å¤ç¶æ", |
| | | prop: "status", |
| | | width: 100, |
| | | dataType: "tag", |
| | | formatData: (v) => v == 1 ? 'å¯ç¨' : 'åç¨', |
| | | formatType: (v) => v == 1 ? 'success' : 'danger', |
| | | }, |
| | | { |
| | | label: "å¯ç¨æ¥æ", |
| | | prop: "activationDate", |
| | | width: 120, |
| | | formatData: (v) => { |
| | | if (!v) return ''; |
| | | if (v.includes(' ')) return v.split(' ')[0]; |
| | | return v; |
| | | }, |
| | | }, |
| | | { |
| | | label: "å½å
¥äºº", |
| | | prop: "createUser", |
| | | }, |
| | | { |
| | | label: "å½å
¥æ¥æ", |
| | | prop: "createTime", |
| | | label: "å»ºæ¡£æ¥æ", |
| | | prop: "filingDate", |
| | | width: 120, |
| | | formatData: (v) => { |
| | | if (!v) return ''; |
| | | // 妿å
嫿¶åç§ï¼åªåæ¥æé¨å |
| | | if (v.includes(' ')) { |
| | | return v.split(' ')[0]; |
| | | } |
| | | if (v.includes(' ')) return v.split(' ')[0]; |
| | | return v; |
| | | }, |
| | | }, |
| | |
| | | name: "详æ
", |
| | | clickFun: (row) => { |
| | | handleDetail(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç¶æåæ´", |
| | | clickFun: (row) => { |
| | | openStatusChange(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "åæ´è®°å½", |
| | | clickFun: (row) => { |
| | | openRecordDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | |
| | | detailDialogVisible.value = true; |
| | | } |
| | | }; |
| | | |
| | | const statusDialog = reactive({ visible: false, row: null, targetStatus: 1, reason: '', loading: false }); |
| | | |
| | | const openStatusChange = (row) => { |
| | | statusDialog.row = row; |
| | | statusDialog.targetStatus = row.status == 1 ? 0 : 1; |
| | | statusDialog.reason = ''; |
| | | statusDialog.visible = true; |
| | | }; |
| | | |
| | | const submitStatusChange = async () => { |
| | | if (!statusDialog.reason.trim()) { |
| | | ElMessage.error("请填åç¶æåæ´åå "); |
| | | return; |
| | | } |
| | | if (statusDialog.targetStatus === statusDialog.row.status) { |
| | | ElMessage.warning("ç®æ ç¶æä¸å½åç¶æä¸è´"); |
| | | return; |
| | | } |
| | | statusDialog.loading = true; |
| | | try { |
| | | const listRes = await listApprovalTemplate(TEMPLATE_TYPE_CUSTOM); |
| | | const templates = listRes.data?.records || listRes.data || []; |
| | | const tpl = templates.find(t => t.businessType === 19 && (t.enabled === 1 || t.enabled === true)); |
| | | if (!tpl) { |
| | | ElMessage.error("æªæ¾å°å¯ç¨ç设å¤ç¶æåæ´å®¡æ¹æ¨¡æ¿"); |
| | | return; |
| | | } |
| | | const detailRes = await getApprovalTemplateDetail(tpl.id); |
| | | const detail = detailRes.data; |
| | | |
| | | const payload = { |
| | | templateId: detail.id, |
| | | templateName: detail.templateName, |
| | | businessType: 19, |
| | | templateSnapshot: JSON.stringify(detail), |
| | | title: `设å¤ç¶æåæ´å®¡æ¹ - ${statusDialog.row.deviceName}`, |
| | | reason: statusDialog.reason, |
| | | businessId: statusDialog.row.id, |
| | | formConfig: JSON.stringify({ |
| | | targetStatus: statusDialog.targetStatus, |
| | | targetStatusName: statusDialog.targetStatus == 1 ? 'å¯ç¨' : 'åç¨', |
| | | deviceId: statusDialog.row.id, |
| | | deviceName: statusDialog.row.deviceName, |
| | | reason: statusDialog.reason |
| | | }), |
| | | flowNodes: detail.flowNodes || [] |
| | | }; |
| | | |
| | | const res = await saveApprovalInstance(payload); |
| | | if (res.code === 200) { |
| | | ElMessage.success("å·²åèµ·ç¶æåæ´å®¡æ¹"); |
| | | statusDialog.visible = false; |
| | | getTableData(); |
| | | } |
| | | } catch (error) { |
| | | console.error(error); |
| | | } finally { |
| | | statusDialog.loading = false; |
| | | } |
| | | }; |
| | | |
| | | const recordDialog = reactive({ |
| | | visible: false, |
| | | loading: false, |
| | | list: [], |
| | | total: 0, |
| | | queryParams: { |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | deviceId: null |
| | | } |
| | | }); |
| | | |
| | | const openRecordDialog = (row) => { |
| | | recordDialog.queryParams.deviceId = row.id; |
| | | recordDialog.queryParams.pageNum = 1; |
| | | recordDialog.visible = true; |
| | | getRecordList(); |
| | | }; |
| | | |
| | | const getRecordList = async () => { |
| | | recordDialog.loading = true; |
| | | try { |
| | | const res = await listStatusChangeRecord(recordDialog.queryParams); |
| | | if (res.code === 200) { |
| | | recordDialog.list = res.data.records; |
| | | recordDialog.total = res.data.total; |
| | | } |
| | | } catch (error) { |
| | | console.error(error); |
| | | } finally { |
| | | recordDialog.loading = false; |
| | | } |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="calculateValidUntil" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææè³ï¼" prop="validUntil"> |
| | | <el-input |
| | | v-model="form.validUntil" |
| | | placeholder="èªå¨è®¡ç®" |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="溯æº/æ ¸æ¥ç±»åï¼" prop="traceabilityType"> |
| | | <el-input |
| | | v-model="form.traceabilityType" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="宿½æºæï¼" prop="implementingAgency"> |
| | | <el-input |
| | | v-model="form.implementingAgency" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¯ä¹¦ç¼å·ï¼" prop="certificateNumber"> |
| | | <el-input |
| | | v-model="form.certificateNumber" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ ¸å¿ç»æï¼" prop="coreResult"> |
| | | <el-input |
| | | v-model="form.coreResult" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»æç¡®è®¤ï¼" prop="resultConfirmation"> |
| | | <el-input |
| | | v-model="form.resultConfirmation" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="确认人ï¼" prop="confirmerId"> |
| | | <el-select |
| | | v-model="form.confirmerId" |
| | | placeholder="è¯·éæ©" |
| | | filterable |
| | | default-first-option |
| | | :reserve-keyword="false" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | ></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥äººï¼" prop="userId"> |
| | | <el-select |
| | | v-model="form.userId" |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥æ¥æï¼" prop="entryDate"> |
| | | <el-date-picker |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import dayjs from "dayjs"; |
| | | import {ref, reactive, toRefs, getCurrentInstance} from "vue"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | |
| | | recordDate: "", |
| | | userId: "", |
| | | entryDate: "", |
| | | tempFileIds: [] |
| | | tempFileIds: [], |
| | | traceabilityType: "", |
| | | implementingAgency: "", |
| | | certificateNumber: "", |
| | | coreResult: "", |
| | | resultConfirmation: "", |
| | | validUntil: "", |
| | | confirmerId: "" |
| | | }, |
| | | rules: { |
| | | code: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | |
| | | if (type === "edit") { |
| | | form.value.valid = row.valid; |
| | | form.value.recordDate = row.recordDate; |
| | | form.value.traceabilityType = row.traceabilityType; |
| | | form.value.implementingAgency = row.implementingAgency; |
| | | form.value.certificateNumber = row.certificateNumber; |
| | | form.value.coreResult = row.coreResult; |
| | | form.value.resultConfirmation = row.resultConfirmation; |
| | | form.value.validUntil = row.validUntil; |
| | | form.value.confirmerId = row.confirmerId; |
| | | fileList.value = row.commonFiles; |
| | | } |
| | | if(type === "add"){ |
| | |
| | | if(type === "verifying"){ |
| | | form.value.valid = row.valid; |
| | | form.value.recordDate = row.mostDate; |
| | | // Initialize empty for new verifying record |
| | | form.value.traceabilityType = ""; |
| | | form.value.implementingAgency = ""; |
| | | form.value.certificateNumber = ""; |
| | | form.value.coreResult = ""; |
| | | form.value.resultConfirmation = ""; |
| | | form.value.confirmerId = ""; |
| | | calculateValidUntil(); |
| | | } |
| | | |
| | | form.value.id = row.id; |
| | |
| | | const handleValidInput = (value) => { |
| | | if (value === '' || value === null || value === undefined) { |
| | | form.value.valid = ''; |
| | | calculateValidUntil(); |
| | | return; |
| | | } |
| | | // 转æ¢ä¸ºåç¬¦ä¸²å¹¶ç§»é¤ææéæ°åå符ï¼å
æ¬è´å·ãå°æ°ç¹çï¼ |
| | | const numStr = String(value).replace(/[^0-9]/g, ''); |
| | | if (numStr === '') { |
| | | form.value.valid = ''; |
| | | calculateValidUntil(); |
| | | return; |
| | | } |
| | | const numValue = parseInt(numStr, 10); |
| | |
| | | } else { |
| | | form.value.valid = ''; |
| | | } |
| | | calculateValidUntil(); |
| | | } |
| | | |
| | | // èªå¨è®¡ç®æææè³ |
| | | const calculateValidUntil = () => { |
| | | if (form.value.recordDate && form.value.valid) { |
| | | form.value.validUntil = dayjs(form.value.recordDate).add(form.value.valid, 'day').format('YYYY-MM-DD'); |
| | | } else { |
| | | form.value.validUntil = ''; |
| | | } |
| | | } |
| | | |
| | | const submitForm = () => { |
| | |
| | | :type="getDeviceStatusType(scope.row.status)" |
| | | size="small" |
| | | > |
| | | <el-icon v-if="scope.row.status === 'è¿è¡ä¸'"><VideoPlay /></el-icon> |
| | | <el-icon v-if="String(scope.row.status) === '1'"><VideoPlay /></el-icon> |
| | | <el-icon v-else><VideoPause /></el-icon> |
| | | {{ scope.row.status || 'æªç¥' }} |
| | | {{ String(scope.row.status) === '1' ? 'è¿è¡ä¸' : '忢è¿è¡' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <!-- æ£å¸¸ç¶ææ¶æ¾ç¤ºå¯¹åºçæä½æé® --> |
| | | <template v-else> |
| | | <el-button |
| | | v-if="scope.row.status === 'è¿è¡ä¸'" |
| | | v-if="String(scope.row.status) === '1'" |
| | | type="danger" |
| | | size="small" |
| | | @click="changeDeviceStatus(scope.row, '忢è¿è¡')" |
| | |
| | | // æ ¹æ®è®¾å¤ç¶æçé |
| | | if (deviceFilter.value !== 'all') { |
| | | if (deviceFilter.value === 'start') { |
| | | filtered = filtered.filter(device => device.status === 'è¿è¡ä¸') |
| | | filtered = filtered.filter(device => String(device.status) === '1') |
| | | } else if (deviceFilter.value === 'stop') { |
| | | filtered = filtered.filter(device => device.status === '忢è¿è¡') |
| | | filtered = filtered.filter(device => String(device.status) === '0') |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | const statusStr = String(row?.status ?? '').trim() |
| | | const isRunning = statusStr === 'è¿è¡ä¸' || statusStr === '1' |
| | | const isRunning = statusStr === '1' |
| | | const endRaw = pickEndTime(row) |
| | | const hasEnd = hasMeaningfulEnd(endRaw) |
| | | |
| | |
| | | |
| | | // æ£æ¥è®¾å¤æ¯å¦è¶
æ¶æªå¯å¨ |
| | | const isOverdue = (device) => { |
| | | if (!device.planRuntimeTime || device.status === 'è¿è¡ä¸' || device.startRuntimeTime) { |
| | | if (!device.planRuntimeTime || String(device.status) === '1' || device.startRuntimeTime) { |
| | | return false |
| | | } |
| | | |
| | |
| | | |
| | | // æ´æ°è®¾å¤ç¶æåç¸å
³æ¶é´å段 |
| | | if (status === 'å¯å¨è¿è¡') { |
| | | device.status = 'è¿è¡ä¸' |
| | | device.status = 1 |
| | | device.startRuntimeTime = currentTime |
| | | device.endRuntimeTime = null // æ¸
ç©ºç»ææ¶é´ |
| | | device.runtimeDuration = null // æ¸
空è¿è¡æ¶é¿ |
| | | } else { |
| | | device.status = '忢è¿è¡' |
| | | device.status = 0 |
| | | device.endRuntimeTime = currentTime |
| | | // 计ç®è¿è¡æ¶é¿ |
| | | if (device.startRuntimeTime) { |
| | |
| | | } |
| | | |
| | | const getDeviceStatusType = (status) => { |
| | | if (status === 'è¿è¡ä¸') { |
| | | if (String(status) === '1') { |
| | | return 'success' |
| | | } else if (status === '忢è¿è¡') { |
| | | } else if (String(status) === '0') { |
| | | return 'danger' |
| | | } else { |
| | | return 'info' |
| | |
| | | @close="handleCancel" |
| | | > |
| | | <el-form :model="form" :rules="rules" label-width="100px"> |
| | | <!-- é
ä»¶ä½¿ç¨æ
åµå±ç¤º --> |
| | | <el-form-item label="é
ä»¶ä½¿ç¨æç»" v-if="sparePartsDetails.length > 0"> |
| | | <el-table :data="sparePartsDetails" size="small" border style="width: 100%"> |
| | | <el-table-column prop="sparePartName" label="é
ä»¶åç§°" /> |
| | | <el-table-column prop="quantity" label="ä½¿ç¨æ°é" width="100" /> |
| | | </el-table> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="éªæ¶äºº" prop="acceptanceName"> |
| | | <el-select |
| | | v-model="form.acceptanceName" |
| | |
| | | import { ref, reactive } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { repairAcceptance } from "@/api/equipmentManagement/repair"; |
| | | import { repairAcceptance, getRepairById } from "@/api/equipmentManagement/repair"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | defineOptions({ |
| | |
| | | const loading = ref(false); |
| | | const repairId = ref(null); |
| | | const userList = ref([]); |
| | | const sparePartsDetails = ref([]); |
| | | |
| | | const form = reactive({ |
| | | acceptanceName: undefined, |
| | |
| | | const open = async (row) => { |
| | | repairId.value = row.id; |
| | | visible.value = true; |
| | | // é置表å |
| | | form.acceptanceName = undefined; |
| | | form.acceptanceTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | form.acceptanceRemark = undefined; |
| | | sparePartsDetails.value = []; |
| | | try { |
| | | const { data } = await getRepairById(row.id); |
| | | if (data && data.sparePartsUseListDetails) { |
| | | sparePartsDetails.value = data.sparePartsUseListDetails; |
| | | } |
| | | } catch(e) {} |
| | | await loadUserList(); |
| | | }; |
| | | |
| | |
| | | <el-form-item label="ç»´ä¿®ç»æ"> |
| | | <el-input v-model="form.maintenanceResult" placeholder="请è¾å
¥ç»´ä¿®ç»æ" /> |
| | | </el-form-item> |
| | | <el-form-item label="åå åæ"> |
| | | <el-input v-model="form.reasonAnalysis" type="textarea" :rows="2" placeholder="请è¾å
¥åå åæ" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¤çæªæ½"> |
| | | <el-input v-model="form.handlingMeasures" type="textarea" :rows="2" placeholder="请è¾å
¥å¤çæªæ½" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç»´ä¿®ç¶æ"> |
| | | <el-select v-model="form.status"> |
| | | <el-option label="å¾
æ¥ä¿®" :value="0"></el-option> |
| | |
| | | const { form, resetForm } = useFormData({ |
| | | maintenanceName: undefined, // ç»´ä¿®åç§° |
| | | maintenanceResult: undefined, // ç»´ä¿®ç»æ |
| | | reasonAnalysis: undefined, // åå åæ |
| | | handlingMeasures: undefined, // å¤çæªæ½ |
| | | maintenanceTime: undefined, // ç»´ä¿®æ¥æ |
| | | status: 0, |
| | | sparePartsIds: [], |
| | |
| | | const setForm = (data) => { |
| | | form.maintenanceName = data.maintenanceName ?? userStore.nickName; |
| | | form.maintenanceResult = data.maintenanceResult; |
| | | form.reasonAnalysis = data.reasonAnalysis; |
| | | form.handlingMeasures = data.handlingMeasures; |
| | | form.maintenanceTime = |
| | | data.maintenanceTime |
| | | ? dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss") |
| | |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥ä¿®ç±»å"> |
| | | <el-select v-model="form.repairType" |
| | | placeholder="è¯·éæ©æ¥ä¿®ç±»å" |
| | | :disabled="operationType === 'view'"> |
| | | <el-option v-for="dict in device_repair_type" |
| | | :key="dict.value" |
| | | :label="dict.label" |
| | | :value="Number(dict.value)"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="维修人"> |
| | | <el-input v-model="form.maintenanceName" |
| | | placeholder="请è¾å
¥ç»´ä¿®äººå§å" |
| | |
| | | :value="1"></el-option> |
| | | <el-option label="失败" |
| | | :value="2"></el-option> |
| | | <el-option label="å¾
éªæ¶" |
| | | :value="3"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.status !== 0"> |
| | | <el-form-item label="ç»´ä¿®æ¶é´"> |
| | | <el-input v-model="form.maintenanceTime" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="form.status !== 0"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="ç»´ä¿®ç»æ"> |
| | | <el-input v-model="form.maintenanceResult" |
| | | :rows="2" |
| | | type="textarea" |
| | | placeholder="ææ ç»´ä¿®ç»æ" |
| | | :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="form.status !== 0"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="åå åæ"> |
| | | <el-input v-model="form.reasonAnalysis" |
| | | :rows="2" |
| | | type="textarea" |
| | | placeholder="请è¾å
¥åå åæ" |
| | | :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å¤çæªæ½"> |
| | | <el-input v-model="form.handlingMeasures" |
| | | :rows="2" |
| | | type="textarea" |
| | | placeholder="请è¾å
¥å¤çæªæ½" |
| | | :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- é
ä»¶ä½¿ç¨æç»å±ç¤º --> |
| | | <el-row v-if="sparePartsDetails.length > 0"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="é
ä»¶ä½¿ç¨æç»"> |
| | | <el-table :data="sparePartsDetails" size="small" border style="width: 100%"> |
| | | <el-table-column prop="sparePartName" label="é
ä»¶åç§°" /> |
| | | <el-table-column prop="quantity" label="ä½¿ç¨æ°é" width="100" /> |
| | | </el-table> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="operationType !== 'view'" |
| | | :gutter="30"> |
| | | <el-col :span="24"> |
| | |
| | | }); |
| | | |
| | | const emits = defineEmits(["ok"]); |
| | | const { proxy } = getCurrentInstance(); |
| | | const { device_repair_type } = proxy.useDict("device_repair_type"); |
| | | |
| | | const id = ref(); |
| | | const visible = ref(false); |
| | |
| | | const userStore = useUserStore(); |
| | | const deviceOptions = ref([]); |
| | | const fileList = ref([]); |
| | | const sparePartsDetails = ref([]); |
| | | |
| | | const loadDeviceName = async () => { |
| | | const { data } = await getDeviceLedger(); |
| | |
| | | deviceName: undefined, // 设å¤åç§° |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | repairTime: dayjs().format("YYYY-MM-DD"), // æ¥ä¿®æ¥æï¼é»è®¤å½å¤© |
| | | repairType: undefined, // æ¥ä¿®ç±»å |
| | | repairName: userStore.nickName, // æ¥ä¿®äºº |
| | | remark: undefined, // æ
éç°è±¡ |
| | | reasonAnalysis: undefined, // åå åæ |
| | | handlingMeasures: undefined, // å¤çæªæ½ |
| | | status: 0, // æ¥ä¿®ç¶æ |
| | | machineryCategory: undefined, |
| | | storageBlobDTOs: [], |
| | | maintenanceName: undefined, // 维修人 |
| | | maintenanceTime: undefined, |
| | | maintenanceResult: undefined, |
| | | }); |
| | | |
| | | const setDeviceModel = deviceId => { |
| | |
| | | form.deviceName = data.deviceName; |
| | | form.deviceModel = data.deviceModel; |
| | | form.repairTime = data.repairTime; |
| | | form.repairType = data.repairType; |
| | | form.repairName = data.repairName; |
| | | form.remark = data.remark; |
| | | form.reasonAnalysis = data.reasonAnalysis; |
| | | form.handlingMeasures = data.handlingMeasures; |
| | | form.status = data.status; |
| | | form.machineryCategory = data.machineryCategory; |
| | | form.storageBlobDTOs = data.storageBlobVOs || []; |
| | | form.maintenanceName = data.maintenanceName; |
| | | form.maintenanceTime = data.maintenanceTime; |
| | | form.maintenanceResult = data.maintenanceResult; |
| | | form.acceptanceName = data.acceptanceName; |
| | | form.acceptanceTime = data.acceptanceTime; |
| | | form.acceptanceRemark = data.acceptanceRemark; |
| | | sparePartsDetails.value = data.sparePartsUseListDetails || []; |
| | | }; |
| | | |
| | | const sendForm = async () => { |
| | |
| | | operationType.value = "add"; |
| | | visible.value = true; |
| | | fileList.value = []; |
| | | sparePartsDetails.value = []; |
| | | await nextTick(); |
| | | await loadDeviceName(); |
| | | }; |
| | |
| | | <el-tag v-if="row.status === 0" |
| | | type="warning">å¾
ç»´ä¿®</el-tag> |
| | | </template> |
| | | <template #repairTypeRef="{ row }"> |
| | | <dict-tag :options="device_repair_type" :value="row.repairType" /> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" |
| | | link |
| | |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const { device_repair_type } = proxy.useDict("device_repair_type"); |
| | | |
| | | // æ¨¡ææ¡å®ä¾ |
| | | const repairModalRef = ref(); |
| | |
| | | prop: "repairName", |
| | | }, |
| | | { |
| | | label: "æ¥ä¿®ç±»å", |
| | | align: "center", |
| | | prop: "repairType", |
| | | dataType: "slot", |
| | | slot: "repairTypeRef", |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | align: "center", |
| | | prop: "status", |
| | |
| | | placeholder="请è¾å
¥ä¿å
ȍȾ" |
| | | type="text" /> |
| | | </el-form-item> |
| | | <el-form-item label="è¿è¡ç¶åµ"> |
| | | <el-input v-model="form.runningStatus" |
| | | placeholder="请è¾å
¥è¿è¡ç¶åµ" |
| | | type="text" /> |
| | | </el-form-item> |
| | | <el-form-item label="设å¤å¤ä»¶"> |
| | | <el-select v-model="form.sparePartsIds" |
| | | :loading="loadingSparePartOptions" |
| | |
| | | maintenanceActuallyName: undefined, // å®é
ä¿å
»äºº |
| | | maintenanceActuallyTime: undefined, // å®é
ä¿å
»æ¥æ |
| | | maintenanceResult: undefined, // ä¿å
ȍȾ |
| | | runningStatus: undefined, // è¿è¡ç¶åµ |
| | | status: 0, // ä¿å
»ç¶æ |
| | | sparePartsIds: [], |
| | | storageBlobDTOs: [], |
| | |
| | | ? dayjs(data.maintenanceActuallyTime).format("YYYY-MM-DD HH:mm:ss") |
| | | : dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | form.maintenanceResult = data.maintenanceResult; |
| | | form.runningStatus = data.runningStatus; |
| | | form.status = 1; // é»è®¤ç¶æä¸ºå®ç» |
| | | // multiple éæ©å¨è¦æ±æ°ç»ï¼å端常è¿å "1,2,3" |
| | | if (Array.isArray(data?.sparePartsIds)) { |
| | |
| | | </el-form-item> |
| | | <el-form-item v-if="id" label="ä¿ä¿®ç¶æ"> |
| | | <el-select v-model="form.status"> |
| | | <el-option label="å¾
ä¿ä¿®" :value="0"></el-option> |
| | | <el-option label="å¾
ä¿å
»" :value="0"></el-option> |
| | | <el-option label="å®ç»" :value="1"></el-option> |
| | | <el-option label="失败" :value="2"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¿å
»ç±»å" prop="maintenanceType"> |
| | | <el-select v-model="form.maintenanceType" placeholder="è¯·éæ©ä¿å
»ç±»å" clearable> |
| | | <el-option label="æ¥å¸¸ä½¿ç¨" value="1"/> |
| | | <el-option label="宿ä¿å
»" value="2"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ§è¡äºº" prop="executorId"> |
| | | <el-select |
| | | v-model="form.executorId" |
| | | filterable |
| | | placeholder="è¯·éæ©æ§è¡äºº" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in userList" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | :key="item.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¿å
»äºº"> |
| | |
| | | machineryCategory: undefined, |
| | | storageBlobDTOs: [], |
| | | maintenancePerson: undefined, // ä¿å
»äºº |
| | | maintenanceType: undefined, |
| | | executorId: undefined |
| | | }); |
| | | |
| | | const setDeviceModel = (deviceId) => { |
| | |
| | | form.status = data.status; |
| | | form.machineryCategory = data.machineryCategory; |
| | | form.maintenancePerson = data.maintenancePerson; |
| | | form.maintenanceType = data.maintenanceType; |
| | | form.executorId = data.executorId; |
| | | if (data.maintenancePlanTime) { |
| | | form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | |
| | | <el-form-item label="ä¿å
»é¡¹ç®" prop="machineryCategory"> |
| | | <el-input |
| | | v-model.trim="form.machineryCategory" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | placeholder="请è¾å
¥å·¥ä½å
容æè¿°(ä¿å
»é¡¹ç®)" |
| | | maxlength="100" |
| | | clearable |
| | | /> |
| | |
| | | maxlength="100" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¿å
»ç±»å" prop="maintenanceType"> |
| | | <el-select v-model="form.maintenanceType" placeholder="è¯·éæ©ä¿å
»ç±»å" clearable> |
| | | <el-option label="æ¥å¸¸ä½¿ç¨" value="1"/> |
| | | <el-option label="宿ä¿å
»" value="2"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ§è¡äºº" prop="executorId"> |
| | | <el-select |
| | | v-model="form.executorId" |
| | | filterable |
| | | placeholder="è¯·éæ©æ§è¡äºº" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in userList" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | :key="item.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | time: '', |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | registrationDate: '', |
| | | maintenancePerson: '' // ä¿å
»äºº |
| | | maintenancePerson: '', // ä¿å
»äºº |
| | | maintenanceType: undefined, |
| | | executorId: undefined |
| | | }, |
| | | rules: { |
| | | taskId: [{ required: true, message: "è¯·éæ©è®¾å¤", trigger: "change" },], |
| | | inspector: [{ required: true, message: "è¯·éæ©å½å
¥äºº", trigger: "blur" },], |
| | | registrationDate: [{ required: true, message: "è¯·éæ©ç»è®°æ¶é´", trigger: "change" }], |
| | | machineryCategory: [{ required: true, message: "请è¾å
¥ä¿å
»é¡¹ç®", trigger: "blur" }] |
| | | machineryCategory: [{ required: true, message: "请è¾å
¥ä¿å
»é¡¹ç®", trigger: "blur" }], |
| | | maintenanceType: [{ required: true, message: "è¯·éæ©ä¿å
»ç±»å", trigger: "change" }], |
| | | executorId: [{ required: true, message: "è¯·éæ©æ§è¡äºº", trigger: "change" }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | |
| | | time: '', |
| | | deviceModel: undefined, |
| | | registrationDate: '', |
| | | maintenancePerson: '' |
| | | maintenancePerson: '', |
| | | maintenanceType: undefined, |
| | | executorId: undefined |
| | | } |
| | | } |
| | | |
| | |
| | | deviceMaintenanceTaskList, |
| | | deviceMaintenanceTaskDel, |
| | | } from "@/api/equipmentManagement/upkeep"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | |
| | | const fileListDialogRef = ref(null); |
| | | const fileDialogVisible = ref(false); |
| | | const currentMaintenanceTaskId = ref(null); |
| | | |
| | | const userList = ref([]); |
| | | const getUserName = (userId) => { |
| | | if (!userId) return "--"; |
| | | const user = userList.value.find(u => String(u.userId) === String(userId)); |
| | | return user ? user.nickName : userId; |
| | | }; |
| | | |
| | | // ä¿å
»è®°å½tabï¼å设å¤ä¿å
»é¡µé¢ï¼ç¸å
³åé |
| | | const filters = reactive({ |
| | |
| | | prop: "machineryCategory", |
| | | minWidth: 120, |
| | | formatData: cell => cell || "--", |
| | | }, |
| | | { |
| | | label: "ä¿å
»ç±»å", |
| | | prop: "maintenanceType", |
| | | minWidth: 100, |
| | | formatData: cell => (cell === "1" ? "æ¥å¸¸ä½¿ç¨" : cell === "2" ? "宿ä¿å
»" : "--") |
| | | }, |
| | | { |
| | | prop: "executorId", |
| | | label: "æ§è¡äºº", |
| | | minWidth: 100, |
| | | formatData: cell => getUserName(cell) |
| | | }, |
| | | { |
| | | prop: "frequencyType", |
| | |
| | | align: "center", |
| | | prop: "machineryCategory", |
| | | formatData: cell => cell || "--", |
| | | }, |
| | | { |
| | | label: "ä¿å
»ç±»å", |
| | | align: "center", |
| | | prop: "maintenanceType", |
| | | formatData: cell => (cell === "1" ? "æ¥å¸¸ä½¿ç¨" : cell === "2" ? "宿ä¿å
»" : "--") |
| | | }, |
| | | { |
| | | label: "æ§è¡äºº", |
| | | align: "center", |
| | | prop: "executorId", |
| | | formatData: cell => getUserName(cell) |
| | | }, |
| | | // { |
| | | // label: "å½å
¥æ¥æ", |
| | |
| | | fileDialogVisible.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | onMounted(async () => { |
| | | const { data } = await userListNoPageByTenantId(); |
| | | userList.value = data || []; |
| | | // æ ¹æ®é»è®¤æ¿æ´»ç Tab è°ç¨å¯¹åºçæ¥è¯¢æ¥å£ |
| | | if (activeTab.value === "scheduled") { |
| | | getScheduledTableData(); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | |
| | | |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <div class="search_form"> |
| | | <div style="flex: 1; padding-right: 20px;"> |
| | | <el-row :gutter="16"> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">å æ²¹å¡å·ï¼</span> |
| | | <el-input v-model="queryParams.cardNumber" placeholder="请è¾å
¥å æ²¹å¡å·" clearable style="flex: 1;" @keyup.enter.native="handleQuery" /> |
| | | </el-col> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">æå±é¨é¨ï¼</span> |
| | | <el-tree-select v-model="queryParams.deptId" :data="deptOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="è¯·éæ©é¨é¨" check-strictly clearable filterable style="flex: 1;" /> |
| | | </el-col> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" icon="Plus" @click="handleAdd">æ°å¢å æ²¹å¡</el-button> |
| | | </div> |
| | | </div> |
| | | <!-- è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%" |
| | | height="calc(100vh - 22em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="cardNumber" label="å æ²¹å¡å·" min-width="150" /> |
| | | <el-table-column prop="deptName" label="æå±é¨é¨" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column prop="issuer" label="åå¡åä½" min-width="120" /> |
| | | <el-table-column prop="initialBalance" label="æåä½é¢" min-width="100" align="right" /> |
| | | <el-table-column prop="currentBalance" label="å½åä½é¢" min-width="100" align="right" /> |
| | | <el-table-column prop="status" label="ç¶æ" min-width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'ACTIVE' ? 'success' : 'danger'"> |
| | | {{ scope.row.status === 'ACTIVE' ? 'æ£å¸¸' : '注é' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" fixed="right" width="200" align="center"> |
| | | <template #default="scope"> |
| | | <el-button type="success" link size="small" @click="handleRecharge(scope.row)">å
å¼ç®¡ç</el-button> |
| | | <el-button type="primary" link size="small" @click="handleUpdate(scope.row)">ç¼è¾</el-button> |
| | | <el-button v-if="scope.row.status !== 'CANCELLED'" type="warning" link size="small" @click="handleCancel(scope.row)">注é</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" |
| | | /> |
| | | </div> |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog :title="title" v-model="open" width="500px" append-to-body> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-form-item label="å æ²¹å¡å·" prop="cardNumber"> |
| | | <el-input v-model="form.cardNumber" placeholder="请è¾å
¥å æ²¹å¡å·" /> |
| | | </el-form-item> |
| | | <el-form-item label="æå±é¨é¨" prop="deptId"> |
| | | <el-tree-select |
| | | v-model="form.deptId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="è¯·éæ©é¨é¨" |
| | | check-strictly |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="åå¡åä½" prop="issuer"> |
| | | <el-input v-model="form.issuer" placeholder="请è¾å
¥åå¡åä½" /> |
| | | </el-form-item> |
| | | <el-form-item label="æåä½é¢" prop="initialBalance" v-if="!form.id"> |
| | | <el-input-number v-model="form.initialBalance" :precision="2" :min="0" :step="100" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio label="ACTIVE">æ£å¸¸</el-radio> |
| | | <el-radio label="CANCELLED">注é</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" placeholder="请è¾å
¥å
容" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">ç¡® å®</el-button> |
| | | <el-button @click="cancel">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- å
å¼ç®¡çæ½å± --> |
| | | <el-drawer v-model="rechargeDrawer.visible" :title="'å
å¼ç®¡ç - ' + rechargeDrawer.cardNumber" size="50%"> |
| | | <div style="padding: 0 20px;"> |
| | | <el-row style="margin-bottom: 15px;"> |
| | | <el-button type="primary" icon="Plus" @click="handleAddRecharge">æ°å¢å
å¼</el-button> |
| | | </el-row> |
| | | <el-table v-loading="rechargeDrawer.loading" :data="rechargeDrawer.list" border> |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="rechargeDate" label="å
弿¶é´" width="160" /> |
| | | <el-table-column prop="rechargeAmount" label="å
å¼éé¢(å
)" width="120" align="right"> |
| | | <template #default="scope"> |
| | | <span style="color: #67C23A; font-weight: bold;">+{{ scope.row.rechargeAmount }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createUserName" label="æä½äºº" width="100" /> |
| | | <el-table-column prop="remark" label="夿³¨" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-button type="danger" link size="small" @click="handleDeleteRecharge(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination |
| | | v-show="rechargeDrawer.total > 0" |
| | | :total="rechargeDrawer.total" |
| | | v-model:page="rechargeDrawer.queryParams.current" |
| | | v-model:limit="rechargeDrawer.queryParams.size" |
| | | @pagination="getRechargeList" |
| | | /> |
| | | </div> |
| | | </el-drawer> |
| | | |
| | | <!-- æ°å¢å
å¼å¼¹çª --> |
| | | <el-dialog :title="rechargeDialog.title" v-model="rechargeDialog.visible" width="500px" append-to-body> |
| | | <el-form ref="rechargeFormRef" :model="rechargeDialog.form" :rules="rechargeDialog.rules" label-width="100px"> |
| | | <el-form-item label="å
å¼éé¢" prop="rechargeAmount"> |
| | | <el-input-number v-model="rechargeDialog.form.rechargeAmount" :min="0.01" :precision="2" :step="100" style="width: 100%;" placeholder="请è¾å
¥å
å¼éé¢" /> |
| | | </el-form-item> |
| | | <el-form-item label="å
弿¥æ" prop="rechargeDate"> |
| | | <el-date-picker v-model="rechargeDialog.form.rechargeDate" type="datetime" placeholder="éæ©å
弿¶é´" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%;" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="rechargeDialog.form.remark" type="textarea" placeholder="请è¾å
¥å¤æ³¨å
容" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitRechargeForm">ç¡® å®</el-button> |
| | | <el-button @click="rechargeDialog.visible = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue'; |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import { listFuelCardPage, addFuelCard, updateFuelCard, delFuelCard } from '@/api/inventoryManagement/vehicle'; |
| | | import request from '@/utils/request'; |
| | | |
| | | import { listAllFuelCard, listRecharge, addRecharge, delRecharge } from '@/api/inventoryManagement/vehicle'; |
| | | const statTotal = ref(0); |
| | | const statActive = ref(0); |
| | | const statCancelled = ref(0); |
| | | const statBalance = ref(0); |
| | | |
| | | function getStats() { |
| | | listAllFuelCard().then(res => { |
| | | const arr = res.data || []; |
| | | statTotal.value = arr.length; |
| | | statActive.value = arr.filter(i => i.status === 'ACTIVE').length; |
| | | statCancelled.value = arr.filter(i => i.status === 'CANCELLED').length; |
| | | statBalance.value = arr.reduce((sum, item) => sum + (item.currentBalance || 0), 0); |
| | | }); |
| | | } |
| | | |
| | | |
| | | const loading = ref(true); |
| | | const tableData = ref([]); |
| | | const total = ref(0); |
| | | const open = ref(false); |
| | | const title = ref(""); |
| | | const formRef = ref(null); |
| | | |
| | | const deptOptions = ref([]); |
| | | |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | cardNumber: undefined, |
| | | deptId: undefined |
| | | }); |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | | cardNumber: undefined, |
| | | deptId: undefined, |
| | | issuer: undefined, |
| | | initialBalance: 0, |
| | | status: 'ACTIVE', |
| | | remark: undefined |
| | | }); |
| | | |
| | | const rules = { |
| | | cardNumber: [{ required: true, message: "å æ²¹å¡å·ä¸è½ä¸ºç©º", trigger: "blur" }], |
| | | deptId: [{ required: true, message: "æå±é¨é¨ä¸è½ä¸ºç©º", trigger: "change" }], |
| | | issuer: [{ required: true, message: "åå¡åä½ä¸è½ä¸ºç©º", trigger: "blur" }] |
| | | }; |
| | | |
| | | function getDicts() { |
| | | request({url: '/system/user/deptTree', method: 'get'}).then(res => { |
| | | deptOptions.value = res.data || []; |
| | | }); |
| | | } |
| | | |
| | | function getList() { |
| | | loading.value = true; |
| | | listFuelCardPage(queryParams).then(response => { |
| | | tableData.value = response.data.records; |
| | | total.value = response.data.total; |
| | | loading.value = false; |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | }); |
| | | } |
| | | |
| | | function handleQuery() { |
| | | queryParams.current = 1; |
| | | getList(); |
| | | getStats(); |
| | | } |
| | | |
| | | function resetQuery() { |
| | | queryParams.cardNumber = undefined; |
| | | queryParams.deptId = undefined; |
| | | handleQuery(); |
| | | } |
| | | |
| | | function reset() { |
| | | Object.assign(form, { |
| | | id: undefined, |
| | | cardNumber: undefined, |
| | | deptId: undefined, |
| | | issuer: undefined, |
| | | initialBalance: 0, |
| | | status: 'ACTIVE', |
| | | remark: undefined |
| | | }); |
| | | if (formRef.value) formRef.value.resetFields(); |
| | | } |
| | | |
| | | function handleAdd() { |
| | | reset(); |
| | | open.value = true; |
| | | title.value = "æ°å¢å æ²¹å¡"; |
| | | } |
| | | |
| | | function handleUpdate(row) { |
| | | reset(); |
| | | Object.assign(form, row); |
| | | open.value = true; |
| | | title.value = "ä¿®æ¹å æ²¹å¡"; |
| | | } |
| | | |
| | | function submitForm() { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | if (form.id != null) { |
| | | updateFuelCard(form).then(response => { |
| | | ElMessage.success("ä¿®æ¹æå"); |
| | | open.value = false; |
| | | getList(); |
| | | getStats(); |
| | | }); |
| | | } else { |
| | | addFuelCard(form).then(response => { |
| | | ElMessage.success("æ°å¢æå"); |
| | | open.value = false; |
| | | getList(); |
| | | getStats(); |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | function handleCancel(row) { |
| | | ElMessageBox.confirm('æ¯å¦ç¡®è®¤æ³¨éå æ²¹å¡å·ä¸º"' + row.cardNumber + '"çå¡çï¼æ³¨éå该å¡ä¸è½ç¨äºæ°å¢å 油记å½ï¼ä½ä½é¢å°ä¿çã', "æç¤º", { |
| | | confirmButtonText: "ç¡®å®æ³¨é", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | }).then(function() { |
| | | return updateFuelCard({ id: row.id, status: 'CANCELLED' }); |
| | | }).then(() => { |
| | | getList(); |
| | | getStats(); |
| | | ElMessage.success("注éæå"); |
| | | }); |
| | | } |
| | | |
| | | function handleDelete(row) { |
| | | ElMessageBox.confirm('æ¯å¦ç¡®è®¤å é¤å æ²¹å¡å·ä¸º"' + row.cardNumber + '"çæ°æ®é¡¹ï¼', "è¦å", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | }).then(function() { |
| | | return delFuelCard(row.id); |
| | | }).then(() => { |
| | | getList(); |
| | | getStats(); |
| | | ElMessage.success("å 餿å"); |
| | | }); |
| | | } |
| | | |
| | | function cancel() { |
| | | open.value = false; |
| | | reset(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getDicts(); |
| | | getList(); |
| | | getStats(); |
| | | }); |
| | | |
| | | // --- å
å¼ç®¡çé»è¾ --- |
| | | const rechargeDrawer = reactive({ |
| | | visible: false, |
| | | loading: false, |
| | | cardNumber: '', |
| | | cardId: null, |
| | | list: [], |
| | | total: 0, |
| | | queryParams: { |
| | | current: 1, |
| | | size: 10, |
| | | cardId: null |
| | | } |
| | | }); |
| | | |
| | | const rechargeDialog = reactive({ |
| | | visible: false, |
| | | title: 'æ°å¢å
å¼', |
| | | form: { |
| | | cardId: null, |
| | | rechargeAmount: null, |
| | | rechargeDate: '', |
| | | remark: '' |
| | | }, |
| | | rules: { |
| | | rechargeAmount: [{ required: true, message: '请è¾å
¥å
å¼éé¢', trigger: 'blur' }], |
| | | rechargeDate: [{ required: true, message: 'è¯·éæ©å
弿¥æ', trigger: 'change' }] |
| | | } |
| | | }); |
| | | |
| | | const rechargeFormRef = ref(null); |
| | | |
| | | function handleRecharge(row) { |
| | | rechargeDrawer.cardId = row.id; |
| | | rechargeDrawer.cardNumber = row.cardNumber; |
| | | rechargeDrawer.queryParams.cardId = row.id; |
| | | rechargeDrawer.visible = true; |
| | | getRechargeList(); |
| | | } |
| | | |
| | | function getRechargeList() { |
| | | rechargeDrawer.loading = true; |
| | | listRecharge(rechargeDrawer.queryParams).then(res => { |
| | | rechargeDrawer.list = res.data.records; |
| | | rechargeDrawer.total = res.data.total; |
| | | rechargeDrawer.loading = false; |
| | | }).catch(() => { |
| | | rechargeDrawer.loading = false; |
| | | }); |
| | | } |
| | | |
| | | function handleAddRecharge() { |
| | | rechargeDialog.form = { |
| | | cardId: rechargeDrawer.cardId, |
| | | rechargeAmount: undefined, |
| | | rechargeDate: new Date().toISOString().slice(0,19).replace('T', ' '), |
| | | remark: '' |
| | | }; |
| | | rechargeDialog.visible = true; |
| | | if (rechargeFormRef.value) { |
| | | rechargeFormRef.value.clearValidate(); |
| | | } |
| | | } |
| | | |
| | | function submitRechargeForm() { |
| | | rechargeFormRef.value.validate(valid => { |
| | | if (valid) { |
| | | addRecharge(rechargeDialog.form).then(res => { |
| | | ElMessage.success('å
弿å'); |
| | | rechargeDialog.visible = false; |
| | | getRechargeList(); |
| | | getList(); // æ´æ°ä¸»å表éé¢ |
| | | getStats(); |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | function handleDeleteRecharge(row) { |
| | | ElMessageBox.confirm('确认å é¤è¯¥ç¬å
å¼è®°å½åï¼ï¼å é¤åä½é¢å°èªå¨æ£åï¼', 'æç¤º', { type: 'warning' }).then(() => { |
| | | return delRecharge(row.id); |
| | | }).then(() => { |
| | | ElMessage.success('å 餿å'); |
| | | getRechargeList(); |
| | | getList(); |
| | | getStats(); |
| | | }); |
| | | } |
| | | // --- å
å¼ç®¡çé»è¾ç»æ --- |
| | | |
| | | </script> |
| | |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <!-- <el-button type="danger" plain @click="handleDelete">å é¤</el-button> --> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | </div> <div class="table_list"> |
| | | |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" :row-key="row => row.id" show-summary style="width: 100%" |
| | | :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)"> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | |
| | | |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">车è¾ç¼å·ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.vehicleCode" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥è½¦è¾ç¼å·" |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">å æ²¹æ¶é´ï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | @change="handleQuery" |
| | | /> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <div class="search_form"> |
| | | <div style="flex: 1; padding-right: 20px;"> |
| | | <el-row :gutter="16" style="margin-bottom: 16px;"> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">æå±é¨é¨ï¼</span> |
| | | <el-tree-select v-model="queryParams.deptId" :data="deptOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="è¯·éæ©é¨é¨" check-strictly clearable filterable style="flex: 1;" /> |
| | | </el-col> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">å æ²¹è½¦è¾ï¼</span> |
| | | <el-select v-model="queryParams.vehicleId" placeholder="æè½¦è¾çé" clearable filterable style="flex: 1;"> |
| | | <el-option v-for="item in vehicleList" :key="item.id" :label="item.plateNumber" :value="item.id" /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">å æ²¹å¡ï¼</span> |
| | | <el-select v-model="queryParams.fuelCardId" placeholder="è¯·éæ©å æ²¹å¡" clearable style="flex: 1;"> |
| | | <el-option v-for="item in fuelCardList" :key="item.id" :label="item.cardNumber + ' - ' + item.issuer" :value="item.id" /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">ç¶æï¼</span> |
| | | <el-select v-model="queryParams.status" placeholder="ç¶æçé" clearable style="flex: 1;"> |
| | | <el-option label="è稿" value="0" /> |
| | | <el-option label="å·²æäº¤" value="1" /> |
| | | </el-select> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="16"> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">å æ²¹æ¶é´ï¼</span> |
| | | <el-date-picker v-model="dateRange" type="daterange" range-separator="è³" start-placeholder="å¼å§" end-placeholder="ç»æ" value-format="YYYY-MM-DD" clearable style="flex: 1; width: 100%;" /> |
| | | </el-col> |
| | | <el-col :span="18" style="display: flex; align-items: center;"> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" icon="Plus" @click="openAdd"> |
| | | æ°å¢å æ²¹è®°å½ |
| | | </el-button> |
| | | <div style="display: flex; align-items: flex-start; height: 100%;"> |
| | | <el-button type="primary" icon="Plus" @click="handleAdd">æ°å¢å 油记å½</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)" |
| | | height="calc(100vh - 22em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | :row-class-name="tableRowClassName" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column |
| | | prop="vehicleCode" |
| | | label="车è¾ç¼å·" |
| | | width="130" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="plateNumber" |
| | | label="车çå·ç " |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="fuelDate" |
| | | label="å æ²¹æ¥æ" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="gunNo" |
| | | label="æ²¹æªå·" |
| | | width="90" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | prop="amount" |
| | | label="éé¢(å
)" |
| | | width="100" |
| | | align="right" |
| | | > |
| | | <el-table-column prop="fuelDate" label="å æ²¹æ¥æ" width="120" /> |
| | | <el-table-column prop="deptName" label="æå±é¨é¨" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column prop="plateNumber" label="å æ²¹è½¦è¾(çç
§)" width="140" /> |
| | | <el-table-column prop="cardNumber" label="å æ²¹å¡å·" min-width="150" /> |
| | | <el-table-column prop="amount" label="éé¢(å
)" width="100" align="right" /> |
| | | <el-table-column prop="liters" label="åæ°(L)" width="100" align="right" /> |
| | | <el-table-column prop="startMileage" label="èµ·å§éç¨(km)" width="120" align="right" /> |
| | | <el-table-column prop="endMileage" label="ç»æéç¨(km)" width="120" align="right" /> |
| | | <el-table-column prop="invoiceNo" label="å票å·ç " width="140" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <span>{{ scope.row.amount?.toFixed(2) }}</span> |
| | | <el-tag v-if="scope.row.status === '0'" type="info">è稿</el-tag> |
| | | <el-tag v-else-if="scope.row.status === '1'" type="success">å·²æäº¤</el-tag> |
| | | <el-tag v-else type="info">è稿</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="liters" |
| | | label="åæ°(L)" |
| | | width="90" |
| | | align="right" |
| | | > |
| | | <el-table-column prop="remark" label="夿³¨" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" fixed="right" width="200" align="center"> |
| | | <template #default="scope"> |
| | | <span>{{ scope.row.liters?.toFixed(2) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="startMileage" |
| | | label="èµ·å§éç¨(km)" |
| | | width="120" |
| | | align="right" |
| | | /> |
| | | <el-table-column |
| | | prop="endMileage" |
| | | label="ç»æéç¨(km)" |
| | | width="120" |
| | | align="right" |
| | | /> |
| | | <el-table-column |
| | | prop="distance" |
| | | label="è¡é©¶éç¨(km)" |
| | | width="120" |
| | | align="right" |
| | | /> |
| | | <el-table-column |
| | | prop="fuelConsumption" |
| | | label="æ²¹è(L/100km)" |
| | | width="130" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <span |
| | | :style="scope.row.isAbnormal ? 'color:#F56C6C;font-weight:600;' : ''" |
| | | > |
| | | {{ scope.row.fuelConsumption != null ? scope.row.fuelConsumption.toFixed(2) : '-' }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="avgConsumption" |
| | | label="车è¾å¹³åæ²¹è" |
| | | width="130" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <span> |
| | | {{ scope.row.avgConsumption != null ? scope.row.avgConsumption.toFixed(2) : '-' }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å¼å¸¸é¢è¦" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.isAbnormal" type="danger" size="small"> |
| | | å¼å¸¸ |
| | | </el-tag> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right" width="140" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="openEdit(scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | @click="removeRow(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | <el-button v-if="scope.row.status === '0' || !scope.row.status" type="success" link size="small" @click="handleSubmitRow(scope.row)">æäº¤</el-button> |
| | | <el-button v-if="scope.row.status === '0' || !scope.row.status" type="primary" link size="small" @click="handleUpdate(scope.row)">ç¼è¾</el-button> |
| | | <el-button v-if="scope.row.status === '0' || !scope.row.status" type="danger" link size="small" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | <el-button v-if="scope.row.status === '1'" type="warning" link size="small" @click="handleRevokeRow(scope.row)">æ¤å</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="640px" |
| | | destroy-on-close |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="110px" |
| | | label-position="right" |
| | | > |
| | | <!-- å页 --> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" |
| | | /> |
| | | </div> |
| | | <!-- å¼¹çª --> |
| | | <el-dialog :title="title" v-model="open" width="640px" append-to-body> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="110px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车è¾ç¼å·ï¼" prop="vehicleCode"> |
| | | <el-input |
| | | v-model="form.vehicleCode" |
| | | placeholder="请è¾å
¥è½¦è¾ç¼å·" |
| | | /> |
| | | <el-form-item label="å æ²¹è½¦è¾" prop="vehicleId"> |
| | | <el-select v-model="form.vehicleId" placeholder="è¯·éæ©è½¦è¾(çç
§)" style="width: 100%" filterable> |
| | | <el-option |
| | | v-for="item in vehicleList" |
| | | :key="item.id" |
| | | :label="item.plateNumber" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车çå·ç ï¼" prop="plateNumber"> |
| | | <el-input |
| | | v-model="form.plateNumber" |
| | | placeholder="请è¾å
¥è½¦çå·ç " |
| | | /> |
| | | <el-form-item label="å
³èå æ²¹å¡" prop="fuelCardId"> |
| | | <el-select v-model="form.fuelCardId" placeholder="è¯·éæ©å æ²¹å¡" style="width: 100%" filterable> |
| | | <el-option |
| | | v-for="item in availableFuelCardList" |
| | | :key="item.id" |
| | | :label="item.cardNumber + ' - ' + item.issuer" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | <div v-if="form.fuelCardId" style="font-size: 12px; color: #999; margin-top: 5px;"> |
| | | å½åä½é¢: {{ getFuelCardBalance(form.fuelCardId) }} å
|
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å æ²¹æ¥æï¼" prop="fuelDate"> |
| | | <el-form-item label="å æ²¹æ¥æ" prop="fuelDate"> |
| | | <el-date-picker |
| | | v-model="form.fuelDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©å æ²¹æ¥æ" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ²¹æªå·ï¼" prop="gunNo"> |
| | | <el-input |
| | | v-model="form.gunNo" |
| | | placeholder="请è¾å
¥æ²¹æªå·" |
| | | /> |
| | | <el-form-item label="å票å·ç " prop="invoiceNo"> |
| | | <el-input v-model="form.invoiceNo" placeholder="请è¾å
¥å票å·ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éé¢(å
)ï¼" prop="amount"> |
| | | <el-input-number |
| | | v-model="form.amount" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | placeholder="请è¾å
¥éé¢" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="éé¢(å
)" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" :step="100" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åæ°(L)ï¼" prop="liters"> |
| | | <el-input-number |
| | | v-model="form.liters" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | placeholder="请è¾å
¥åæ°" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="åæ°(L)" prop="liters"> |
| | | <el-input-number v-model="form.liters" :min="0" :precision="2" :step="10" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµ·å§éç¨(km)ï¼" prop="startMileage"> |
| | | <el-input-number |
| | | v-model="form.startMileage" |
| | | :min="0" |
| | | :step="1" |
| | | placeholder="请è¾å
¥èµ·å§éç¨" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="èµ·å§éç¨(km)" prop="startMileage"> |
| | | <el-input-number v-model="form.startMileage" :min="0" :precision="2" :step="100" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»æéç¨(km)ï¼" prop="endMileage"> |
| | | <el-input-number |
| | | v-model="form.endMileage" |
| | | :min="0" |
| | | :step="1" |
| | | placeholder="请è¾å
¥ç»æéç¨" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="ç»æéç¨(km)" prop="endMileage"> |
| | | <el-input-number v-model="form.endMileage" :min="0" :precision="2" :step="100" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" placeholder="请è¾å
¥å¤æ³¨å
容" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="handleCancel">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ä¿ å</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡® å®</el-button> |
| | | <el-button @click="cancel">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { ref, reactive, onMounted, computed } from 'vue'; |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import request from '@/utils/request'; |
| | | import { |
| | | listFuelRecord, |
| | | addFuelRecord, |
| | | updateFuelRecord, |
| | | delFuelRecord, |
| | | listVehicleInfo, |
| | | listAllFuelCard, |
| | | submitFuelRecord, |
| | | revokeFuelRecord |
| | | } from '@/api/inventoryManagement/vehicle'; |
| | | |
| | | // 模æå æ²¹è®°å½æ°æ® |
| | | const rawRecords = ref([ |
| | | { |
| | | id: 1, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | fuelDate: "2024-12-01", |
| | | gunNo: "01", |
| | | amount: 500, |
| | | liters: 70, |
| | | startMileage: 12000, |
| | | endMileage: 12600, |
| | | }, |
| | | { |
| | | id: 2, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | fuelDate: "2024-12-15", |
| | | gunNo: "02", |
| | | amount: 520, |
| | | liters: 72, |
| | | startMileage: 12600, |
| | | endMileage: 13250, |
| | | }, |
| | | { |
| | | id: 3, |
| | | vehicleCode: "CL-202402", |
| | | plateNumber: "粤B67890", |
| | | fuelDate: "2024-12-05", |
| | | gunNo: "03", |
| | | amount: 430, |
| | | liters: 60, |
| | | startMileage: 8000, |
| | | endMileage: 8520, |
| | | }, |
| | | { |
| | | id: 4, |
| | | vehicleCode: "CL-202402", |
| | | plateNumber: "粤B67890", |
| | | fuelDate: "2024-12-20", |
| | | gunNo: "01", |
| | | amount: 450, |
| | | liters: 63, |
| | | startMileage: 8520, |
| | | endMileage: 9000, |
| | | }, |
| | | { |
| | | id: 5, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | fuelDate: "2025-01-05", |
| | | gunNo: "01", |
| | | amount: 700, |
| | | liters: 90, |
| | | startMileage: 13250, |
| | | endMileage: 13600, // ææ¾å¼å¸¸æ²¹è |
| | | }, |
| | | ]); |
| | | const loading = ref(true); |
| | | const tableData = ref([]); |
| | | const total = ref(0); |
| | | const open = ref(false); |
| | | const title = ref(""); |
| | | const formRef = ref(null); |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | vehicleCode: "", |
| | | dateRange: [], |
| | | const vehicleList = ref([]); |
| | | const fuelCardList = ref([]); |
| | | const deptOptions = ref([]); |
| | | const dateRange = ref([]); |
| | | |
| | | const availableFuelCardList = computed(() => { |
| | | return fuelCardList.value.filter(item => item.status !== 'CANCELLED'); |
| | | }); |
| | | |
| | | // è¡¨æ ¼æ°æ®ï¼å
å«è®¡ç®åæ®µï¼ |
| | | const tableData = ref([]); |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | deptId: undefined, |
| | | vehicleId: undefined, |
| | | fuelCardId: undefined, |
| | | beginDate: undefined, |
| | | endDate: undefined |
| | | }); |
| | | |
| | | // å¼¹çª & 表å |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref("æ°å¢å 油记å½"); |
| | | const isEdit = ref(false); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | fuelDate: "", |
| | | gunNo: "", |
| | | amount: null, |
| | | liters: null, |
| | | startMileage: null, |
| | | endMileage: null, |
| | | id: undefined, |
| | | vehicleId: undefined, |
| | | fuelCardId: undefined, |
| | | fuelDate: undefined, |
| | | amount: undefined, |
| | | liters: undefined, |
| | | startMileage: undefined, |
| | | endMileage: undefined, |
| | | invoiceNo: undefined, |
| | | remark: undefined |
| | | }); |
| | | |
| | | const rules = { |
| | | vehicleCode: [{ required: true, message: "请è¾å
¥è½¦è¾ç¼å·", trigger: "blur" }], |
| | | plateNumber: [{ required: true, message: "请è¾å
¥è½¦çå·ç ", trigger: "blur" }], |
| | | fuelDate: [{ required: true, message: "è¯·éæ©å æ²¹æ¥æ", trigger: "change" }], |
| | | gunNo: [{ required: true, message: "请è¾å
¥æ²¹æªå·", trigger: "blur" }], |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | liters: [{ required: true, message: "请è¾å
¥åæ°", trigger: "blur" }], |
| | | startMileage: [{ required: true, message: "请è¾å
¥èµ·å§éç¨", trigger: "blur" }], |
| | | endMileage: [{ required: true, message: "请è¾å
¥ç»æéç¨", trigger: "blur" }], |
| | | vehicleId: [{ required: true, message: "å æ²¹è½¦è¾ä¸è½ä¸ºç©º", trigger: "change" }], |
| | | fuelCardId: [{ required: true, message: "å æ²¹å¡ä¸è½ä¸ºç©º", trigger: "change" }], |
| | | fuelDate: [{ required: true, message: "å æ²¹æ¥æä¸è½ä¸ºç©º", trigger: "change" }], |
| | | amount: [{ required: true, message: "éé¢ä¸è½ä¸ºç©º", trigger: "blur" }] |
| | | }; |
| | | |
| | | // éæ°è®¡ç®æ²¹èã平忲¹èåå¼å¸¸é¢è¦ |
| | | const recomputeTable = () => { |
| | | const records = rawRecords.value; |
| | | |
| | | // 1. å
æè½¦è¾ç»è®¡å¹³åæ²¹è |
| | | const stats = {}; |
| | | records.forEach((r) => { |
| | | const distance = r.endMileage - r.startMileage; |
| | | if (distance <= 0 || !r.liters) return; |
| | | const cons = (r.liters / distance) * 100; // L/100km |
| | | if (!stats[r.vehicleCode]) { |
| | | stats[r.vehicleCode] = { totalCons: 0, count: 0 }; |
| | | } |
| | | stats[r.vehicleCode].totalCons += cons; |
| | | stats[r.vehicleCode].count += 1; |
| | | function getDictData() { |
| | | request({url: '/system/user/deptTree', method: 'get'}).then(res => { |
| | | deptOptions.value = res.data || []; |
| | | }); |
| | | |
| | | const avgMap = {}; |
| | | Object.keys(stats).forEach((key) => { |
| | | avgMap[key] = stats[key].totalCons / stats[key].count; |
| | | listVehicleInfo({current: 1, size: 1000}).then(res => { |
| | | vehicleList.value = res.data.records; |
| | | }); |
| | | listAllFuelCard().then(res => { |
| | | fuelCardList.value = res.data; |
| | | }); |
| | | } |
| | | |
| | | // 2. æçéæ¡ä»¶è¿æ»¤å¹¶è¡¥å
计ç®å段 |
| | | const filtered = records |
| | | .filter((r) => { |
| | | if ( |
| | | searchForm.vehicleCode && |
| | | !r.vehicleCode.includes(searchForm.vehicleCode.trim()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if (Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) { |
| | | const [start, end] = searchForm.dateRange; |
| | | if (r.fuelDate < start || r.fuelDate > end) { |
| | | return false; |
| | | function getFuelCardBalance(id) { |
| | | const c = fuelCardList.value.find(item => item.id === id); |
| | | return c ? c.currentBalance : 0; |
| | | } |
| | | |
| | | |
| | | const statTotalAmount = ref(0); |
| | | const statTotalLiters = ref(0); |
| | | |
| | | function getStats() { |
| | | const query = { ...queryParams, current: 1, size: 10000 }; |
| | | listFuelRecord(query).then(res => { |
| | | const arr = res.data.records || []; |
| | | statTotalAmount.value = arr.reduce((sum, item) => sum + (item.amount || 0), 0); |
| | | statTotalLiters.value = arr.reduce((sum, item) => sum + (item.liters || 0), 0); |
| | | }); |
| | | } |
| | | |
| | | function getList() { |
| | | loading.value = true; |
| | | if (dateRange.value && dateRange.value.length === 2) { |
| | | queryParams.beginDate = dateRange.value[0]; |
| | | queryParams.endDate = dateRange.value[1]; |
| | | } else { |
| | | queryParams.beginDate = undefined; |
| | | queryParams.endDate = undefined; |
| | | } |
| | | listFuelRecord(queryParams).then(response => { |
| | | tableData.value = response.data.records; |
| | | total.value = response.data.total; |
| | | loading.value = false; |
| | | getStats(); |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | getStats(); |
| | | }); |
| | | } |
| | | |
| | | function handleQuery() { |
| | | queryParams.current = 1; |
| | | getList(); |
| | | } |
| | | |
| | | function resetQuery() { |
| | | queryParams.deptId = undefined; |
| | | queryParams.vehicleId = undefined; |
| | | queryParams.fuelCardId = undefined; |
| | | dateRange.value = []; |
| | | handleQuery(); |
| | | } |
| | | |
| | | function reset() { |
| | | Object.assign(form, { |
| | | id: undefined, |
| | | vehicleId: undefined, |
| | | fuelCardId: undefined, |
| | | fuelDate: undefined, |
| | | amount: undefined, |
| | | liters: undefined, |
| | | startMileage: undefined, |
| | | endMileage: undefined, |
| | | invoiceNo: undefined, |
| | | remark: undefined |
| | | }); |
| | | if (formRef.value) formRef.value.resetFields(); |
| | | } |
| | | |
| | | function handleAdd() { |
| | | reset(); |
| | | open.value = true; |
| | | title.value = "æ°å¢å 油记å½"; |
| | | } |
| | | |
| | | function handleUpdate(row) { |
| | | reset(); |
| | | Object.assign(form, row); |
| | | open.value = true; |
| | | title.value = "ä¿®æ¹å 油记å½"; |
| | | } |
| | | |
| | | function submitForm() { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | if (!form.id) { |
| | | const bal = getFuelCardBalance(form.fuelCardId); |
| | | if (bal < form.amount) { |
| | | ElMessage.warning("è¯¥å æ²¹å¡ä½é¢ä¸è¶³ï¼å½åä½é¢: " + bal); |
| | | } |
| | | } |
| | | return true; |
| | | }) |
| | | .map((r) => { |
| | | const distance = r.endMileage - r.startMileage; |
| | | const fuelConsumption = |
| | | distance > 0 && r.liters |
| | | ? (r.liters / distance) * 100 |
| | | : null; |
| | | const avgConsumption = |
| | | avgMap[r.vehicleCode] != null ? avgMap[r.vehicleCode] : null; |
| | | const isAbnormal = |
| | | avgConsumption != null && |
| | | fuelConsumption != null && |
| | | fuelConsumption > avgConsumption * 1.2; |
| | | |
| | | return { |
| | | ...r, |
| | | distance, |
| | | fuelConsumption, |
| | | avgConsumption, |
| | | isAbnormal, |
| | | }; |
| | | }); |
| | | |
| | | tableData.value = filtered; |
| | | }; |
| | | |
| | | // æ¥è¯¢ |
| | | const handleQuery = () => { |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.vehicleCode = ""; |
| | | searchForm.dateRange = []; |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | // è¡æ ·å¼ï¼å¼å¸¸é«äº®ï¼ |
| | | const tableRowClassName = ({ row }) => { |
| | | if (row.isAbnormal) { |
| | | return "row-abnormal"; |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | // æ°å¢ |
| | | const openAdd = () => { |
| | | dialogTitle.value = "æ°å¢å 油记å½"; |
| | | isEdit.value = false; |
| | | Object.assign(form, { |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | fuelDate: "", |
| | | gunNo: "", |
| | | amount: null, |
| | | liters: null, |
| | | startMileage: null, |
| | | endMileage: null, |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ç¼è¾ |
| | | const openEdit = (row) => { |
| | | dialogTitle.value = "ç¼è¾å 油记å½"; |
| | | isEdit.value = true; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ä¿å |
| | | const handleSubmit = () => { |
| | | if (!formRef.value) return; |
| | | formRef.value.validate((valid) => { |
| | | if (!valid) return; |
| | | |
| | | if (form.endMileage <= form.startMileage) { |
| | | ElMessage.warning("ç»æéç¨å¿
须大äºèµ·å§éç¨"); |
| | | return; |
| | | } |
| | | |
| | | if (isEdit.value) { |
| | | const index = rawRecords.value.findIndex((r) => r.id === form.id); |
| | | if (index !== -1) { |
| | | rawRecords.value[index] = { ...form }; |
| | | |
| | | if (form.id != null) { |
| | | updateFuelRecord(form).then(response => { |
| | | ElMessage.success("ä¿®æ¹æå"); |
| | | open.value = false; |
| | | getList(); |
| | | getDictData(); |
| | | }); |
| | | } else { |
| | | addFuelRecord(form).then(response => { |
| | | ElMessage.success("æ°å¢æå"); |
| | | open.value = false; |
| | | getList(); |
| | | getDictData(); |
| | | }); |
| | | } |
| | | ElMessage.success("å æ²¹è®°å½å·²æ´æ°"); |
| | | } else { |
| | | const newId = rawRecords.value.length |
| | | ? Math.max(...rawRecords.value.map((r) => r.id)) + 1 |
| | | : 1; |
| | | rawRecords.value.push({ ...form, id: newId }); |
| | | ElMessage.success("å æ²¹è®°å½å·²æ°å¢"); |
| | | } |
| | | dialogVisible.value = false; |
| | | recomputeTable(); |
| | | }); |
| | | }; |
| | | } |
| | | |
| | | const handleCancel = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | // å é¤ |
| | | const removeRow = (row) => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å é¤è¯¥å 油记å½ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | function handleDelete(row) { |
| | | ElMessageBox.confirm('æ¯å¦ç¡®è®¤å é¤è¯¥è稿记å½ï¼', "è¦å", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | rawRecords.value = rawRecords.value.filter((r) => r.id !== row.id); |
| | | recomputeTable(); |
| | | ElMessage.success("å 餿å"); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | type: "warning" |
| | | }).then(function() { |
| | | return delFuelRecord(row.id); |
| | | }).then(() => { |
| | | getList(); |
| | | getDictData(); |
| | | ElMessage.success("å 餿å"); |
| | | }); |
| | | } |
| | | |
| | | function cancel() { |
| | | open.value = false; |
| | | reset(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | recomputeTable(); |
| | | getDictData(); |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dialog-footer { |
| | | text-align: right; |
| | | function handleSubmitRow(row) { |
| | | ElMessageBox.confirm('确认æäº¤è¯¥ç¬å 油记å½åï¼æäº¤åå°ä»å æ²¹å¡ä¸æ£é¤ä½é¢', 'æç¤º', { type: 'warning' }).then(() => { |
| | | return submitFuelRecord(row.id); |
| | | }).then(() => { |
| | | ElMessage.success('æäº¤æå'); |
| | | getList(); |
| | | getStats(); |
| | | }).catch(() => {}); |
| | | } |
| | | |
| | | :deep(.row-abnormal) { |
| | | background-color: #fff5f5; |
| | | function handleRevokeRow(row) { |
| | | ElMessageBox.confirm('确认æ¤å该ç¬å 油记å½åï¼æ¤ååå°éè¿å æ²¹å¡ä½é¢', 'æç¤º', { type: 'warning' }).then(() => { |
| | | return revokeFuelRecord(row.id); |
| | | }).then(() => { |
| | | ElMessage.success('æ¤åæå'); |
| | | getList(); |
| | | getStats(); |
| | | }).catch(() => {}); |
| | | } |
| | | </style> |
| | | |
| | | </script> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">车çå·ç ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.plateNumber" |
| | | style="width: 180px" |
| | | placeholder="请è¾å
¥è½¦çå·ç " |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">车è¾ç±»åï¼</span> |
| | | <el-select |
| | | v-model="searchForm.vehicleType" |
| | | style="width: 160px" |
| | | placeholder="è¯·éæ©è½¦è¾ç±»å" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in vehicleTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | |
| | | <span class="search_title ml10">æå±é¨é¨ï¼</span> |
| | | <el-select |
| | | v-model="searchForm.department" |
| | | style="width: 160px" |
| | | placeholder="è¯·éæ©æå±é¨é¨" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in departmentOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | |
| | | <span class="search_title ml10">ç¶æï¼</span> |
| | | <el-select |
| | | v-model="searchForm.status" |
| | | style="width: 140px" |
| | | placeholder="è¯·éæ©ç¶æ" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | |
| | | <span class="search_title ml10">彿¡£ç¶æï¼</span> |
| | | <el-select |
| | | v-model="searchForm.archived" |
| | | style="width: 140px" |
| | | placeholder="è¯·éæ©å½æ¡£ç¶æ" |
| | | clearable |
| | | > |
| | | <el-option label="æªå½æ¡£" value="false" /> |
| | | <el-option label="已彿¡£" value="true" /> |
| | | </el-select> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <div class="search_form" > |
| | | <div style="flex: 1; padding-right: 20px;"> |
| | | <el-row :gutter="16"> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">车çå·ç ï¼</span> |
| | | <el-input v-model="queryParams.plateNumber" placeholder="请è¾å
¥è½¦çå·ç " clearable style="flex: 1;" @keyup.enter.native="handleQuery" /> |
| | | </el-col> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">æå±é¨é¨ï¼</span> |
| | | <el-tree-select v-model="queryParams.deptId" :data="deptOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="è¯·éæ©é¨é¨" check-strictly clearable filterable style="flex: 1;" /> |
| | | </el-col> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <span class="search_title" style="white-space: nowrap;">ç¶æï¼</span> |
| | | <el-select v-model="queryParams.vehicleStatus" placeholder="è¯·éæ©ç¶æ" clearable style="flex: 1;"> |
| | | <el-option label="å¨ç¨" value="NORMAL" /> |
| | | <el-option label="é²ç½®" value="IDLE" /> |
| | | <el-option label="ç»´ä¿®" value="REPAIR" /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6" style="display: flex; align-items: center;"> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" icon="Plus" @click="openAdd">æ°å¢è½¦è¾</el-button> |
| | | <el-button type="primary" icon="Plus" @click="handleAdd">æ°å¢è½¦è¾</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)" |
| | | height="calc(100vh - 22em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column |
| | | prop="vehicleCode" |
| | | label="车è¾ç¼å·" |
| | | width="140" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="plateNumber" |
| | | label="车çå·ç " |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="vehicleType" |
| | | label="车è¾ç±»å" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="department" |
| | | label="æå±é¨é¨" |
| | | width="140" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="purchaseDate" |
| | | label="è´ç½®æ¥æ" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="licenseNumber" |
| | | label="è¡é©¶è¯ç¼å·" |
| | | width="160" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="licenseIssueDate" |
| | | label="åè¯æ¥æ" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="licenseExpireDate" |
| | | label="å°ææ¥æ" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="ç¶æ" width="100" align="center"> |
| | | <el-table-column prop="plateNumber" label="车çå·ç " min-width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="deptName" label="æå±é¨é¨" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column prop="userName" label="责任人" min-width="120" /> |
| | | <el-table-column prop="propertyOwnership" label="äº§æææ" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="å¹´æ£å°ææ¥æ" min-width="140"> |
| | | <template #default="scope"> |
| | | <el-tag :type="statusTagType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | <span>{{ scope.row.inspectionExpireDate }}</span> |
| | | <el-tag v-if="isExpired(scope.row.inspectionExpireDate)" type="danger" size="small" style="margin-left:5px;">å·²è¶
æ</el-tag> |
| | | <el-tag v-else-if="isExpiringSoon(scope.row.inspectionExpireDate)" type="warning" size="small" style="margin-left:5px;">å³å°å°æ</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä¿é©å°ææ¥æ" min-width="140"> |
| | | <template #default="scope"> |
| | | <span>{{ scope.row.insuranceExpireDate }}</span> |
| | | <el-tag v-if="isExpired(scope.row.insuranceExpireDate)" type="danger" size="small" style="margin-left:5px;">å·²è¶
æ</el-tag> |
| | | <el-tag v-else-if="isExpiringSoon(scope.row.insuranceExpireDate)" type="warning" size="small" style="margin-left:5px;">å³å°å°æ</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç¶æ" min-width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="statusTagType(scope.row.vehicleStatus)"> |
| | | {{ statusLabel(scope.row.vehicleStatus) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="彿¡£ç¶æ" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.archived ? 'info' : 'success'"> |
| | | {{ scope.row.archived ? '已彿¡£' : 'æªå½æ¡£' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" fixed="right" width="220" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="openEdit(scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="warning" |
| | | link |
| | | size="small" |
| | | :disabled="scope.row.archived" |
| | | @click="archiveRow(scope.row)" |
| | | > |
| | | 彿¡£ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | @click="removeRow(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | <el-button type="success" link size="small" @click="handleCost(scope.row)">è´¹ç¨ç®¡ç</el-button> |
| | | <el-button type="primary" link size="small" @click="handleUpdate(scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link size="small" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="600px" |
| | | destroy-on-close |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | label-position="right" |
| | | > |
| | | <!-- å页 --> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" |
| | | /> |
| | | </div> |
| | | <!-- æ°å¢/ç¼è¾è½¦è¾å¼¹çª --> |
| | | <el-dialog :title="title" v-model="open" width="600px" append-to-body> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="110px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车è¾ç¼å·ï¼" prop="vehicleCode"> |
| | | <el-input v-model="form.vehicleCode" placeholder="请è¾å
¥è½¦è¾ç¼å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车çå·ç ï¼" prop="plateNumber"> |
| | | <el-form-item label="车çå·ç " prop="plateNumber"> |
| | | <el-input v-model="form.plateNumber" placeholder="请è¾å
¥è½¦çå·ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车è¾ç±»åï¼" prop="vehicleType"> |
| | | <el-select |
| | | v-model="form.vehicleType" |
| | | placeholder="è¯·éæ©è½¦è¾ç±»å" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in vehicleTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æå±é¨é¨ï¼" prop="department"> |
| | | <el-select |
| | | v-model="form.department" |
| | | placeholder="è¯·éæ©æå±é¨é¨" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in departmentOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | <el-form-item label="äº§æææ" prop="propertyOwnership"> |
| | | <el-input v-model="form.propertyOwnership" placeholder="å¦:èªæ,ç§èµç" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è´ç½®æ¥æï¼" prop="purchaseDate"> |
| | | <el-date-picker |
| | | v-model="form.purchaseDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©è´ç½®æ¥æ" |
| | | <el-form-item label="æå±é¨é¨" prop="deptId"> |
| | | <el-tree-select |
| | | v-model="form.deptId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="è¯·éæ©é¨é¨" |
| | | check-strictly |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æï¼" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ"> |
| | | <el-form-item label="责任人" prop="userId"> |
| | | <el-select v-model="form.userId" placeholder="è¯·éæ©è´£ä»»äºº" style="width: 100%" filterable> |
| | | <el-option |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.userId + ' - ' + (item.nickName || item.userName)" |
| | | :value="item.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¡é©¶è¯ç¼å·ï¼" prop="licenseNumber"> |
| | | <el-input |
| | | v-model="form.licenseNumber" |
| | | placeholder="请è¾å
¥è¡é©¶è¯ç¼å·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åè¯æ¥æï¼" prop="licenseIssueDate"> |
| | | <el-date-picker |
| | | v-model="form.licenseIssueDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©åè¯æ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="ç¶æ" prop="vehicleStatus"> |
| | | <el-select v-model="form.vehicleStatus" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="å¨ç¨" value="NORMAL" /> |
| | | <el-option label="é²ç½®" value="IDLE" /> |
| | | <el-option label="ç»´ä¿®" value="REPAIR" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å°ææ¥æï¼" prop="licenseExpireDate"> |
| | | <el-form-item label="å¹´æ£å°æ" prop="inspectionExpireDate"> |
| | | <el-date-picker |
| | | v-model="form.licenseExpireDate" |
| | | v-model="form.inspectionExpireDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©å°ææ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¿é©å°æ" prop="insuranceExpireDate"> |
| | | <el-date-picker |
| | | v-model="form.insuranceExpireDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©å°ææ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" placeholder="请è¾å
¥å
容" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="handleCancel">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ä¿ å</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡® å®</el-button> |
| | | <el-button @click="cancel">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- è´¹ç¨ç®¡çæ½å± --> |
| | | <el-drawer v-model="costDrawer" :title="'è´¹ç¨ç®¡ç - ' + currentVehiclePlate" size="50%"> |
| | | <div style="padding: 20px;"> |
| | | <div style="margin-bottom: 20px;"> |
| | | <el-button type="primary" @click="handleAddCost">æ°å¢è®°å½</el-button> |
| | | </div> |
| | | <el-table v-loading="costLoading" :data="costList" border> |
| | | <el-table-column label="è´¹ç¨ç±»å" prop="costType"> |
| | | <template #default="scope"> |
| | | {{ costTypeLabel(scope.row.costType) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="éé¢(å
)" prop="amount" align="right" /> |
| | | <el-table-column label="åçæ¥æ" prop="costDate" /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-button type="danger" link size="small" @click="handleDeleteCost(scope.row)"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination |
| | | v-show="costTotal > 0" |
| | | :total="costTotal" |
| | | v-model:page="costQuery.current" |
| | | v-model:limit="costQuery.size" |
| | | @pagination="getCostList" |
| | | /> |
| | | </div> |
| | | </el-drawer> |
| | | |
| | | <!-- æ°å¢è´¹ç¨å¼¹çª --> |
| | | <el-dialog title="æ°å¢è´¹ç¨" v-model="costOpen" width="400px" append-to-body> |
| | | <el-form ref="costFormRef" :model="costForm" :rules="costRules" label-width="100px"> |
| | | <el-form-item label="è´¹ç¨ç±»å" prop="costType"> |
| | | <el-select v-model="costForm.costType" placeholder="è¯·éæ©" style="width:100%"> |
| | | <el-option label="ä¿å
»" value="MAINTENANCE" /> |
| | | <el-option label="å¹´æ£" value="INSPECTION" /> |
| | | <el-option label="ä¿é©" value="INSURANCE" /> |
| | | <el-option label="ç»´ä¿®" value="REPAIR" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="éé¢(å
)" prop="amount"> |
| | | <el-input-number v-model="costForm.amount" :precision="2" :min="0" style="width:100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="åçæ¥æ" prop="costDate"> |
| | | <el-date-picker v-model="costForm.costDate" type="date" value-format="YYYY-MM-DD" style="width:100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="costForm.remark" type="textarea" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitCostForm">ç¡® å®</el-button> |
| | | <el-button @click="costOpen = false">å æ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { ref, reactive, onMounted } from 'vue'; |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import { listVehicleInfo, getWarningList, addVehicleInfo, updateVehicleInfo, delVehicleInfo } from '@/api/inventoryManagement/vehicle'; |
| | | import request from '@/utils/request'; |
| | | |
| | | // 模æè½¦è¾åºç¡æ°æ® |
| | | const allVehicles = ref([ |
| | | { |
| | | id: 1, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | vehicleType: "å¢å¼è´§è½¦", |
| | | department: "ç©æµä¸é¨", |
| | | purchaseDate: "2022-03-15", |
| | | licenseNumber: "4401-2022-0001", |
| | | licenseIssueDate: "2022-03-10", |
| | | licenseExpireDate: "2026-03-10", |
| | | status: "å¨ç¨", |
| | | archived: false, |
| | | }, |
| | | { |
| | | id: 2, |
| | | vehicleCode: "CL-202402", |
| | | plateNumber: "粤B67890", |
| | | vehicleType: "å·è车", |
| | | department: "ç©æµäºé¨", |
| | | purchaseDate: "2021-08-01", |
| | | licenseNumber: "4401-2021-0123", |
| | | licenseIssueDate: "2021-07-28", |
| | | licenseExpireDate: "2025-07-28", |
| | | status: "ç»´ä¿®", |
| | | archived: false, |
| | | }, |
| | | { |
| | | id: 3, |
| | | vehicleCode: "CL-202403", |
| | | plateNumber: "粤C11223", |
| | | vehicleType: "çµå¼è½¦", |
| | | department: "项ç®è¿è¾é¨", |
| | | purchaseDate: "2020-05-20", |
| | | licenseNumber: "4401-2020-0456", |
| | | licenseIssueDate: "2020-05-18", |
| | | licenseExpireDate: "2024-05-18", |
| | | status: "é²ç½®", |
| | | archived: false, |
| | | }, |
| | | { |
| | | id: 4, |
| | | vehicleCode: "CL-202404", |
| | | plateNumber: "粤D33445", |
| | | vehicleType: "å¢å¼è´§è½¦", |
| | | department: "èµäº§ç®¡çé¨", |
| | | purchaseDate: "2019-11-11", |
| | | licenseNumber: "4401-2019-0789", |
| | | licenseIssueDate: "2019-11-08", |
| | | licenseExpireDate: "2023-11-08", |
| | | status: "å¨ç¨", |
| | | archived: true, |
| | | }, |
| | | ]); |
| | | const loading = ref(true); |
| | | const tableData = ref([]); |
| | | const total = ref(0); |
| | | const open = ref(false); |
| | | const title = ref(""); |
| | | const formRef = ref(null); |
| | | |
| | | // 䏿æä¸¾ |
| | | const vehicleTypeOptions = [ |
| | | { label: "å¢å¼è´§è½¦", value: "å¢å¼è´§è½¦" }, |
| | | { label: "å·è车", value: "å·è车" }, |
| | | { label: "çµå¼è½¦", value: "çµå¼è½¦" }, |
| | | { label: "å
¶ä»", value: "å
¶ä»" }, |
| | | ]; |
| | | const deptOptions = ref([]); |
| | | const userList = ref([]); |
| | | |
| | | const departmentOptions = [ |
| | | { label: "ç©æµä¸é¨", value: "ç©æµä¸é¨" }, |
| | | { label: "ç©æµäºé¨", value: "ç©æµäºé¨" }, |
| | | { label: "项ç®è¿è¾é¨", value: "项ç®è¿è¾é¨" }, |
| | | { label: "èµäº§ç®¡çé¨", value: "èµäº§ç®¡çé¨" }, |
| | | ]; |
| | | |
| | | const statusOptions = [ |
| | | { label: "å¨ç¨", value: "å¨ç¨" }, |
| | | { label: "é²ç½®", value: "é²ç½®" }, |
| | | { label: "ç»´ä¿®", value: "ç»´ä¿®" }, |
| | | ]; |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | plateNumber: "", |
| | | vehicleType: "", |
| | | department: "", |
| | | status: "", |
| | | archived: "", |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | plateNumber: undefined, |
| | | deptId: undefined, |
| | | vehicleStatus: undefined |
| | | }); |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | const tableData = ref([...allVehicles.value]); |
| | | |
| | | // å¼¹çª & 表å |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref("æ°å¢è½¦è¾"); |
| | | const isEdit = ref(false); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | vehicleType: "", |
| | | department: "", |
| | | purchaseDate: "", |
| | | licenseNumber: "", |
| | | licenseIssueDate: "", |
| | | licenseExpireDate: "", |
| | | status: "å¨ç¨", |
| | | archived: false, |
| | | id: undefined, |
| | | plateNumber: undefined, |
| | | propertyOwnership: undefined, |
| | | deptId: undefined, |
| | | userId: undefined, |
| | | vehicleStatus: 'NORMAL', |
| | | inspectionExpireDate: undefined, |
| | | insuranceExpireDate: undefined, |
| | | remark: undefined |
| | | }); |
| | | |
| | | const rules = { |
| | | vehicleCode: [{ required: true, message: "请è¾å
¥è½¦è¾ç¼å·", trigger: "blur" }], |
| | | plateNumber: [{ required: true, message: "请è¾å
¥è½¦çå·ç ", trigger: "blur" }], |
| | | vehicleType: [{ required: true, message: "è¯·éæ©è½¦è¾ç±»å", trigger: "change" }], |
| | | department: [{ required: true, message: "è¯·éæ©æå±é¨é¨", trigger: "change" }], |
| | | purchaseDate: [{ required: true, message: "è¯·éæ©è´ç½®æ¥æ", trigger: "change" }], |
| | | status: [{ required: true, message: "è¯·éæ©ç¶æ", trigger: "change" }], |
| | | licenseNumber: [{ required: true, message: "请è¾å
¥è¡é©¶è¯ç¼å·", trigger: "blur" }], |
| | | licenseIssueDate: [{ required: true, message: "è¯·éæ©åè¯æ¥æ", trigger: "change" }], |
| | | plateNumber: [{ required: true, message: "车çå·ç ä¸è½ä¸ºç©º", trigger: "blur" }], |
| | | deptId: [{ required: true, message: "æå±é¨é¨ä¸è½ä¸ºç©º", trigger: "change" }], |
| | | vehicleStatus: [{ required: true, message: "ç¶æä¸è½ä¸ºç©º", trigger: "change" }] |
| | | }; |
| | | |
| | | // æ¥è¯¢ |
| | | const handleQuery = () => { |
| | | tableData.value = allVehicles.value.filter((item) => { |
| | | if ( |
| | | searchForm.plateNumber && |
| | | !item.plateNumber.includes(searchForm.plateNumber.trim()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if (searchForm.vehicleType && item.vehicleType !== searchForm.vehicleType) { |
| | | return false; |
| | | } |
| | | if (searchForm.department && item.department !== searchForm.department) { |
| | | return false; |
| | | } |
| | | if (searchForm.status && item.status !== searchForm.status) { |
| | | return false; |
| | | } |
| | | if (searchForm.archived !== "") { |
| | | const targetArchived = searchForm.archived === "true"; |
| | | if (item.archived !== targetArchived) return false; |
| | | } |
| | | return true; |
| | | // åå
¸è·å |
| | | function getDicts() { |
| | | request({url: '/system/user/deptTree', method: 'get'}).then(res => { |
| | | deptOptions.value = res.data || []; |
| | | }); |
| | | }; |
| | | request({url: '/system/user/list', method: 'get'}).then(res => { |
| | | userList.value = res.rows || res.data || []; |
| | | }); |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.plateNumber = ""; |
| | | searchForm.vehicleType = ""; |
| | | searchForm.department = ""; |
| | | searchForm.status = ""; |
| | | searchForm.archived = ""; |
| | | |
| | | const statTotal = ref(0); |
| | | const statNormal = ref(0); |
| | | const statIdle = ref(0); |
| | | const statRepair = ref(0); |
| | | |
| | | function getStats() { |
| | | listVehicleInfo({ current: 1, size: 10000 }).then(res => { |
| | | const arr = res.data.records || []; |
| | | statTotal.value = arr.length; |
| | | statNormal.value = arr.filter(i => i.vehicleStatus === 'NORMAL').length; |
| | | statIdle.value = arr.filter(i => i.vehicleStatus === 'IDLE').length; |
| | | statRepair.value = arr.filter(i => i.vehicleStatus === 'REPAIR').length; |
| | | }); |
| | | } |
| | | |
| | | function getList() { |
| | | loading.value = true; |
| | | listVehicleInfo(queryParams).then(response => { |
| | | tableData.value = response.data.records; |
| | | total.value = response.data.total; |
| | | loading.value = false; |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | }); |
| | | } |
| | | |
| | | function handleWarningList() { |
| | | loading.value = true; |
| | | getWarningList().then(response => { |
| | | tableData.value = response.data; |
| | | total.value = response.data.length; |
| | | loading.value = false; |
| | | ElMessage.info("å·²å±ç¤º30天å
å°æçé¢è¦è½¦è¾"); |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | }); |
| | | } |
| | | |
| | | function handleQuery() { |
| | | queryParams.current = 1; |
| | | getList(); |
| | | getStats(); |
| | | } |
| | | |
| | | function resetQuery() { |
| | | queryParams.plateNumber = undefined; |
| | | queryParams.deptId = undefined; |
| | | queryParams.vehicleStatus = undefined; |
| | | handleQuery(); |
| | | }; |
| | | } |
| | | |
| | | // æ°å¢ |
| | | const openAdd = () => { |
| | | dialogTitle.value = "æ°å¢è½¦è¾"; |
| | | isEdit.value = false; |
| | | function reset() { |
| | | Object.assign(form, { |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | vehicleType: "", |
| | | department: "", |
| | | purchaseDate: "", |
| | | licenseInfo: "", |
| | | status: "å¨ç¨", |
| | | archived: false, |
| | | id: undefined, |
| | | plateNumber: undefined, |
| | | propertyOwnership: undefined, |
| | | deptId: undefined, |
| | | userId: undefined, |
| | | vehicleStatus: 'NORMAL', |
| | | inspectionExpireDate: undefined, |
| | | insuranceExpireDate: undefined, |
| | | remark: undefined |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | if (formRef.value) formRef.value.resetFields(); |
| | | } |
| | | |
| | | // ç¼è¾ |
| | | const openEdit = (row) => { |
| | | dialogTitle.value = "ç¼è¾è½¦è¾"; |
| | | isEdit.value = true; |
| | | function handleAdd() { |
| | | reset(); |
| | | open.value = true; |
| | | title.value = "æ°å¢è½¦è¾"; |
| | | } |
| | | |
| | | function handleUpdate(row) { |
| | | reset(); |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | open.value = true; |
| | | title.value = "ä¿®æ¹è½¦è¾"; |
| | | } |
| | | |
| | | // ä¿å |
| | | const handleSubmit = () => { |
| | | if (!formRef.value) return; |
| | | formRef.value.validate((valid) => { |
| | | if (!valid) return; |
| | | if (isEdit.value) { |
| | | const index = allVehicles.value.findIndex((v) => v.id === form.id); |
| | | if (index !== -1) { |
| | | allVehicles.value[index] = { ...form }; |
| | | function submitForm() { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | if (form.id != null) { |
| | | updateVehicleInfo(form).then(response => { |
| | | ElMessage.success("ä¿®æ¹æå"); |
| | | open.value = false; |
| | | getList(); |
| | | getStats(); |
| | | }); |
| | | } else { |
| | | addVehicleInfo(form).then(response => { |
| | | ElMessage.success("æ°å¢æå"); |
| | | open.value = false; |
| | | getList(); |
| | | getStats(); |
| | | }); |
| | | } |
| | | ElMessage.success("车è¾ä¿¡æ¯å·²æ´æ°"); |
| | | } else { |
| | | const newId = allVehicles.value.length |
| | | ? Math.max(...allVehicles.value.map((v) => v.id)) + 1 |
| | | : 1; |
| | | allVehicles.value.push({ ...form, id: newId }); |
| | | ElMessage.success("车è¾ä¿¡æ¯å·²æ°å¢"); |
| | | } |
| | | dialogVisible.value = false; |
| | | handleQuery(); |
| | | }); |
| | | }; |
| | | } |
| | | |
| | | const handleCancel = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | // 彿¡£ |
| | | const archiveRow = (row) => { |
| | | if (row.archived) return; |
| | | ElMessageBox.confirm( |
| | | "æ¯å¦ç¡®è®¤å°è¯¥è½¦è¾å½æ¡£ï¼å½æ¡£åä»
ä¿çæ¥è¯¢ï¼ä¸ååä¸è¿è¾ä»»å¡åé
ã", |
| | | "彿¡£æç¤º", |
| | | { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | } |
| | | ) |
| | | .then(() => { |
| | | row.archived = true; |
| | | if (row.status === "å¨ç¨") { |
| | | row.status = "é²ç½®"; |
| | | } |
| | | ElMessage.success("车è¾å·²å½æ¡£"); |
| | | handleQuery(); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const removeRow = (row) => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å é¤è¯¥è½¦è¾åºç¡ä¿¡æ¯ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | function handleDelete(row) { |
| | | ElMessageBox.confirm('æ¯å¦ç¡®è®¤å é¤è¯¥è½¦è¾æ°æ®ï¼', "è¦å", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | allVehicles.value = allVehicles.value.filter((v) => v.id !== row.id); |
| | | handleQuery(); |
| | | ElMessage.success("å 餿å"); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | type: "warning" |
| | | }).then(function() { |
| | | return delVehicleInfo(row.id); |
| | | }).then(() => { |
| | | getList(); |
| | | getStats(); |
| | | ElMessage.success("å 餿å"); |
| | | }); |
| | | } |
| | | |
| | | // ç¶ææ ·å¼ |
| | | function cancel() { |
| | | open.value = false; |
| | | reset(); |
| | | } |
| | | |
| | | |
| | | function isExpired(dateStr) { |
| | | if (!dateStr) return false; |
| | | const targetDate = new Date(dateStr).getTime(); |
| | | const now = new Date().getTime(); |
| | | return targetDate < now; |
| | | } |
| | | |
| | | function isExpiringSoon(dateStr) { |
| | | if (!dateStr) return false; |
| | | const targetDate = new Date(dateStr).getTime(); |
| | | const now = new Date().getTime(); |
| | | const diffDays = (targetDate - now) / (1000 * 3600 * 24); |
| | | return diffDays >= 0 && diffDays <= 30; |
| | | } |
| | | const statusTagType = (status) => { |
| | | if (status === "å¨ç¨") return "success"; |
| | | if (status === "é²ç½®") return "info"; |
| | | if (status === "ç»´ä¿®") return "warning"; |
| | | if (status === "NORMAL") return "success"; |
| | | if (status === "IDLE") return "info"; |
| | | if (status === "REPAIR") return "warning"; |
| | | return "default"; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dialog-footer { |
| | | text-align: right; |
| | | const statusLabel = (status) => { |
| | | if (status === "NORMAL") return "å¨ç¨"; |
| | | if (status === "IDLE") return "é²ç½®"; |
| | | if (status === "REPAIR") return "ç»´ä¿®"; |
| | | return status; |
| | | }; |
| | | |
| | | // è´¹ç¨ç®¡çé»è¾ |
| | | const costDrawer = ref(false); |
| | | const currentVehicleId = ref(null); |
| | | const currentVehiclePlate = ref(""); |
| | | const costLoading = ref(false); |
| | | const costList = ref([]); |
| | | const costTotal = ref(0); |
| | | const costQuery = reactive({ current: 1, size: 10, vehicleId: null }); |
| | | |
| | | function costTypeLabel(type) { |
| | | const map = { MAINTENANCE: 'ä¿å
»', INSPECTION: 'å¹´æ£', INSURANCE: 'ä¿é©', REPAIR: 'ç»´ä¿®' }; |
| | | return map[type] || type; |
| | | } |
| | | </style> |
| | | |
| | | function handleCost(row) { |
| | | currentVehicleId.value = row.id; |
| | | currentVehiclePlate.value = row.plateNumber; |
| | | costQuery.vehicleId = row.id; |
| | | costDrawer.value = true; |
| | | getCostList(); |
| | | } |
| | | |
| | | function getCostList() { |
| | | costLoading.value = true; |
| | | request({ url: '/vehicle/cost/listPage', method: 'get', params: costQuery }).then(res => { |
| | | costList.value = res.data.records; |
| | | costTotal.value = res.data.total; |
| | | costLoading.value = false; |
| | | }); |
| | | } |
| | | |
| | | const costOpen = ref(false); |
| | | const costFormRef = ref(null); |
| | | const costForm = reactive({ vehicleId: null, costType: undefined, amount: undefined, costDate: undefined, remark: undefined }); |
| | | const costRules = { |
| | | costType: [{ required: true, message: "è´¹ç¨ç±»åä¸è½ä¸ºç©º", trigger: "change" }], |
| | | amount: [{ required: true, message: "éé¢ä¸è½ä¸ºç©º", trigger: "blur" }], |
| | | costDate: [{ required: true, message: "æ¥æä¸è½ä¸ºç©º", trigger: "change" }] |
| | | }; |
| | | |
| | | function handleAddCost() { |
| | | costForm.vehicleId = currentVehicleId.value; |
| | | costForm.costType = undefined; |
| | | costForm.amount = undefined; |
| | | costForm.costDate = undefined; |
| | | costForm.remark = undefined; |
| | | if(costFormRef.value) costFormRef.value.resetFields(); |
| | | costOpen.value = true; |
| | | } |
| | | |
| | | function submitCostForm() { |
| | | costFormRef.value.validate(valid => { |
| | | if (valid) { |
| | | request({ url: '/vehicle/cost', method: 'post', data: costForm }).then(res => { |
| | | ElMessage.success("æ·»å æå"); |
| | | costOpen.value = false; |
| | | getCostList(); |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | function handleDeleteCost(row) { |
| | | ElMessageBox.confirm('ç¡®å®å é¤è¯¥è´¹ç¨è®°å½ï¼', 'æç¤º', { type: 'warning' }).then(() => { |
| | | request({ url: '/vehicle/cost/' + row.id, method: 'delete' }).then(() => { |
| | | ElMessage.success("å 餿å"); |
| | | getCostList(); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getDicts(); |
| | | getList(); |
| | | getStats(); |
| | | }); |
| | | </script> |
| | |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- ç¶æåæ´å®¡æ¹è¯¦æ
--> |
| | | <template v-else-if="row.businessType === 19"> |
| | | <div class="device-status-detail"> |
| | | <el-divider content-position="left">设å¤ç¶æåæ´è¯¦æ
</el-divider> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="设å¤åç§°">{{ detailData.deviceName || "--" }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç®æ ç¶æ"> |
| | | <el-tag :type="detailData.targetStatus === 1 ? 'success' : 'danger'"> |
| | | {{ detailData.targetStatusName || "--" }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="åæ´åå " :span="2">{{ detailData.reason || "--" }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | <div v-if="attachmentList.length" |
| | | class="detail-block"> |
| | |
| | | const formResolved = computed(() => resolveInstanceFormFields(props.row)); |
| | | |
| | | |
| | | // æ¯å¦ä¸ºç¹æ®å®¡æ¹ç±»åï¼éè´ãåè´§ãæ¥ä»·ï¼ |
| | | // æ¯å¦ä¸ºç¹æ®å®¡æ¹ç±»åï¼éè´ãåè´§ãæ¥ä»·ã设å¤ç¶æåæ´ï¼ |
| | | const isSpecialApprovalType = computed(() => { |
| | | return [5, 7, 6].includes(props.row.businessType); |
| | | return [5, 7, 6, 19].includes(props.row.businessType); |
| | | }); |
| | | |
| | | // 详æ
æ°æ®ï¼ç´æ¥ä½¿ç¨ä¼ å
¥ç detail-data åæ°ï¼ |
| | |
| | | } |
| | | } |
| | | |
| | | // 设å¤ç¶æåæ´å®¡æ¹ |
| | | else if (row.businessType === 19) { |
| | | if (row.formConfig) { |
| | | try { |
| | | detailData.value = JSON.parse(row.formConfig); |
| | | } catch(e) { |
| | | detailData.value = {}; |
| | | } |
| | | } else { |
| | | detailData.value = {}; |
| | | } |
| | | } |
| | | |
| | | // å
¶ä»å®¡æ¹ç±»å |
| | | else { |
| | | detailData.value = {}; |
| | |
| | | } |
| | | } |
| | | |
| | | // 设å¤ç¶æåæ´å®¡æ¹ |
| | | else if (row.businessType === 19) { |
| | | if (row.formConfig) { |
| | | try { |
| | | detailData.value = JSON.parse(row.formConfig); |
| | | } catch(e) { |
| | | detailData.value = {}; |
| | | } |
| | | } else { |
| | | detailData.value = {}; |
| | | } |
| | | } |
| | | |
| | | // å
¶ä»å®¡æ¹ç±»å |
| | | else { |
| | | detailData.value = {}; |
| | |
| | | <el-descriptions-item label="åå·¥å§å">{{ row.employeeName || row.applicantName || "â" }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥éåå " :span="2">{{ row.reimburseReason || "â" }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥ééé¢">{{ row.applyAmount != null ? `${row.applyAmount} å
` : "â" }}</el-descriptions-item> |
| | | <el-descriptions-item label="é¨é¨">{{ row.applicantDeptName || row.deptName || "â" }}</el-descriptions-item> |
| | | <el-descriptions-item label="仿¬¾æ¹">{{ row.payer || "â" }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¶æ¬¾äºº">{{ row.payee || "â" }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¶æ¬¾è´¦å·">{{ row.payeeAccount || "â" }}</el-descriptions-item> |
| | | <el-descriptions-item label="弿·æ¯è¡">{{ row.bankBranch || "â" }}</el-descriptions-item> |
| | |
| | | reimburseReason: "", |
| | | applyAmount: undefined, |
| | | payee: "", |
| | | payer: "", |
| | | payeeAccount: "", |
| | | bankBranch: "", |
| | | expenseDetails: [], |
| | |
| | | reimburseReason: raw.reimburseReason ?? "", |
| | | applyAmount, |
| | | payee: raw.payee ?? "", |
| | | payer: raw.payer ?? "", |
| | | payeeAccount: raw.payeeAccount ?? "", |
| | | bankBranch: raw.bankBranch ?? "", |
| | | expenseDetails, |
| | |
| | | :prefix-icon="Search" |
| | | @keyup.enter="handleQuery" |
| | | /> |
| | | <span class="search_title" style="margin-left: 10px;">é¨é¨ï¼</span> |
| | | <el-tree-select |
| | | v-model="searchForm.deptId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="éæ©é¨é¨" |
| | | check-strictly |
| | | clearable |
| | | style="width: 180px" |
| | | /> |
| | | <span class="search_title" style="margin-left: 10px;">项ç®ï¼</span> |
| | | <el-select v-model="searchForm.expenseCategory" clearable placeholder="鿩项ç®" style="width: 150px"> |
| | | <el-option |
| | | v-for="opt in EXPENSE_CATEGORY_OPTIONS" |
| | | :key="opt.value" |
| | | :label="opt.label" |
| | | :value="opt.value" |
| | | /> |
| | | </el-select> |
| | | <span class="search_title" style="margin-left: 10px;">ç³è¯·æ¶é´ï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.applyTimeRange" |
| | | type="daterange" |
| | | range-separator="-" |
| | | start-placeholder="å¼å§" |
| | | end-placeholder="ç»æ" |
| | | value-format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 240px" |
| | | /> |
| | | <el-button type="primary" style="margin-left: 10px" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </div> |
| | | <div class="search_actions"> |
| | | <!-- <el-button type="success" plain @click="handleImportClick">导å
¥</el-button>--> |
| | | <!-- <el-button type="warning" plain @click="handleExport">导åº</el-button>--> |
| | | <el-button type="warning" plain @click="handleExport">导åº</el-button> |
| | | <el-button type="primary" @click="openFormDialog('add')">æ°å¢è´¹ç¨æ¥é</el-button> |
| | | </div> |
| | | </div> |
| | |
| | | <el-card class="form-section" shadow="never"> |
| | | <template #header><span class="card-header-title">åºæ¬ä¿¡æ¯</span></template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åå·¥ç¼å·"> |
| | | <el-input v-model="form.employeeNo" readonly placeholder="éæ©åå·¥åèªå¨å¸¦åº" /> |
| | | <el-col :span="8"> |
| | | <el-form-item label="å½å±é¨é¨" prop="deptId"> |
| | | <el-tree-select |
| | | v-model="form.deptId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="è¯·éæ©é¨é¨" |
| | | check-strictly |
| | | style="width: 100%" |
| | | @change="onDeptChange" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="åå·¥å§å" prop="applicantId"> |
| | | <el-select |
| | | v-model="form.applicantId" |
| | |
| | | :value="u.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="åå·¥ç¼å·"> |
| | | <el-input v-model="form.employeeNo" readonly placeholder="éæ©åå·¥åèªå¨å¸¦åº" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-card class="form-section" shadow="never"> |
| | | <template #header><span class="card-header-title">æ¶æ¬¾ä¿¡æ¯</span></template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="仿¬¾æ¹" prop="payer"> |
| | | <el-input v-model="form.payer" placeholder="请è¾å
¥ä»æ¬¾æ¹" maxlength="50" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="æ¶æ¬¾äºº" prop="payee"> |
| | | <el-input v-model="form.payee" placeholder="请è¾å
¥æ¶æ¬¾äºº" maxlength="50" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="æ¶æ¬¾è´¦å·" prop="payeeAccount"> |
| | | <el-input v-model="form.payeeAccount" placeholder="é¶è¡å¡å·" maxlength="30" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="弿·æ¯è¡" prop="bankBranch"> |
| | | <el-input v-model="form.bankBranch" placeholder="弿·æ¯è¡å
¨ç§°" maxlength="100" /> |
| | | </el-form-item> |
| | |
| | | approveOpinion, |
| | | applicantFormSearchLoading, |
| | | applicantFormOptions, |
| | | deptOptions, |
| | | flowUserOptions, |
| | | detailTotalAmount, |
| | | approvalRuleHint, |
| | |
| | | remoteSearchApplicantForm, |
| | | userSelectLabel, |
| | | onApplicantChange, |
| | | onDeptChange, |
| | | onExpenseCategoryChange, |
| | | applyTemplate, |
| | | onDetailAmountChange, |
| | |
| | | persistFinReimbursement, |
| | | } from "@/api/officeProcessAutomation/finReimbursement.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { userListNoPageByTenantId, deptTreeSelect } from "@/api/system/user.js"; |
| | | import { computed, getCurrentInstance, nextTick, onMounted, reactive, ref } from "vue"; |
| | | import { |
| | | buildCostReimbursementSaveDto, |
| | |
| | | |
| | | const searchForm = reactive({ |
| | | applicantKeyword: "", |
| | | deptId: undefined, |
| | | expenseCategory: "", |
| | | applyTimeRange: [], |
| | | }); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ current: 1, size: 10, total: 0 }); |
| | |
| | | const allUsersCache = ref([]); |
| | | const applicantFormSearchLoading = ref(false); |
| | | const applicantFormOptions = ref([]); |
| | | const deptOptions = ref([]); |
| | | const formRef = ref(); |
| | | const form = reactive(createEmptyForm()); |
| | | const formDialog = reactive({ visible: false, title: "", mode: "add", readonly: false }); |
| | |
| | | { label: "æ¥éåå·", prop: "reimburseNo", width: 150 }, |
| | | { label: "ç³è¯·äººç¼å·", prop: "applicantNo", width: 110 }, |
| | | { label: "ç³è¯·äºº", prop: "applicantName", minWidth: 90 }, |
| | | { label: "é¨é¨", prop: "applicantDeptName", minWidth: 100 }, |
| | | { label: "è´¹ç¨ç±»å", prop: "expenseCategory", minWidth: 90, formatData: (v) => expenseCategoryLabel(v) }, |
| | | { label: "仿¬¾æ¹", prop: "payer", minWidth: 100 }, |
| | | { label: "æ¥ééé¢(å
)", prop: "applyAmount", width: 110 }, |
| | | { label: "æ¥éåå ", prop: "reimburseReason", minWidth: 160, showOverflowTooltip: true }, |
| | | { label: "ç³è¯·æ¶é´", prop: "applyTime", width: 165 }, |
| | |
| | | ]); |
| | | |
| | | const formRules = { |
| | | deptId: [{ required: true, message: "è¯·éæ©å½å±é¨é¨", trigger: "change" }], |
| | | applicantId: [{ required: true, message: "è¯·éæ©åå·¥", trigger: "change" }], |
| | | expenseCategory: [{ required: true, message: "è¯·éæ©è´¹ç¨ç±»å", trigger: "change" }], |
| | | reimburseReason: [{ required: true, message: "è¯·å¡«åæ¥éåå ", trigger: "blur" }], |
| | |
| | | |
| | | async function loadUserPool() { |
| | | try { |
| | | allUsersCache.value = unwrapArray(await userListNoPageByTenantId()); |
| | | allUsersCache.value = unwrapArray(await userListNoPageByTenantId({})); |
| | | } catch { |
| | | allUsersCache.value = []; |
| | | } |
| | | } |
| | | |
| | | function formatDeptOptions(list) { |
| | | return (list || []).map((d) => ({ |
| | | ...d, |
| | | rawLabel: d.label, |
| | | label: d.label, // é¨é¨ä¸æä¸è¦æ¾ç¤ºid |
| | | children: d.children && d.children.length ? formatDeptOptions(d.children) : undefined, |
| | | })); |
| | | } |
| | | |
| | | async function loadDeptOptions() { |
| | | try { |
| | | const res = await deptTreeSelect(); |
| | | deptOptions.value = formatDeptOptions(res.data || []); |
| | | } catch { |
| | | deptOptions.value = []; |
| | | } |
| | | } |
| | | |
| | | function userSelectLabel(u) { |
| | | const nick = u.nickName || ""; |
| | | const name = u.userName || ""; |
| | | if (nick && name && nick !== name) return `${nick}ï¼${name}ï¼`; |
| | | return nick || name || `ç¨æ·${u.userId ?? u.id ?? ""}`; |
| | | const empNo = employeeNoFromUser(u); |
| | | return `${nick || name || "ç¨æ·"} - ${empNo}`; |
| | | } |
| | | |
| | | function userById(id) { |
| | | return allUsersCache.value.find((u) => String(u.userId ?? u.id) === String(id)); |
| | | return applicantFormOptions.value.find((u) => String(u.userId ?? u.id) === String(id)); |
| | | } |
| | | |
| | | function employeeNoFromUser(u) { |
| | |
| | | return u.userName ?? u.userCode ?? u.jobNumber ?? u.workNo ?? (u.userId != null ? String(u.userId) : ""); |
| | | } |
| | | |
| | | function filterUsersByQuery(query) { |
| | | const list = allUsersCache.value.filter(isActiveUser); |
| | | const q = (query || "").trim().toLowerCase(); |
| | | if (!q) return [...list]; |
| | | return list.filter((u) => { |
| | | const nick = (u.nickName || "").toLowerCase(); |
| | | const uname = (u.userName || "").toLowerCase(); |
| | | return nick.includes(q) || uname.includes(q); |
| | | }); |
| | | } |
| | | |
| | | async function remoteSearchApplicantForm(query) { |
| | | applicantFormSearchLoading.value = true; |
| | | try { |
| | | if (!allUsersCache.value.length) await loadUserPool(); |
| | | applicantFormOptions.value = filterUsersByQuery(query); |
| | | const q = (query || "").trim(); |
| | | const params = {}; |
| | | if (form.deptId) { |
| | | params.deptId = form.deptId; |
| | | } |
| | | if (q) { |
| | | params.nickName = q; // You can pass nickName to backend |
| | | } |
| | | const res = await userListNoPageByTenantId(params); |
| | | applicantFormOptions.value = unwrapArray(res).filter(isActiveUser); |
| | | } catch { |
| | | applicantFormOptions.value = []; |
| | | } finally { |
| | | applicantFormSearchLoading.value = false; |
| | | } |
| | |
| | | form.employeeName = u.nickName || u.userName || ""; |
| | | form.employeeNo = employeeNoFromUser(u); |
| | | form.payee = form.payee || form.employeeName; |
| | | form.deptId = String(u.deptId ?? u.sysDeptId ?? ""); |
| | | form.deptName = u.dept?.deptName ?? u.deptName ?? ""; |
| | | const userDeptId = String(u.deptId ?? u.sysDeptId ?? ""); |
| | | if (userDeptId && form.deptId !== userDeptId) { |
| | | form.deptId = userDeptId; |
| | | const name = findDeptLabel(deptOptions.value, userDeptId); |
| | | form.deptName = name || (u.dept?.deptName ?? u.deptName ?? ""); |
| | | } |
| | | } else { |
| | | form.employeeName = ""; |
| | | form.employeeNo = ""; |
| | | } |
| | | } |
| | | |
| | | function findDeptLabel(nodes, id) { |
| | | if (!nodes) return null; |
| | | for (const node of nodes) { |
| | | if (String(node.id) === String(id)) return node.rawLabel || node.label; |
| | | if (node.children) { |
| | | const hit = findDeptLabel(node.children, id); |
| | | if (hit) return hit; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | function onDeptChange(deptId) { |
| | | if (deptId) { |
| | | const name = findDeptLabel(deptOptions.value, deptId); |
| | | if (name) form.deptName = name; |
| | | |
| | | remoteSearchApplicantForm(""); |
| | | if (form.applicantId) { |
| | | const u = userById(form.applicantId); |
| | | if (u && String(u.deptId ?? u.sysDeptId) !== String(deptId)) { |
| | | form.applicantId = ""; |
| | | onApplicantChange(""); |
| | | } |
| | | } |
| | | } else { |
| | | form.deptName = ""; |
| | | remoteSearchApplicantForm(""); |
| | | } |
| | | } |
| | | |
| | |
| | | proxy?.$modal?.msgError?.("å è½½æ¥é详æ
失败"); |
| | | return; |
| | | } |
| | | Object.assign(form, { |
| | | ...JSON.parse(JSON.stringify(editRow)), |
| | | reimbursementId: editRow.reimbursementId ?? editRow.id, |
| | | attachmentList: JSON.parse(JSON.stringify(editRow.attachmentList || editRow.invoiceAttachments || [])), |
| | | approvalFlowNodes: JSON.parse(JSON.stringify(editRow.approvalFlowNodes || [])), |
| | | expenseDetails: JSON.parse(JSON.stringify(editRow.expenseDetails || [])), |
| | | }); |
| | | Object.assign(form, buildCostReimbursementSaveDto(editRow)); |
| | | formDialog.title = "ç¼è¾è´¹ç¨æ¥é"; |
| | | await remoteSearchApplicantForm(""); |
| | | const u = userById(editRow.applicantId); |
| | | applicantFormOptions.value = u |
| | | ? [u] |
| | | : [{ userId: editRow.applicantId, nickName: editRow.employeeName, userName: editRow.employeeNo }]; |
| | | if (!u) { |
| | | applicantFormOptions.value.unshift({ userId: editRow.applicantId, nickName: editRow.employeeName, userName: editRow.applicantCode }); |
| | | } |
| | | } else { |
| | | form.approvalFlowNodes = buildAutoApprovalFlow(0, "other"); |
| | | remoteSearchApplicantForm(""); |
| | |
| | | } |
| | | |
| | | function handleExport() { |
| | | const data = allRows.value; |
| | | const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json;charset=utf-8" }); |
| | | const url = URL.createObjectURL(blob); |
| | | const a = document.createElement("a"); |
| | | a.href = url; |
| | | a.download = `è´¹ç¨æ¥é导åº_${dayjs().format("YYYYMMDDHHmmss")}.json`; |
| | | a.click(); |
| | | URL.revokeObjectURL(url); |
| | | proxy?.$modal?.msgSuccess?.(`å·²å¯¼åº ${data.length} æ¡`); |
| | | const params = buildFinReimbursementListParams({ |
| | | page: { current: 1, size: 99999 }, // Just so the function doesn't crash, backend export ignores pagination anyway |
| | | searchForm, |
| | | reimbursementType: FIN_REIMBURSEMENT_TYPE.COST, |
| | | }); |
| | | // Remove pagination parameters since export doesn't need them |
| | | delete params.current; |
| | | delete params.size; |
| | | delete params["page.current"]; |
| | | delete params["page.size"]; |
| | | proxy.download("/finReimbursement/export", params, `è´¹ç¨æ¥é导åº_${dayjs().format("YYYYMMDDHHmmss")}.xlsx`); |
| | | } |
| | | |
| | | function handleImportClick() { |
| | |
| | | |
| | | onMounted(async () => { |
| | | loadUserPool(); |
| | | loadDeptOptions(); |
| | | await fetchList(); |
| | | const editPayload = consumeReimburseEditFromApprove(); |
| | | if (editPayload?.reimbursementId != null) { |
| | |
| | | approveOpinion, |
| | | applicantFormSearchLoading, |
| | | applicantFormOptions, |
| | | deptOptions, |
| | | flowUserOptions, |
| | | detailTotalAmount, |
| | | approvalRuleHint, |
| | |
| | | remoteSearchApplicantForm, |
| | | userSelectLabel, |
| | | onApplicantChange, |
| | | onDeptChange, |
| | | onExpenseCategoryChange, |
| | | applyTemplate, |
| | | onDetailAmountChange, |
| | |
| | | return out; |
| | | } |
| | | |
| | | /** æ¯å¦åå¨å表ç鿡件ï¼ä»
ç³è¯·äººï¼ */ |
| | | export function hasActiveReimbursementSearch(searchForm = {}) { |
| | | return Boolean((searchForm?.applicantKeyword || "").trim()); |
| | | |
| | | export function expenseTypeToCategory(typeLabel) { |
| | | const hit = EXPENSE_CATEGORY_OPTIONS.find((x) => x.label === typeLabel || x.value === typeLabel); |
| | | return hit?.value || typeLabel || ""; |
| | | } |
| | | |
| | | /** æå¡ç«¯æªçææ¶ï¼æç³è¯·äººåå端å
åºçé */ |
| | | /** æ¯å¦åå¨å表ç鿡件 */ |
| | | export function hasActiveReimbursementSearch(searchForm = {}) { |
| | | return Boolean( |
| | | (searchForm?.applicantKeyword || "").trim() || |
| | | searchForm?.deptId || |
| | | searchForm?.expenseCategory || |
| | | (searchForm?.applyTimeRange && searchForm.applyTimeRange.length === 2) |
| | | ); |
| | | } |
| | | |
| | | /** æå¡ç«¯æªçææ¶ï¼ææ¡ä»¶åå端å
åºçé */ |
| | | export function filterReimbursementRowsBySearch(rows, searchForm = {}) { |
| | | const list = Array.isArray(rows) ? rows : []; |
| | | const kw = (searchForm?.applicantKeyword || "").trim().toLowerCase(); |
| | | if (!kw) return list; |
| | | let result = list; |
| | | |
| | | return list.filter((row) => { |
| | | const parts = [ |
| | | row.applicantName, |
| | | row.employeeName, |
| | | row.applicantNo, |
| | | row.applicantCode, |
| | | row.employeeNo, |
| | | ] |
| | | .filter((v) => v != null && v !== "") |
| | | .map((v) => String(v).toLowerCase()); |
| | | return parts.some((p) => p.includes(kw)); |
| | | }); |
| | | const kw = (searchForm?.applicantKeyword || "").trim().toLowerCase(); |
| | | if (kw) { |
| | | result = result.filter((row) => { |
| | | const parts = [ |
| | | row.applicantName, |
| | | row.employeeName, |
| | | row.applicantNo, |
| | | row.applicantCode, |
| | | row.employeeNo, |
| | | ] |
| | | .filter((v) => v != null && v !== "") |
| | | .map((v) => String(v).toLowerCase()); |
| | | return parts.some((p) => p.includes(kw)); |
| | | }); |
| | | } |
| | | |
| | | if (searchForm?.deptId) { |
| | | result = result.filter(row => String(row.deptId) === String(searchForm.deptId)); |
| | | } |
| | | |
| | | if (searchForm?.expenseCategory) { |
| | | result = result.filter(row => row.expenseCategory === searchForm.expenseCategory); |
| | | } |
| | | |
| | | if (searchForm?.applyTimeRange && searchForm.applyTimeRange.length === 2) { |
| | | const start = searchForm.applyTimeRange[0]; |
| | | const end = searchForm.applyTimeRange[1]; |
| | | result = result.filter(row => { |
| | | const t = (row.applyTime || row.createTime || "").substring(0, 10); |
| | | return t >= start && t <= end; |
| | | }); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** æå¹³å为 Spring GET å¯ç»å®ç queryï¼finReimbursementDto.xxxï¼å¿ç¨æ¹æ¬å·ï¼ */ |
| | |
| | | }) { |
| | | const dto = { |
| | | reimbursementType, |
| | | deptId: searchForm.deptId || undefined, |
| | | expenseType: searchForm.expenseCategory ? expenseCategoryToType(searchForm.expenseCategory) : undefined, |
| | | createTimeStart: searchForm.applyTimeRange?.[0] || undefined, |
| | | createTimeEnd: searchForm.applyTimeRange?.[1] || undefined, |
| | | ...pickApplicantQuery(searchForm), |
| | | ...(extraDto && typeof extraDto === "object" ? extraDto : {}), |
| | | }; |
| | |
| | | "page.size": page.size, |
| | | ...dto, |
| | | }; |
| | | |
| | | if (searchForm.applyTimeRange && searchForm.applyTimeRange.length === 2) { |
| | | params["params[beginTime]"] = searchForm.applyTimeRange[0] + " 00:00:00"; |
| | | params["params[endTime]"] = searchForm.applyTimeRange[1] + " 23:59:59"; |
| | | } |
| | | |
| | | appendDotNotationQuery(params, "finReimbursementDto", dto); |
| | | return params; |
| | | } |
| | |
| | | applicantName: row.applicantName || "", |
| | | employeeNo: row.applicantCode || "", |
| | | employeeName: row.applicantName || "", |
| | | applicantDeptName: row.applicantDeptName || "", |
| | | applicantDeptName: row.deptName || "", |
| | | deptId: row.deptId, |
| | | reimburseReason: row.reason || "", |
| | | travelStartTime: formatReimbursementDateTime(travel.startTime), |
| | | travelEndTime: formatReimbursementDateTime(travel.endTime), |
| | |
| | | applicantName: row.applicantName || "", |
| | | employeeNo: row.applicantCode || "", |
| | | employeeName: row.applicantName || "", |
| | | applicantDeptName: row.applicantDeptName || "", |
| | | applicantDeptName: row.deptName || "", |
| | | deptId: row.deptId, |
| | | reimburseReason: row.reason || "", |
| | | expenseCategory: row.expenseType || "", |
| | | expenseCategory: expenseTypeToCategory(row.expenseType) || "", |
| | | applyAmount: row.applyAmount, |
| | | applyTime: formatReimbursementDateTime(row.createTime), |
| | | payee: row.payeeName || "", |
| | | payer: row.payer || "", |
| | | payeeAccount: row.payeeAccount || "", |
| | | bankBranch: row.payeeBank || "", |
| | | billStatus: row.billStatus, |
| | |
| | | applyAmount, |
| | | detailTotalAmount: detailTotal, |
| | | payeeName: form.payee || "", |
| | | payer: form.payer || "", |
| | | payeeAccount: form.payeeAccount || "", |
| | | payeeBank: form.bankBranch || form.payeeBank || "", |
| | | billStatus: "IN_APPROVAL", |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog |
| | | title="ææ¡£å½æ¡£" |
| | | v-model="visible" |
| | | width="650px" |
| | | destroy-on-close |
| | | append-to-body |
| | | > |
| | | <div class="archive-container" v-loading="loading"> |
| | | <el-empty v-if="!loading && (!archiveGroups || archiveGroups.length === 0)" description="ææ å½æ¡£éä»¶" /> |
| | | |
| | | <el-collapse v-else v-model="activeNames"> |
| | | <el-collapse-item |
| | | v-for="(group, index) in archiveGroups" |
| | | :key="index" |
| | | :title="`${group.moduleName} (${group.files ? group.files.length : 0})`" |
| | | :name="index" |
| | | > |
| | | <div class="attachment-list"> |
| | | <div |
| | | v-for="(file, fileIdx) in group.files" |
| | | :key="fileIdx" |
| | | class="attachment-item" |
| | | > |
| | | <el-icon class="file-icon"><Document /></el-icon> |
| | | <a |
| | | :href="getRealUrl(file.url)" |
| | | target="_blank" |
| | | class="file-link" |
| | | :title="file.name" |
| | | > |
| | | {{ file.name }} |
| | | </a> |
| | | <el-button link type="primary" class="download-btn" @click="handleDownload(file.url, file.name)">ä¸è½½</el-button> |
| | | </div> |
| | | </div> |
| | | </el-collapse-item> |
| | | </el-collapse> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="visible = false">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch } from 'vue' |
| | | import { getProjectAttachments } from '@/api/projectManagement/project' |
| | | import { Document } from '@element-plus/icons-vue' |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | projectId: { |
| | | type: [Number, String], |
| | | default: null |
| | | } |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:modelValue']) |
| | | |
| | | const visible = ref(false) |
| | | const loading = ref(false) |
| | | const archiveGroups = ref([]) |
| | | const activeNames = ref([]) |
| | | |
| | | watch(() => props.modelValue, (val) => { |
| | | visible.value = val |
| | | if (val && props.projectId) { |
| | | fetchAttachments() |
| | | } else { |
| | | archiveGroups.value = [] |
| | | activeNames.value = [] |
| | | } |
| | | }) |
| | | |
| | | watch(() => visible.value, (val) => { |
| | | emit('update:modelValue', val) |
| | | }) |
| | | |
| | | const fetchAttachments = async () => { |
| | | loading.value = true |
| | | try { |
| | | const res = await getProjectAttachments(props.projectId) |
| | | if (res.code === 200) { |
| | | archiveGroups.value = res.data || [] |
| | | // é»è®¤å±å¼ææé¢æ¿ |
| | | activeNames.value = archiveGroups.value.map((_, index) => index) |
| | | } |
| | | } catch (error) { |
| | | console.error('è·åé件失败', error) |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // è·åçå®å°åï¼å¤çå¯è½çç¸å¯¹è·¯å¾åç¼ï¼ |
| | | const getRealUrl = (url) => { |
| | | if (!url) return '#' |
| | | if (url.startsWith('http://') || url.startsWith('https://')) { |
| | | return url |
| | | } |
| | | return import.meta.env.VITE_APP_BASE_API + url |
| | | } |
| | | |
| | | // æåæä»¶å(å
¼å®¹æ§é»è¾) |
| | | const getFileName = (url) => { |
| | | if (!url) return 'æªç¥æä»¶' |
| | | const parts = url.split('/') |
| | | const name = parts[parts.length - 1] |
| | | // å¤çå¯è½çæ¶é´æ³åç¼ |
| | | const nameParts = name.split('_') |
| | | return nameParts.length > 1 ? nameParts.slice(1).join('_') : name |
| | | } |
| | | |
| | | const handleDownload = (url, name) => { |
| | | const realUrl = getRealUrl(url) |
| | | const a = document.createElement('a') |
| | | a.style.display = 'none' |
| | | a.href = realUrl |
| | | a.download = name || getFileName(url) |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .archive-container { |
| | | min-height: 200px; |
| | | } |
| | | .attachment-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | padding: 5px 0; |
| | | } |
| | | .attachment-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 8px 12px; |
| | | background-color: #f8f9fa; |
| | | border-radius: 4px; |
| | | transition: background-color 0.3s; |
| | | } |
| | | .attachment-item:hover { |
| | | background-color: #f0f2f5; |
| | | } |
| | | .file-icon { |
| | | font-size: 16px; |
| | | color: #909399; |
| | | margin-right: 8px; |
| | | } |
| | | .file-link { |
| | | flex: 1; |
| | | color: #606266; |
| | | text-decoration: none; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | margin-right: 15px; |
| | | } |
| | | .file-link:hover { |
| | | color: #409EFF; |
| | | text-decoration: underline; |
| | | } |
| | | .download-btn { |
| | | margin-left: auto; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog |
| | | title="éå®ååå表" |
| | | v-model="visible" |
| | | width="800px" |
| | | append-to-body |
| | | destroy-on-close |
| | | @open="handleOpen" |
| | | > |
| | | <div v-loading="loading"> |
| | | <el-table :data="tableData" border style="width: 100%"> |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="salesContractNo" label="éå®ååç¼å·" align="center" show-overflow-tooltip /> |
| | | <el-table-column prop="customerName" label="客æ·åç§°" align="center" show-overflow-tooltip /> |
| | | </el-table> |
| | | |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" |
| | | /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="visible = false">å
³ é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch, reactive } from 'vue' |
| | | import { ledgerListPage } from '@/api/salesManagement/salesLedger' |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | projectId: { |
| | | type: [Number, String], |
| | | default: null |
| | | } |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:modelValue']) |
| | | |
| | | const visible = ref(false) |
| | | const loading = ref(false) |
| | | const tableData = ref([]) |
| | | const total = ref(0) |
| | | |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | projectId: undefined |
| | | }) |
| | | |
| | | watch(() => props.modelValue, (val) => { |
| | | visible.value = val |
| | | }) |
| | | |
| | | watch(visible, (val) => { |
| | | emit('update:modelValue', val) |
| | | }) |
| | | |
| | | function handleOpen() { |
| | | queryParams.current = 1 |
| | | queryParams.projectId = props.projectId |
| | | getList() |
| | | } |
| | | |
| | | function getList() { |
| | | if (!queryParams.projectId) { |
| | | tableData.value = [] |
| | | total.value = 0 |
| | | return |
| | | } |
| | | |
| | | loading.value = true |
| | | ledgerListPage(queryParams).then(res => { |
| | | const records = res?.data?.records || res?.records || res?.rows || [] |
| | | |
| | | // 妿䏿¡è®°å½ä¹æ²¡æä¸å¨ç¬¬ä¸é¡µä»¥åï¼å¯è½éè¦éç½®å页 |
| | | // åç«¯è¦æ±æ¾ç¤ºâéå®ååç¼å·âåâ客æ·åç§°âï¼è¿äºåæ®µå¨ salesLedgerListPage è¿åçæ°æ®ä¸åå¨ |
| | | tableData.value = records |
| | | total.value = res?.data?.total || res?.total || 0 |
| | | }).catch(() => { |
| | | tableData.value = [] |
| | | total.value = 0 |
| | | }).finally(() => { |
| | | loading.value = false |
| | | }) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | </style> |
| | |
| | | <template #projectStage="{ row }"> |
| | | <dict-tag :options="plan_status" :value="row.projectStage" /> |
| | | </template> |
| | | <template #salesContractCount="{ row }"> |
| | | <el-button link type="primary" @click="handleOpenContractDialog(row)">{{ row.salesContractCount || 0 }}</el-button> |
| | | </template> |
| | | <template #action="{ row }"> |
| | | <el-button link type="primary" @click="handleEdit(row)">ç¼è¾</el-button> |
| | | <el-button link type="primary" :loading="progressBtnLoadingId===row.id" @click="handleProgressReport(row)">è¿åº¦æ±æ¥</el-button> |
| | | <el-button link type="primary" @click="handleArchive(row)">ææ¡£å½æ¡£</el-button> |
| | | <el-button link type="primary" @click="handleDetail(row)">详æ
</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDia ref="formDiaRef" @completed="getList" /> |
| | | <ArchiveDialog v-model="archiveVisible" :project-id="archiveProjectId" /> |
| | | <ContractListDialog v-model="contractVisible" :project-id="contractProjectId" /> |
| | | <ProgressReportDialog |
| | | v-model="progressReportVisible" |
| | | :project-id="progressProjectId" |
| | |
| | | import SearchPanel from '@/components/SearchPanel/index.vue' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import FormDia from './components/formDia.vue' |
| | | import ArchiveDialog from './components/ArchiveDialog.vue' |
| | | import ContractListDialog from './components/ContractListDialog.vue' |
| | | import ProgressReportDialog from '@/components/ProjectManagement/ProgressReportDialog.vue' |
| | | import { |
| | | listProject, |
| | |
| | | const progressPlanNodes = ref([]) |
| | | const progressDefaultPlanNodeId = ref(undefined) |
| | | const progressBtnLoadingId = ref(null) |
| | | const archiveVisible = ref(false) |
| | | const archiveProjectId = ref(null) |
| | | const contractVisible = ref(false) |
| | | const contractProjectId = ref(null) |
| | | const submitLoading = ref(false) |
| | | const auditLoading = ref(false) |
| | | const reverseAuditLoading = ref(false) |
| | |
| | | { label: 'ç«é¡¹æ¥æ', prop: 'setupDate', align: 'center', width: '120' }, |
| | | { label: 'é¡¹ç®æ¥æº', prop: 'projectSource', align: 'center' }, |
| | | { label: '项ç®åç±»', prop: 'projectClassification', align: 'center' }, |
| | | { label: 'éå®ååæ»æ°', prop: 'salesContractCount', align: 'center', dataType: 'slot', slot: 'salesContractCount' }, |
| | | { label: 'æä½', prop: 'action', align: 'center', width: '250', dataType: 'slot', slot: 'action', fixed: 'right' } |
| | | ] |
| | | |
| | |
| | | projectType: r.planName ?? r.projectType, |
| | | projectSource: r.source ?? r.projectSource, |
| | | projectClassification: r.departmentName ?? r.projectClassification, |
| | | salesContractCount: r.salesContractCount, |
| | | raw: r |
| | | })) |
| | | pagination.value.total = response?.total || response?.data?.total || 0 |
| | |
| | | formDiaRef.value?.openDialog({ operationType: 'edit', row }) |
| | | } |
| | | |
| | | function handleArchive(row) { |
| | | if (!row?.id) return |
| | | archiveProjectId.value = row.id |
| | | archiveVisible.value = true |
| | | } |
| | | |
| | | function handleOpenContractDialog(row) { |
| | | if (!row?.id) return |
| | | contractProjectId.value = row.id |
| | | contractVisible.value = true |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card" shadow="never"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ä¸å¡åæ°è®¾ç½®</span> |
| | | </div> |
| | | </template> |
| | | <el-form |
| | | ref="configFormRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="240px" |
| | | style="max-width: 600px; margin-top: 20px;" |
| | | > |
| | | <el-form-item label="ååå件约å®åä¼ æåæé天æ°" prop="contractReturnReminderDays"> |
| | | <el-input-number |
| | | v-model="form.contractReturnReminderDays" |
| | | :min="1" |
| | | :max="365" |
| | | :step="1" |
| | | placeholder="请è¾å
¥å¤©æ°" |
| | | style="width: 200px" |
| | | /> |
| | | <span style="margin-left: 10px; color: #999;">天</span> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button type="primary" :loading="submitLoading" @click="handleSubmit">ä¿å设置</el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup name="ProjectConfig"> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { getConfig, updateConfig } from '@/api/projectManagement/config' |
| | | |
| | | const configFormRef = ref(null) |
| | | const submitLoading = ref(false) |
| | | const originalData = ref({}) |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | | contractReturnReminderDays: 3 |
| | | }) |
| | | |
| | | const rules = { |
| | | contractReturnReminderDays: [ |
| | | { required: true, message: 'æåæé天æ°ä¸è½ä¸ºç©º', trigger: 'blur' } |
| | | ] |
| | | } |
| | | |
| | | const loadData = async () => { |
| | | try { |
| | | const res = await getConfig() |
| | | if (res.code === 200 && res.data) { |
| | | Object.assign(form, res.data) |
| | | originalData.value = JSON.parse(JSON.stringify(res.data)) |
| | | } |
| | | } catch (error) { |
| | | console.error('è·åé
置失败:', error) |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = async () => { |
| | | if (!configFormRef.value) return |
| | | await configFormRef.value.validate(async (valid) => { |
| | | if (valid) { |
| | | submitLoading.value = true |
| | | try { |
| | | const res = await updateConfig(form) |
| | | if (res.code === 200) { |
| | | ElMessage.success('é
ç½®æ´æ°æå') |
| | | loadData() |
| | | } |
| | | } catch (error) { |
| | | console.error('æ´æ°é
置失败:', error) |
| | | } finally { |
| | | submitLoading.value = false |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleReset = () => { |
| | | if (originalData.value.id) { |
| | | Object.assign(form, originalData.value) |
| | | } else { |
| | | form.contractReturnReminderDays = 3 |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .box-card { |
| | | min-height: calc(100vh - 124px); |
| | | } |
| | | </style> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <el-row class="mb8" v-if="!isViewMode"> |
| | | <el-button type="primary" plain icon="Plus" @click="handleAddRow">æ°å¢ä¸è¡</el-button> |
| | | </el-row> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="400"> |
| | | <template #parameterItemSlot="{ row }"> |
| | | <span v-if="isViewMode">{{ row.parameterItem }}</span> |
| | | <el-input v-else v-model="row.parameterItem" placeholder="请è¾å
¥" /> |
| | | </template> |
| | | <template #parameterTypeSlot="{ row }"> |
| | | <span v-if="isViewMode">{{ row.parameterType === 1 ? 'æ°å' : row.parameterType === 0 ? 'æå' : '' }}</span> |
| | | <el-select v-else v-model="row.parameterType" @change="handleTypeChange(row)"> |
| | | <el-option label="æå" :value="0" /> |
| | | <el-option label="æ°å" :value="1" /> |
| | | </el-select> |
| | | </template> |
| | | <template #maxValueSlot="{ row }"> |
| | | <span v-if="isViewMode">{{ row.maxValue }}</span> |
| | | <el-input-number v-else v-model="row.maxValue" :controls="false" :disabled="row.parameterType !== 1" style="width: 100%" /> |
| | | </template> |
| | | <template #minValueSlot="{ row }"> |
| | | <span v-if="isViewMode">{{ row.minValue }}</span> |
| | | <el-input-number v-else v-model="row.minValue" :controls="false" :disabled="row.parameterType !== 1" style="width: 100%" /> |
| | | </template> |
| | | <template #unitSlot="{ row }"> |
| | | <span v-if="isViewMode">{{ row.unit }}</span> |
| | | <el-input v-else v-model="row.unit" /> |
| | | </template> |
| | | <template #standardValueSlot="{ row }"> |
| | | <span v-if="isViewMode">{{ row.standardValue }}</span> |
| | | <el-input v-else v-model="row.standardValue" :disabled="row.parameterType !== 0" /> |
| | | </template> |
| | | <template #controlValueSlot="{ row }"> |
| | | <span v-if="isViewMode">{{ row.controlValue }}</span> |
| | | <el-input v-else v-model="row.controlValue" /> |
| | | </template> |
| | | <template #operationSlot="{ row }"> |
| | | <el-button v-if="!isViewMode" type="danger" link @click="removeRow(row)">å é¤</el-button> |
| | | </template> |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" |
| | | <el-input-number v-if="row.parameterType === 1" |
| | | v-model="row.testValue" |
| | | :controls="false" |
| | | clearable |
| | | :disabled="isViewMode" |
| | | style="width: 100%" /> |
| | | <el-input v-else |
| | | v-model="row.testValue" |
| | | clearable |
| | | :disabled="isViewMode" /> |
| | | </template> |
| | |
| | | computed, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | watch |
| | | } from "vue"; |
| | | import { getOptions } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | |
| | | }); |
| | | const supplierList = ref([]); |
| | | const productOptions = ref([]); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ææ ", |
| | | prop: "parameterItem", |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "æ åå¼", |
| | | prop: "standardValue", |
| | | }, |
| | | { |
| | | label: "å
æ§å¼", |
| | | prop: "controlValue", |
| | | }, |
| | | { |
| | | label: "æ£éªå¼", |
| | | prop: "testValue", |
| | | dataType: "slot", |
| | | slot: "slot", |
| | | }, |
| | | ]); |
| | | const tableColumn = computed(() => { |
| | | const cols = [ |
| | | { |
| | | label: "ææ ", |
| | | prop: "parameterItem", |
| | | dataType: "slot", |
| | | slot: "parameterItemSlot" |
| | | }, |
| | | { |
| | | label: "åæ°ç±»å", |
| | | prop: "parameterType", |
| | | dataType: "slot", |
| | | slot: "parameterTypeSlot", |
| | | width: "120" |
| | | }, |
| | | { |
| | | label: "æå¤§å¼", |
| | | prop: "maxValue", |
| | | dataType: "slot", |
| | | slot: "maxValueSlot", |
| | | width: "100" |
| | | }, |
| | | { |
| | | label: "æå°å¼", |
| | | prop: "minValue", |
| | | dataType: "slot", |
| | | slot: "minValueSlot", |
| | | width: "100" |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | dataType: "slot", |
| | | slot: "unitSlot", |
| | | width: "100" |
| | | }, |
| | | { |
| | | label: "æ åå¼", |
| | | prop: "standardValue", |
| | | dataType: "slot", |
| | | slot: "standardValueSlot" |
| | | }, |
| | | { |
| | | label: "å
æ§å¼", |
| | | prop: "controlValue", |
| | | dataType: "slot", |
| | | slot: "controlValueSlot" |
| | | }, |
| | | { |
| | | label: "æ£éªå¼", |
| | | prop: "testValue", |
| | | dataType: "slot", |
| | | slot: "slot", |
| | | } |
| | | ]; |
| | | if (!isViewMode.value) { |
| | | cols.push({ |
| | | label: "æä½", |
| | | prop: "operation", |
| | | dataType: "slot", |
| | | slot: "operationSlot", |
| | | width: "80", |
| | | fixed: "right" |
| | | }); |
| | | } |
| | | return cols; |
| | | }); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const userList = ref([]); |
| | | const currentProductId = ref(0); |
| | | const testStandardOptions = ref([]); // ææ éæ©ä¸ææ¡æ°æ® |
| | | const modelOptions = ref([]); |
| | | |
| | | watch(() => tableData.value, (newVal) => { |
| | | if (isViewMode.value) return; |
| | | if (!newVal || newVal.length === 0) return; |
| | | |
| | | let passCount = 0; |
| | | let failCount = 0; |
| | | |
| | | newVal.forEach(item => { |
| | | let isPass = false; |
| | | const val = item.testValue; |
| | | |
| | | if (val === null || val === undefined || val === '') { |
| | | isPass = false; |
| | | } else if (item.parameterType === 1) { |
| | | const numVal = Number(val); |
| | | const min = Number(item.minValue); |
| | | const max = Number(item.maxValue); |
| | | if (!isNaN(numVal) && numVal >= min && numVal <= max) { |
| | | isPass = true; |
| | | } |
| | | } else { |
| | | if (String(val) === String(item.standardValue)) { |
| | | isPass = true; |
| | | } |
| | | } |
| | | |
| | | if (isPass) { |
| | | passCount++; |
| | | } else { |
| | | failCount++; |
| | | } |
| | | }); |
| | | |
| | | if (failCount === 0 && passCount > 0) { |
| | | form.value.checkResult = "åæ ¼"; |
| | | } else if (passCount === 0 && failCount > 0) { |
| | | form.value.checkResult = "ä¸åæ ¼"; |
| | | } else { |
| | | form.value.checkResult = "é¨ååæ ¼"; |
| | | } |
| | | }, { deep: true }); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | |
| | | ...item, |
| | | id: null, |
| | | })); |
| | | |
| | | if (tableData.value.some(item => item.parameterType === null || item.parameterType === undefined)) { |
| | | proxy.$modal.msgWarning("åæ°ç±»åæªé
ç½®ï¼è¯·é
ç½®ææ ç»´æ¤æ¨¡æ¿"); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("è·åæ ååæ°å¤±è´¥:", error); |
| | |
| | | tableData.value = res.data; |
| | | }); |
| | | }; |
| | | |
| | | const handleAddRow = () => { |
| | | tableData.value.push({ |
| | | parameterItem: '', |
| | | parameterType: 0, |
| | | maxValue: undefined, |
| | | minValue: undefined, |
| | | unit: '', |
| | | standardValue: '', |
| | | controlValue: '', |
| | | testValue: '' |
| | | }); |
| | | }; |
| | | |
| | | const removeRow = (row) => { |
| | | const index = tableData.value.indexOf(row); |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1); |
| | | } |
| | | }; |
| | | |
| | | const handleTypeChange = (row) => { |
| | | if (row.parameterType === 0) { |
| | | row.maxValue = undefined; |
| | | row.minValue = undefined; |
| | | } else { |
| | | row.standardValue = ''; |
| | | } |
| | | row.testValue = ''; |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { |
| | | downloadQualityInspect, |
| | | downloadQualityInspectPdf, |
| | | qualityInspectDel, |
| | | qualityInspectListPage, |
| | | qualityInspectUpdate, |
| | |
| | | }, |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: row => { |
| | | downLoadFile(row); |
| | | type: "dropdown", |
| | | dropdownItems: [ |
| | | { label: "Wordæ ¼å¼", command: "word" }, |
| | | { label: "PDFæ ¼å¼", command: "pdf" } |
| | | ], |
| | | commandFun: (command, row) => { |
| | | if (command === 'word') { |
| | | downLoadFile(row); |
| | | } else if (command === 'pdf') { |
| | | downLoadPdfFile(row); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | |
| | | |
| | | const link = document.createElement("a"); |
| | | link.href = downloadUrl; |
| | | link.download = "åæææ£éªæ¥å.docx"; |
| | | link.download = "åºåæ£éªæ¥å.docx"; |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | |
| | | document.body.removeChild(link); |
| | | window.URL.revokeObjectURL(downloadUrl); |
| | | }); |
| | | }; |
| | | |
| | | const downLoadPdfFile = row => { |
| | | downloadQualityInspectPdf({ id: row.id }).then(blobData => { |
| | | const blob = new Blob([blobData], { type: "application/pdf" }); |
| | | const downloadUrl = window.URL.createObjectURL(blob); |
| | | |
| | | const link = document.createElement("a"); |
| | | link.href = downloadUrl; |
| | | link.download = "åºåæ£éªæ¥å.pdf"; |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | |
| | |
| | | <el-form-item label="åä½" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥åä½" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ åå¼" prop="standardValue"> |
| | | <el-form-item label="åæ°ç±»å" prop="parameterType"> |
| | | <el-radio-group v-model="form.parameterType"> |
| | | <el-radio :label="0">æå</el-radio> |
| | | <el-radio :label="1">æ°å</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="æ åå¼" prop="standardValue" v-if="form.parameterType === 0"> |
| | | <el-input v-model="form.standardValue" placeholder="请è¾å
¥æ åå¼" /> |
| | | </el-form-item> |
| | | <template v-if="form.parameterType === 1"> |
| | | <el-form-item label="æå¤§å¼" prop="maxValue"> |
| | | <el-input-number v-model="form.maxValue" :controls="false" placeholder="请è¾å
¥æå¤§å¼" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="æå°å¼" prop="minValue"> |
| | | <el-input-number v-model="form.minValue" :controls="false" placeholder="请è¾å
¥æå°å¼" style="width: 100%" /> |
| | | </el-form-item> |
| | | </template> |
| | | <el-form-item label="å
æ§å¼" prop="controlValue"> |
| | | <el-input v-model="form.controlValue" placeholder="请è¾å
¥å
æ§å¼" /> |
| | | </el-form-item> |
| | |
| | | |
| | | const formRef = ref(null) |
| | | |
| | | const rules = { |
| | | parameterItem: [{ required: true, message: '请è¾å
¥åæ°é¡¹', trigger: 'blur' }], |
| | | unit: [{ required: true, message: '请è¾å
¥åä½', trigger: 'blur' }] |
| | | const validateMaxValue = (rule, value, callback) => { |
| | | if (value === undefined || value === null || value === '') { |
| | | callback(new Error('请è¾å
¥æå¤§å¼')) |
| | | } else if (props.form.minValue !== undefined && props.form.minValue !== null && props.form.minValue !== '' && Number(value) < Number(props.form.minValue)) { |
| | | callback(new Error('æå¤§å¼ä¸è½å°äºæå°å¼')) |
| | | } else { |
| | | if (props.form.minValue !== undefined && props.form.minValue !== null && props.form.minValue !== '') { |
| | | formRef.value?.clearValidate('minValue') |
| | | } |
| | | callback() |
| | | } |
| | | } |
| | | |
| | | const validateMinValue = (rule, value, callback) => { |
| | | if (value === undefined || value === null || value === '') { |
| | | callback(new Error('请è¾å
¥æå°å¼')) |
| | | } else if (props.form.maxValue !== undefined && props.form.maxValue !== null && props.form.maxValue !== '' && Number(value) > Number(props.form.maxValue)) { |
| | | callback(new Error('æå°å¼ä¸è½å¤§äºæå¤§å¼')) |
| | | } else { |
| | | if (props.form.maxValue !== undefined && props.form.maxValue !== null && props.form.maxValue !== '') { |
| | | formRef.value?.clearValidate('maxValue') |
| | | } |
| | | callback() |
| | | } |
| | | } |
| | | |
| | | const rules = computed(() => { |
| | | const baseRules = { |
| | | parameterItem: [{ required: true, message: '请è¾å
¥åæ°é¡¹', trigger: 'blur' }], |
| | | unit: [{ required: true, message: '请è¾å
¥åä½', trigger: 'blur' }], |
| | | parameterType: [{ required: true, message: 'è¯·éæ©åæ°ç±»å', trigger: 'change' }] |
| | | }; |
| | | if (props.form.parameterType === 1) { |
| | | baseRules.maxValue = [{ required: true, validator: validateMaxValue, trigger: 'blur' }]; |
| | | baseRules.minValue = [{ required: true, validator: validateMinValue, trigger: 'blur' }]; |
| | | } |
| | | return baseRules; |
| | | }) |
| | | |
| | | const computedTitle = computed(() => (props.operationType === 'edit' ? 'ç¼è¾æ ååæ°' : 'æ°å¢æ ååæ°')) |
| | | |
| | | const handleConfirm = () => { |
| | |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="parameterItem" label="忰项" min-width="120" /> |
| | | <el-table-column prop="unit" label="åä½" width="80" /> |
| | | <el-table-column prop="parameterType" label="åæ°ç±»å" width="100"> |
| | | <template #default="{ row }"> |
| | | {{ row.parameterType === 1 ? 'æ°å' : (row.parameterType === 0 ? 'æå' : '') }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="standardValue" label="æ åå¼" min-width="120" /> |
| | | <el-table-column prop="maxValue" label="æå¤§å¼" min-width="100" /> |
| | | <el-table-column prop="minValue" label="æå°å¼" min-width="100" /> |
| | | <el-table-column prop="controlValue" label="å
æ§å¼" min-width="120" /> |
| | | <el-table-column prop="defaultValue" label="é»è®¤å¼" min-width="120" /> |
| | | <el-table-column label="æä½" width="140" fixed="right" align="center"> |
| | |
| | | id: undefined, |
| | | parameterItem: '', |
| | | unit: '', |
| | | parameterType: 0, |
| | | standardValue: '', |
| | | maxValue: null, |
| | | minValue: null, |
| | | controlValue: '', |
| | | defaultValue: '' |
| | | }) |
| | |
| | | id: undefined, |
| | | parameterItem: '', |
| | | unit: '', |
| | | parameterType: 0, |
| | | standardValue: '', |
| | | maxValue: null, |
| | | minValue: null, |
| | | controlValue: '', |
| | | defaultValue: '' |
| | | }) |
| | |
| | | prop: "parameterItem", |
| | | }, |
| | | { |
| | | label: "åæ°ç±»å", |
| | | prop: "parameterType", |
| | | formatter: (row) => (row.parameterType === 1 ? "æ°å" : row.parameterType === 0 ? "æå" : ""), |
| | | }, |
| | | { |
| | | label: "æå¤§å¼", |
| | | prop: "maxValue", |
| | | }, |
| | | { |
| | | label: "æå°å¼", |
| | | prop: "minValue", |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | }, |
| | |
| | | ...item, |
| | | id: null, |
| | | })); |
| | | |
| | | if (tableData.value.some(item => item.parameterType === null || item.parameterType === undefined)) { |
| | | proxy.$modal.msgWarning("åæ°ç±»åæªé
ç½®ï¼è¯·é
ç½®ææ ç»´æ¤æ¨¡æ¿"); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("è·åæ ååæ°å¤±è´¥:", error); |
| | |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="400"> |
| | | <template #typeSlot="{ row }"> |
| | | {{ row.parameterType === 1 ? 'æ°å' : (row.parameterType === 0 ? 'æå' : '') }} |
| | | </template> |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" |
| | | clearable |
| | |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "åæ°ç±»å", |
| | | prop: "parameterType", |
| | | dataType: "slot", |
| | | slot: "typeSlot", |
| | | }, |
| | | { |
| | | label: "æ åå¼", |
| | | prop: "standardValue", |
| | | }, |
| | | { |
| | | label: "æå¤§å¼", |
| | | prop: "maxValue", |
| | | }, |
| | | { |
| | | label: "æå°å¼", |
| | | prop: "minValue", |
| | | }, |
| | | { |
| | | label: "å
æ§å¼", |
| | |
| | | ...item, |
| | | id: null, |
| | | })); |
| | | |
| | | if (tableData.value.some(item => item.parameterType === null || item.parameterType === undefined)) { |
| | | proxy.$modal.msgWarning("åæ°ç±»åæªé
ç½®ï¼è¯·é
ç½®ææ ç»´æ¤æ¨¡æ¿"); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("è·åæ ååæ°å¤±è´¥:", error); |
| | |
| | | const formRef = ref(); |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | | projectId: "", |
| | | projectName: "", |
| | | bidDocName: "", |
| | |
| | | Object.keys(form).forEach((key) => { |
| | | form[key] = ""; |
| | | }); |
| | | form.id = undefined; |
| | | form.bidDocFee = undefined; |
| | | form.bidDeposit = undefined; |
| | | }; |
| | |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | // 䏿 ååèµ·éå®ååå½å
¥ï¼è·³è½¬åå管çé¡µï¼æºå¸¦é¡¹ç®ä¸ææ åä½ä¿¡æ¯ |
| | | // 䏿 ååèµ·éå®å°è´¦å½å
¥ï¼è·³è½¬éå®å°è´¦ï¼æºå¸¦é¡¹ç®ä¿¡æ¯ |
| | | const handleStartContract = (row) => { |
| | | proxy.$tab.openPage("åèµ·éå®ååå½å
¥", "/salesManagement/contractManagement", { |
| | | proxy.$tab.openPage("éå®å°è´¦", "/salesManagement/salesLedger", { |
| | | action: "add", |
| | | projectId: row.projectId, |
| | | projectName: row.projectName, |
| | | signingUnit: row.tenderUnit, |
| | | }); |
| | | }; |
| | | |
| | |
| | | @change="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="项ç®åç§°ï¼" prop="projectId"> |
| | | <el-select |
| | | v-model="searchForm.projectId" |
| | | placeholder="è¯·éæ©é¡¹ç®" |
| | | clearable |
| | | filterable |
| | | style="width: 200px" |
| | | @change="handleQuery" |
| | | > |
| | | <el-option |
| | | v-for="item in projectOptions" |
| | | :key="item.id" |
| | | :label="item.no + ' - ' + item.title" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åè´§æ¥æï¼" prop="shippingDateRange"> |
| | | <el-date-picker |
| | | v-model="searchForm.shippingDateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 240px" |
| | | @change="handleQuery" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery"> æç´¢</el-button> |
| | | </el-form-item> |
| | |
| | | <el-table-column |
| | | label="å货订åå·" |
| | | prop="shippingNo" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="项ç®åç§°" |
| | | prop="projectName" |
| | | min-width="150" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | |
| | | show-overflow-tooltip |
| | | width="130" |
| | | /> |
| | | <el-table-column label="éè´§å" align="center" width="140"> |
| | | <template #default="scope"> |
| | | <el-button v-if="scope.row.returnNoList && scope.row.returnNoList.length > 0" link type="primary" @click="openReturnNosDialog(scope.row)"> |
| | | æ¥ç {{ scope.row.returnNoList.length }} 个éè´§å |
| | | </el-button> |
| | | <span v-else>--</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="æä½" width="220" align="center"> |
| | | <template #default="scope"> |
| | | <!-- <el-button--> |
| | | <!-- link--> |
| | | <!-- type="primary"--> |
| | | <!-- :disabled="!isApproved(scope.row.status)"--> |
| | | <!-- @click="openForm('edit', scope.row)">åè´§--> |
| | | <!-- </el-button>--> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | :disabled="scope.row.status === 'å®¡æ ¸éè¿' || scope.row.status === 'å·²åè´§'" |
| | | @click="openForm('edit', scope.row)">è¡¥å
åè´§ä¿¡æ¯ |
| | | </el-button> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | |
| | | @click="openDetail(scope.row)" |
| | | >详æ
|
| | | </el-button> |
| | | <!-- <el-button--> |
| | | <!-- link--> |
| | | <!-- type="danger"--> |
| | | <!-- :disabled="isApproving(scope.row.status)"--> |
| | | <!-- @click="handleDeleteSingle(scope.row)"--> |
| | | <!-- >å é¤--> |
| | | <!-- </el-button>--> |
| | | <el-button |
| | | link |
| | | type="danger" |
| | | :disabled="scope.row.status === 'å·²åè´§' || scope.row.status === 'å®¡æ ¸éè¿'" |
| | | @click="handleDeleteSingle(scope.row)" |
| | | >å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="åè´§åæ®ï¼" prop="shippingDocument"> |
| | | <el-input |
| | | v-model="form.shippingDocument" |
| | | placeholder="请è¾å
¥åè´§åæ®" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ¯å¦å¯¹è´¦ï¼" prop="isReconciled"> |
| | | <el-switch v-model="form.isReconciled" :active-value="1" :inactive-value="0" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ¯å¦ç»ç®ï¼" prop="isSettled"> |
| | | <el-switch v-model="form.isSettled" :active-value="1" :inactive-value="0" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ¯å¦å·²å¼ç¥¨ï¼" prop="isInvoiced"> |
| | | <el-switch v-model="form.isInvoiced" :active-value="1" :inactive-value="0" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12" v-if="form.isReconciled === 1"> |
| | | <el-form-item label="对账å彿¡£æ
åµï¼" prop="statementArchivingStatus"> |
| | | <el-input |
| | | type="textarea" |
| | | v-model="form.statementArchivingStatus" |
| | | placeholder="请è¾å
¥å¯¹è´¦å彿¡£æ
åµ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.isSettled === 1"> |
| | | <el-form-item label="ç»ç®å彿¡£æ
åµï¼" prop="settlementArchivingStatus"> |
| | | <el-input |
| | | type="textarea" |
| | | v-model="form.settlementArchivingStatus" |
| | | placeholder="请è¾å
¥ç»ç®å彿¡£æ
åµ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | <el-descriptions-item label="åºåºåå·" :span="2">{{ |
| | | detailRow.outboundBatches || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="åè´§åæ®">{{ |
| | | detailRow.shippingDocument || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¯å¦å¯¹è´¦">{{ |
| | | detailRow.isReconciled === 1 ? 'æ¯' : 'å¦' |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¯å¦ç»ç®">{{ |
| | | detailRow.isSettled === 1 ? 'æ¯' : 'å¦' |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¯å¦å·²å¼ç¥¨">{{ |
| | | detailRow.isInvoiced === 1 ? 'æ¯' : 'å¦' |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="对账å彿¡£æ
åµ" :span="2">{{ |
| | | detailRow.statementArchivingStatus || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç»ç®å彿¡£æ
åµ" :span="2">{{ |
| | | detailRow.settlementArchivingStatus || "--" |
| | | }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-table |
| | | :data="getDeliveryProductInfoList()" |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- éè´§åå·å¼¹æ¡ --> |
| | | <el-dialog v-model="returnNosDialogVisible" title="éè´§åå·å表" width="400px" @close="closeReturnNosDialog"> |
| | | <el-table :data="currentReturnNos" border size="small"> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column label="éè´§åå·" prop="returnNo" align="center" /> |
| | | </el-table> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeReturnNosDialog">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | getDeliveryDetail, |
| | | } from "@/api/salesManagement/deliveryLedger.js"; |
| | | import { delLedgerFile } from "@/api/salesManagement/salesLedger.js"; |
| | | import { listProject } from "@/api/projectManagement/project.js"; |
| | | import ImageUpload from "@/components/AttachmentUpload/image/index.vue"; |
| | | import ImagePreview from "@/components/AttachmentPreview/image/index.vue"; |
| | | |
| | |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const salesOrderOptions = ref([]); |
| | | const projectOptions = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | |
| | | const detailRow = ref(null); |
| | | const detailProductList = ref([]); |
| | | |
| | | // éè´§åå·å¼¹æ¡ |
| | | const returnNosDialogVisible = ref(false); |
| | | const currentReturnNos = ref([]); |
| | | |
| | | const openReturnNosDialog = (row) => { |
| | | currentReturnNos.value = (row.returnNoList || []).map(no => ({ returnNo: no })); |
| | | returnNosDialogVisible.value = true; |
| | | }; |
| | | |
| | | const closeReturnNosDialog = () => { |
| | | returnNosDialogVisible.value = false; |
| | | currentReturnNos.value = []; |
| | | }; |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | salesContractNo: "", // éå®è®¢åå· |
| | | shippingCarNumber: "", // 车çå· |
| | | expressNumber: "", // å¿«éåå· |
| | | salesContractNo: undefined, |
| | | shippingCarNumber: undefined, |
| | | expressNumber: undefined, |
| | | projectId: undefined, |
| | | shippingDateRange: [] |
| | | }, |
| | | form: { |
| | | id: null, |
| | |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | deliveryLedgerListPage({ ...searchForm.value, ...page }) |
| | | const params = { ...searchForm.value }; |
| | | if (params.shippingDateRange && params.shippingDateRange.length === 2) { |
| | | params.beginShippingDate = params.shippingDateRange[0]; |
| | | params.endShippingDate = params.shippingDateRange[1]; |
| | | } |
| | | delete params.shippingDateRange; |
| | | deliveryLedgerListPage({ ...params, ...page }) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records || []; |
| | |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = async (type, row) => { |
| | | // åè´§ï¼ä»
âå®¡æ ¸éè¿âå
许ç¼è¾ |
| | | if (type === "edit" && row && !isApproved(row.status)) { |
| | | proxy.$modal.msgWarning("åªæå®¡æ ¸éè¿çæ°æ®æå¯ä»¥åè´§"); |
| | | if (type === "edit" && row && (row.status === "å®¡æ ¸éè¿" || row.status === "å·²åè´§")) { |
| | | proxy.$modal.msgWarning("è¯¥ç¶æçæ°æ®ä¸å¯ä»¥ç¼è¾åè´§ä¿¡æ¯"); |
| | | return; |
| | | } |
| | | |
| | |
| | | shippingCarNumber: row.shippingCarNumber ?? "", |
| | | expressCompany: row.expressCompany ?? "", |
| | | expressNumber: row.expressNumber ?? "", |
| | | shippingDocument: row.shippingDocument ?? "", |
| | | isReconciled: row.isReconciled ?? 0, |
| | | statementArchivingStatus: row.statementArchivingStatus ?? "", |
| | | isSettled: row.isSettled ?? 0, |
| | | settlementArchivingStatus: row.settlementArchivingStatus ?? "", |
| | | isInvoiced: row.isInvoiced ?? 0, |
| | | }; |
| | | deliveryFileList.value = row.storageBlobVOs || []; |
| | | } |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/shippingInfo/export", {}, "åè´§å°è´¦.xlsx"); |
| | | const params = { ...searchForm.value }; |
| | | if (params.shippingDateRange && params.shippingDateRange.length === 2) { |
| | | params.beginShippingDate = params.shippingDateRange[0]; |
| | | params.endShippingDate = params.shippingDateRange[1]; |
| | | } |
| | | delete params.shippingDateRange; |
| | | proxy.download("/shippingInfo/export", params, "åè´§å°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | listProject({}).then(res => projectOptions.value = res.rows || res.data.records || res.data); |
| | | getList(); |
| | | }); |
| | | </script> |
| | |
| | | const fileList = ref([]); |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | | caseNo: "", |
| | | projectId: "", |
| | | projectName: "", |
| | |
| | | |
| | | const initProjects = async () => { |
| | | try { |
| | | const res = await listProject({ pageSize: 1000 }); |
| | | const rows = res?.rows || res?.data?.rows || []; |
| | | projectOptions.value = rows.map((p) => ({ label: p.projectName, value: p.id })); |
| | | const res = await listProject({ pageSize: 1000, current: 1, size: 1000 }); |
| | | const rows = res?.records || res?.rows || res?.data?.records || res?.data?.rows || res?.data || []; |
| | | projectOptions.value = rows.map((p) => ({ |
| | | label: (p.no ? p.no + ' - ' : '') + (p.title || ''), |
| | | value: p.id, |
| | | title: p.title |
| | | })); |
| | | } catch (e) { |
| | | console.error("å 载项ç®å表失败", e); |
| | | } |
| | |
| | | const initContracts = async () => { |
| | | try { |
| | | const res = await contractListNoPage({}); |
| | | const list = Array.isArray(res) ? res : res?.data ?? res?.records ?? []; |
| | | const list = Array.isArray(res) ? res : (res?.data?.records || res?.data || res?.records || []); |
| | | contractOptions.value = list.map((item) => ({ |
| | | label: item.contractNo + (item.signingUnit ? `ï¼${item.signingUnit}ï¼` : ""), |
| | | value: item.id, |
| | |
| | | |
| | | const handleProjectChange = (val) => { |
| | | const project = projectOptions.value.find((item) => item.value === val); |
| | | form.projectName = project ? project.label : ""; |
| | | form.projectName = project ? (project.title || project.label) : ""; |
| | | }; |
| | | |
| | | const handleContractChange = (val) => { |
| | |
| | | form[key] = ""; |
| | | }); |
| | | form.claimAmount = undefined; |
| | | form.id = undefined; |
| | | }; |
| | | |
| | | const openForm = async (type, row) => { |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form" |
| | | style="margin-bottom: 20px;"> |
| | | <div> |
| | | <span class="search_title">客æ·åç§°ï¼</span> |
| | | <el-input v-model="searchForm.searchText" |
| | | style="width: 240px" |
| | | placeholder="è¾å
¥å®¢æ·åç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | prefix-icon="Search" /> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="项ç®åç§°ï¼"> |
| | | <el-input |
| | | v-model="searchForm.projectName" |
| | | placeholder="请è¾å
¥é¡¹ç®åç§°" |
| | | clearable |
| | | prefix-icon="Search" |
| | | style="width: 200px" |
| | | @change="handleSearch" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°ï¼"> |
| | | <el-input |
| | | v-model="searchForm.customerName" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°" |
| | | clearable |
| | | prefix-icon="Search" |
| | | style="width: 200px" |
| | | @change="handleSearch" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="åååç§°ï¼"> |
| | | <el-input |
| | | v-model="searchForm.contractName" |
| | | placeholder="请è¾å
¥åååç§°" |
| | | clearable |
| | | prefix-icon="Search" |
| | | style="width: 200px" |
| | | @change="handleSearch" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <div class="app-container layout-fullscreen"> |
| | | <!-- Search Form --> |
| | | <div class="summary-strip"> |
| | | <div class="summary-stats"> |
| | | <span>å½å项ç®ï¼<b>{{ summaryData.totalProjects || 0 }}</b> 个</span> |
| | | <span>å
³è客æ·ï¼<b>{{ summaryData.totalCustomers || 0 }}</b> å®¶</span> |
| | | <span>ååæ»æ°ï¼<b>{{ summaryData.totalContracts || 0 }}</b> 份</span> |
| | | <span>ç¾çº¦æ»é¢ï¼<b>{{ fmtMoney(summaryData.totalAmount || 0) }}</b></span> |
| | | <span class="warn-summary">â ç¾è®¢è¶
30天æªå®æå款ï¼<span>{{ |
| | | summaryData.totalOverdueCount || 0 |
| | | }}</span> 份</span> |
| | | </div> |
| | | </div> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="table_list" |
| | | style="width: 100%"> |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | :row-key="(row) => row.customerId" |
| | | show-summary |
| | | :summary-method="summarizeMainTable" |
| | | @row-click="rowClickMethod" |
| | | highlight-current-row |
| | | height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="客æ·åç§°" |
| | | prop="customerName" |
| | | show-overflow-tooltip |
| | | width="200" /> |
| | | <el-table-column label="ååéé¢(å
)" |
| | | prop="contractAmounts" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="忬¾éé¢(å
)" |
| | | prop="receiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="éè´§éé¢(å
)" |
| | | prop="returnAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="åºæ¶éé¢(å
)" |
| | | prop="receiptableAmount" |
| | | show-overflow-tooltip |
| | | width="200"> |
| | | <template #default="{ row, column }"> |
| | | <el-text type="danger"> |
| | | {{ formattedNumber(row, column, row.receiptableAmount) }} |
| | | </el-text> |
| | | |
| | | <!-- Layout Columns --> |
| | | <div class="layout-columns"> |
| | | <!-- Column 1: Projects --> |
| | | <div class="column-panel" v-loading="prjLoading"> |
| | | <div class="panel-header"> |
| | | <span>1. é¡¹ç®æ±æ» ({{ prjTotal }})</span> |
| | | </div> |
| | | <div class="panel-body"> |
| | | <div v-if="projects.length === 0" class="empty-state">æ å¹é
项ç®</div> |
| | | |
| | | <div v-for="p in projects" :key="p.projectId" |
| | | class="item-card" :class="{ active: p.projectId === selectedProjectId }" |
| | | @click="handleSelectProject(p)"> |
| | | <div class="card-title-row"> |
| | | <div class="card-main-title">{{ p.projectName }}</div> |
| | | </div> |
| | | <div class="card-code">{{ p.projectCode || 'ææ é¡¹ç®ç¼å·' }}</div> |
| | | <div class="meta-row" style="margin-top: 8px;"> |
| | | <span>æ»é¢: <strong>{{ fmtMoney(p.contractAmounts) }}</strong></span> |
| | | <span>å¼ç¥¨: <strong>{{ fmtMoney(p.invoiceAmount) }}</strong></span> |
| | | </div> |
| | | <div class="meta-row"> |
| | | <span>忬¾: <strong>{{ fmtMoney(p.receiptPaymentAmount) }}</strong></span> |
| | | <span>åºæ¶: <strong>{{ fmtMoney(p.receiptableAmount) }}</strong></span> |
| | | </div> |
| | | <div class="meta-row"> |
| | | <span>éè´§: <strong>{{ fmtMoney(p.returnAmount) }}</strong></span> |
| | | <span>è¿åº¦: <strong :class="{'text-danger': p.overdueCount > 0}">{{ |
| | | calcRate(p.receiptPaymentAmount, p.contractAmounts) |
| | | }}%</strong></span> |
| | | </div> |
| | | <div class="meta-row"> |
| | | <span>客æ·: <strong>{{ p.customerCount || 0 }} å®¶</strong></span> |
| | | <span>åå: <strong>{{ p.contractCount || 0 }} 份</strong></span> |
| | | </div> |
| | | <div v-if="p.overdueCount > 0" style="margin-top: 6px;"> |
| | | <el-tag type="danger" size="small" effect="light">â {{ p.overdueCount }} 份è¶
æåå</el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-pagination"> |
| | | <el-pagination background small layout="total, prev, pager, next" :total="prjTotal" :page-size="prjPage.size" |
| | | v-model:current-page="prjPage.current" @current-change="handlePrjPageChange"/> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Column 2: Customers --> |
| | | <div class="column-panel" v-loading="custLoading"> |
| | | <div class="panel-header"> |
| | | <span>2. å®¢æ·æ±æ» ({{ custTotal }})</span> |
| | | </div> |
| | | <div class="panel-body"> |
| | | <div v-if="!selectedProjectId" class="empty-state">请å
å¨å·¦ä¾§éæ©é¡¹ç®</div> |
| | | <div v-else-if="customers.length === 0" class="empty-state">该项ç®ä¸æ å¹é
ç客æ·ä¿¡æ¯</div> |
| | | |
| | | <div v-for="c in customers" :key="c.customerId" |
| | | class="item-card" :class="{ active: c.customerId === selectedCustomerId }" |
| | | @click="handleSelectCustomer(c)"> |
| | | <div class="card-title-row"> |
| | | <div class="card-main-title">{{ c.customerName || 'æªå½å客æ·' }}</div> |
| | | </div> |
| | | <div class="meta-row" style="margin-top: 8px;"> |
| | | <span>ç¾çº¦: <strong>{{ fmtMoney(c.contractAmounts) }}</strong></span> |
| | | <span>è¿åº¦: <strong :class="{'text-danger': c.overdueCount > 0}">{{ |
| | | calcRate(c.receiptPaymentAmount, c.contractAmounts) |
| | | }}%</strong></span> |
| | | </div> |
| | | <div class="meta-row"> |
| | | <span>éè´§: <strong>{{ fmtMoney(c.returnAmount) }}</strong></span> |
| | | <span>åºæ¶: <strong>{{ fmtMoney(c.receiptableAmount) }}</strong></span> |
| | | </div> |
| | | <div class="meta-row"> |
| | | <span>已忬¾: <strong>{{ fmtMoney(c.receiptPaymentAmount) }}</strong></span> |
| | | <span>ååæ°: <strong>{{ c.contractCount || 0 }} 份</strong></span> |
| | | </div> |
| | | <div style="margin-top: 6px;"> |
| | | <el-tag v-if="c.overdueCount > 0" type="danger" size="small" effect="light">â {{ c.overdueCount }} 份è¶
æ</el-tag> |
| | | <el-tag v-else type="success" size="small" effect="light">æ£å¸¸å±¥çº¦</el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-pagination"> |
| | | <el-pagination background small layout="total, prev, pager, next" :total="custTotal" |
| | | :page-size="custPage.size" v-model:current-page="custPage.current" |
| | | @current-change="handleCustPageChange"/> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Column 3: Contracts --> |
| | | <div class="column-panel" v-loading="contractLoading"> |
| | | <div class="panel-header"> |
| | | <span>3. ååæç» ({{ contractTotal }})</span> |
| | | </div> |
| | | <div class="panel-body" style="padding: 0;"> |
| | | <div v-if="!selectedCustomerId" class="empty-state" style="padding-top: 40px;">请å
å¨ä¸é´æ 鿩客æ·</div> |
| | | <div v-else-if="contracts.length === 0" class="empty-state" style="padding-top: 40px;"> |
| | | 该客æ·ä¸æ å¹é
çååæç» |
| | | </div> |
| | | |
| | | <el-table v-else :data="contracts" size="small" style="width: 100%; height: 100%;" stripe |
| | | :row-class-name="tableRowClassName"> |
| | | <el-table-column label="ååä¿¡æ¯" min-width="160" show-overflow-tooltip> |
| | | <template #default="{ row }"> |
| | | <div style="font-weight: 600; color: #0f172a;">{{ |
| | | row.contractName || row.salesContractNo || 'æªå½å' |
| | | }} |
| | | </div> |
| | | <div style="font-size: 11px; color: #64748b;">ååç¼å·ï¼{{ row.salesContractNo }}</div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="éé¢ä¸å款" min-width="150"> |
| | | <template #default="{ row }"> |
| | | <div style="font-size: 12px; color: #64748b;">æ»é¢: <span |
| | | style="color: #0f172a; font-weight: 500;">{{ fmtMoney(row.contractAmount) }}</span></div> |
| | | <div :class="row.isPaymentTimeout ? 'text-danger' : ''" style="font-size: 12px; font-weight: 500;"> |
| | | å·²å: {{ fmtMoney(row.receiptPaymentAmount) }} |
| | | ({{ calcRate(row.receiptPaymentAmount, row.contractAmount) }}%) |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="åºæ¶ä¸éè´§" min-width="130"> |
| | | <template #default="{ row }"> |
| | | <div style="font-size: 12px; color: #64748b;">åºæ¶: <span |
| | | style="color: #0f172a;">{{ fmtMoney(row.receiptableAmount) }}</span></div> |
| | | <div style="font-size: 12px; color: #64748b;">éè´§: <span |
| | | style="color: #0f172a;">{{ fmtMoney(row.returnAmount) }}</span></div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="ç¶æ" width="100" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-tag v-if="row.isPaymentTimeout" type="danger" size="small" effect="dark">è¶
ææªå</el-tag> |
| | | <el-tag |
| | | v-else-if="(row.receiptPaymentAmount || 0) >= (row.contractAmount || 0) && (row.contractAmount || 0) > 0" |
| | | type="success" size="small" effect="dark">å·²ç»æ¸
|
| | | </el-tag> |
| | | <el-tag v-else type="primary" size="small" effect="plain">å±¥è¡ä¸</el-tag> |
| | | </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> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="table_list" |
| | | style="width: 100%"> |
| | | <el-table :data="receiptRecord" |
| | | border |
| | | :row-key="(row) => row.id" |
| | | show-summary |
| | | :summary-method="summarizeMainTable1" |
| | | height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="ååç¾è®¢æ¥æ" |
| | | prop="executionDate" |
| | | show-overflow-tooltip |
| | | width="110" /> |
| | | <el-table-column label="éå®ååå·" |
| | | prop="salesContractNo" |
| | | show-overflow-tooltip |
| | | width="200" /> |
| | | <el-table-column label="ååéé¢(å
)" |
| | | prop="contractAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="忬¾éé¢(å
)" |
| | | prop="receiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="éè´§éé¢(å
)" |
| | | prop="returnAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="åºæ¶éé¢(å
)" |
| | | prop="receiptableAmount" |
| | | show-overflow-tooltip |
| | | width="200"> |
| | | <template #default="{ row, column }"> |
| | | <el-text type="danger"> |
| | | {{ formattedNumber(row, column, row.receiptableAmount) }} |
| | | </el-text> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="recordTotal > 0" |
| | | :total="recordTotal" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="recordPage.current" |
| | | :limit="recordPage.size" |
| | | @pagination="recordPaginationChange" /> |
| | | <div class="panel-pagination"> |
| | | <el-pagination background small layout="total, prev, pager, next" :total="contractTotal" |
| | | :page-size="contractPage.size" v-model:current-page="contractPage.current" |
| | | @current-change="handleContractPageChange"/> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { |
| | | customewTransactions, |
| | | customewTransactionsDetails, |
| | | } from "@/api/salesManagement/indicatorStats.js"; |
| | | import Pagination from "../../../components/PIMTable/Pagination.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const receiptRecord = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | import {onMounted, ref, reactive, toRefs} from "vue"; |
| | | import { |
| | | projectTransactions, |
| | | customewTransactions, |
| | | customewTransactionsDetails, |
| | | transactionsSummary |
| | | } from "@/api/salesManagement/indicatorStats.js"; |
| | | |
| | | const summaryData = ref({}); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | projectName: "", |
| | | customerName: "", |
| | | contractName: "", |
| | | }, |
| | | }); |
| | | const {searchForm} = toRefs(data); |
| | | |
| | | // Column 1: Projects |
| | | const projects = ref([]); |
| | | const prjLoading = ref(false); |
| | | const prjTotal = ref(0); |
| | | const prjPage = reactive({current: 1, size: 20}); |
| | | const selectedProjectId = ref(null); |
| | | |
| | | // Column 2: Customers |
| | | const customers = ref([]); |
| | | const custLoading = ref(false); |
| | | const custTotal = ref(0); |
| | | const custPage = reactive({current: 1, size: 20}); |
| | | const selectedCustomerId = ref(null); |
| | | |
| | | // Column 3: Contracts |
| | | const contracts = ref([]); |
| | | const contractLoading = ref(false); |
| | | const contractTotal = ref(0); |
| | | const contractPage = reactive({current: 1, size: 20}); |
| | | |
| | | const getSummary = () => { |
| | | transactionsSummary().then(res => { |
| | | summaryData.value = res.data || {}; |
| | | }); |
| | | const recordPage = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }; |
| | | |
| | | const handleSearch = () => { |
| | | prjPage.current = 1; |
| | | loadProjects(); |
| | | getSummary(); |
| | | }; |
| | | |
| | | const handleReset = () => { |
| | | searchForm.value = { |
| | | projectName: "", |
| | | customerName: "", |
| | | contractName: "", |
| | | }; |
| | | handleSearch(); |
| | | }; |
| | | |
| | | const loadProjects = () => { |
| | | prjLoading.value = true; |
| | | projectTransactions({...searchForm.value, ...prjPage}).then(res => { |
| | | prjLoading.value = false; |
| | | projects.value = res.data.records || []; |
| | | prjTotal.value = res.data.total || 0; |
| | | |
| | | if (projects.value.length > 0) { |
| | | handleSelectProject(projects.value[0]); |
| | | } else { |
| | | selectedProjectId.value = null; |
| | | customers.value = []; |
| | | custTotal.value = 0; |
| | | contracts.value = []; |
| | | contractTotal.value = 0; |
| | | } |
| | | }).catch(() => { |
| | | prjLoading.value = false; |
| | | }); |
| | | const total = ref(0); |
| | | const recordTotal = ref(0); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | searchText: "", |
| | | invoiceDate: "", |
| | | }, |
| | | }; |
| | | |
| | | const handleSelectProject = (prj) => { |
| | | selectedProjectId.value = prj.projectId; |
| | | custPage.current = 1; |
| | | loadCustomers(); |
| | | }; |
| | | |
| | | const loadCustomers = () => { |
| | | if (!selectedProjectId.value) return; |
| | | custLoading.value = true; |
| | | const params = { |
| | | projectId: selectedProjectId.value, |
| | | customerName: searchForm.value.customerName, |
| | | contractName: searchForm.value.contractName, |
| | | ...custPage |
| | | }; |
| | | customewTransactions(params).then(res => { |
| | | custLoading.value = false; |
| | | const records = res.data.records || res.data || []; |
| | | customers.value = records; |
| | | custTotal.value = res.data.total || records.length || 0; |
| | | |
| | | if (customers.value.length > 0) { |
| | | handleSelectCustomer(customers.value[0]); |
| | | } else { |
| | | selectedCustomerId.value = null; |
| | | contracts.value = []; |
| | | contractTotal.value = 0; |
| | | } |
| | | }).catch(() => { |
| | | custLoading.value = false; |
| | | }); |
| | | 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 getList = () => { |
| | | tableLoading.value = true; |
| | | customewTransactions({ ...searchForm.value, ...page }).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | if (tableData.value.length > 0) { |
| | | recordPage.current = 1; |
| | | customerId.value = tableData.value[0].customerId; |
| | | receiptPaymentList(customerId.value); |
| | | } |
| | | }); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return cellValue ? parseFloat(cellValue).toFixed(2) : "0.00"; |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "contractAmounts", |
| | | "receiptPaymentAmount", |
| | | "returnAmount", |
| | | "receiptableAmount", |
| | | ], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeMainTable1 = param => { |
| | | var summarizeTable = proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "contractAmount", |
| | | "receiptPaymentAmount", |
| | | "returnAmount", |
| | | "receiptableAmount", |
| | | ], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | return summarizeTable; |
| | | }; |
| | | }; |
| | | |
| | | const receiptPaymentList = id => { |
| | | const param = { |
| | | customerId: id, |
| | | current: recordPage.current, |
| | | size: recordPage.size, |
| | | }; |
| | | customewTransactionsDetails(param).then(res => { |
| | | if (Array.isArray(res.data)) { |
| | | originReceiptRecord.value = res.data; |
| | | recordTotal.value = res.data.length; |
| | | handlePagination({ page: 1, limit: recordPage.size }); |
| | | } else { |
| | | receiptRecord.value = res.data.records; |
| | | recordTotal.value = res.data.total; |
| | | } |
| | | }); |
| | | const handleSelectCustomer = (cust) => { |
| | | selectedCustomerId.value = cust.customerId; |
| | | contractPage.current = 1; |
| | | loadContracts(); |
| | | }; |
| | | |
| | | const loadContracts = () => { |
| | | if (!selectedCustomerId.value) return; |
| | | contractLoading.value = true; |
| | | const params = { |
| | | projectId: selectedProjectId.value, |
| | | customerId: selectedCustomerId.value, |
| | | contractName: searchForm.value.contractName, |
| | | ...contractPage |
| | | }; |
| | | |
| | | // æ±æ¬¾è®°å½å表å页 |
| | | const recordPaginationChange = pagination => { |
| | | recordPage.current = pagination.page; |
| | | recordPage.size = pagination.limit; |
| | | receiptPaymentList(customerId.value); |
| | | }; |
| | | |
| | | const rowClickMethod = row => { |
| | | customerId.value = row.customerId; |
| | | 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); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | customewTransactionsDetails(params).then(res => { |
| | | contractLoading.value = false; |
| | | const records = Array.isArray(res.data) ? res.data : (res.data.records || []); |
| | | contracts.value = records; |
| | | contractTotal.value = res.data.total || records.length || 0; |
| | | }).catch(() => { |
| | | contractLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handlePrjPageChange = (val) => { |
| | | prjPage.current = val; |
| | | loadProjects(); |
| | | }; |
| | | |
| | | const handleCustPageChange = (val) => { |
| | | custPage.current = val; |
| | | loadCustomers(); |
| | | }; |
| | | |
| | | const handleContractPageChange = (val) => { |
| | | contractPage.current = val; |
| | | loadContracts(); |
| | | }; |
| | | |
| | | const fmtMoney = (num) => { |
| | | if (num === null || num === undefined || isNaN(num)) return 'Â¥ 0.00'; |
| | | return 'Â¥ ' + parseFloat(num).toLocaleString('zh-CN', {minimumFractionDigits: 2, maximumFractionDigits: 2}); |
| | | }; |
| | | |
| | | const calcRate = (paid, amount) => { |
| | | if (!amount || amount == 0) return '0.0'; |
| | | const val = ((paid || 0) / amount) * 100; |
| | | return val.toFixed(1); |
| | | }; |
| | | |
| | | const getDaysFromSign = (dateStr) => { |
| | | if (!dateStr) return 0; |
| | | const sign = new Date(dateStr); |
| | | const now = new Date(); |
| | | const diffTime = now - sign; |
| | | return Math.max(0, Math.floor(diffTime / (1000 * 60 * 60 * 24))); |
| | | }; |
| | | |
| | | const tableRowClassName = ({row}) => { |
| | | if (row.isPaymentTimeout) { |
| | | return 'warning-row'; |
| | | } |
| | | return ''; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | loadProjects(); |
| | | getSummary(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .table_list { |
| | | width: 50%; |
| | | <style scoped> |
| | | /* CSS Variables */ |
| | | .layout-fullscreen { |
| | | --bg: #f8fafc; |
| | | --card: #ffffff; |
| | | --text: #0f172a; |
| | | --muted: #64748b; |
| | | --border: #e2e8f0; |
| | | --primary: #2563eb; |
| | | --primary-50: #eff6ff; |
| | | --success: #16a34a; |
| | | --success-50: #dcfce7; |
| | | --warning: #d97706; |
| | | --danger: #dc2626; |
| | | --danger-50: #fef2f2; |
| | | --danger-border: #fecaca; |
| | | |
| | | height: calc(100vh - 84px); /* Fallback height, allows container to take viewport height */ |
| | | display: flex; |
| | | flex-direction: column; |
| | | background-color: var(--bg); |
| | | padding: 16px; |
| | | overflow: hidden; /* Hide body scroll, let columns scroll */ |
| | | } |
| | | |
| | | /* é¡¶é¨æç´¢ */ |
| | | .search_form { |
| | | background: var(--card); |
| | | border: 1px solid var(--border); |
| | | border-radius: 8px; |
| | | padding: 16px 20px 0 20px; |
| | | margin-bottom: 12px; |
| | | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02); |
| | | } |
| | | |
| | | /* 卿ç»è®¡æ±æ»æ */ |
| | | .summary-strip { |
| | | background: var(--card); |
| | | border: 1px solid var(--border); |
| | | border-radius: 8px; |
| | | padding: 10px 20px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-size: 13px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .summary-stats { |
| | | display: flex; |
| | | gap: 20px; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .summary-stats b { |
| | | color: var(--primary); |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .warn-summary { |
| | | background: var(--danger-50); |
| | | color: var(--danger); |
| | | padding: 2px 8px; |
| | | border-radius: 4px; |
| | | font-weight: 600; |
| | | border: 1px solid var(--danger-border); |
| | | } |
| | | |
| | | /* 䏿 èå¨ */ |
| | | .layout-columns { |
| | | flex: 1; |
| | | display: grid; |
| | | grid-template-columns: 310px 330px 1fr; |
| | | gap: 16px; |
| | | margin-top: 16px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | @media (max-width: 1400px) { |
| | | .layout-columns { |
| | | grid-template-columns: 280px 300px 1fr; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 1200px) { |
| | | .layout-columns { |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .layout-fullscreen { |
| | | overflow: auto; |
| | | } |
| | | |
| | | .column-panel { |
| | | min-height: 400px; |
| | | } |
| | | } |
| | | |
| | | .column-panel { |
| | | background: var(--card); |
| | | border: 1px solid var(--border); |
| | | border-radius: 8px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02); |
| | | } |
| | | |
| | | .panel-header { |
| | | padding: 12px 16px; |
| | | background: #f8fafc; |
| | | border-bottom: 1px solid var(--border); |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | flex-shrink: 0; |
| | | color: var(--text); |
| | | } |
| | | |
| | | .panel-body { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | padding: 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .panel-pagination { |
| | | padding: 8px; |
| | | background: #ffffff; |
| | | border-top: 1px solid var(--border); |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | /* å表å¡ç (é¡¹ç® & 客æ·) */ |
| | | .item-card { |
| | | background: #ffffff; |
| | | border: 1px solid var(--border); |
| | | border-radius: 6px; |
| | | padding: 12px 14px; |
| | | cursor: pointer; |
| | | transition: all 0.15s; |
| | | } |
| | | |
| | | .item-card:hover { |
| | | border-color: #cbd5e1; |
| | | background: #f8fafc; |
| | | } |
| | | |
| | | .item-card.active { |
| | | border-color: var(--primary); |
| | | background: var(--primary-50); |
| | | box-shadow: 0 0 0 1px var(--primary); |
| | | } |
| | | |
| | | .card-title-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 6px; |
| | | } |
| | | |
| | | .card-main-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: var(--text); |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .card-code { |
| | | font-size: 11px; |
| | | color: var(--muted); |
| | | background: #f1f5f9; |
| | | padding: 2px 6px; |
| | | border-radius: 4px; |
| | | display: inline-block; |
| | | } |
| | | |
| | | .meta-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | font-size: 12px; |
| | | color: var(--muted); |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .meta-row strong { |
| | | color: var(--text); |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: var(--danger) !important; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | :deep(.warning-row) { |
| | | background-color: var(--danger-50) !important; |
| | | } |
| | | |
| | | .empty-state { |
| | | text-align: center; |
| | | color: var(--muted); |
| | | padding: 40px 16px; |
| | | font-size: 13px; |
| | | } |
| | | </style> |
| | |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | |
| | | <el-table-column |
| | | label="éå®ååå·" |
| | | prop="salesContractNo" |
| | |
| | | label="仿¬¾æ¹å¼" |
| | | prop="paymentMethod" |
| | | show-overflow-tooltip |
| | | /> |
| | | > |
| | | <template #default="scope"> |
| | | <dict-tag |
| | | :options="checkout_payment" |
| | | :value="scope.row.paymentMethod" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="ååéé¢(å
)" |
| | | prop="contractAmount" |
| | |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="left">å
³è项ç®</el-divider> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°ï¼" prop="customerId"> |
| | | <el-select |
| | | v-model="form.customerId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | filterable |
| | | > |
| | | <el-option |
| | | v-for="item in customerOption" |
| | | :key="item.id" |
| | | :label="item.customerName" |
| | | :value="item.id" |
| | | > |
| | | {{ |
| | | item.customerName + "ââ" + item.taxpayerIdentificationNumber |
| | | }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°ï¼" prop="projectId"> |
| | | <el-select |
| | | v-model="form.projectId" |
| | | placeholder="è¯·éæ©é¡¹ç®" |
| | | clearable |
| | | filterable |
| | | :disabled="operationType === 'view'" |
| | | @focus="previousProjectId = form.projectId" |
| | | @change="handleProjectChange" |
| | | > |
| | | <el-option |
| | | v-for="item in projectOptions" |
| | | :key="item.id" |
| | | :label="item.no + ' - ' + item.title" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å
³èååï¼" prop="associatedContractIds"> |
| | | <el-select |
| | | v-model="form.associatedContractIds" |
| | | multiple |
| | | placeholder="è¯·éæ©å
³èåå" |
| | | clearable |
| | | filterable |
| | | :disabled="operationType === 'view'" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in contractOptions" |
| | | :key="item.id" |
| | | :label="item.contractNo" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="left">å°è´¦ä¿¡æ¯</el-divider> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éå®ååå·ï¼" prop="salesContractNo"> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°ï¼" prop="customerId"> |
| | | <el-select |
| | | v-model="form.customerId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | filterable |
| | | > |
| | | <el-option |
| | | v-for="item in customerOption" |
| | | :key="item.id" |
| | | :label="item.customerName" |
| | | :value="item.id" |
| | | > |
| | | {{ |
| | | item.customerName + "ââ" + item.taxpayerIdentificationNumber |
| | | }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°ï¼" prop="projectName"> |
| | | <el-input |
| | | v-model="form.projectName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¾è®¢æ¥æï¼" prop="executionDate"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.executionDate" |
| | | @change="(val) => { if(val) form.year = parseInt(val.substring(0, 4)) }" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼"> |
| | | <el-input |
| | | <el-select |
| | | v-model="form.paymentMethod" |
| | | placeholder="请è¾å
¥" |
| | | placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | > |
| | | <el-option |
| | | v-for="item in checkout_payment" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¾ååä½ï¼" prop="signingUnitId"> |
| | | <el-tree-select |
| | | v-model="form.signingUnitId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="è¯·éæ©ç¾ååä½" |
| | | check-strictly |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åäºå¤ï¼" prop="officeId"> |
| | | <el-tree-select |
| | | v-model="form.officeId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="è¯·éæ©åäºå¤" |
| | | check-strictly |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»è´è´£äººï¼" prop="groupLeaderId"> |
| | | <el-select |
| | | v-model="form.groupLeaderId" |
| | | filterable |
| | | placeholder="è¯·éæ©ç»è´è´£äºº" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | > |
| | | <el-option |
| | | v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="年份ï¼"> |
| | | <el-input |
| | | v-model="form.year" |
| | | placeholder="èªå¨åç¾è®¢æ¥æ" |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦å¼å票ï¼"> |
| | | <el-switch |
| | | v-model="form.isInvoice" |
| | | :active-value="1" |
| | | :inactive-value="0" |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦ä¸æ ï¼"> |
| | | <el-switch |
| | | v-model="form.isBid" |
| | | :active-value="1" |
| | | :inactive-value="0" |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12" v-if="form.isBid === 1"> |
| | | <el-form-item label="䏿 ä»·ï¼"> |
| | | <el-input-number |
| | | v-model="form.bidPrice" |
| | | :precision="2" |
| | | :step="1000" |
| | | :min="0" |
| | | style="width: 100%" |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.isBid !== 1"> |
| | | <el-form-item label="æªä¸æ åå ï¼"> |
| | | <el-input |
| | | v-model="form.unbidReason" |
| | | placeholder="请è¾å
¥æªä¸æ åå " |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååå件约å®åä¼ æ¥æï¼"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.contractOriginalReturnDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨ï¼" prop="remarks"> |
| | | <el-input |
| | | v-model="form.remarks" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | type="textarea" |
| | | :rows="2" |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="operationType !== 'view'" :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" prop="salesLedgerFiles"> |
| | | <FileUpload |
| | | v-model:file-list="fileList" |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="left">产åä¿¡æ¯</el-divider> |
| | | <el-row> |
| | | <el-form-item label="产åä¿¡æ¯ï¼" prop="entryDate"> |
| | | <el-form-item label="" prop="entryDate"> |
| | | <el-button |
| | | v-if="operationType !== 'view'" |
| | | type="primary" |
| | | @click="openProductForm('add')" |
| | | >æ·»å |
| | | >æ·»å æç» |
| | | </el-button> |
| | | <el-button |
| | | v-if="operationType !== 'view'" |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨ï¼" prop="remarks"> |
| | | <el-input |
| | | v-model="form.remarks" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | type="textarea" |
| | | :rows="2" |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="operationType !== 'view'" :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" prop="salesLedgerFiles"> |
| | | <FileUpload |
| | | v-model:file-list="fileList" |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </FormDialog> |
| | | <!-- 仿¥ä»·å导å
¥ï¼ä»
审æ¹éè¿ï¼ --> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="åè´§åæ®ï¼" prop="shippingDocument"> |
| | | <el-input |
| | | v-model="deliveryForm.shippingDocument" |
| | | placeholder="请è¾å
¥åè´§åæ®" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ¯å¦å¯¹è´¦ï¼" prop="isReconciled"> |
| | | <el-switch v-model="deliveryForm.isReconciled" :active-value="1" :inactive-value="0" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ¯å¦ç»ç®ï¼" prop="isSettled"> |
| | | <el-switch v-model="deliveryForm.isSettled" :active-value="1" :inactive-value="0" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ¯å¦å·²å¼ç¥¨ï¼" prop="isInvoiced"> |
| | | <el-switch v-model="deliveryForm.isInvoiced" :active-value="1" :inactive-value="0" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12" v-if="deliveryForm.isReconciled === 1"> |
| | | <el-form-item label="对账å彿¡£æ
åµï¼" prop="statementArchivingStatus"> |
| | | <el-input |
| | | type="textarea" |
| | | v-model="deliveryForm.statementArchivingStatus" |
| | | placeholder="请è¾å
¥å¯¹è´¦å彿¡£æ
åµ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="deliveryForm.isSettled === 1"> |
| | | <el-form-item label="ç»ç®å彿¡£æ
åµï¼" prop="settlementArchivingStatus"> |
| | | <el-input |
| | | type="textarea" |
| | | v-model="deliveryForm.settlementArchivingStatus" |
| | | placeholder="请è¾å
¥ç»ç®å彿¡£æ
åµ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="åºåï¼" prop="batchNo"> |
| | | <el-table |
| | | :data="deliveryForm.batchNoList" |
| | |
| | | import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { userListNoPage, deptTreeSelect } from "@/api/system/user.js"; |
| | | import { listProject, getProject } from "@/api/projectManagement/project.js"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; |
| | | import { contractListNoPage } from "@/api/salesManagement/contractManagement.js"; |
| | | import { |
| | | ledgerListPage, |
| | | productList, |
| | |
| | | const route = useRoute(); |
| | | const userStore = useUserStore(); |
| | | const { proxy } = getCurrentInstance(); |
| | | const { tax_rate } = proxy.useDict("tax_rate"); |
| | | const { tax_rate, checkout_payment } = proxy.useDict("tax_rate", "checkout_payment"); |
| | | const tableData = ref([]); |
| | | const productData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const productSelectedRows = ref([]); |
| | | const userList = ref([]); |
| | | const deptOptions = ref([]); |
| | | const projectOptions = ref([]); |
| | | const contractOptions = ref([]); |
| | | const customerOption = ref([]); |
| | | const productOptions = ref([]); |
| | | const modelOptions = ref([]); |
| | |
| | | maintenanceTime: "", |
| | | productData: [], |
| | | executionDate: "", |
| | | projectId: undefined, |
| | | associatedContractIds: [], |
| | | |
| | | year: "", |
| | | signingUnitId: undefined, |
| | | officeId: undefined, |
| | | groupLeaderId: undefined, |
| | | isInvoice: 0, |
| | | bidPrice: undefined, |
| | | isBid: 0, |
| | | unbidReason: "", |
| | | contractOriginalReturnDate: "", |
| | | hasProductionRecord: false, |
| | | createTime: "", |
| | | }, |
| | |
| | | total: 0, |
| | | }); |
| | | const selectedQuotation = ref(null); |
| | | |
| | | const previousProjectId = ref(null); |
| | | const handleProjectChange = (val) => { |
| | | if (productData.value && productData.value.length > 0) { |
| | | ElMessageBox.confirm("å½åå·²å½å
¥äº§åæç»ï¼åæ¢é¡¹ç®å°è¢«è¦çï¼æ¯å¦ç»§ç»ï¼", "æç¤º", { |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | previousProjectId.value = val; |
| | | fetchAndPopulateProject(val); |
| | | }) |
| | | .catch(() => { |
| | | form.value.projectId = previousProjectId.value; |
| | | }); |
| | | } else { |
| | | previousProjectId.value = val; |
| | | fetchAndPopulateProject(val); |
| | | } |
| | | }; |
| | | |
| | | const fetchAndPopulateProject = (val) => { |
| | | let proj = projectOptions.value.find((p) => p.id === val); |
| | | form.value.projectName = proj ? proj.title : ""; |
| | | |
| | | if (!val) { |
| | | productData.value = []; |
| | | return; |
| | | } |
| | | |
| | | getProject(val).then((res) => { |
| | | const projectInfo = res.data; |
| | | if (projectInfo.info && projectInfo.info.clientId) { |
| | | form.value.customerId = projectInfo.info.clientId; |
| | | } |
| | | if (projectInfo.salesLedgerProductList) { |
| | | productData.value = projectInfo.salesLedgerProductList.map((item) => ({ |
| | | ...item, |
| | | id: undefined, |
| | | salesLedgerId: undefined |
| | | })); |
| | | } else { |
| | | productData.value = []; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // åè´§ç¸å
³ |
| | | const deliveryFormVisible = ref(false); |
| | |
| | | expressCompany: "", |
| | | expressNumber: "", |
| | | type: "货车", // 货车, å¿«é |
| | | shippingDocument: "", |
| | | isReconciled: 0, |
| | | statementArchivingStatus: "", |
| | | isSettled: 0, |
| | | settlementArchivingStatus: "", |
| | | isInvoiced: 0, |
| | | }, |
| | | deliveryRules: { |
| | | shippingCarNumber: [ |
| | |
| | | form.value = {}; |
| | | productData.value = []; |
| | | selectedQuotation.value = null; |
| | | deptTreeSelect().then(res => deptOptions.value = res.data); |
| | | listProject({}).then(res => projectOptions.value = res.rows || res.data.records || res.data); |
| | | contractListNoPage({}).then(res => contractOptions.value = res.data); |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | listCustomer({ current: -1, size: -1, type: 0 }).then((res) => { |
| | |
| | | let res = await userStore.getInfo(); |
| | | currentFactoryName.value = res.user.currentFactoryName; |
| | | }; |
| | | onMounted(() => { |
| | | onMounted(async () => { |
| | | searchForm.salesContractNo = route.query.salesContractNo; |
| | | getList(); |
| | | userListNoPage().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | getCurrentFactoryName(); |
| | | |
| | | if (route.query.action === "add") { |
| | | await openForm("add"); |
| | | if (route.query.projectId) { |
| | | const pId = Number(route.query.projectId); |
| | | form.value.projectId = pId; |
| | | fetchAndPopulateProject(pId); |
| | | } |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="8"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.quotationNo" |
| | | placeholder="请è¾å
¥æ¥ä»·åå·" |
| | |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.customerId" placeholder="è¯·éæ©å®¢æ·" clearable> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"> |
| | | {{ |
| | |
| | | <!-- <el-option label="å·²è¿æ" value="å·²è¿æ"></el-option>--> |
| | | <!-- </el-select>--> |
| | | <!-- </el-col>--> |
| | | <el-col :span="8"> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.projectId" placeholder="è¯·éæ©é¡¹ç®åç§°" clearable filterable> |
| | | <el-option v-for="item in projectOptions" :key="item.id" :label="item.no + ' - ' + item.title" :value="item.id" /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | |
| | | > |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column prop="quotationNo" label="æ¥ä»·åå·" /> |
| | | <el-table-column prop="projectName" label="项ç®åç§°" /> |
| | | <el-table-column prop="customer" label="客æ·åç§°" /> |
| | | <el-table-column prop="salesperson" label="ä¸å¡å" width="100" /> |
| | | <el-table-column prop="quotationDate" label="æ¥ä»·æ¥æ" width="120" /> |
| | |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-input v-model="form.paymentMethod" placeholder="请è¾å
¥ä»æ¬¾æ¹å¼" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°" prop="projectId"> |
| | | <el-select v-model="form.projectId" placeholder="è¯·éæ©é¡¹ç®åç§°" style="width: 100%" clearable filterable> |
| | | <el-option v-for="item in projectOptions" :key="item.id" :label="item.no + ' - ' + item.title" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-dialog v-model="viewDialogVisible" title="æ¥ä»·è¯¦æ
" width="800px"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="æ¥ä»·åå·">{{ currentQuotation.quotationNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®åç§°">{{ currentQuotation.projectName }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·åç§°">{{ currentQuotation.customer }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸å¡å">{{ currentQuotation.salesperson }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥ä»·æ¥æ">{{ currentQuotation.quotationDate }}</el-descriptions-item> |
| | |
| | | import {modelList, productTreeList} from "@/api/basicData/product.js"; |
| | | import {listCustomer} from "@/api/basicData/customer.js"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { listProject } from "@/api/projectManagement/project.js"; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | quotationNo: '', |
| | | customerId: '', |
| | | status: '' |
| | | status: '', |
| | | projectId: undefined |
| | | }) |
| | | |
| | | const quotationList = ref([]) |
| | | const userList = ref([]) |
| | | const productOptions = ref([]); |
| | | const modelOptions = ref([]); |
| | | const projectOptions = ref([]); |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | |
| | | const form = reactive({ |
| | | quotationNo: '', |
| | | customerId: undefined, |
| | | projectId: undefined, |
| | | customer: '', |
| | | salesperson: '', |
| | | quotationDate: '', |
| | |
| | | searchForm.quotationNo = '' |
| | | searchForm.customer = '' |
| | | searchForm.status = '' |
| | | searchForm.projectId = undefined |
| | | // éç½®å°ç¬¬ä¸é¡µå¹¶éæ°æ¥è¯¢ |
| | | pagination.currentPage = 1 |
| | | handleSearch() |
| | |
| | | resetForm() |
| | | dialogVisible.value = true |
| | | getProductOptions(); |
| | | fetchCustomerOptions() |
| | | fetchCustomerOptions(); |
| | | fetchProjectOptions(); |
| | | } |
| | | |
| | | const fetchProjectOptions = () => { |
| | | if (projectOptions.value.length > 0) return |
| | | listProject({current: -1,size:-1}).then((res) => { |
| | | projectOptions.value = res.data.records; |
| | | }); |
| | | } |
| | | |
| | | const fetchCustomerOptions = () => { |
| | |
| | | unitPrice: product.unitPrice || 0, |
| | | amount: product.amount || 0 |
| | | })) : [], |
| | | totalAmount: row.totalAmount || 0 |
| | | totalAmount: row.totalAmount || 0, |
| | | projectName: row.projectName || '' |
| | | } |
| | | viewDialogVisible.value = true |
| | | } |
| | |
| | | // å
å è½½äº§åæ æ°æ®ï¼å¦å el-tree-select æ æ³åæ¾äº§ååç§° |
| | | await getProductOptions() |
| | | await fetchCustomerOptions() |
| | | await fetchProjectOptions() |
| | | |
| | | // åªå¤å¶éè¦çåæ®µï¼é¿å
å°ç»ä»¶å¼ç¨æ¾å
¥ååºå¼å¯¹è±¡ |
| | | form.quotationNo = row.quotationNo || '' |
| | | form.customer = row.customer || '' |
| | | form.customerId = row.customerId || undefined |
| | | form.projectId = row.projectId || undefined |
| | | form.salesperson = row.salesperson || '' |
| | | form.quotationDate = row.quotationDate || '' |
| | | form.validDate = row.validDate || '' |
| | |
| | | |
| | | const resetForm = () => { |
| | | form.customer = '' |
| | | form.customerId = undefined |
| | | form.projectId = undefined |
| | | form.salesperson = '' |
| | | form.quotationDate = '' |
| | | form.validDate = '' |
| | |
| | | getUserList() |
| | | handleSearch() |
| | | fetchCustomerOptions() |
| | | fetchProjectOptions() |
| | | }) |
| | | </script> |
| | | |