| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>reconciliation repro</title></head> |
| | | <body> |
| | | <div id="app"></div> |
| | | <script type="module"> |
| | | import {createApp} from '/node_modules/.vite/deps/vue.js'; |
| | | import ElementPlus from '/node_modules/.vite/deps/element-plus.js'; |
| | | import '/node_modules/element-plus/dist/index.css'; |
| | | import ReconciliationWorkspace |
| | | from '/src/views/financialManagement/financialReconciliation/ReconciliationWorkspace.vue'; |
| | | |
| | | createApp(ReconciliationWorkspace).use(ElementPlus).mount('#app'); |
| | | </script> |
| | | </body> |
| | | </html> |
| | |
| | | }) |
| | | } |
| | | |
| | | // æ¥è¯¢ä¾åºå宿´æ¡£æ¡ï¼åºç¡ä¿¡æ¯ãèµè´¨ã产åãåä½è®°å½ï¼ |
| | | export function getSupplierArchive(id) { |
| | | return request({ url: '/system/supplier/' + id + '/archive', method: 'get' }) |
| | | } |
| | | |
| | | // ä¿åä¾åºå宿´æ¡£æ¡ |
| | | export function saveSupplierArchive(data) { |
| | | return request({ url: '/system/supplier/archive/save', method: 'post', data }) |
| | | } |
| | | |
| | | // æ°å¢åä½è¯ä»·è®°å½ |
| | | export function addSupplierCooperation(id, data) { |
| | | return request({ url: '/system/supplier/' + id + '/cooperation', method: 'post', data }) |
| | | } |
| | | |
| | |
| | | url: '/approveNode/details/' + query, |
| | | method: 'get', |
| | | }) |
| | | } |
| | | } |
| | | |
| | | export function approveFlowDefinitionList(query) { |
| | | return request({ |
| | | url: '/approveFlowDefinition/list', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | | export function approveFlowDefinitionGet(id) { |
| | | return request({ |
| | | url: '/approveFlowDefinition/' + id, |
| | | method: 'get', |
| | | }) |
| | | } |
| | | |
| | | export function approveFlowDefinitionAdd(data) { |
| | | return request({ url: '/approveFlowDefinition', method: 'post', data }) |
| | | } |
| | | |
| | | export function approveFlowDefinitionUpdate(id, data) { |
| | | return request({ url: '/approveFlowDefinition/' + id, method: 'put', data }) |
| | | } |
| | | |
| | | export function approveFlowDefinitionEnabled(id, enabled) { |
| | | return request({ url: '/approveFlowDefinition/' + id + '/enabled', method: 'put', params: { enabled } }) |
| | | } |
| | | |
| | | export function approveFlowDefinitionDelete(id) { |
| | | return request({ url: '/approveFlowDefinition/' + id, method: 'delete' }) |
| | | } |
| | |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | export function getExpenseCategoryReport(params) { |
| | | return request({ |
| | | url: "/account/accountExpense/report/category", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | |
| | | data: ids, |
| | | }); |
| | | }; |
| | | |
| | | export const getReconciliationTaskPage = (params) => request({ url: "/finance/reconciliation/task/page", method: "get", params }); |
| | | export const createReconciliationTask = (data) => request({ url: "/finance/reconciliation/task", method: "post", data }); |
| | | export const getReconciliationDiff = (id) => request({ url: `/finance/reconciliation/task/${id}/diff`, method: "get" }); |
| | | export const getReconciliationExternal = (id) => request({ url: `/finance/reconciliation/task/${id}/external`, method: "get" }); |
| | | export const handleReconciliationTask = (id, data) => request({ url: `/finance/reconciliation/task/${id}/handle`, method: "put", data }); |
| | | export const archiveReconciliationTask = (id) => request({ url: `/finance/reconciliation/task/${id}/archive`, method: "put" }); |
| | | export const exportReconciliationDiff = (taskId) => request({ url: "/finance/reconciliation/export", method: "post", data: { taskId }, responseType: "blob" }); |
| | |
| | | // æ°å¢èµéè®°å½ |
| | | export const addFunds = (data) => { |
| | | return request({ |
| | | url: "/finance/funds", |
| | | url: "/finance/funds/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | export const transferFunds = (data) => request({ url: "/finance/funds/transfer", method: "post", data }); |
| | | |
| | | export const getFinanceAccountPage = (params) => request({ url: "/finance/account/page", method: "get", params }); |
| | | export const getFinanceAccountList = () => request({ url: "/finance/account/list", method: "get" }); |
| | | export const addFinanceAccount = (data) => request({ url: "/finance/account", method: "post", data }); |
| | | export const updateFinanceAccount = (data) => request({ url: "/finance/account", method: "put", data }); |
| | | export const deleteFinanceAccount = (ids) => request({ url: "/finance/account", method: "delete", data: ids }); |
| | | export const getFundsTrend = (params) => request({ url: "/finance/account/trend", method: "get", params }); |
| | | |
| | | export const getCashflowPlanPage = (params) => request({ url: "/finance/cashflowPlan/page", method: "get", params }); |
| | | export const addCashflowPlan = (data) => request({ url: "/finance/cashflowPlan", method: "post", data }); |
| | | export const updateCashflowPlan = (data) => request({ url: "/finance/cashflowPlan", method: "put", data }); |
| | | export const deleteCashflowPlan = (ids) => request({ url: "/finance/cashflowPlan", method: "delete", data: ids }); |
| | | export const getCashflowForecast = (params) => request({ url: "/finance/cashflowPlan/forecast", method: "get", params }); |
| | | |
| | | // æ´æ°èµéè®°å½ |
| | | export const updateFunds = (data) => { |
| | | return request({ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // ååå°è´¦å页 |
| | | export function contractListPage(query) { |
| | | return request({ url: '/staff/contract/listPage', method: 'get', params: query }) |
| | | } |
| | | // åå详æ
|
| | | export function contractInfo(id) { |
| | | return request({ url: '/staff/contract/' + id, method: 'get' }) |
| | | } |
| | | // æ°å¢åå |
| | | export function contractAdd(data) { |
| | | return request({ url: '/staff/contract/add', method: 'post', data }) |
| | | } |
| | | // ä¿®æ¹åå |
| | | export function contractUpdate(data) { |
| | | return request({ url: '/staff/contract/update', method: 'post', data }) |
| | | } |
| | | // å é¤åå |
| | | export function contractDel(ids) { |
| | | return request({ url: '/staff/contract/del', method: 'delete', data: ids }) |
| | | } |
| | | // ç»ç¾åå |
| | | export function contractRenew(id, data) { |
| | | return request({ url: '/staff/contract/renew/' + id, method: 'post', data }) |
| | | } |
| | | // è§£é¤åå |
| | | export function contractTerminate(id, data) { |
| | | return request({ url: '/staff/contract/terminate/' + id, method: 'post', data }) |
| | | } |
| | |
| | | data: params, |
| | | }); |
| | | } |
| | | |
| | | // å¨èå°è´¦ç»è®¡ï¼å¨èäººæ° / èªèµåè®¡ï¼ |
| | | export function staffOnJobStatistics() { |
| | | return request({ |
| | | url: "/staff/staffOnJob/statistics", |
| | | method: "get", |
| | | }); |
| | | } |
| | |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <el-input v-model="searchForm.supplierCode" style="width: 180px; margin-left: 10px" placeholder="ä¾åºåç¼ç " clearable /> |
| | | <el-input v-model="searchForm.productKeyword" style="width: 180px; margin-left: 10px" placeholder="ä¾åºäº§å" clearable /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | |
| | | > |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºåç¼ç ï¼" prop="supplierCode"> |
| | | <el-input v-model="form.supplierCode" placeholder="ç空èªå¨çæ" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºåç®ç§°ï¼"><el-input v-model="form.supplierShortName" clearable /></el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="ä¾åºååç±»ï¼"><el-input v-model="form.supplierCategory" /></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="ä¾åºåç级ï¼"><el-select v-model="form.supplierLevel" clearable style="width:100%"><el-option v-for="v in ['A','B','C','D']" :key="v" :label="v" :value="v" /></el-select></el-form-item></el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºååç§°ï¼" prop="supplierName"> |
| | | <el-input |
| | | v-model="form.supplierName" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="åä½ç¶æï¼"><el-select v-model="form.cooperationStatus" style="width:100%"><el-option label="åä½ä¸" :value="1"/><el-option label="æååä½" :value="2"/><el-option label="åç¨" :value="0"/></el-select></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="馿¬¡å使¥æï¼"><el-date-picker v-model="form.cooperationStartDate" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-form-item label="ä¾åºäº§åï¼"><el-input v-model="form.suppliedProductsSummary" type="textarea" :rows="2" placeholder="å¤ä¸ªäº§åå¯ç¨é¡¿å·åé" /></el-form-item> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="èµè´¨ç¶æï¼"><el-select v-model="form.qualificationStatus" style="width:100%"><el-option label="å¾
å®å" :value="0"/><el-option label="ææ" :value="1"/><el-option label="å³å°å°æ" :value="2"/><el-option label="å·²è¿æ" :value="3"/></el-select></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="èµè´¨å°ææ¥ï¼"><el-date-picker v-model="form.qualificationExpiryDate" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-form-item label="åä½åå²ï¼"><el-input v-model="form.cooperationHistorySummary" type="textarea" :rows="2" placeholder="å¡«ååå²åä½ä¸è¯ä»·æè¦" /></el-form-item> |
| | | <el-form-item label="夿³¨ï¼"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬å¸å°åï¼" prop="companyAddress"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-divider content-position="left">èµè´¨è¯æ</el-divider> |
| | | <el-button type="primary" plain @click="form.qualifications.push({ status: 1, longTermValid: 0 })">æ°å¢èµè´¨</el-button> |
| | | <el-table :data="form.qualifications" border style="margin:10px 0 20px"> |
| | | <el-table-column label="èµè´¨åç§°"><template #default="{row}"><el-input v-model="row.qualificationName" /></template></el-table-column> |
| | | <el-table-column label="ç±»å"><template #default="{row}"><el-input v-model="row.qualificationType" /></template></el-table-column> |
| | | <el-table-column label="è¯ä¹¦ç¼å·"><template #default="{row}"><el-input v-model="row.certificateNo" /></template></el-table-column> |
| | | <el-table-column label="å°ææ¥æ" width="180"><template #default="{row}"><el-date-picker v-model="row.expiryDate" value-format="YYYY-MM-DD" style="width:100%" /></template></el-table-column> |
| | | <el-table-column label="æä½" width="80"><template #default="{$index}"><el-button type="danger" link @click="form.qualifications.splice($index,1)">å é¤</el-button></template></el-table-column> |
| | | </el-table> |
| | | <el-divider content-position="left">ä¾åºäº§å</el-divider> |
| | | <el-button type="primary" plain @click="form.products.push({ status: 1, currency: 'CNY' })">æ°å¢äº§å</el-button> |
| | | <el-table :data="form.products" border style="margin:10px 0 20px"> |
| | | <el-table-column label="产ååç§°"><template #default="{row}"><el-input v-model="row.productName" /></template></el-table-column> |
| | | <el-table-column label="åç±»"><template #default="{row}"><el-input v-model="row.productCategory" /></template></el-table-column> |
| | | <el-table-column label="è§æ ¼åå·"><template #default="{row}"><el-input v-model="row.specificationModel" /></template></el-table-column> |
| | | <el-table-column label="ä¾è´§å¨æ(天)" width="140"><template #default="{row}"><el-input-number v-model="row.leadTimeDays" :min="0" /></template></el-table-column> |
| | | <el-table-column label="åèåä»·" width="140"><template #default="{row}"><el-input-number v-model="row.referencePrice" :min="0" :precision="2" /></template></el-table-column> |
| | | <el-table-column label="æä½" width="80"><template #default="{$index}"><el-button type="danger" link @click="form.products.splice($index,1)">å é¤</el-button></template></el-table-column> |
| | | </el-table> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | getSupplier, |
| | | listSupplier, |
| | | updateSupplier, |
| | | getSupplierArchive, |
| | | saveSupplierArchive, |
| | | } from "@/api/basicData/supplierManageFile.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | |
| | | const userStore = useUserStore(); |
| | | |
| | | const tableColumn = ref([ |
| | | { label: "ä¾åºåç¼ç ", prop: "supplierCode", width: 140 }, |
| | | { |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | |
| | | prop: "contactUserPhone", |
| | | width: 150, |
| | | }, |
| | | { label: "ä¾åºäº§å", prop: "suppliedProductsSummary", width: 220 }, |
| | | { label: "ç级", prop: "supplierLevel", width: 80 }, |
| | | { label: "èµè´¨ç¶æ", prop: "qualificationStatus", width: 100, formatData: v => ({0:'å¾
å®å',1:'ææ',2:'å³å°å°æ',3:'å·²è¿æ'}[v] || 'å¾
å®å') }, |
| | | { label: "åä½ç¶æ", prop: "cooperationStatus", width: 100, formatData: v => ({0:'åç¨',1:'åä½ä¸',2:'æå'}[v] || 'åä½ä¸') }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainUserName", |
| | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | | supplierCode: "", |
| | | productKeyword: "", |
| | | }, |
| | | form: { |
| | | supplierCode: "", |
| | | supplierShortName: "", |
| | | supplierCategory: "", |
| | | supplierLevel: "", |
| | | cooperationStatus: 1, |
| | | cooperationStartDate: "", |
| | | qualificationStatus: 0, |
| | | qualificationExpiryDate: "", |
| | | suppliedProductsSummary: "", |
| | | cooperationHistorySummary: "", |
| | | remark: "", |
| | | qualifications: [], |
| | | products: [], |
| | | supplierName: "", |
| | | taxpayerIdentificationNum: "", |
| | | companyAddress: "", |
| | |
| | | form.value = {}; |
| | | form.value.maintainUserId = userStore.id; |
| | | form.value.maintainTime = getCurrentDate(); |
| | | form.value.qualifications = []; |
| | | form.value.products = []; |
| | | userListNoPage().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | if (type === "edit") { |
| | | getSupplier(row.id).then((res) => { |
| | | form.value = { ...res.data }; |
| | | getSupplierArchive(row.id).then((res) => { |
| | | form.value = { ...res.data.supplier, qualifications: res.data.qualifications || [], products: res.data.products || [] }; |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | |
| | | }; |
| | | // æäº¤æ°å¢ |
| | | const submitAdd = () => { |
| | | addSupplier(form.value).then((res) => { |
| | | const { qualifications, products, ...supplier } = form.value; |
| | | saveSupplierArchive({ supplier, qualifications, products }).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | |
| | | }; |
| | | // æäº¤ä¿®æ¹ |
| | | const submitEdit = () => { |
| | | updateSupplier(form.value).then((res) => { |
| | | const { qualifications, products, ...supplier } = form.value; |
| | | saveSupplierArchive({ supplier, qualifications, products }).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <el-descriptions v-if="[1, 3, 5].includes(Number(props.approveType))" :column="2" border style="margin-bottom: 18px"> |
| | | <el-descriptions-item label="ä¸å¡ç¼å·">{{ form.businessNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å®¡æ¹æ é¢">{{ form.businessTitle || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="éé¢">{{ form.amount == null ? '-' : `Â¥${form.amount}` }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºç±">{{ form.approveReason || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <!-- æ¥ä»·å®¡æ¹ï¼å±ç¤ºæ¥ä»·è¯¦æ
ï¼å¤ç¨é宿¥ä»·âæ¥ç详æ
å¯¹è¯æ¡âå
å®¹ç»æï¼ --> |
| | | <div v-if="isQuotationApproval" style="margin: 10px 0 18px;"> |
| | | <el-divider content-position="left">æ¥ä»·è¯¦æ
</el-divider> |
| | |
| | | // æäº¤å®¡æ¹ |
| | | const submitForm = (status) => { |
| | | const filteredActivities = activities.value.filter(activity => activity.isShen); |
| | | if (!filteredActivities.length) { |
| | | proxy.$modal.msgError("å½å没æå¯å®¡æ¹çèç¹"); |
| | | return; |
| | | } |
| | | const opinion = filteredActivities[0].approveNodeReason?.trim(); |
| | | if (!opinion || (status === 2 && opinion.length < 2)) { |
| | | proxy.$modal.msgError(status === 2 ? "驳åæè§è³å°è¾å
¥2个å符" : "请è¾å
¥å®¡æ¹æè§"); |
| | | return; |
| | | } |
| | | filteredActivities[0].approveNodeStatus = status; |
| | | // 夿æ¯å¦ä¸ºæå䏿¥ |
| | | const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1; |
| | | updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { |
| | | updateApproveNode(filteredActivities[0]).then(() => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }); |
| | |
| | | width: 200px; |
| | | height: 60px; |
| | | } |
| | | </style> |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="visible" title="é¢è®¾å®¡æ¹æµç¨" width="820px"> |
| | | <div class="toolbar"><el-button type="primary" @click="editFlow()">æ°å¢æµç¨</el-button></div> |
| | | <el-table :data="flowList" border> |
| | | <el-table-column prop="flowName" label="æµç¨åç§°" /> |
| | | <el-table-column label="é»è®¤æµç¨" width="100"><template #default="{ row }">{{ row.isDefault === 1 ? 'æ¯' : 'å¦' }}</template></el-table-column> |
| | | <el-table-column label="ç¶æ" width="90"><template #default="{ row }"><el-tag :type="row.enabled === 1 ? 'success' : 'info'">{{ row.enabled === 1 ? 'å¯ç¨' : 'åç¨' }}</el-tag></template></el-table-column> |
| | | <el-table-column label="æä½" width="250"> |
| | | <template #default="{ row }"> |
| | | <el-button link type="primary" @click="editFlow(row)">ç¼è¾</el-button> |
| | | <el-button link type="primary" @click="toggleEnabled(row)">{{ row.enabled === 1 ? 'åç¨' : 'å¯ç¨' }}</el-button> |
| | | <el-button link type="danger" @click="removeFlow(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <el-dialog v-model="editorVisible" :title="editor.definition.id ? 'ç¼è¾æµç¨' : 'æ°å¢æµç¨'" width="720px" append-to-body> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="æµç¨åç§°"><el-input v-model="editor.definition.flowName" /></el-form-item> |
| | | <el-form-item label="设为é»è®¤"><el-switch v-model="editor.definition.isDefault" :active-value="1" :inactive-value="0" /></el-form-item> |
| | | <el-form-item label="å¯ç¨"><el-switch v-model="editor.definition.enabled" :active-value="1" :inactive-value="0" /></el-form-item> |
| | | </el-form> |
| | | <el-divider content-position="left">审æ¹èç¹</el-divider> |
| | | <div v-for="(node, index) in editor.nodes" :key="node.key" class="node-row"> |
| | | <span>{{ index + 1 }}</span> |
| | | <el-input v-model="node.nodeName" placeholder="èç¹åç§°" style="width: 150px" /> |
| | | <el-select v-model="node.approverValue" filterable placeholder="审æ¹äºº" style="width: 150px"> |
| | | <el-option v-for="user in users" :key="user.userId" :label="user.nickName" :value="String(user.userId)" /> |
| | | </el-select> |
| | | <el-select v-model="node.conditionOperator" style="width: 130px"> |
| | | <el-option label="å§ç»æ§è¡" value="ALWAYS" /><el-option label="éé¢å¤§äº" value="GT" /><el-option label="éé¢å¤§äºçäº" value="GTE" /> |
| | | </el-select> |
| | | <el-input-number v-if="node.conditionOperator !== 'ALWAYS'" v-model="node.conditionValue" :min="0" :precision="2" style="width: 120px" /> |
| | | <el-button link type="danger" :disabled="editor.nodes.length === 1" @click="editor.nodes.splice(index, 1)">å é¤</el-button> |
| | | </div> |
| | | <el-button type="primary" plain @click="editor.nodes.push(createNode())">æ°å¢èç¹</el-button> |
| | | <template #footer><el-button @click="editorVisible = false">åæ¶</el-button><el-button type="primary" :loading="saving" @click="saveFlow">ä¿å</el-button></template> |
| | | </el-dialog> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { getCurrentInstance, reactive, ref } from 'vue' |
| | | import { approveFlowDefinitionAdd, approveFlowDefinitionDelete, approveFlowDefinitionEnabled, approveFlowDefinitionGet, approveFlowDefinitionList, approveFlowDefinitionUpdate } from '@/api/collaborativeApproval/approvalProcess' |
| | | import { userListNoPageByTenantId } from '@/api/system/user' |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const visible = ref(false) |
| | | const editorVisible = ref(false) |
| | | const saving = ref(false) |
| | | const approveType = ref(3) |
| | | const flowList = ref([]) |
| | | const users = ref([]) |
| | | let key = 1 |
| | | const createNode = (data = {}) => ({ key: key++, nodeName: data.nodeName || '', approverType: 'USER', approverValue: data.approverValue ? String(data.approverValue) : '', conditionField: data.conditionField || null, conditionOperator: data.conditionOperator || 'ALWAYS', conditionValue: data.conditionValue == null ? null : Number(data.conditionValue) }) |
| | | const editor = reactive({ definition: {}, nodes: [] }) |
| | | |
| | | const load = async () => { |
| | | const [flows, userRes] = await Promise.all([approveFlowDefinitionList({ approveType: approveType.value }), userListNoPageByTenantId()]) |
| | | flowList.value = flows.data || [] |
| | | users.value = userRes.data || [] |
| | | } |
| | | const open = async type => { approveType.value = Number(type); visible.value = true; await load() } |
| | | const editFlow = async row => { |
| | | if (row?.id) { |
| | | const res = await approveFlowDefinitionGet(row.id) |
| | | editor.definition = { ...res.data.definition } |
| | | editor.nodes = (res.data.nodes || []).map(createNode) |
| | | } else { |
| | | editor.definition = { id: null, flowName: '', approveType: approveType.value, enabled: 1, isDefault: 0 } |
| | | editor.nodes = [createNode()] |
| | | } |
| | | editorVisible.value = true |
| | | } |
| | | const saveFlow = async () => { |
| | | if (!editor.definition.flowName?.trim()) return proxy.$modal.msgError('请è¾å
¥æµç¨åç§°') |
| | | if (editor.nodes.some(node => !node.nodeName?.trim() || !node.approverValue)) return proxy.$modal.msgError('è¯·å®æ´å¡«å审æ¹èç¹') |
| | | const payload = { definition: { ...editor.definition, approveType: approveType.value }, nodes: editor.nodes.map((node, index) => ({ ...node, id: null, nodeOrder: index + 1, conditionField: node.conditionOperator === 'ALWAYS' ? null : 'amount', conditionValue: node.conditionValue == null ? null : String(node.conditionValue) })) } |
| | | saving.value = true |
| | | try { |
| | | if (editor.definition.id) await approveFlowDefinitionUpdate(editor.definition.id, payload) |
| | | else await approveFlowDefinitionAdd(payload) |
| | | proxy.$modal.msgSuccess('ä¿åæå') |
| | | editorVisible.value = false |
| | | await load() |
| | | } finally { saving.value = false } |
| | | } |
| | | const toggleEnabled = async row => { await approveFlowDefinitionEnabled(row.id, row.enabled === 1 ? 0 : 1); await load() } |
| | | const removeFlow = async row => { |
| | | await proxy.$modal.confirm(`确认å 餿µç¨â${row.flowName}âåï¼`) |
| | | await approveFlowDefinitionDelete(row.id) |
| | | await load() |
| | | } |
| | | defineExpose({ open }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .toolbar { margin-bottom: 12px; text-align: right; } |
| | | .node-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢å®¡æ¹æµç¨' : 'ç¼è¾å®¡æ¹æµç¨'" |
| | | width="50%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æµç¨ç¼å·ï¼" prop="approveId"> |
| | | <el-input v-model="form.approveId" placeholder="èªå¨ç¼å·" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <!-- ç³è¯·é¨é¨ï¼æ ¡éªä½¿ç¨é¨é¨IDï¼ä¾¿äºä¸æéæ©åç«å³éè¿æ ¡éª --> |
| | | <el-form-item label="ç³è¯·é¨é¨ï¼" prop="approveDeptId"> |
| | | <!-- <el-input v-model="form.approveDeptName" placeholder="请è¾å
¥" clearable/>--> |
| | | <el-select |
| | | v-model="form.approveDeptId" |
| | | placeholder="éæ©é¨é¨" |
| | | @change="handleDeptChange" |
| | | > |
| | | <el-option |
| | | v-for="user in productOptions" |
| | | :key="user.deptId" |
| | | :label="user.deptName" |
| | | :value="user.deptId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item :label="props.approveType == 5 ? 'éè´è¯´æï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> |
| | | <el-input v-model="form.approveReason" placeholder="请è¾å
¥" clearable type="textarea" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- è¯·åæ¶é´ï¼ä»
å½ approveType 为 2 æ¶æ¾ç¤ºï¼ --> |
| | | <el-row :gutter="30" v-if="props.approveType == 2"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="请åå¼å§æ¶é´ï¼" prop="startDate"> |
| | | <el-date-picker |
| | | v-model="form.startDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©å¼å§æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="请åç»ææ¶é´ï¼" prop="endDate"> |
| | | <el-date-picker |
| | | v-model="form.endDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- æ¥ééé¢ï¼ä»
å½ approveType 为 4 æ¶æ¾ç¤ºï¼ --> |
| | | <el-row v-if="props.approveType == 4"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ¥ééé¢ï¼" prop="price"> |
| | | <el-input-number |
| | | v-model="form.price" |
| | | placeholder="请è¾å
¥æ¥ééé¢" |
| | | :min="0" |
| | | :precision="2" |
| | | :step="0.01" |
| | | style="width: 100%" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- åºå·®å°ç¹ï¼ä»
å½ approveType 为 3 æ¶æ¾ç¤ºï¼ --> |
| | | <el-row v-if="props.approveType == 3"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨ï¼" prop="location"> |
| | | <el-input |
| | | v-model="form.location" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- 审æ¹äººéæ©ï¼å¨æèç¹ï¼ --> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <template #label> |
| | | <span>审æ¹äººéæ©ï¼</span> |
| | | <el-button type="primary" @click="addApproverNode" style="margin-left: 8px;">æ°å¢èç¹</el-button> |
| | | </template> |
| | | <div style="display: flex; align-items: flex-end; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(node, index) in approverNodes" |
| | | :key="node.id" |
| | | style="margin-right: 30px; text-align: center; margin-bottom: 10px;" |
| | | > |
| | | <div> |
| | | <span>审æ¹äºº</span> |
| | | â |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="éæ©äººå" |
| | | style="width: 120px; margin-bottom: 8px;" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | <div> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | @click="removeApproverNode(index)" |
| | | v-if="approverNodes.length > 1" |
| | | >å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·äººï¼" prop="approveUser"> |
| | | <el-select |
| | | v-model="form.approveUser" |
| | | placeholder="éæ©äººå" |
| | | filterable |
| | | default-first-option |
| | | :reserve-keyword="false" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·æ¥æï¼" prop="approveTime"> |
| | | <el-date-picker |
| | | v-model="form.approveTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" prop="remark"> |
| | | <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload |
| | | :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" |
| | | :on-success="handleUploadSuccess" :on-remove="handleRemove"> |
| | | <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button> |
| | | <template #tip v-if="operationType !== 'view'"> |
| | | <div class="el-upload__tip"> |
| | | æä»¶æ ¼å¼æ¯æ |
| | | docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | <el-dialog v-model="visible" :title="operationType === 'add' ? `æ°å¢${typeName}` : `ç¼è¾${typeName}`" width="760px" @close="closeDialog"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-form-item label="æµç¨ç¼å·"> |
| | | <el-input v-model="form.approveId" disabled placeholder="æäº¤åèªå¨çæ" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item v-if="approveTypeNumber === 3" label="éå®åå" prop="businessId"> |
| | | <el-select v-model="form.businessId" filterable placeholder="è¯·éæ©éå®åå" style="width: 100%" @change="handleContractChange"> |
| | | <el-option v-for="item in contractOptions" :key="item.id" :value="item.id" |
| | | :label="`${item.salesContractNo || '-'} / ${item.customerName || '-'} / ¥${item.contractAmount || 0}`" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item v-if="approveTypeNumber === 3 && selectedContract" label="ååä¿¡æ¯"> |
| | | <el-descriptions :column="2" border style="width: 100%"> |
| | | <el-descriptions-item label="客æ·">{{ selectedContract.customerName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®">{{ selectedContract.projectName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸å¡å">{{ selectedContract.salesman || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ååéé¢">Â¥{{ selectedContract.contractAmount || 0 }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-form-item> |
| | | |
| | | <el-form-item v-if="[1, 5].includes(approveTypeNumber)" label="å®¡æ¹æ é¢" prop="businessTitle"> |
| | | <el-input v-model="form.businessTitle" maxlength="200" show-word-limit /> |
| | | </el-form-item> |
| | | |
| | | <template v-if="approveTypeNumber === 1"> |
| | | <el-form-item label="äºé¡¹ç±»å" prop="category"> |
| | | <el-select v-model="form.category" style="width: 100%"> |
| | | <el-option label="临æ¶è´¹ç¨æ¥é" value="临æ¶è´¹ç¨æ¥é" /> |
| | | <el-option label="é¨é¨åä½" value="é¨é¨åä½" /> |
| | | <el-option label="å
¶ä»" value="å
¶ä»" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¶åéé¢"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | |
| | | <template v-if="approveTypeNumber === 5"> |
| | | <el-row :gutter="16"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éè´ç±»å«" prop="category"><el-input v-model="form.category" /></el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç´§æ¥ç¨åº¦" prop="urgency"> |
| | | <el-select v-model="form.urgency" style="width: 100%"> |
| | | <el-option label="æ®é" value="æ®é" /><el-option label="ç´§æ¥" value="ç´§æ¥" /><el-option label="ç¹æ¥" value="ç¹æ¥" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="éè´å
容" prop="purchaseContent"><el-input v-model="form.purchaseContent" type="textarea" /></el-form-item> |
| | | <el-row :gutter="16"> |
| | | <el-col :span="8"><el-form-item label="æ°é" prop="quantity"><el-input-number v-model="form.quantity" :min="0.01" :precision="2" style="width: 100%" /></el-form-item></el-col> |
| | | <el-col :span="8"><el-form-item label="åä½" prop="unit"><el-input v-model="form.unit" /></el-form-item></el-col> |
| | | <el-col :span="8"><el-form-item label="é¢ç®éé¢" prop="amount"><el-input-number v-model="form.amount" :min="0.01" :precision="2" style="width: 100%" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-form-item label="ææå°è´§æ¥æ" prop="expectedDate"> |
| | | <el-date-picker v-model="form.expectedDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /> |
| | | </el-form-item> |
| | | </template> |
| | | |
| | | <el-row v-if="approveTypeNumber === 2" :gutter="16"> |
| | | <el-col :span="12"><el-form-item label="请åå¼å§æ¥æ" prop="startDate"><el-date-picker v-model="form.startDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="请åç»ææ¥æ" prop="endDate"><el-date-picker v-model="form.endDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-form-item v-if="approveTypeNumber === 4" label="æ¥ééé¢" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0.01" :precision="2" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç³è¯·äºç±" prop="approveReason"> |
| | | <el-input v-model="form.approveReason" type="textarea" :rows="3" maxlength="1000" show-word-limit /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç³è¯·é¨é¨" prop="approveDeptId"> |
| | | <el-select v-model="form.approveDeptId" style="width: 100%"> |
| | | <el-option v-for="dept in departmentOptions" :key="dept.deptId" :label="dept.deptName" :value="dept.deptId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item v-if="[3, 5].includes(approveTypeNumber) && flowOptions.length" label="é¢è®¾å®¡æ¹æµç¨"> |
| | | <el-select v-model="form.flowDefinitionId" clearable placeholder="è¯·éæ©é¢è®¾æµç¨ï¼çç©ºå¯æå¨è®¾ç½®èç¹" style="width: 100%"> |
| | | <el-option v-for="flow in flowOptions" :key="flow.id" :label="flow.flowName" :value="flow.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-divider content-position="left">审æ¹èç¹</el-divider> |
| | | <el-alert v-if="form.flowDefinitionId && operationType === 'add'" title="æäº¤åå°æé¢è®¾æµç¨çæå®¡æ¹èç¹" type="info" :closable="false" style="margin-bottom: 12px" /> |
| | | <div v-for="(node, index) in approverNodes" v-show="!form.flowDefinitionId || operationType === 'edit'" :key="node.key" class="node-row"> |
| | | <span class="node-order">{{ index + 1 }}</span> |
| | | <el-input v-model="node.nodeName" placeholder="èç¹åç§°" style="width: 150px" /> |
| | | <el-select v-model="node.userId" filterable placeholder="审æ¹äºº" style="width: 150px"> |
| | | <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> |
| | | </el-select> |
| | | <el-select v-model="node.conditionOperator" style="width: 130px" @change="handleConditionChange(node)"> |
| | | <el-option label="å§ç»æ§è¡" value="ALWAYS" /> |
| | | <el-option label="éé¢å¤§äº" value="GT" /> |
| | | <el-option label="éé¢å¤§äºçäº" value="GTE" /> |
| | | <el-option label="éé¢å°äº" value="LT" /> |
| | | <el-option label="éé¢å°äºçäº" value="LTE" /> |
| | | </el-select> |
| | | <el-input-number v-if="node.conditionOperator !== 'ALWAYS'" v-model="node.conditionValue" :min="0" :precision="2" style="width: 130px" /> |
| | | <el-button type="danger" link :disabled="approverNodes.length === 1" @click="removeNode(index)">å é¤</el-button> |
| | | <el-button link :disabled="index === 0" @click="moveNode(index, -1)">ä¸ç§»</el-button> |
| | | <el-button link :disabled="index === approverNodes.length - 1" @click="moveNode(index, 1)">ä¸ç§»</el-button> |
| | | </div> |
| | | <el-button v-if="!form.flowDefinitionId || operationType === 'edit'" type="primary" plain class="add-node" @click="addNode">æ°å¢å®¡æ¹èç¹</el-button> |
| | | |
| | | <el-form-item label="ç³è¯·æ¥æ"> |
| | | <el-date-picker v-model="form.approveTime" type="date" value-format="YYYY-MM-DD" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="éä»¶ææ"> |
| | | <el-upload v-model:file-list="fileList" :action="upload.url" :headers="upload.headers" multiple |
| | | :before-upload="beforeUpload" :on-success="uploadSuccess" :on-error="uploadError" :on-remove="removeFile"> |
| | | <el-button type="primary">ä¸ä¼ æä»¶</el-button> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="closeDialog">åæ¶</el-button> |
| | | <el-button type="primary" :loading="submitting" @click="submit">æäº¤</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, toRefs, getCurrentInstance} from "vue"; |
| | | import { |
| | | approveProcessAdd, approveProcessGetInfo, |
| | | approveProcessUpdate, |
| | | getDept |
| | | } from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import { |
| | | delLedgerFile, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import { getToken } from "@/utils/auth"; |
| | | const { proxy } = getCurrentInstance() |
| | | import { computed, getCurrentInstance, reactive, ref } from 'vue' |
| | | import { approveFlowDefinitionList, approveProcessAdd, approveProcessDetails, approveProcessGetInfo, approveProcessUpdate, getDept } from '@/api/collaborativeApproval/approvalProcess' |
| | | import { delLedgerFile, ledgerListNoPage } from '@/api/salesManagement/salesLedger' |
| | | import { userListNoPageByTenantId } from '@/api/system/user' |
| | | import { getToken } from '@/utils/auth' |
| | | import { getCurrentDate } from '@/utils' |
| | | import useUserStore from '@/store/modules/user' |
| | | |
| | | const props = defineProps({ approveType: { type: [Number, String], default: 1 } }) |
| | | const emit = defineEmits(['close']) |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | import log from "@/views/monitor/job/log.vue"; |
| | | const userStore = useUserStore(); |
| | | const { proxy } = getCurrentInstance() |
| | | const userStore = useUserStore() |
| | | const approveTypeNumber = computed(() => Number(props.approveType)) |
| | | const typeName = computed(() => ({ 1: 'èªç±åå审æ¹', 3: 'éå®å®¡æ¹', 5: 'éè´å®¡æ¹' }[approveTypeNumber.value] || 'å®¡æ¹æµç¨')) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const fileList = ref([]); |
| | | const upload = reactive({ |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | | approveId: "", |
| | | approveUser: "", |
| | | approveDeptId: "", |
| | | approveDeptName: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [], // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid |
| | | startDate: "", // 请åå¼å§æ¶é´ |
| | | endDate: "", // 请åç»ææ¶é´ |
| | | price: null, // æ¥ééé¢ |
| | | location: "" // åºå·®å°ç¹ |
| | | }, |
| | | rules: { |
| | | approveTime: [{ required: false, message: "请è¾å
¥", trigger: "change" }], |
| | | approveId: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | approveUser: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | // 使ç¨é¨é¨IDåå¿
å¡«æ ¡éªï¼é¿å
åç§°æªåæ¥å¯¼è´è¯¯æ¥ |
| | | approveDeptId: [{ required: true, message: "è¯·éæ©ç³è¯·é¨é¨", trigger: "change" }], |
| | | approveReason: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkResult: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | startDate: [{ required: true, message: "è¯·éæ©è¯·åå¼å§æ¶é´", trigger: "change" }], |
| | | endDate: [{ required: true, message: "è¯·éæ©è¯·åç»ææ¶é´", trigger: "change" }], |
| | | price: [{ required: true, message: "请è¾å
¥æ¥ééé¢", trigger: "blur" }], |
| | | location: [{ required: true, message: "请è¾å
¥åºå·®å°ç¹", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const productOptions = ref([]); |
| | | const currentApproveStatus = ref(0) |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | } |
| | | }) |
| | | |
| | | // 审æ¹äººèç¹ç¸å
³ |
| | | const approverNodes = ref([ |
| | | { id: 1, userId: null } |
| | | ]) |
| | | let nextApproverId = 2 |
| | | const visible = ref(false) |
| | | const submitting = ref(false) |
| | | const operationType = ref('add') |
| | | const formRef = ref() |
| | | const userList = ref([]) |
| | | function addApproverNode() { |
| | | approverNodes.value.push({ id: nextApproverId++, userId: null }) |
| | | const departmentOptions = ref([]) |
| | | const contractOptions = ref([]) |
| | | const flowOptions = ref([]) |
| | | const selectedContract = ref(null) |
| | | const fileList = ref([]) |
| | | let nodeKey = 1 |
| | | const approverNodes = ref([]) |
| | | const upload = reactive({ url: import.meta.env.VITE_APP_BASE_API + '/file/upload', headers: { Authorization: 'Bearer ' + getToken() } }) |
| | | const emptyForm = () => ({ |
| | | id: null, approveId: '', approveUser: userStore.id, approveDeptId: userStore.currentDeptId, |
| | | approveTime: getCurrentDate(), approveReason: '', businessId: null, businessNo: '', businessTitle: '', |
| | | amount: null, businessData: '', flowDefinitionId: null, category: '', urgency: 'æ®é', purchaseContent: '', quantity: null, |
| | | unit: '', expectedDate: '', startDate: '', endDate: '', tempFileIds: [] |
| | | }) |
| | | const form = ref(emptyForm()) |
| | | const rules = { |
| | | approveDeptId: [{ required: true, message: 'è¯·éæ©ç³è¯·é¨é¨', trigger: 'change' }], |
| | | approveReason: [{ required: true, message: '请è¾å
¥ç³è¯·äºç±', trigger: 'blur' }], |
| | | businessId: [{ required: true, message: 'è¯·éæ©éå®åå', trigger: 'change' }], |
| | | businessTitle: [{ required: true, message: '请è¾å
¥å®¡æ¹æ é¢', trigger: 'blur' }], |
| | | category: [{ required: true, message: '请è¾å
¥æéæ©ç±»å«', trigger: 'blur' }], |
| | | urgency: [{ required: true, message: 'è¯·éæ©ç´§æ¥ç¨åº¦', trigger: 'change' }], |
| | | purchaseContent: [{ required: true, message: '请è¾å
¥éè´å
容', trigger: 'blur' }], |
| | | quantity: [{ required: true, message: '请è¾å
¥æ°é', trigger: 'blur' }], |
| | | unit: [{ required: true, message: '请è¾å
¥åä½', trigger: 'blur' }], |
| | | amount: [{ required: true, message: '请è¾å
¥é¢ç®éé¢', trigger: 'blur' }], |
| | | expectedDate: [{ required: true, message: 'è¯·éæ©ææå°è´§æ¥æ', trigger: 'change' }], |
| | | startDate: [{ required: true, message: 'è¯·éæ©è¯·åå¼å§æ¥æ', trigger: 'change' }], |
| | | endDate: [{ required: true, message: 'è¯·éæ©è¯·åç»ææ¥æ', trigger: 'change' }] |
| | | } |
| | | function removeApproverNode(index) { |
| | | approverNodes.value.splice(index, 1) |
| | | |
| | | const createNode = (data = {}) => ({ key: nodeKey++, userId: data.userId || null, nodeName: data.nodeName || '审æ¹', conditionField: data.conditionField || null, conditionOperator: data.conditionOperator || 'ALWAYS', conditionValue: data.conditionValue == null ? null : Number(data.conditionValue) }) |
| | | const addNode = () => approverNodes.value.push(createNode()) |
| | | const removeNode = index => approverNodes.value.splice(index, 1) |
| | | const moveNode = (index, offset) => { |
| | | const target = index + offset |
| | | if (target < 0 || target >= approverNodes.value.length) return |
| | | const item = approverNodes.value.splice(index, 1)[0] |
| | | approverNodes.value.splice(target, 0, item) |
| | | } |
| | | // å¤çé¨é¨éæ©åå |
| | | const handleDeptChange = (deptId) => { |
| | | if (deptId) { |
| | | const selectedDept = productOptions.value.find(dept => dept.deptId === deptId); |
| | | if (selectedDept) { |
| | | form.value.approveDeptName = selectedDept.deptName; |
| | | } |
| | | const handleConditionChange = node => { |
| | | node.conditionField = node.conditionOperator === 'ALWAYS' ? null : 'amount' |
| | | if (node.conditionOperator === 'ALWAYS') node.conditionValue = null |
| | | } |
| | | |
| | | const loadOptions = async () => { |
| | | const tasks = [userListNoPageByTenantId(), getDept()] |
| | | if (approveTypeNumber.value === 3) tasks.push(ledgerListNoPage({})) |
| | | const flowPromise = [3, 5].includes(approveTypeNumber.value) ? approveFlowDefinitionList({ approveType: approveTypeNumber.value }) : Promise.resolve({ data: [] }) |
| | | tasks.push(flowPromise) |
| | | const results = await Promise.all(tasks) |
| | | userList.value = results[0].data || [] |
| | | departmentOptions.value = results[1].data || [] |
| | | if (approveTypeNumber.value === 3) { |
| | | contractOptions.value = results[2]?.data || [] |
| | | flowOptions.value = results[3]?.data || [] |
| | | } else { |
| | | form.value.approveDeptName = ''; |
| | | contractOptions.value = [] |
| | | flowOptions.value = results[2]?.data || [] |
| | | } |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | getProductOptions(); |
| | | form.value = {} |
| | | approverNodes.value = [ |
| | | { id: 1, userId: null } |
| | | ] |
| | | form.value.approveUser = userStore.id; |
| | | form.value.approveTime = getCurrentDate(); |
| | | |
| | | // è·åå½åç¨æ·ä¿¡æ¯å¹¶è®¾ç½®é¨é¨ID |
| | | form.value.approveDeptId = userStore.currentDeptId |
| | | if (operationType.value === 'edit') { |
| | | fileList.value = row.commonFileList |
| | | form.value.tempFileIds = fileList.value.map(file => file.id) |
| | | currentApproveStatus.value = row.approveStatus |
| | | approveProcessGetInfo({id: row.approveId,approveReason: '1'}).then(res => { |
| | | form.value = {...res.data} |
| | | // 忾审æ¹äºº |
| | | if (res.data && res.data.approveUserIds) { |
| | | const userIds = res.data.approveUserIds.split(',') |
| | | approverNodes.value = userIds.map((userId, idx) => ({ |
| | | id: idx + 1, |
| | | userId: parseInt(userId.trim()) |
| | | })) |
| | | nextApproverId = userIds.length + 1 |
| | | } else { |
| | | approverNodes.value = [{ id: 1, userId: null }] |
| | | nextApproverId = 2 |
| | | } |
| | | }) |
| | | } |
| | | const defaultFlow = flowOptions.value.find(item => item.isDefault === 1 && item.enabled === 1) |
| | | if (defaultFlow && operationType.value === 'add') form.value.flowDefinitionId = defaultFlow.id |
| | | } |
| | | const getProductOptions = () => { |
| | | getDept().then((res) => { |
| | | productOptions.value = res.data; |
| | | }); |
| | | }; |
| | | function convertIdToValue(data) { |
| | | return data.map((item) => { |
| | | const { id, children, ...rest } = item; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // å° id æ¹ä¸º value |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitForm = () => { |
| | | // æ¶éææèç¹ç审æ¹äººid |
| | | form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',') |
| | | form.value.approveType = props.approveType |
| | | // 审æ¹äººå¿
å¡«æ ¡éª |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId) |
| | | if (hasEmptyApprover) { |
| | | proxy.$modal.msgError("请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼") |
| | | return |
| | | } |
| | | // å½ approveType 为 2 æ¶ï¼æ ¡éªè¯·åæ¶é´ |
| | | if (props.approveType == 2) { |
| | | if (!form.value.startDate) { |
| | | proxy.$modal.msgError("è¯·éæ©è¯·åå¼å§æ¶é´ï¼") |
| | | return |
| | | } |
| | | if (!form.value.endDate) { |
| | | proxy.$modal.msgError("è¯·éæ©è¯·åç»ææ¶é´ï¼") |
| | | return |
| | | } |
| | | // æ ¡éªç»ææ¶é´ä¸è½æ©äºå¼å§æ¶é´ |
| | | if (new Date(form.value.endDate) < new Date(form.value.startDate)) { |
| | | proxy.$modal.msgError("请åç»ææ¶é´ä¸è½æ©äºå¼å§æ¶é´ï¼") |
| | | return |
| | | } |
| | | } |
| | | // å½ approveType 为 3 æ¶ï¼æ ¡éªåºå·®å°ç¹ |
| | | if (props.approveType == 3) { |
| | | if (!form.value.location || form.value.location.trim() === '') { |
| | | proxy.$modal.msgError("请è¾å
¥åºå·®å°ç¹ï¼") |
| | | return |
| | | } |
| | | } |
| | | // å½ approveType 为 4 æ¶ï¼æ ¡éªæ¥ééé¢ |
| | | if (props.approveType == 4) { |
| | | if (!form.value.price || form.value.price <= 0) { |
| | | proxy.$modal.msgError("请è¾å
¥ææçæ¥ééé¢ï¼") |
| | | return |
| | | } |
| | | } |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "add" || currentApproveStatus.value == 3) { |
| | | approveProcessAdd(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } else { |
| | | approveProcessUpdate(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type |
| | | visible.value = true |
| | | form.value = emptyForm() |
| | | approverNodes.value = [createNode()] |
| | | fileList.value = [] |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | | // æ ¡æ£æä»¶å¤§å° |
| | | // if (file.size > 1024 * 1024 * 10) { |
| | | // proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿10MB!"); |
| | | // return false; |
| | | // } |
| | | proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); |
| | | return true; |
| | | } |
| | | // ä¸ä¼ 失败 |
| | | function handleUploadError(err) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | // ä¸ä¼ æååè° |
| | | function handleUploadSuccess(res, file, uploadFiles) { |
| | | proxy.$modal.closeLoading(); |
| | | if (res.code === 200) { |
| | | // ç¡®ä¿ tempFileIds åå¨ä¸ä¸ºæ°ç» |
| | | if (!form.value.tempFileIds) { |
| | | form.value.tempFileIds = []; |
| | | selectedContract.value = null |
| | | await loadOptions() |
| | | if (type === 'edit' && row?.approveId) { |
| | | const [processRes, nodeRes] = await Promise.all([approveProcessGetInfo({ id: row.approveId }), approveProcessDetails(row.approveId)]) |
| | | const process = processRes.data || {} |
| | | let business = {} |
| | | try { business = process.businessData ? JSON.parse(process.businessData) : {} } catch (_) { business = {} } |
| | | form.value = { ...emptyForm(), ...process, ...business, tempFileIds: [] } |
| | | fileList.value = process.commonFileList || [] |
| | | approverNodes.value = (nodeRes.data || []).map(item => createNode({ |
| | | userId: item.approveNodeUserId, |
| | | nodeName: item.approveNodeName, |
| | | conditionField: item.conditionField, |
| | | conditionOperator: item.conditionOperator, |
| | | conditionValue: item.conditionValue |
| | | })) |
| | | if (!approverNodes.value.length) approverNodes.value = [createNode()] |
| | | if (approveTypeNumber.value === 3) { |
| | | selectedContract.value = contractOptions.value.find(item => item.id === process.businessId) || business |
| | | } |
| | | form.value.tempFileIds.push(res.data.tempId); |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | } |
| | | } |
| | | // ç§»é¤æä»¶ |
| | | function handleRemove(file) { |
| | | if (operationType.value === "edit") { |
| | | let ids = []; |
| | | ids.push(file.id); |
| | | delLedgerFile(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | const handleContractChange = id => { |
| | | selectedContract.value = contractOptions.value.find(item => item.id === id) || null |
| | | if (selectedContract.value) form.value.businessTitle = `éå®åå审æ¹-${selectedContract.value.salesContractNo || ''}` |
| | | } |
| | | |
| | | const buildBusinessData = () => { |
| | | if (approveTypeNumber.value === 5) return JSON.stringify({ category: form.value.category, urgency: form.value.urgency, purchaseContent: form.value.purchaseContent, quantity: form.value.quantity, unit: form.value.unit, expectedDate: form.value.expectedDate }) |
| | | if (approveTypeNumber.value === 1) return JSON.stringify({ category: form.value.category }) |
| | | if (approveTypeNumber.value === 2) return JSON.stringify({ startDate: form.value.startDate, endDate: form.value.endDate }) |
| | | if (approveTypeNumber.value === 4) return JSON.stringify({ amount: form.value.amount }) |
| | | return form.value.businessData || '' |
| | | } |
| | | |
| | | const submit = async () => { |
| | | await formRef.value.validate() |
| | | if (approveTypeNumber.value === 2 && new Date(form.value.endDate) < new Date(form.value.startDate)) return proxy.$modal.msgError('请åç»ææ¥æä¸è½æ©äºå¼å§æ¥æ') |
| | | const usePresetFlow = operationType.value === 'add' && !!form.value.flowDefinitionId |
| | | if (!usePresetFlow && approverNodes.value.some(item => !item.userId || !item.nodeName)) return proxy.$modal.msgError('è¯·å®æ´å¡«å审æ¹èç¹') |
| | | if (!usePresetFlow && approverNodes.value.some((item, index) => index > 0 && item.userId === approverNodes.value[index - 1].userId)) return proxy.$modal.msgError('ç¸é»å®¡æ¹èç¹ä¸è½éæ©åä¸å®¡æ¹äºº') |
| | | if (!usePresetFlow && approverNodes.value.some(item => item.conditionOperator !== 'ALWAYS' && item.conditionValue == null)) return proxy.$modal.msgError('请填åèç¹æ¡ä»¶éé¢') |
| | | const nodes = usePresetFlow ? [] : approverNodes.value.map(item => ({ userId: item.userId, nodeName: item.nodeName, conditionField: item.conditionOperator === 'ALWAYS' ? null : 'amount', conditionOperator: item.conditionOperator, conditionValue: item.conditionValue == null ? null : String(item.conditionValue) })) |
| | | const payload = { ...form.value, approveType: approveTypeNumber.value, approveUser: userStore.id, nodes, approveUserIds: nodes.map(item => item.userId).join(','), businessData: buildBusinessData() } |
| | | submitting.value = true |
| | | try { |
| | | if (operationType.value === 'add') await approveProcessAdd(payload) |
| | | else await approveProcessUpdate(payload) |
| | | proxy.$modal.msgSuccess('æäº¤æå') |
| | | closeDialog() |
| | | } finally { |
| | | submitting.value = false |
| | | } |
| | | } |
| | | |
| | | const beforeUpload = () => { proxy.$modal.loading('æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å...'); return true } |
| | | const uploadSuccess = (res, file) => { |
| | | proxy.$modal.closeLoading() |
| | | if (res.code === 200) form.value.tempFileIds.push(res.data.tempId) |
| | | else { proxy.$modal.msgError(res.msg); proxy.$refs.fileUpload?.handleRemove(file) } |
| | | } |
| | | const uploadError = () => { proxy.$modal.closeLoading(); proxy.$modal.msgError('ä¸ä¼ 失败') } |
| | | const removeFile = file => { |
| | | if (file.id) delLedgerFile([file.id]) |
| | | if (file.response?.data?.tempId) form.value.tempFileIds = form.value.tempFileIds.filter(id => id !== file.response.data.tempId) |
| | | } |
| | | const closeDialog = () => { visible.value = false; emit('close') } |
| | | |
| | | defineExpose({ openDialog }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | | .node-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; } |
| | | .node-order { display: inline-flex; width: 26px; height: 26px; align-items: center; justify-content: center; border-radius: 50%; color: #fff; background: #409eff; } |
| | | .add-node { margin: 0 0 20px 120px; } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æ ç¾é¡µåæ¢ä¸åç审æ¹ç±»å --> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs"> |
| | | <el-tab-pane label="åå审æ¹" name="1"></el-tab-pane> |
| | | <el-tabs v-if="!fixedApproveType" v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs"> |
| | | <el-tab-pane label="èªç±åå审æ¹" name="1"></el-tab-pane> |
| | | <el-tab-pane label="请å管ç" name="2"></el-tab-pane> |
| | | <el-tab-pane label="éå®å®¡æ¹" name="3"></el-tab-pane> |
| | | <el-tab-pane label="æ¥é审æ¹" name="4"></el-tab-pane> |
| | |
| | | > |
| | | </div> |
| | | <div> |
| | | <el-button v-if="[3, 5].includes(currentApproveType)" @click="openFlowDefinition">æµç¨é
ç½®</el-button> |
| | | <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 6">æ°å¢</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia> |
| | | <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia> |
| | | <FileList ref="fileListRef" /> |
| | | <FlowDefinitionDia ref="flowDefinitionRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FileList from "./fileList.vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance} from "vue"; |
| | | import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance, watch} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import { useRoute } from 'vue-router'; |
| | | import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue"; |
| | | import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue"; |
| | | import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import FlowDefinitionDia from "./components/flowDefinitionDia.vue"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const route = useRoute(); |
| | | const props = defineProps({ |
| | | approveType: { type: [Number, String], default: null } |
| | | }) |
| | | const fixedApproveType = computed(() => props.approveType !== null && props.approveType !== undefined) |
| | | |
| | | // å½åéä¸çæ ç¾é¡µï¼é»è®¤ä¸ºå
¬åºç®¡ç |
| | | const activeTab = ref('1'); |
| | | const activeTab = ref(String(props.approveType || '1')); |
| | | |
| | | // å½å审æ¹ç±»åï¼æ ¹æ®éä¸çæ ç¾é¡µè®¡ç® |
| | | const currentApproveType = computed(() => { |
| | |
| | | const isLeaveType = currentApproveType.value === 2; // 请å管ç |
| | | const isReimburseType = currentApproveType.value === 4; // æ¥é管ç |
| | | const isQuotationType = currentApproveType.value === 6; // æ¥ä»·å®¡æ¹ |
| | | const isBusinessApproval = [1, 3, 5].includes(currentApproveType.value) |
| | | |
| | | // åºç¡åé
ç½® |
| | | const baseColumns = [ |
| | |
| | | width: 220 |
| | | }, |
| | | { |
| | | label: isQuotationType ? "æ¥ä»·åå·" : "审æ¹äºç±", |
| | | prop: "approveReason", |
| | | label: isBusinessApproval ? "ä¸å¡ç¼å·" : (isQuotationType ? "æ¥ä»·åå·" : "审æ¹äºç±"), |
| | | prop: isBusinessApproval ? "businessNo" : "approveReason", |
| | | width: 200 |
| | | }, |
| | | ...(isBusinessApproval ? [{ label: "å®¡æ¹æ é¢", prop: "businessTitle", width: 220 }] : []), |
| | | { |
| | | label: "ç³è¯·äºº", |
| | | prop: "approveUserName", |
| | |
| | | prop: "price", |
| | | width: 120 |
| | | }); |
| | | } |
| | | if ([1, 3, 5].includes(currentApproveType.value)) { |
| | | baseColumns.push({ label: currentApproveType.value === 3 ? "ååéé¢ï¼å
ï¼" : "éé¢ï¼å
ï¼", prop: "amount", width: 140 }) |
| | | } |
| | | |
| | | // æ¥æåï¼æ ¹æ®ç±»å卿é
ç½®ï¼ |
| | |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => currentApproveType.value === 6 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4 |
| | | disabled: (row) => currentApproveType.value === 6 || ![0, 3].includes(row.approveStatus) || row.approveUser !== userStore.id |
| | | }, |
| | | { |
| | | name: "å®¡æ ¸", |
| | |
| | | }); |
| | | const infoFormDia = ref() |
| | | const approvalDia = ref() |
| | | const flowDefinitionRef = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | // æ¥è¯¢å表 |
| | |
| | | } |
| | | const url = urlMap[type] || urlMap[0] |
| | | const nameMap = { |
| | | 0: "åå审æ¹ç®¡ç表", |
| | | 1: "å
¬åºç®¡ç审æ¹è¡¨", |
| | | 0: "èªç±åå审æ¹è¡¨", |
| | | 1: "èªç±åå审æ¹è¡¨", |
| | | 2: "请å管ç审æ¹è¡¨", |
| | | 3: "åºå·®ç®¡ç审æ¹è¡¨", |
| | | 3: "éå®åå审æ¹è¡¨", |
| | | 4: "æ¥é管ç审æ¹è¡¨", |
| | | 5: "éè´ç³è¯·å®¡æ¹è¡¨", |
| | | 5: "éè´è®¡å审æ¹è¡¨", |
| | | 6: "æ¥ä»·å®¡æ¹è¡¨", |
| | | 7: "åºåºå®¡æ¹è¡¨", |
| | | } |
| | |
| | | approvalDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | const openFlowDefinition = () => flowDefinitionRef.value?.open(currentApproveType.value) |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | |
| | | // æ¥è¯¢å表 |
| | | getList(); |
| | | }); |
| | | |
| | | watch(() => props.approveType, value => { |
| | | if (value !== null && value !== undefined) { |
| | | activeTab.value = String(value) |
| | | handleQuery() |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | name: 'FreeCollaborationApproval' |
| | | }) |
| | | </script> |
| | | |
| | |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | name: 'SalesApproval' |
| | | }) |
| | | </script> |
| | | |
| | |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | name: 'PurchaseApproval' |
| | | }) |
| | | </script> |
| | | |
| | |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-collapse class="report-panel"> |
| | | <el-collapse-item title="æ¯åºå¤§ç±»å æ¯ä¸è¶å¿" name="category"> |
| | | <div class="charts"> |
| | | <div ref="pieRef"></div> |
| | | <div ref="trendRef"></div> |
| | | </div> |
| | | </el-collapse-item> |
| | | </el-collapse> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, nextTick } from "vue"; |
| | | import * as echarts from "echarts"; |
| | | import { getExpenseCategoryReport } from "@/api/financialManagement/expenseManagement"; |
| | | const pieRef = ref(), |
| | | trendRef = ref(); |
| | | onMounted(async () => { |
| | | const r = await getExpenseCategoryReport({}); |
| | | const share = r.data?.share || [], |
| | | trend = r.data?.trend || []; |
| | | await nextTick(); |
| | | echarts.init(pieRef.value).setOption({ |
| | | title: { text: "æ¯åºå¤§ç±»å æ¯", left: "center" }, |
| | | tooltip: { trigger: "item" }, |
| | | series: [ |
| | | { |
| | | type: "pie", |
| | | radius: ["35%", "68%"], |
| | | data: share.map((x) => ({ name: x.categoryName, value: x.amount })), |
| | | }, |
| | | ], |
| | | }); |
| | | const months = [...new Set(trend.map((x) => x.month))], |
| | | names = [...new Set(trend.map((x) => x.categoryName))]; |
| | | echarts.init(trendRef.value).setOption({ |
| | | title: { text: "æ¯åºå¤§ç±»æåº¦è¶å¿", left: "center" }, |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { top: 28 }, |
| | | xAxis: { type: "category", data: months }, |
| | | yAxis: { type: "value" }, |
| | | series: names.map((name) => ({ |
| | | name, |
| | | type: "line", |
| | | data: months.map( |
| | | (month) => |
| | | trend.find((x) => x.month === month && x.categoryName === name) |
| | | ?.amount || 0 |
| | | ), |
| | | })), |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .report-panel { |
| | | margin-bottom: 12px; |
| | | } |
| | | .charts { |
| | | display: grid; |
| | | grid-template-columns: 1fr 2fr; |
| | | gap: 12px; |
| | | } |
| | | .charts > div { |
| | | height: 300px; |
| | | } |
| | | @media (max-width: 900px) { |
| | | .charts { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯åºå¤§ç±»" prop="expenseCategory"> |
| | | <el-select v-model="form.expenseCategory" placeholder="è¯·éæ©æ¯åºå¤§ç±»" clearable> |
| | | <el-option v-for="item in finance_expense_category" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯åºç±»å" prop="expenseType"> |
| | | <el-select |
| | | v-model="form.expenseType" |
| | |
| | | name: "æ°å¢æ¯åº", |
| | | }); |
| | | const { expense_types } = proxy.useDict("expense_types"); |
| | | const { finance_expense_category } = proxy.useDict("finance_expense_category"); |
| | | const { checkout_payment } = proxy.useDict("checkout_payment"); |
| | | const formRef = ref(null); |
| | | const formRules = { |
| | |
| | | expenseDescribed: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | expenseDate: [{ required: true, trigger: "change", message: "è¯·éæ©" }], |
| | | expenseType: [{ required: true, trigger: "change", message: "è¯·éæ©" }], |
| | | expenseCategory: [{ required: true, trigger: "change", message: "è¯·éæ©æ¯åºå¤§ç±»" }], |
| | | expenseMethod: [{ required: true, trigger: "change", message: "è¯·éæ©" }], |
| | | } |
| | | |
| | | const { form, resetForm } = useFormData({ |
| | | expenseDate: undefined, // æ¯åºæ¥æ |
| | | expenseType: undefined, // æ¯åºç±»å |
| | | expenseCategory: undefined, // æ¯åºå¤§ç±» |
| | | supplierName: undefined, // 客æ·åç§° |
| | | expenseMoney: undefined, // æ¯åºéé¢ |
| | | expenseDescribed: undefined, // æ¯åºæè¿° |
| | |
| | | if (code == 200) { |
| | | form.expenseDate = data.expenseDate; |
| | | form.expenseType = data.expenseType; |
| | | form.expenseCategory = data.expenseCategory; |
| | | form.supplierName = data.supplierName; |
| | | form.expenseMoney = data.expenseMoney; |
| | | form.expenseDescribed = data.expenseDescribed; |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <ExpenseCategoryReport /> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="æ¯åºæ¥æ:"> |
| | | <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯åºå¤§ç±»:"> |
| | | <el-select v-model="filters.expenseCategory" placeholder="è¯·éæ©" clearable style="width: 160px"> |
| | | <el-option v-for="item in finance_expense_category" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾æ¹å¼:"> |
| | | <el-select |
| | |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import FilesDia from "../revenueManagement/filesDia.vue"; |
| | | import ExpenseCategoryReport from "./ExpenseCategoryReport.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "æ¯åºç®¡ç", |
| | |
| | | const modalRef = ref(); |
| | | const { checkout_payment } = proxy.useDict("checkout_payment"); |
| | | const { expense_types } = proxy.useDict("expense_types"); |
| | | const { finance_expense_category } = proxy.useDict("finance_expense_category"); |
| | | const filesDia = ref() |
| | | |
| | | const { |
| | |
| | | listPage, |
| | | { |
| | | expenseMethod: undefined, |
| | | expenseCategory: undefined, |
| | | entryDate: undefined, |
| | | }, |
| | | [ |
| | |
| | | prop: "expenseDate", |
| | | }, |
| | | { |
| | | label: "æ¯åºå¤§ç±»", |
| | | align: "center", |
| | | prop: "expenseCategory", |
| | | dataType: "tag", |
| | | formatData: (params) => finance_expense_category.value.find((m) => m.value == params)?.label || "æªåç±»", |
| | | }, |
| | | { |
| | | label: "æ¯åºç±»å", |
| | | align: "center", |
| | | prop: "expenseType", |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="reconciliation-workspace"> |
| | | <div class="workspace-heading"> |
| | | <h2>è´¢å¡å¯¹è´¦</h2> |
| | | <p>æ¯å¯¹æ¶æ¯ãèµéæµæ°´ä¸å¤é¨åè¯ï¼è®°å½å·®å¼å¤çæ¹æ¡å¹¶å½æ¡£ã</p> |
| | | </div> |
| | | <el-form inline :model="query"> |
| | | <el-form-item label="对账类å" |
| | | ><el-select v-model="query.reconType" clearable style="width: 150px" |
| | | ><el-option |
| | | v-for="(label, key) in reconTypeText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="ç¶æ" |
| | | ><el-select v-model="query.status" clearable style="width: 130px" |
| | | ><el-option |
| | | v-for="(label, key) in statusText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="对象" |
| | | ><el-input v-model="query.target" clearable |
| | | /></el-form-item> |
| | | <el-form-item |
| | | ><el-button type="primary" @click="loadTasks">æ¥è¯¢</el-button |
| | | ><el-button @click="openCreate">å起对账</el-button></el-form-item |
| | | > |
| | | </el-form> |
| | | |
| | | <el-alert |
| | | v-if="loadError" |
| | | :title="loadError" |
| | | type="error" |
| | | show-icon |
| | | :closable="false" |
| | | class="load-error" |
| | | /> |
| | | <el-table |
| | | :data="tasks" |
| | | border |
| | | v-loading="loading" |
| | | empty-text="ææ å¯¹è´¦ä»»å¡ï¼å¯ç¹å»âå起对账âå建" |
| | | > |
| | | <el-table-column prop="id" label="ä»»å¡å·" width="80" /> |
| | | <el-table-column label="ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | reconTypeText[row.reconType] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column label="æé´" min-width="190" |
| | | ><template #default="{ row }" |
| | | >{{ row.periodStart }} è³ {{ row.periodEnd }}</template |
| | | ></el-table-column |
| | | > |
| | | <el-table-column prop="target" label="对账对象" /> |
| | | <el-table-column prop="systemAmount" label="ç³»ç»éé¢" /> |
| | | <el-table-column prop="externalAmount" label="对æ¹éé¢" /> |
| | | <el-table-column prop="diffAmount" label="å·®é¢" /> |
| | | <el-table-column label="ç»æ" |
| | | ><template #default="{ row }" |
| | | ><el-tag :type="row.result === '0' ? 'success' : 'danger'">{{ |
| | | row.result === "0" ? "ä¸è´" : "æå·®å¼" |
| | | }}</el-tag></template |
| | | ></el-table-column |
| | | > |
| | | <el-table-column label="ç¶æ" |
| | | ><template #default="{ row }">{{ |
| | | statusText[row.status] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column label="æä½" width="260" fixed="right" |
| | | ><template #default="{ row }"> |
| | | <el-button link type="primary" @click="showDiff(row)" |
| | | >差弿ç»</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | :disabled="row.status === '2'" |
| | | @click="openHandle(row)" |
| | | >å¤ç</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | type="success" |
| | | :disabled="row.status === '2'" |
| | | @click="archive(row)" |
| | | >彿¡£</el-button |
| | | > |
| | | <el-button link @click="exportDiff(row)">导åº</el-button> |
| | | </template></el-table-column |
| | | > |
| | | </el-table> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | :page="query.current" |
| | | :limit="query.size" |
| | | @pagination=" |
| | | (p) => { |
| | | query.current = p.page; |
| | | query.size = p.limit; |
| | | loadTasks(); |
| | | } |
| | | " |
| | | /> |
| | | |
| | | <el-dialog v-model="createVisible" title="å起对账" width="760px"> |
| | | <el-form :model="createForm" label-width="100px"> |
| | | <el-form-item label="对账类å" |
| | | ><el-select v-model="createForm.reconType" |
| | | ><el-option |
| | | v-for="(label, key) in reconTypeText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="对账æé´" |
| | | ><el-date-picker |
| | | v-model="period" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | /></el-form-item> |
| | | <el-form-item label="èµéè´¦æ·" |
| | | ><el-select v-model="createForm.accountId" clearable |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="对账对象" |
| | | ><el-input |
| | | v-model="createForm.target" |
| | | placeholder="é¶è¡/ä¾åºå/客æ·åç§°" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template v-if="createForm.reconType !== '0'"> |
| | | <el-divider content-position="left">å¤é¨æµæ°´ï¼æå·¥å½å
¥ï¼</el-divider> |
| | | <div class="external-actions"> |
| | | <el-button size="small" @click="addExternal">æ°å¢å¤é¨æµæ°´</el-button> |
| | | </div> |
| | | <el-table :data="createForm.externalList" border max-height="260"> |
| | | <el-table-column label="æ¥æ" width="145" |
| | | ><template #default="{ row }" |
| | | ><el-date-picker |
| | | v-model="row.extDate" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 125px" /></template |
| | | ></el-table-column> |
| | | <el-table-column label="徿¥åä½" |
| | | ><template #default="{ row }" |
| | | ><el-input v-model="row.extCounterparty" /></template |
| | | ></el-table-column> |
| | | <el-table-column label="æ¹å" width="100" |
| | | ><template #default="{ row }" |
| | | ><el-select v-model="row.direction" |
| | | ><el-option label="æ¶å
¥" :value="1" /><el-option |
| | | label="æ¯åº" |
| | | :value="-1" /></el-select></template |
| | | ></el-table-column> |
| | | <el-table-column label="éé¢" width="140" |
| | | ><template #default="{ row }" |
| | | ><el-input-number |
| | | v-model="row.amount" |
| | | :min="0.01" |
| | | :precision="2" |
| | | style="width: 120px" /></template |
| | | ></el-table-column> |
| | | <el-table-column label="åè¯å·" |
| | | ><template #default="{ row }" |
| | | ><el-input v-model="row.voucherNo" /></template |
| | | ></el-table-column> |
| | | <el-table-column width="60" |
| | | ><template #default="{ $index }" |
| | | ><el-button |
| | | link |
| | | type="danger" |
| | | @click="createForm.externalList.splice($index, 1)" |
| | | >å </el-button |
| | | ></template |
| | | ></el-table-column |
| | | > |
| | | </el-table> |
| | | </template> |
| | | <template #footer |
| | | ><el-button @click="createVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="submitTask" |
| | | >å¼å§æ¯å¯¹</el-button |
| | | ></template |
| | | > |
| | | </el-dialog> |
| | | |
| | | <el-drawer v-model="diffVisible" title="å¯¹è´¦å·®å¼æç»" size="70%"> |
| | | <el-descriptions v-if="currentTask" :column="4" border class="summary"> |
| | | <el-descriptions-item label="ä»»å¡å·">{{ |
| | | currentTask.id |
| | | }}</el-descriptions-item |
| | | ><el-descriptions-item label="ç³»ç»éé¢">{{ |
| | | currentTask.systemAmount |
| | | }}</el-descriptions-item |
| | | ><el-descriptions-item label="对æ¹éé¢">{{ |
| | | currentTask.externalAmount |
| | | }}</el-descriptions-item |
| | | ><el-descriptions-item label="å·®é¢">{{ |
| | | currentTask.diffAmount |
| | | }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-descriptions |
| | | v-if="currentTask?.handleNote" |
| | | :column="1" |
| | | border |
| | | class="summary" |
| | | > |
| | | <el-descriptions-item label="å¤çæ¹æ¡">{{ |
| | | currentTask.handleNote |
| | | }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-table :data="diffs" border> |
| | | <el-table-column label="å·®å¼ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | diffTypeText[row.diffType] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="bizType" label="ä¸å¡ç±»å" /><el-table-column |
| | | prop="bizId" |
| | | label="ä¸å¡ID" |
| | | /><el-table-column prop="bizDate" label="æ¥æ" /><el-table-column |
| | | prop="counterparty" |
| | | label="徿¥åä½" |
| | | /><el-table-column |
| | | prop="systemAmount" |
| | | label="ç³»ç»éé¢" |
| | | /><el-table-column |
| | | prop="externalAmount" |
| | | label="对æ¹éé¢" |
| | | /><el-table-column prop="remark" label="说æ" min-width="190" /> |
| | | </el-table> |
| | | <el-divider content-position="left">å¤é¨åè¯</el-divider> |
| | | <el-table :data="externals" border |
| | | ><el-table-column prop="extDate" label="æ¥æ" /><el-table-column |
| | | prop="extCounterparty" |
| | | label="徿¥åä½" /><el-table-column |
| | | prop="amount" |
| | | label="éé¢" /><el-table-column prop="direction" label="æ¹å" |
| | | ><template #default="{ row }">{{ |
| | | row.direction === 1 ? "æ¶å
¥" : "æ¯åº" |
| | | }}</template></el-table-column |
| | | ><el-table-column prop="voucherNo" label="åè¯å·" |
| | | /></el-table> |
| | | </el-drawer> |
| | | |
| | | <el-dialog v-model="handleVisible" title="å¡«åå¤çæ¹æ¡" width="520px"> |
| | | <el-input |
| | | v-model="handleForm.handleNote" |
| | | type="textarea" |
| | | :rows="5" |
| | | placeholder="请è¾å
¥å·®å¼å¤çæ¹æ¡" |
| | | /> |
| | | <template #footer |
| | | ><el-button @click="handleVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="saveHandle" |
| | | >ä¿åå¹¶æ è®°å¤çä¸</el-button |
| | | ></template |
| | | > |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { saveAs } from "file-saver"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { getFinanceAccountList } from "@/api/financialManagement/fundsManagement"; |
| | | import { |
| | | getReconciliationTaskPage, |
| | | createReconciliationTask, |
| | | getReconciliationDiff, |
| | | getReconciliationExternal, |
| | | handleReconciliationTask, |
| | | archiveReconciliationTask, |
| | | exportReconciliationDiff, |
| | | } from "@/api/financialManagement/financialReconciliation"; |
| | | |
| | | const reconTypeText = { |
| | | 0: "å
é¨å¯¹è´¦", |
| | | 1: "é¶è¡å¯¹è´¦", |
| | | 2: "ä¾åºå对账", |
| | | 3: "客æ·å¯¹è´¦", |
| | | }, |
| | | statusText = { 0: "å¾
å¤ç", 1: "å¤çä¸", 2: "已彿¡£" }, |
| | | diffTypeText = { |
| | | 0: "ç³»ç»æå¤é¨æ ", |
| | | 1: "å¤é¨æç³»ç»æ ", |
| | | 2: "éé¢ä¸ç¬¦", |
| | | 3: "æ¥æä¸ç¬¦", |
| | | }; |
| | | const tasks = ref([]), |
| | | total = ref(0), |
| | | loading = ref(false), |
| | | loadError = ref(""), |
| | | accounts = ref([]), |
| | | createVisible = ref(false), |
| | | diffVisible = ref(false), |
| | | handleVisible = ref(false), |
| | | period = ref([]), |
| | | diffs = ref([]), |
| | | externals = ref([]), |
| | | currentTask = ref(null); |
| | | const query = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | reconType: "", |
| | | status: "", |
| | | target: "", |
| | | }), |
| | | createForm = reactive({ |
| | | reconType: "0", |
| | | accountId: null, |
| | | target: "", |
| | | externalList: [], |
| | | }), |
| | | handleForm = reactive({ id: null, handleNote: "" }); |
| | | const loadTasks = async () => { |
| | | loading.value = true; |
| | | loadError.value = ""; |
| | | try { |
| | | const r = await getReconciliationTaskPage(query); |
| | | tasks.value = r.data?.records || []; |
| | | total.value = r.data?.total || 0; |
| | | } catch (error) { |
| | | tasks.value = []; |
| | | total.value = 0; |
| | | loadError.value = |
| | | error?.message || |
| | | error?.msg || |
| | | "对账任å¡å 载失败ï¼è¯·æ£æ¥å端æå¡åæ°æ®åºèæ¬"; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | const openCreate = () => { |
| | | Object.assign(createForm, { |
| | | reconType: "0", |
| | | accountId: null, |
| | | target: "", |
| | | externalList: [], |
| | | }); |
| | | period.value = []; |
| | | createVisible.value = true; |
| | | }; |
| | | const addExternal = () => |
| | | createForm.externalList.push({ |
| | | extDate: "", |
| | | extCounterparty: "", |
| | | amount: null, |
| | | direction: 1, |
| | | voucherNo: "", |
| | | remark: "", |
| | | }); |
| | | const submitTask = async () => { |
| | | if (period.value.length !== 2) { |
| | | ElMessage.warning("è¯·éæ©å¯¹è´¦æé´"); |
| | | return; |
| | | } |
| | | if (createForm.reconType !== "0" && !createForm.target.trim()) { |
| | | ElMessage.warning("请è¾å
¥é¶è¡ãä¾åºåæå®¢æ·åç§°"); |
| | | return; |
| | | } |
| | | if ( |
| | | createForm.reconType !== "0" && |
| | | createForm.externalList.some( |
| | | (row) => !row.extDate || !row.amount || row.amount <= 0 |
| | | ) |
| | | ) { |
| | | ElMessage.warning("请填åå¤é¨åè¯çæ¥æåæ£æ°éé¢"); |
| | | return; |
| | | } |
| | | try { |
| | | await createReconciliationTask({ |
| | | ...createForm, |
| | | periodStart: period.value[0], |
| | | periodEnd: period.value[1], |
| | | }); |
| | | ElMessage.success("对账任å¡å·²çæ"); |
| | | createVisible.value = false; |
| | | await loadTasks(); |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || "对账任å¡çæå¤±è´¥"); |
| | | } |
| | | }; |
| | | const showDiff = async (row) => { |
| | | currentTask.value = row; |
| | | const [d, e] = await Promise.all([ |
| | | getReconciliationDiff(row.id), |
| | | getReconciliationExternal(row.id), |
| | | ]); |
| | | diffs.value = d.data || []; |
| | | externals.value = e.data || []; |
| | | diffVisible.value = true; |
| | | }; |
| | | const openHandle = (row) => { |
| | | handleForm.id = row.id; |
| | | handleForm.handleNote = row.handleNote || ""; |
| | | handleVisible.value = true; |
| | | }; |
| | | const saveHandle = async () => { |
| | | await handleReconciliationTask(handleForm.id, { |
| | | handleNote: handleForm.handleNote, |
| | | status: "1", |
| | | }); |
| | | ElMessage.success("å¤çæ¹æ¡å·²ä¿å"); |
| | | handleVisible.value = false; |
| | | loadTasks(); |
| | | }; |
| | | const archive = async (row) => { |
| | | if (row.result === "1" && !row.handleNote?.trim()) { |
| | | ElMessage.warning("请å
å¡«åå·®å¼å¤çæ¹æ¡"); |
| | | return; |
| | | } |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | "彿¡£å该任å¡å°è¿å
¥å·²å½æ¡£ç¶æï¼æ¯å¦ç»§ç»ï¼", |
| | | "彿¡£" |
| | | ); |
| | | await archiveReconciliationTask(row.id); |
| | | ElMessage.success("已彿¡£"); |
| | | await loadTasks(); |
| | | } catch (error) { |
| | | if (error !== "cancel" && error !== "close") { |
| | | ElMessage.error(error?.message || "彿¡£å¤±è´¥"); |
| | | } |
| | | } |
| | | }; |
| | | const exportDiff = async (row) => { |
| | | const blob = await exportReconciliationDiff(row.id); |
| | | saveAs(blob, `对账差å¼-${row.id}.xlsx`); |
| | | }; |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | loadTasks(); |
| | | getFinanceAccountList() |
| | | .then((r) => { |
| | | accounts.value = r.data || []; |
| | | }) |
| | | .catch((error) => { |
| | | accounts.value = []; |
| | | console.error("èµéè´¦æ·å 载失败ï¼", error); |
| | | }); |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .reconciliation-workspace { |
| | | padding: 4px; |
| | | } |
| | | .workspace-heading { |
| | | margin-bottom: 16px; |
| | | } |
| | | .workspace-heading h2 { |
| | | margin: 0 0 6px; |
| | | font-size: 20px; |
| | | } |
| | | .workspace-heading p { |
| | | margin: 0; |
| | | color: #606266; |
| | | } |
| | | .load-error { |
| | | margin-bottom: 12px; |
| | | } |
| | | .external-actions { |
| | | margin-bottom: 8px; |
| | | } |
| | | .summary { |
| | | margin-bottom: 16px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æç´¢åº --> |
| | | <div class="search_form"> |
| | | <el-form :inline="true" :model="searchForm"> |
| | | <el-form-item label="徿¥åä½ï¼"> |
| | | <el-input v-model="searchForm.counterparty" placeholder="请è¾å
¥å¾æ¥åä½" clearable style="width: 180px" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¯¹è´¦ç¶æï¼"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©" clearable style="width: 150px"> |
| | | <el-option label="æªå¯¹è´¦" value="pending" /> |
| | | <el-option label="对账ä¸" value="processing" /> |
| | | <el-option label="已宿" value="done" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <!-- <el-form-item label="æ¥æï¼"> |
| | | <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 |
| | | /> |
| | | </el-form-item> --> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- æä½åº --> |
| | | <div class="actions"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼ --> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | style="width: 100%" |
| | | height="calc(100vh - 280px)" |
| | | :row-key="row => row.id" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="accountDate" label="å¯¹è´¦æ¥æ" width="130" show-overflow-tooltip /> |
| | | <el-table-column prop="counterparty" label="徿¥åä½" width="180" show-overflow-tooltip /> |
| | | <el-table-column prop="amount" label="对账éé¢(å
)" width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="statusText" label="ç¶æ" width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="remark" label="夿³¨" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="150" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openDialog('edit', scope.row)">ç¼è¾</el-button> |
| | | </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 class="app-container financial-reconciliation-page"> |
| | | <el-alert |
| | | v-if="runtimeError" |
| | | title="è´¢å¡å¯¹è´¦é¡µé¢å 载失败" |
| | | type="error" |
| | | :description="runtimeError" |
| | | show-icon |
| | | :closable="false" |
| | | /> |
| | | |
| | | <!-- å¼¹çª --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="520px" @close="resetForm"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-form-item label="å¯¹è´¦æ¥æï¼" prop="accountDate"> |
| | | <el-date-picker v-model="form.accountDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="徿¥åä½ï¼" prop="counterparty"> |
| | | <el-input v-model="form.counterparty" placeholder="请è¾å
¥å¾æ¥åä½" /> |
| | | </el-form-item> |
| | | <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-form-item label="ç¶æï¼" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©" style="width: 100%"> |
| | | <el-option label="æªå¯¹è´¦" value="pending" /> |
| | | <el-option label="对账ä¸" value="processing" /> |
| | | <el-option label="已宿" value="done" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨ï¼"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <ReconciliationWorkspace v-else :key="workspaceKey" /> |
| | | <el-button v-if="runtimeError" type="primary" class="retry-button" @click="retry"> |
| | | éæ°å è½½ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import { |
| | | getReconciliationPage, |
| | | addReconciliation, |
| | | updateReconciliation, |
| | | deleteReconciliation |
| | | } from '@/api/financialManagement/financialReconciliation.js' |
| | | import { nextTick, onErrorCaptured, ref } from "vue"; |
| | | import ReconciliationWorkspace from "./ReconciliationWorkspace.vue"; |
| | | |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const selectedRows = ref([]) |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢') |
| | | const formRef = ref(null) |
| | | const runtimeError = ref(""); |
| | | const workspaceKey = ref(0); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100 |
| | | }) |
| | | const total = ref(0) |
| | | onErrorCaptured((error) => { |
| | | console.error("è´¢å¡å¯¹è´¦é¡µé¢è¿è¡å¼å¸¸ï¼", error); |
| | | runtimeError.value = error?.stack || error?.message || String(error); |
| | | return false; |
| | | }); |
| | | |
| | | const searchForm = reactive({ |
| | | counterparty: '', |
| | | status: '', |
| | | dateRange: [] |
| | | }) |
| | | |
| | | const form = reactive({ |
| | | id: null, |
| | | accountDate: '', |
| | | counterparty: '', |
| | | amount: null, |
| | | status: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const rules = { |
| | | accountDate: [{ required: true, message: 'è¯·éæ©å¯¹è´¦æ¥æ', trigger: 'change' }], |
| | | counterparty: [{ required: true, message: '请è¾å
¥å¾æ¥åä½', trigger: 'blur' }], |
| | | amount: [{ required: true, message: '请è¾å
¥å¯¹è´¦éé¢', trigger: 'blur' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const statusTextMap = { |
| | | pending: 'æªå¯¹è´¦', |
| | | processing: '对账ä¸', |
| | | done: '已宿' |
| | | } |
| | | |
| | | // è·åå表 |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | const params = { |
| | | ...searchForm, |
| | | current: page.current, |
| | | size: page.size |
| | | } |
| | | if (params.dateRange && params.dateRange.length === 2) { |
| | | params.beginDate = params.dateRange[0] |
| | | params.endDate = params.dateRange[1] |
| | | } |
| | | delete params.dateRange |
| | | |
| | | getReconciliationPage(params) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | tableData.value = (res.data?.records || []).map(item => ({ |
| | | ...item, |
| | | statusText: statusTextMap[item.status] || item.status |
| | | })) |
| | | total.value = res.data?.total || 0 |
| | | } |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false |
| | | }) |
| | | } |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | const resetQuery = () => { |
| | | searchForm.counterparty = '' |
| | | searchForm.status = '' |
| | | searchForm.dateRange = [] |
| | | handleQuery() |
| | | } |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page |
| | | page.size = obj.limit |
| | | getList() |
| | | } |
| | | |
| | | const handleSelectionChange = (rows) => { |
| | | selectedRows.value = rows |
| | | } |
| | | |
| | | const openDialog = (type, row) => { |
| | | dialogTitle.value = type === 'add' ? 'æ°å¢å¯¹è´¦è®°å½' : 'ç¼è¾å¯¹è´¦è®°å½' |
| | | if (type === 'edit' && row) { |
| | | Object.assign(form, row) |
| | | } else { |
| | | resetForm() |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | id: null, |
| | | date: '', |
| | | counterparty: '', |
| | | amount: null, |
| | | status: '', |
| | | remark: '' |
| | | }) |
| | | } |
| | | |
| | | const submitForm = () => { |
| | | formRef.value?.validate(valid => { |
| | | if (!valid) return |
| | | const payload = { ...form } |
| | | const request = payload.id ? updateReconciliation(payload) : addReconciliation(payload) |
| | | request |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('æäº¤æå') |
| | | dialogVisible.value = false |
| | | getList() |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg || 'æäº¤å¤±è´¥') |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | const handleDelete = () => { |
| | | if (!selectedRows.value.length) { |
| | | ElMessage.warning('è¯·éæ©è¦å é¤çè®°å½') |
| | | return |
| | | } |
| | | ElMessageBox.confirm('确认å é¤éä¸è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const ids = selectedRows.value.map(r => r.id) |
| | | deleteReconciliation(ids).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | getList() |
| | | selectedRows.value = [] |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg || 'å é¤å¤±è´¥') |
| | | }) |
| | | }).catch(() => {}) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | const retry = async () => { |
| | | runtimeError.value = ""; |
| | | await nextTick(); |
| | | workspaceKey.value += 1; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | padding: 20px; |
| | | |
| | | .search_form { |
| | | background: #fff; |
| | | padding: 16px; |
| | | border-radius: 6px; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .actions { |
| | | margin-bottom: 12px; |
| | | } |
| | | <style scoped> |
| | | .financial-reconciliation-page { |
| | | min-height: calc(100vh - 84px); |
| | | background: #fff; |
| | | } |
| | | .retry-button { |
| | | margin-top: 16px; |
| | | } |
| | | </style> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | defineOptions({ name: 'FinancialStatements' }) |
| | | import { ref, computed, onMounted, reactive } from 'vue'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import 'element-plus/dist/index.css'; |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import { reportForms,reportIncome,reportExpense } from "@/api/financialManagement/financialStatements"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // æ¥æèå´ |
| | | const dateRange = ref(null); |
| | | const currentYear = dayjs().year(); |
| | | const dateRange = ref([ |
| | | dayjs().startOf('year').format('YYYY-MM-DD'), |
| | | dayjs().endOf('year').format('YYYY-MM-DD') |
| | | ]); |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | |
| | | }) |
| | | |
| | | |
| | | const pageInfo = ref({ |
| | | }) |
| | | const emptyPageInfo = () => ({ |
| | | totalIncome: 0, |
| | | incomeNumber: 0, |
| | | totalExpense: 0, |
| | | expenseNumber: 0, |
| | | netRevenue: 0, |
| | | incomeType: [], |
| | | expenseType: [] |
| | | }); |
| | | const pageInfo = ref(emptyPageInfo()); |
| | | |
| | | const getData = async () => { |
| | | if (!dateRange.value || !dateRange.value.length) { |
| | |
| | | try { |
| | | const {code,data} = await reportForms({entryDateStart:dateRange.value[0], entryDateEnd:dateRange.value[1]}); |
| | | if(code === 200) { |
| | | pageInfo.value = data |
| | | pieData0.value = data.incomeType.map(item=>({ |
| | | pageInfo.value = { ...emptyPageInfo(), ...(data || {}) } |
| | | pieData0.value = (data?.incomeType || []).map(item=>({ |
| | | name:item.typeName, |
| | | value:item.account, |
| | | percent:`${item.proportion*100}%`, |
| | | amount:`Â¥${item.account}` |
| | | })) |
| | | pieData1.value = data.expenseType.map(item=>({ |
| | | pieData1.value = (data?.expenseType || []).map(item=>({ |
| | | name:item.typeName, |
| | | value:item.account, |
| | | percent:`${item.proportion*100}%`, |
| | |
| | | |
| | | } |
| | | } catch (error) { |
| | | pageInfo.value = emptyPageInfo(); |
| | | pieData0.value = []; |
| | | pieData1.value = []; |
| | | console.error('è·åè´¢å¡ææ æ°æ®å¤±è´¥ï¼', error); |
| | | ElMessage.error(error?.msg || 'è´¢å¡æ±æ»æ°æ®å 载失败'); |
| | | } |
| | | try{ |
| | | const {code,data} = await reportIncome(); |
| | |
| | | lineSeries0.value = data.map(item=>({ |
| | | name:item.typeName, |
| | | type: 'line', |
| | | data:item.account.map(item=>Number(item)) |
| | | data:(item.account || []).map(value => Number(value || 0)) |
| | | })) |
| | | |
| | | } |
| | |
| | | lineSeries1.value = data.map(item=>({ |
| | | name:item.typeName, |
| | | type: 'line', |
| | | data:item.account.map(item=>Number(item)) |
| | | data:(item.account || []).map(value => Number(value || 0)) |
| | | })) |
| | | |
| | | } |
| | |
| | | |
| | | // åå§å |
| | | onMounted(() => { |
| | | // ä¸è®¾ç½®é»è®¤æ¥æï¼ç±ç¨æ·æå¨éæ© |
| | | getData(); |
| | | }); |
| | | |
| | | // å¤çæ¥æèå´åå |
| | |
| | | |
| | | // éç½®æ¥æèå´ |
| | | const resetDateRange = () => { |
| | | dateRange.value = null; |
| | | dateRange.value = [ |
| | | `${currentYear}-01-01`, |
| | | `${currentYear}-12-31` |
| | | ]; |
| | | getData(); |
| | | }; |
| | | |
| | | </script> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="finance-workspace"> |
| | | <el-tabs v-model="activeTab" @tab-change="loadTab"> |
| | | <el-tab-pane label="èµéè´¦æ·" name="accounts"> |
| | | <div class="toolbar"> |
| | | <el-button type="primary" @click="openAccount()">æ°å¢è´¦æ·</el-button> |
| | | </div> |
| | | <div class="balance-grid"> |
| | | <el-card v-for="item in accounts" :key="item.id" shadow="hover"> |
| | | <div class="balance-title"> |
| | | {{ item.accountName }} |
| | | <el-tag size="small">{{ |
| | | accountTypeText[item.accountType] |
| | | }}</el-tag> |
| | | </div> |
| | | <div class="balance-amount">Â¥ {{ money(item.currentBalance) }}</div> |
| | | <div class="muted">å®å
¨çº¿ Â¥ {{ money(item.safetyLine) }}</div> |
| | | </el-card> |
| | | </div> |
| | | <el-table :data="accounts" border v-loading="loading"> |
| | | <el-table-column prop="accountName" label="è´¦æ·åç§°" /> |
| | | <el-table-column label="ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | accountTypeText[row.accountType] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="bankName" label="弿·è¡" /> |
| | | <el-table-column prop="bankNo" label="è´¦å·" /> |
| | | <el-table-column prop="openingBalance" label="æåä½é¢" /> |
| | | <el-table-column prop="currentBalance" label="å½åä½é¢" /> |
| | | <el-table-column label="ç¶æ" |
| | | ><template #default="{ row }">{{ |
| | | row.status === "0" ? "å¯ç¨" : "åç¨" |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column label="æä½" width="140" |
| | | ><template #default="{ row }" |
| | | ><el-button link type="primary" @click="openAccount(row)" |
| | | >ç¼è¾</el-button |
| | | ><el-button link type="danger" @click="removeAccount(row)" |
| | | >å é¤</el-button |
| | | ></template |
| | | ></el-table-column |
| | | > |
| | | </el-table> |
| | | <el-divider content-position="left">è¿30æ¥èµéæµè¶å¿</el-divider> |
| | | <div ref="trendRef" class="chart"></div> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="èµéæµæ°´" name="flows"> |
| | | <el-form inline :model="flowQuery"> |
| | | <el-form-item label="è´¦æ·" |
| | | ><el-select |
| | | v-model="flowQuery.accountId" |
| | | clearable |
| | | style="width: 180px" |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="ç±»å" |
| | | ><el-select |
| | | v-model="flowQuery.flowType" |
| | | clearable |
| | | style="width: 130px" |
| | | ><el-option |
| | | v-for="(label, key) in flowTypeText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item |
| | | ><el-button type="primary" @click="loadFlows" |
| | | >æ¥è¯¢</el-button |
| | | ></el-form-item |
| | | > |
| | | </el-form> |
| | | <div class="toolbar"> |
| | | <el-button type="primary" @click="openFlow()">ç»è®°æµæ°´</el-button |
| | | ><el-button @click="openTransfer">è´¦æ·è½¬è´¦</el-button> |
| | | </div> |
| | | <el-table :data="flows" border v-loading="loading"> |
| | | <el-table-column prop="accountDate" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="accountName" label="è´¦æ·" /> |
| | | <el-table-column label="ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | flowTypeText[row.flowType] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="amount" label="éé¢" /> |
| | | <el-table-column prop="counterparty" label="徿¥åä½" /> |
| | | <el-table-column label="å
³è" |
| | | ><template #default="{ row }">{{ |
| | | row.linkType ? `${row.linkType} #${row.linkId}` : "-" |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="remark" label="夿³¨" /> |
| | | <el-table-column label="æä½" width="140" |
| | | ><template #default="{ row }" |
| | | ><template v-if="row.flowType !== '2'" |
| | | ><el-button link type="primary" @click="openFlow(row)" |
| | | >ç¼è¾</el-button |
| | | ><el-button link type="danger" @click="removeFlow(row)" |
| | | >å é¤</el-button |
| | | ></template |
| | | ><span v-else class="muted">æå¯¹æµæ°´</span></template |
| | | ></el-table-column |
| | | > |
| | | </el-table> |
| | | <pagination |
| | | v-show="flowTotal > 0" |
| | | :total="flowTotal" |
| | | :page="flowQuery.current" |
| | | :limit="flowQuery.size" |
| | | @pagination=" |
| | | (p) => { |
| | | flowQuery.current = p.page; |
| | | flowQuery.size = p.limit; |
| | | loadFlows(); |
| | | } |
| | | " |
| | | /> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="æ¶æ¯è®¡å" name="plans"> |
| | | <el-alert |
| | | v-if="forecast" |
| | | :type=" |
| | | forecast.signal === 'gap' |
| | | ? 'error' |
| | | : forecast.signal === 'surplus' |
| | | ? 'success' |
| | | : 'info' |
| | | " |
| | | :closable="false" |
| | | show-icon |
| | | class="forecast" |
| | | > |
| | | <template #title |
| | | >30天颿µä½é¢ï¼Â¥ {{ money(forecast.finalBalance) }}ï¼{{ |
| | | forecast.signal === "gap" |
| | | ? "åå¨èµé缺å£" |
| | | : forecast.signal === "surplus" |
| | | ? "é«äºå®å
¨çº¿ï¼åå¨çä½" |
| | | : "å¤äºå®å
¨çº¿" |
| | | }}</template |
| | | > |
| | | </el-alert> |
| | | <div class="toolbar"> |
| | | <el-button type="primary" @click="openPlan()">æ°å¢è®¡å</el-button |
| | | ><el-button @click="loadForecast">å·æ°é¢æµ</el-button> |
| | | </div> |
| | | <el-table :data="plans" border v-loading="loading"> |
| | | <el-table-column prop="planDate" label="è®¡åæ¥æ" /> |
| | | <el-table-column label="ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | row.planType === "0" ? "è®¡åæ¶å
¥" : "è®¡åæ¯åº" |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="expectedAmount" label="é¢è®¡éé¢" /> |
| | | <el-table-column prop="category" label="åç±»" /> |
| | | <el-table-column prop="counterparty" label="徿¥åä½" /> |
| | | <el-table-column label="ç¶æ" |
| | | ><template #default="{ row }">{{ |
| | | planStatusText[row.status] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column label="æä½" width="140" |
| | | ><template #default="{ row }" |
| | | ><el-button link type="primary" @click="openPlan(row)" |
| | | >ç¼è¾</el-button |
| | | ><el-button link type="danger" @click="removePlan(row)" |
| | | >å é¤</el-button |
| | | ></template |
| | | ></el-table-column |
| | | > |
| | | </el-table> |
| | | <pagination |
| | | v-show="planTotal > 0" |
| | | :total="planTotal" |
| | | :page="planQuery.current" |
| | | :limit="planQuery.size" |
| | | @pagination=" |
| | | (p) => { |
| | | planQuery.current = p.page; |
| | | planQuery.size = p.limit; |
| | | loadPlans(); |
| | | } |
| | | " |
| | | /> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <el-dialog |
| | | v-model="accountVisible" |
| | | :title="accountForm.id ? 'ç¼è¾è´¦æ·' : 'æ°å¢è´¦æ·'" |
| | | width="560px" |
| | | > |
| | | <el-form :model="accountForm" label-width="100px"> |
| | | <el-form-item label="è´¦æ·åç§°" |
| | | ><el-input v-model="accountForm.accountName" |
| | | /></el-form-item> |
| | | <el-form-item label="è´¦æ·ç±»å" |
| | | ><el-radio-group v-model="accountForm.accountType" |
| | | ><el-radio value="0">é¶è¡è´¦æ·</el-radio |
| | | ><el-radio value="1">ç°éè´¦æ·</el-radio></el-radio-group |
| | | ></el-form-item |
| | | > |
| | | <el-form-item label="弿·è¡" |
| | | ><el-input v-model="accountForm.bankName" |
| | | /></el-form-item> |
| | | <el-form-item label="é¶è¡è´¦å·" |
| | | ><el-input v-model="accountForm.bankNo" |
| | | /></el-form-item> |
| | | <el-form-item label="æåä½é¢" |
| | | ><el-input-number |
| | | v-model="accountForm.openingBalance" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | /></el-form-item> |
| | | <el-form-item label="å®å
¨çº¿" |
| | | ><el-input-number |
| | | v-model="accountForm.safetyLine" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | /></el-form-item> |
| | | <el-form-item label="ç¶æ" |
| | | ><el-switch |
| | | v-model="accountForm.status" |
| | | active-value="0" |
| | | inactive-value="1" |
| | | active-text="å¯ç¨" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template #footer |
| | | ><el-button @click="accountVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="saveAccount" |
| | | >ä¿å</el-button |
| | | ></template |
| | | > |
| | | </el-dialog> |
| | | |
| | | <el-dialog v-model="flowVisible" title="ç»è®°èµéæµæ°´" width="560px"> |
| | | <el-form :model="flowForm" label-width="100px"> |
| | | <el-form-item label="æ¥æ" |
| | | ><el-date-picker |
| | | v-model="flowForm.accountDate" |
| | | value-format="YYYY-MM-DD" |
| | | /></el-form-item> |
| | | <el-form-item label="è´¦æ·" |
| | | ><el-select v-model="flowForm.accountId" |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="æµæ°´ç±»å" |
| | | ><el-select v-model="flowForm.flowType" |
| | | ><el-option |
| | | v-for="(label, key) in nonTransferTypes" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="éé¢" |
| | | ><el-input-number |
| | | v-model="flowForm.amount" |
| | | :min="0.01" |
| | | :precision="2" |
| | | /></el-form-item> |
| | | <el-form-item label="徿¥åä½" |
| | | ><el-input v-model="flowForm.counterparty" |
| | | /></el-form-item> |
| | | <el-form-item label="å
³èç±»å" |
| | | ><el-select v-model="flowForm.linkType" clearable |
| | | ><el-option label="æ¶å
¥" value="Income" /><el-option |
| | | label="æ¯åº" |
| | | value="Expense" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="å
³èID" |
| | | ><el-input-number v-model="flowForm.linkId" :min="1" |
| | | /></el-form-item> |
| | | <el-form-item label="夿³¨" |
| | | ><el-input v-model="flowForm.remark" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template #footer |
| | | ><el-button @click="flowVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="saveFlow">ä¿å</el-button></template |
| | | > |
| | | </el-dialog> |
| | | |
| | | <el-dialog v-model="transferVisible" title="è´¦æ·è½¬è´¦" width="520px"> |
| | | <el-form :model="transferForm" label-width="100px"> |
| | | <el-form-item label="转åºè´¦æ·" |
| | | ><el-select v-model="transferForm.fromAccountId" |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="转å
¥è´¦æ·" |
| | | ><el-select v-model="transferForm.toAccountId" |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="éé¢" |
| | | ><el-input-number |
| | | v-model="transferForm.amount" |
| | | :min="0.01" |
| | | :precision="2" |
| | | /></el-form-item> |
| | | <el-form-item label="æ¥æ" |
| | | ><el-date-picker |
| | | v-model="transferForm.transferDate" |
| | | value-format="YYYY-MM-DD" |
| | | /></el-form-item> |
| | | <el-form-item label="夿³¨" |
| | | ><el-input v-model="transferForm.remark" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template #footer |
| | | ><el-button @click="transferVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="saveTransfer" |
| | | >转账</el-button |
| | | ></template |
| | | > |
| | | </el-dialog> |
| | | |
| | | <el-dialog |
| | | v-model="planVisible" |
| | | :title="planForm.id ? 'ç¼è¾è®¡å' : 'æ°å¢è®¡å'" |
| | | width="560px" |
| | | > |
| | | <el-form :model="planForm" label-width="100px"> |
| | | <el-form-item label="计åç±»å" |
| | | ><el-radio-group v-model="planForm.planType" |
| | | ><el-radio value="0">è®¡åæ¶å
¥</el-radio |
| | | ><el-radio value="1">è®¡åæ¯åº</el-radio></el-radio-group |
| | | ></el-form-item |
| | | > |
| | | <el-form-item label="è®¡åæ¥æ" |
| | | ><el-date-picker |
| | | v-model="planForm.planDate" |
| | | value-format="YYYY-MM-DD" |
| | | /></el-form-item> |
| | | <el-form-item label="é¢è®¡éé¢" |
| | | ><el-input-number |
| | | v-model="planForm.expectedAmount" |
| | | :min="0.01" |
| | | :precision="2" |
| | | /></el-form-item> |
| | | <el-form-item label="åç±»" |
| | | ><el-input v-model="planForm.category" |
| | | /></el-form-item> |
| | | <el-form-item label="徿¥åä½" |
| | | ><el-input v-model="planForm.counterparty" |
| | | /></el-form-item> |
| | | <el-form-item label="ç¶æ" |
| | | ><el-select v-model="planForm.status" |
| | | ><el-option |
| | | v-for="(label, key) in planStatusText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="夿³¨" |
| | | ><el-input v-model="planForm.remark" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template #footer |
| | | ><el-button @click="planVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="savePlan">ä¿å</el-button></template |
| | | > |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from "vue"; |
| | | import * as echarts from "echarts"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { |
| | | getFundsPage, |
| | | addFunds, |
| | | updateFunds, |
| | | deleteFunds, |
| | | transferFunds, |
| | | getFinanceAccountPage, |
| | | addFinanceAccount, |
| | | updateFinanceAccount, |
| | | deleteFinanceAccount, |
| | | getFundsTrend, |
| | | getCashflowPlanPage, |
| | | addCashflowPlan, |
| | | updateCashflowPlan, |
| | | deleteCashflowPlan, |
| | | getCashflowForecast, |
| | | } from "@/api/financialManagement/fundsManagement"; |
| | | |
| | | const activeTab = ref("accounts"), |
| | | loading = ref(false), |
| | | accounts = ref([]), |
| | | flows = ref([]), |
| | | plans = ref([]), |
| | | forecast = ref(null); |
| | | const flowTotal = ref(0), |
| | | planTotal = ref(0), |
| | | trendRef = ref(), |
| | | accountVisible = ref(false), |
| | | flowVisible = ref(false), |
| | | transferVisible = ref(false), |
| | | planVisible = ref(false); |
| | | const accountTypeText = { 0: "é¶è¡è´¦æ·", 1: "ç°éè´¦æ·" }, |
| | | flowTypeText = { 0: "æ¶å
¥", 1: "æ¯åº", 2: "转账", 3: "åå
¥", 4: "ååº" }, |
| | | nonTransferTypes = { 0: "æ¶å
¥", 1: "æ¯åº", 3: "åå
¥", 4: "ååº" }, |
| | | planStatusText = { 0: "å¾
åç", 1: "å·²åç", 2: "已忶" }; |
| | | const flowQuery = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | accountId: null, |
| | | flowType: "", |
| | | }), |
| | | planQuery = reactive({ current: 1, size: 10 }); |
| | | const accountForm = reactive({}), |
| | | flowForm = reactive({}), |
| | | transferForm = reactive({}), |
| | | planForm = reactive({}); |
| | | const money = (v) => |
| | | Number(v || 0).toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }); |
| | | const loadAccounts = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const r = await getFinanceAccountPage({ current: 1, size: 100 }); |
| | | accounts.value = r.data?.records || []; |
| | | await nextTick(); |
| | | loadTrend(); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | const loadTrend = async () => { |
| | | if (!trendRef.value) return; |
| | | const end = new Date(), |
| | | start = new Date(Date.now() - 29 * 86400000); |
| | | const fmt = (d) => d.toISOString().slice(0, 10); |
| | | const r = await getFundsTrend({ start: fmt(start), end: fmt(end) }); |
| | | const rows = r.data || []; |
| | | echarts.init(trendRef.value).setOption({ |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["æ¶å
¥", "æ¯åº", "åé¢"] }, |
| | | xAxis: { type: "category", data: rows.map((x) => x.date) }, |
| | | yAxis: { type: "value" }, |
| | | series: [ |
| | | { name: "æ¶å
¥", type: "line", data: rows.map((x) => x.income) }, |
| | | { name: "æ¯åº", type: "line", data: rows.map((x) => x.expense) }, |
| | | { name: "åé¢", type: "line", data: rows.map((x) => x.net) }, |
| | | ], |
| | | }); |
| | | }; |
| | | const loadFlows = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const r = await getFundsPage(flowQuery); |
| | | flows.value = r.data?.records || []; |
| | | flowTotal.value = r.data?.total || 0; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | const loadPlans = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const r = await getCashflowPlanPage(planQuery); |
| | | plans.value = r.data?.records || []; |
| | | planTotal.value = r.data?.total || 0; |
| | | await loadForecast(); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | const loadForecast = async () => { |
| | | const r = await getCashflowForecast({ days: 30 }); |
| | | forecast.value = r.data; |
| | | }; |
| | | const loadTab = (name) => |
| | | name === "accounts" |
| | | ? loadAccounts() |
| | | : name === "flows" |
| | | ? (accounts.value.length || loadAccounts(), loadFlows()) |
| | | : loadPlans(); |
| | | const openAccount = (row) => { |
| | | Object.assign( |
| | | accountForm, |
| | | { |
| | | id: null, |
| | | accountName: "", |
| | | accountType: "0", |
| | | bankName: "", |
| | | bankNo: "", |
| | | openingBalance: 0, |
| | | safetyLine: 0, |
| | | status: "0", |
| | | }, |
| | | row || {} |
| | | ); |
| | | accountVisible.value = true; |
| | | }; |
| | | const saveAccount = async () => { |
| | | await (accountForm.id |
| | | ? updateFinanceAccount({ ...accountForm }) |
| | | : addFinanceAccount({ ...accountForm })); |
| | | ElMessage.success("ä¿åæå"); |
| | | accountVisible.value = false; |
| | | loadAccounts(); |
| | | }; |
| | | const removeAccount = (row) => |
| | | ElMessageBox.confirm(`确认å é¤è´¦æ·â${row.accountName}âåï¼`, "æç¤º").then( |
| | | async () => { |
| | | await deleteFinanceAccount([row.id]); |
| | | ElMessage.success("å 餿å"); |
| | | loadAccounts(); |
| | | } |
| | | ); |
| | | const openFlow = (row) => { |
| | | Object.assign( |
| | | flowForm, |
| | | { |
| | | id: null, |
| | | accountDate: new Date().toISOString().slice(0, 10), |
| | | accountId: null, |
| | | flowType: "0", |
| | | amount: null, |
| | | counterparty: "", |
| | | linkType: "", |
| | | linkId: null, |
| | | remark: "", |
| | | }, |
| | | row || {} |
| | | ); |
| | | flowVisible.value = true; |
| | | }; |
| | | const saveFlow = async () => { |
| | | await (flowForm.id |
| | | ? updateFunds({ ...flowForm }) |
| | | : addFunds({ ...flowForm })); |
| | | ElMessage.success("ä¿åæå"); |
| | | flowVisible.value = false; |
| | | loadFlows(); |
| | | }; |
| | | const removeFlow = (row) => |
| | | ElMessageBox.confirm("确认å é¤è¯¥èµéæµæ°´åï¼", "æç¤º").then(async () => { |
| | | await deleteFunds([row.id]); |
| | | ElMessage.success("å 餿å"); |
| | | loadFlows(); |
| | | }); |
| | | const openTransfer = () => { |
| | | Object.assign(transferForm, { |
| | | fromAccountId: null, |
| | | toAccountId: null, |
| | | amount: null, |
| | | transferDate: new Date().toISOString().slice(0, 10), |
| | | remark: "", |
| | | }); |
| | | transferVisible.value = true; |
| | | }; |
| | | const saveTransfer = async () => { |
| | | await transferFunds({ ...transferForm }); |
| | | ElMessage.success("转账æå"); |
| | | transferVisible.value = false; |
| | | loadFlows(); |
| | | }; |
| | | const openPlan = (row) => { |
| | | Object.assign( |
| | | planForm, |
| | | { |
| | | id: null, |
| | | planType: "0", |
| | | planDate: "", |
| | | expectedAmount: null, |
| | | category: "", |
| | | counterparty: "", |
| | | status: "0", |
| | | remark: "", |
| | | }, |
| | | row || {} |
| | | ); |
| | | planVisible.value = true; |
| | | }; |
| | | const savePlan = async () => { |
| | | await (planForm.id |
| | | ? updateCashflowPlan({ ...planForm }) |
| | | : addCashflowPlan({ ...planForm })); |
| | | ElMessage.success("ä¿åæå"); |
| | | planVisible.value = false; |
| | | loadPlans(); |
| | | }; |
| | | const removePlan = (row) => |
| | | ElMessageBox.confirm("确认å é¤è¯¥æ¶æ¯è®¡ååï¼", "æç¤º").then(async () => { |
| | | await deleteCashflowPlan([row.id]); |
| | | ElMessage.success("å 餿å"); |
| | | loadPlans(); |
| | | }); |
| | | onMounted(loadAccounts); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .finance-workspace { |
| | | padding: 4px; |
| | | } |
| | | .toolbar { |
| | | margin: 12px 0; |
| | | } |
| | | .balance-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); |
| | | gap: 12px; |
| | | margin-bottom: 16px; |
| | | } |
| | | .balance-title { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | .balance-amount { |
| | | font-size: 25px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin: 14px 0; |
| | | } |
| | | .muted { |
| | | color: #909399; |
| | | font-size: 13px; |
| | | } |
| | | .chart { |
| | | height: 300px; |
| | | } |
| | | .forecast { |
| | | margin-bottom: 12px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="funds-management-page"> |
| | | <FinanceWorkspace /> |
| | | <div v-if="false" class="app-container"> |
| | | <!-- æç´¢åº --> |
| | | <div class="search_form"> |
| | | <el-form :inline="true" :model="searchForm"> |
| | |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import FinanceWorkspace from './FinanceWorkspace.vue' |
| | | defineOptions({ name: 'FundsManagement' }) |
| | | import { |
| | | getFundsPage, |
| | | addFunds, |
| | |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | // æ°çèµéå·¥ä½åºèªè¡å è½½ä¸ä¸ªä¸å¡ååºçæ°æ®ã |
| | | }) |
| | | </script> |
| | | |
| | |
| | | import {listPage, deleteLedger, update} from "@/api/lavorissce/ledger.js"; |
| | | import {ElMessageBox, ElMessage} from "element-plus"; |
| | | const { proxy } = getCurrentInstance(); |
| | | import { getCurrentMonth } from "@/utils/util" |
| | | |
| | | const page = ref({ |
| | | current: 1, |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="out-tabs"> |
| | | <el-tab-pane label="åæ" name="issue" /> |
| | | <el-tab-pane label="éå®åºåº" name="sales" /> |
| | | <el-tab-pane label="éè´§åºåº" name="purchaseReturn" /> |
| | | </el-tabs> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title ml10">产å大类ï¼</span> |
| | |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | @keyup.enter="handleQuery" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | |
| | | 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)" |
| | | height="calc(100vh - 21em)" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="æ¹æ¬¡å·" prop="code" width="130" show-overflow-tooltip /> |
| | | <el-table-column label="æ¹æ¬¡å·" width="150" show-overflow-tooltip> |
| | | <template #default="scope">{{ scope.row.code || scope.row.inboundBatches }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="产å大类" prop="productCategory" show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" width="70" show-overflow-tooltip /> |
| | | <el-table-column label="ç©åç±»å" prop="itemType" show-overflow-tooltip /> |
| | | <el-table-column label="å©ä½åºå" prop="inboundNum0" width="90" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="100" align="center"> |
| | | <el-table-column v-if="activeTab === 'issue'" label="ç©åç±»å" prop="itemType" show-overflow-tooltip /> |
| | | <el-table-column v-if="activeTab === 'sales'" label="客æ·åç§°" prop="customerName" show-overflow-tooltip /> |
| | | <el-table-column v-if="activeTab === 'purchaseReturn'" label="ä¾åºå" prop="supplierName" show-overflow-tooltip /> |
| | | <el-table-column :label="cfg.availLabel" prop="inboundNum0" width="110" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="110" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openForm(scope.row);">åæ</el-button> |
| | | <el-button link type="primary" size="small" @click="openForm(scope.row)">{{ cfg.outLabel }}</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | <el-dialog v-model="dialogFormVisible" :title="getDialogTitle()" width="40%" @close="closeDia" draggable> |
| | | <el-dialog v-model="dialogFormVisible" :title="cfg.dialogTitle" width="40%" @close="closeDia" draggable> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <div>{{getAvailableQuantityText()}}:{{currentRowNum}}</div> |
| | | <el-form-item :label="getQuantityLabel()" prop="inboundQuantity"> |
| | | <div v-if="activeTab === 'purchaseReturn'" style="margin-bottom: 12px"> |
| | | ä¾åºåï¼{{ currentRow.supplierName || '-' }} |
| | | </div> |
| | | <div>{{ cfg.availLabel }}ï¼{{ currentRowNum }}</div> |
| | | <el-form-item :label="cfg.qtyLabel" prop="inboundQuantity"> |
| | | <el-input-number :step="0.01" :min="0" :max="currentRowNum" style="width: 100%" v-model="form.inboundQuantity" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | <el-form-item :label="getDateLabel()" prop="inboundTime"> |
| | | <el-form-item :label="cfg.dateLabel" prop="inboundTime"> |
| | | <el-date-picker style="width: 100%" v-model="form.inboundTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD" |
| | | type="date" placeholder="è¯·éæ©" clearable /> |
| | | </el-form-item> |
| | | <el-form-item :label="getPersonLabel()" prop="nickName"> |
| | | <el-form-item :label="cfg.personLabel" prop="nickName"> |
| | | <el-select v-model="form.nickName" |
| | | filterable |
| | | default-first-option |
| | |
| | | |
| | | <script setup> |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue' |
| | | import { ref, reactive, toRefs, onMounted, computed, getCurrentInstance } from 'vue' |
| | | import { ElMessageBox } from "element-plus"; |
| | | import useUserStore from '@/store/modules/user' |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { |
| | | getInPageByCustom |
| | | getInPageByCustom, |
| | | getStockInPage, |
| | | getStockInPageByProduction, |
| | | } from "@/api/inventoryManagement/stockIn.js"; |
| | | import { |
| | | delStockManage, |
| | | stockOut, |
| | | stockOut, |
| | | } from "@/api/inventoryManagement/stockManage.js"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | |
| | | const userStore = useUserStore() |
| | | const { proxy } = getCurrentInstance() |
| | | const tableData = ref([]) |
| | | const selectedRows = ref([]) |
| | | const userList = ref([]) |
| | | const tableLoading = ref(false) |
| | | const activeTab = ref('issue') |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | const total = ref(0) |
| | | const fileList = ref([]) |
| | | |
| | | // ä¸ç§åºåºç±»åé
ç½®ï¼åæ(type=3 èªå®ä¹åºåº)ãéå®åºåº(type=2)ãéè´§åºåº(type=5 éè´éè´§åºåº) |
| | | const tabConfig = { |
| | | issue: { |
| | | type: 3, |
| | | outLabel: 'åæ', |
| | | dialogTitle: 'æ°å¢åæ', |
| | | availLabel: 'å¯åææ°é', |
| | | qtyLabel: 'åææ°éï¼', |
| | | dateLabel: 'åææ¥æï¼', |
| | | personLabel: 'åæäººï¼', |
| | | exportUrl: '/stockmanagement/exportTwo', |
| | | exportName: 'åæå°è´¦.xlsx', |
| | | }, |
| | | sales: { |
| | | type: 2, |
| | | outLabel: 'éå®åºåº', |
| | | dialogTitle: 'æ°å¢éå®åºåº', |
| | | availLabel: 'å¯åºåºæ°é', |
| | | qtyLabel: 'åºåºæ°éï¼', |
| | | dateLabel: 'åºåºæ¥æï¼', |
| | | personLabel: 'ç»æäººï¼', |
| | | exportUrl: '/stockmanagement/exportOne', |
| | | exportName: 'éå®åºåºå°è´¦.xlsx', |
| | | }, |
| | | purchaseReturn: { |
| | | type: 5, |
| | | outLabel: 'éè´§åºåº', |
| | | dialogTitle: 'æ°å¢éè´§åºåº', |
| | | availLabel: 'å¯éè´§æ°é', |
| | | qtyLabel: 'éè´§æ°éï¼', |
| | | dateLabel: 'éè´§æ¥æï¼', |
| | | personLabel: 'ç»æäººï¼', |
| | | exportUrl: '/stockmanagement/exportPurchaseReturn', |
| | | exportName: 'éè´§åºåºå°è´¦.xlsx', |
| | | }, |
| | | } |
| | | const cfg = computed(() => tabConfig[activeTab.value] || tabConfig.issue) |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const dialogFormVisible = ref(false) |
| | | const currentRow = ref({}) |
| | | const currentRowNum = ref(0) |
| | | const data = reactive({ |
| | | searchForm: { |
| | | inboundQuantity:'', |
| | | inboundTime:'', |
| | | nickName: '', |
| | | userId: '', |
| | | productCategory:'', |
| | | // timeStr: getCurrentDate(), |
| | | productCategory: '', |
| | | }, |
| | | form: { |
| | | productrecordId: '', |
| | |
| | | }) |
| | | const { searchForm, form, rules } = toRefs(data) |
| | | |
| | | // æ¥è¯¢å表 |
| | | const handleTabChange = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | |
| | | page.size = obj.limit; |
| | | getList() |
| | | } |
| | | // æ tab éæ©"å¯åºåºåºå"æ¥æºï¼åæ=èªå®ä¹å
¥åºï¼éå®åºåº=ç产å
¥åºï¼éè´§åºåº=éè´å
¥åº |
| | | const fetchSource = (params) => { |
| | | if (activeTab.value === 'sales') return getStockInPageByProduction(params) |
| | | if (activeTab.value === 'purchaseReturn') return getStockInPage(params) |
| | | return getInPageByCustom(params) |
| | | } |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | const params = { ...page } |
| | | params.productCategory = searchForm.value.productCategory |
| | | params.flag = true |
| | | const apiCall = getInPageByCustom(params) |
| | | apiCall.then(res => { |
| | | fetchSource(params).then(res => { |
| | | tableLoading.value = false |
| | | tableData.value = res.data.records |
| | | tableData.value = res.data.records || [] |
| | | total.value = res.data.total |
| | | }).catch(() => { |
| | | tableLoading.value = false |
| | | }) |
| | | } |
| | | |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // æ¾å°èç¹ï¼è¿å该èç¹ |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundNode = findNodeById(nodes[i].children, productId); |
| | | if (foundNode) { |
| | | return foundNode.label; // å¨åèç¹ä¸æ¾å°ï¼è¿å该èç¹ |
| | | } |
| | | } |
| | | } |
| | | return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | // è¿æ»¤æåæ°æ® |
| | | selectedRows.value = selection.filter(item => item.id); |
| | | console.log('selection', selectedRows.value) |
| | | } |
| | | const expandedRowKeys = ref([]) |
| | | |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = (param) => { |
| | | return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']); |
| | | }; |
| | | const currentRowId = ref(null) // æ°å¢ï¼åå¨å½åæä½çè¡ID |
| | | |
| | | const currentRowNum = ref(0) |
| | | const salesLedgerProductId = ref(null); |
| | | return proxy.summarizeTable(param, ['taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']); |
| | | } |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = async (row) => { |
| | | dialogFormVisible.value = true |
| | | currentRowId.value = row.id |
| | | currentRowNum.value = row.inboundNum0 |
| | | salesLedgerProductId.value = row.salesLedgerProductId |
| | | currentRow.value = row || {} |
| | | currentRowNum.value = row ? row.inboundNum0 : 0 |
| | | form.value = {} |
| | | |
| | | // å è½½ç¨æ·å表 |
| | | try { |
| | | const userLists = await userListNoPageByTenantId() |
| | | userList.value = userLists.data |
| | | |
| | | // ææåºåºé»è®¤åæäºº |
| | | const defaultPersonName = 'å´çæ¢
' |
| | | const defaultPerson = userList.value.find(user => user.nickName === defaultPersonName) |
| | | const defaultUserId = defaultPerson ? defaultPerson.userId : '' |
| | | |
| | | // åå§åè¡¨åæ°æ® |
| | | form.value = { |
| | | productrecordId: '', |
| | | inboundQuantity: currentRowNum.value, // 使ç¨currentRowNumä½ä¸ºé»è®¤å¼ |
| | | inboundTime: getCurrentDate(), // é»è®¤å½åæ¥æ |
| | | nickName: defaultUserId, // æ ¹æ®tabç±»å设置é»è®¤å货人 |
| | | inboundQuantity: currentRowNum.value, |
| | | inboundTime: getCurrentDate(), |
| | | nickName: defaultUserId, |
| | | } |
| | | console.log('form',form.value) |
| | | } catch (error) { |
| | | console.error('å è½½ç¨æ·å表失败:', error) |
| | | // 妿å 载失败ï¼ä½¿ç¨ç©ºå¼åå§å |
| | | form.value = { |
| | | productrecordId: '', |
| | | inboundQuantity: currentRowNum.value, |
| | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | let num = Number(form.value.inboundQuantity) |
| | | if(num <= 0 || num > currentRowNum.value){ |
| | | if (num <= 0 || num > currentRowNum.value) { |
| | | return proxy.$modal.msgWarning("请填å
¥æææ°å") |
| | | } |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid && currentRowId.value) { |
| | | if (valid && currentRow.value && currentRow.value.id) { |
| | | const outData = { |
| | | id: currentRowId.value, // åå§è®°å½ID |
| | | salesLedgerProductId: 0, |
| | | id: currentRow.value.id, // æ¥æºå
¥åºè®°å½ID |
| | | salesLedgerProductId: currentRow.value.salesLedgerProductId || 0, |
| | | quantity: form.value.inboundQuantity, // åºåºæ°é |
| | | time: form.value.inboundTime, // åºåºæ¶é´ |
| | | userId: form.value.nickName, // æä½äºº |
| | | type: 3 // åºåºç±»åï¼èªå®ä¹/ææ |
| | | userId: form.value.nickName, // ç»æäºº |
| | | type: cfg.value.type, // åºåºç±»åï¼3åæ / 2éå®åºåº / 5éè´§åºåº |
| | | supplierName: currentRow.value.supplierName || '', // éè´§åºåº-éåä¾åºå |
| | | } |
| | | console.log(outData) |
| | | |
| | | stockOut(outData).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå") |
| | | closeDia() |
| | |
| | | type: 'warning', |
| | | } |
| | | ).then(() => { |
| | | const exportUrl = "/stockin/exportTwo" |
| | | proxy.download(exportUrl, {}, 'åºåºå°è´¦.xlsx') |
| | | proxy.download(cfg.value.exportUrl, {}, cfg.value.exportName) |
| | | }).catch(() => { |
| | | proxy.$modal.msg("已忶") |
| | | }) |
| | | } |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = [] |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning('è¯·éæ©æ°æ®') |
| | | return |
| | | } |
| | | ElMessageBox.confirm( |
| | | 'éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼', |
| | | '导åº', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | } |
| | | ).then(() => { |
| | | delStockManage(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å") |
| | | getList() |
| | | }) |
| | | }).catch(() => { |
| | | proxy.$modal.msg("已忶") |
| | | }) |
| | | } |
| | | |
| | | // æ ¹æ®tabç±»åè·åå¼¹æ¡æ é¢ |
| | | const getDialogTitle = () => { |
| | | return 'æ°å¢åæ'; |
| | | }; |
| | | |
| | | // æ ¹æ®tabç±»åè·åå¯åºåºæ°éææ¬ |
| | | const getAvailableQuantityText = () => { |
| | | return 'å¯åææ°é'; |
| | | }; |
| | | |
| | | // æ ¹æ®tabç±»åè·åæ°éåæ®µæ ç¾ |
| | | const getQuantityLabel = () => { |
| | | return 'åææ°éï¼'; |
| | | }; |
| | | |
| | | // æ ¹æ®tabç±»åè·åæ¥æåæ®µæ ç¾ |
| | | const getDateLabel = () => { |
| | | return 'åææ¥æï¼'; |
| | | }; |
| | | |
| | | // æ ¹æ®tabç±»åè·å人ååæ®µæ ç¾ |
| | | const getPersonLabel = () => { |
| | | return 'åæäººï¼'; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| | | |
| | | |
| | | |
| | | <style scoped lang="scss"> |
| | | .out-tabs { |
| | | margin-bottom: 6px; |
| | | } |
| | | </style> |
| | |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveUnitPrice" @change="() => calculateTotalPrice(scope.row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" width="130"> |
| | | <template #default="scope"> |
| | | <el-select v-model="scope.row.taxRate" placeholder="è¯·éæ©ç¨ç" @change="() => calculateExclusivePrice(scope.row)"> |
| | | <el-option v-for="item in taxRateOptions" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | |
| | | <el-table-column label="ç©åç±»å" prop="itemType" show-overflow-tooltip /> |
| | | <el-table-column label="å
¥åºæ°é" prop="inboundNum" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="å©ä½åºå" prop="inboundNum0" show-overflow-tooltip /> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" width="100" /> |
| | | <el-table-column label="åä»·(å
)" prop="taxInclusiveUnitPrice" width="150" /> |
| | | <el-table-column label="æ»ä»·(å
)" prop="taxInclusiveTotalPrice" width="150" /> |
| | | <el-table-column label="å
¥åºäºº" prop="createBy" width="80" show-overflow-tooltip /> |
| | |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å§åï¼</span> |
| | | <el-input v-model="searchForm.staffName" style="width: 240px" placeholder="请è¾å
¥å§åæç´¢" @change="handleQuery" |
| | | clearable :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" class="search_title">ååç»ææ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-input v-model="searchForm.staffName" style="width: 200px" placeholder="请è¾å
¥å§åæç´¢" @change="handleQuery" clearable :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" class="search_title">ç¶æï¼</span> |
| | | <el-select v-model="searchForm.status" placeholder="å
¨é¨" clearable style="width: 140px" @change="handleQuery"> |
| | | <el-option label="ææ" :value="0" /> |
| | | <el-option label="å·²ç»ç¾" :value="1" /> |
| | | <el-option label="已解é¤" :value="2" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div> |
| | | <!-- <el-button type="primary" @click="openForm('add')">æ°å¢å
¥è</el-button>--> |
| | | <!-- <el-button type="info" @click="handleImport">导å
¥</el-button>--> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢åå</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <!-- <el-button type="danger" plain @click="handleDelete">å é¤</el-button>--> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | |
| | | @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" |
| | | :total="page.total"></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | |
| | | <!-- åå导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body |
| | | > |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :auto-upload="false" |
| | | drag |
| | | > |
| | | <el-icon class="el-icon--upload"><upload-filled /></el-icon> |
| | | <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> |
| | | <template #tip> |
| | | <div class="el-upload__tip text-center"> |
| | | <span>ä»
å
许导å
¥xlsãxlsxæ ¼å¼æä»¶ã</span> |
| | | <!-- <el-link |
| | | type="primary" |
| | | :underline="false" |
| | | style="font-size: 12px; vertical-align: baseline" |
| | | @click="importTemplate" |
| | | >ä¸è½½æ¨¡æ¿</el-link |
| | | > --> |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | <!-- æ°å¢/ç¼è¾åå --> |
| | | <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢åå' : 'ç¼è¾åå'" width="60%" @close="closeDia"> |
| | | <el-form :model="form" label-width="120px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="åå·¥å§åï¼" prop="staffName"><el-input v-model="form.staffName" placeholder="请è¾å
¥" /></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="åå·¥ç¼å·ï¼" prop="staffNo"><el-input v-model="form.staffNo" placeholder="请è¾å
¥" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="å²ä½ï¼" prop="postJob"><el-input v-model="form.postJob" placeholder="请è¾å
¥" /></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="ååå¹´éï¼" prop="contractTerm"><el-input-number v-model="form.contractTerm" :min="0" :step="1" :precision="0" style="width:100%" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="ç¾è®¢æ¥æï¼" prop="signDate"><el-date-picker v-model="form.signDate" 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="startDate"><el-date-picker v-model="form.startDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width:100%" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="ååç»ææ¥æï¼" prop="endDate"><el-date-picker v-model="form.endDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width:100%" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"><el-form-item label="ååå
容ï¼" prop="contractContent"><el-input v-model="form.contractContent" type="textarea" :rows="4" placeholder="请è¾å
¥ååå
容" /></el-form-item></el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button @click="upload.open = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <files-dia ref="filesDia"></files-dia> |
| | | |
| | | <!-- ç»ç¾ --> |
| | | <el-dialog v-model="renewVisible" title="ç»ç¾åå" width="40%"> |
| | | <el-form :model="renewForm" label-width="140px" label-position="top"> |
| | | <el-form-item label="ç»ç¾æ¥æï¼"><el-date-picker v-model="renewForm.renewDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width:100%" /></el-form-item> |
| | | <el-form-item label="ç»ç¾åå°ææ¥æï¼"><el-date-picker v-model="renewForm.renewEndDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width:100%" /></el-form-item> |
| | | <el-form-item label="ååå¹´éï¼"><el-input-number v-model="renewForm.contractTerm" :min="0" :step="1" :precision="0" style="width:100%" /></el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitRenew">确认ç»ç¾</el-button> |
| | | <el-button @click="renewVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- è§£é¤ --> |
| | | <el-dialog v-model="terminateVisible" title="è§£é¤åå" width="40%"> |
| | | <el-form :model="terminateForm" label-width="140px" label-position="top"> |
| | | <el-form-item label="è§£é¤æ¥æï¼"><el-date-picker v-model="terminateForm.terminateDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width:100%" /></el-form-item> |
| | | <el-form-item label="è§£é¤åå ï¼"><el-input v-model="terminateForm.terminateReason" type="textarea" :rows="3" placeholder="请è¾å
¥è§£é¤åå " /></el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitTerminate">确认解é¤</el-button> |
| | | <el-button @click="terminateVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref } from "vue"; |
| | | import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js"; |
| | | import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue"; |
| | | import { |
| | | contractListPage, contractAdd, contractUpdate, contractDel, contractRenew, contractTerminate, |
| | | } from "@/api/personnelManagement/contract.js"; |
| | | import dayjs from "dayjs"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import FilesDia from "./filesDia.vue"; |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | entryDate: [ |
| | | dayjs().format("YYYY-MM-DD"), |
| | | dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | ], // å½å
¥æ¥æ |
| | | entryDateStart: dayjs().format("YYYY-MM-DD"), |
| | | entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | }, |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const data = reactive({ searchForm: { staffName: "", status: undefined } }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const statusText = (s) => (s == 0 ? "ææ" : s == 1 ? "å·²ç»ç¾" : s == 2 ? "已解é¤" : ""); |
| | | const statusType = (s) => (s == 0 ? "primary" : s == 1 ? "warning" : s == 2 ? "danger" : ""); |
| | | |
| | | const tableColumn = ref([ |
| | | { label: "åå·¥ç¼å·", prop: "staffNo" }, |
| | | { label: "åå·¥å§å", prop: "staffName" }, |
| | | { label: "å²ä½", prop: "postJob" }, |
| | | { label: "ç¾è®¢æ¥æ", prop: "signDate", width: 120 }, |
| | | { label: "ååå¼å§æ¥æ", prop: "startDate", width: 120 }, |
| | | { label: "ååç»ææ¥æ", prop: "endDate", width: 120 }, |
| | | { label: "ååå¹´é", prop: "contractTerm", width: 90 }, |
| | | { label: "ååå
容", prop: "contractContent", showOverflowTooltip: true }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å¨è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | label: "ç¶æ", prop: "status", dataType: "tag", |
| | | formatData: (p) => statusText(p), formatType: (p) => statusType(p), |
| | | }, |
| | | { label: "ç»ç¾æ¥æ", prop: "renewDate", width: 120 }, |
| | | { label: "è§£é¤æ¥æ", prop: "terminateDate", width: 120 }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "ç±è´¯", |
| | | prop: "nativePlace", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postJob", |
| | | }, |
| | | { |
| | | label: "å®¶åºä½å", |
| | | prop: "adress", |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "身份è¯å·", |
| | | prop: "identityCard", |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width: 150 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width: 150 |
| | | }, |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | // { |
| | | // label: "ååå¼å§æ¥æ", |
| | | // prop: "contractStartTime", |
| | | // width: 120 |
| | | // }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractExpireTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | dataType: "action", label: "æä½", align: "center", fixed: "right", width: 200, |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | { name: "ç¼è¾", type: "text", clickFun: (row) => openForm("edit", row) }, |
| | | { name: "ç»ç¾", type: "text", clickFun: (row) => openRenew(row) }, |
| | | { name: "è§£é¤", type: "text", clickFun: (row) => openTerminate(row) }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const filesDia = ref() |
| | | |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | const page = reactive({ current: 1, size: 100, total: 0 }); |
| | | const formDia = ref(); |
| | | |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref(""); |
| | | const formRef = ref(); |
| | | const form = ref({}); |
| | | const rules = { |
| | | staffName: [{ required: true, message: "请è¾å
¥åå·¥å§å", trigger: "blur" }], |
| | | signDate: [{ required: true, message: "è¯·éæ©ç¾è®¢æ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | if (type === "add") { |
| | | form.value = { status: 0, contractTerm: 1 }; |
| | | } else { |
| | | form.value = { ...row }; |
| | | } |
| | | getList(); |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (row) => { |
| | | console.log(row) |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog( row,'åå') |
| | | }) |
| | | const closeDia = () => { proxy.resetForm("formRef"); dialogFormVisible.value = false; }; |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (!valid) return; |
| | | const payload = { ...form.value }; |
| | | const p = operationType.value === "add" ? contractAdd(payload) : contractUpdate(payload); |
| | | p.then(() => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | |
| | | const renewVisible = ref(false); |
| | | const renewForm = ref({}); |
| | | const currentId = ref(null); |
| | | const openRenew = (row) => { |
| | | currentId.value = row.id; |
| | | renewForm.value = { renewDate: dayjs().format("YYYY-MM-DD"), renewEndDate: row.endDate, contractTerm: row.contractTerm }; |
| | | renewVisible.value = true; |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | const submitRenew = () => { |
| | | contractRenew(currentId.value, renewForm.value).then(() => { |
| | | proxy.$modal.msgSuccess("ç»ç¾æå"); |
| | | renewVisible.value = false; |
| | | getList(); |
| | | }); |
| | | }; |
| | | |
| | | const terminateVisible = ref(false); |
| | | const terminateForm = ref({}); |
| | | const openTerminate = (row) => { |
| | | currentId.value = row.id; |
| | | terminateForm.value = { terminateDate: dayjs().format("YYYY-MM-DD"), terminateReason: "" }; |
| | | terminateVisible.value = true; |
| | | }; |
| | | const submitTerminate = () => { |
| | | contractTerminate(currentId.value, terminateForm.value).then(() => { |
| | | proxy.$modal.msgSuccess("è§£é¤æå"); |
| | | terminateVisible.value = false; |
| | | getList(); |
| | | }); |
| | | }; |
| | | |
| | | const handleQuery = () => { page.current = 1; getList(); }; |
| | | const pagination = (obj) => { page.current = obj.page; page.size = obj.limit; getList(); }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined |
| | | staffOnJobListPage(params).then(res => { |
| | | contractListPage({ ...searchForm.value, ...page }).then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }).catch(() => { tableLoading.value = false; }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const handleSelectionChange = (selection) => { selectedRows.value = selection; }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | const handleDelete = () => { |
| | | if (!selectedRows.value.length) { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } |
| | | const ids = selectedRows.value.map((i) => i.id); |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", |
| | | }).then(() => { |
| | | contractDel(ids).then(() => { proxy.$modal.msgSuccess("å 餿å"); getList(); }); |
| | | }).catch(() => { proxy.$modal.msg("已忶"); }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffOnJob/export", {}, "åå管ç.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", |
| | | }).then(() => { |
| | | proxy.download("/staff/contract/export", { ...searchForm.value }, "åå管ç.xlsx"); |
| | | }).catch(() => { proxy.$modal.msg("已忶"); }); |
| | | }; |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼åå导å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼åå导å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // æ¯å¦æ´æ°å·²ç»åå¨çç¨æ·æ°æ® |
| | | updateSupport: 1, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/staff/staffOnJob/import", |
| | | }); |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "åå导å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | console.log(upload.url + '?updateSupport=' + upload.updateSupport) |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | /**æä»¶ä¸ä¼ ä¸å¤ç */ |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true; |
| | | }; |
| | | /** æä»¶ä¸ä¼ æåå¤ç */ |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.open = false; |
| | | upload.isUploading = false; |
| | | proxy.$refs["uploadRef"].handleRemove(file); |
| | | getList(); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | onMounted(() => { getList(); }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¦»èæ¥æï¼" prop="leaveDate"> |
| | | <el-date-picker v-model="form.leaveDate" type="date" placeholder="è¯·éæ©ç¦»èæ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="离èåå ï¼" prop="leaveReason"> |
| | | <el-input v-model="form.leaveReason" placeholder="请è¾å
¥ç¦»èåå " clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å·¥ä½äº¤æ¥æ
åµï¼" prop="handoverInfo"> |
| | | <el-input v-model="form.handoverInfo" type="textarea" :rows="3" placeholder="请è¾å
¥å·¥ä½äº¤æ¥æ
åµ" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | contractStartTime: "", |
| | | contractEndTime: "", |
| | | staffState: "", |
| | | leaveDate: "", |
| | | leaveReason: "", |
| | | handoverInfo: "", |
| | | }, |
| | | rules: { |
| | | staffNo: [{ required: true, message: "请è¾å
¥", trigger: "blur" },], |
| | |
| | | contractTerm: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contractStartTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contractEndTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | leaveDate: [{ required: true, message: "è¯·éæ©ç¦»èæ¥æ", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | |
| | | > |
| | | </div> |
| | | <div> |
| | | <!-- <el-button type="primary" @click="openForm('add')">æ°å¢å
¥è</el-button>--> |
| | | <el-button type="primary" @click="openEdit('add')">æ°å¢åå·¥</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <!-- <el-button type="danger" plain @click="handleDelete">å é¤</el-button>--> |
| | | </div> |
| | | </div> |
| | | <div class="stat_bar"> |
| | | <span>å¨è人æ°ï¼<b>{{ stats.onJobCount }}</b></span> |
| | | <span style="margin-left: 24px">èªèµå计ï¼<b>{{ stats.salaryTotal }}</b></span> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | |
| | | <!-- ç¼è¾/æ°å¢å¨èåå·¥ --> |
| | | <el-dialog v-model="editVisible" :title="editType === 'add' ? 'æ°å¢å¨èåå·¥' : 'ç¼è¾å¨èåå·¥'" width="60%" @close="closeEdit"> |
| | | <el-form :model="editForm" label-width="120px" label-position="top" :rules="editRules" ref="editFormRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="å§åï¼" prop="staffName"><el-input v-model="editForm.staffName" placeholder="请è¾å
¥" /></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="åå·¥ç¼å·ï¼" prop="staffNo"><el-input v-model="editForm.staffNo" placeholder="请è¾å
¥" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="å²ä½ï¼" prop="postJob"><el-input v-model="editForm.postJob" placeholder="请è¾å
¥" /></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="èç³»çµè¯ï¼" prop="phone"><el-input v-model="editForm.phone" placeholder="请è¾å
¥" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="å
¥èæ¶é´ï¼" prop="entryDate"><el-date-picker v-model="editForm.entryDate" 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="salary"><el-input-number v-model="editForm.salary" :min="0" :precision="2" :step="100" style="width:100%" /></el-form-item></el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"><el-form-item label="ååå¹´éï¼" prop="contractTerm"><el-input v-model="editForm.contractTerm" placeholder="请è¾å
¥" /></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="ååç»ææ¥æï¼" prop="contractExpireTime"><el-date-picker v-model="editForm.contractExpireTime" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width:100%" /></el-form-item></el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitEdit">确认</el-button> |
| | | <el-button @click="editVisible = false">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import { onMounted, reactive, ref, toRefs, getCurrentInstance, nextTick } from "vue"; |
| | | import FormDia from "@/views/personnelManagement/employeeRecord/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js"; |
| | | import { createStaffOnJob, updateStaffOnJob, staffOnJobStatistics } from "@/api/personnelManagement/staffOnJob.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | entryDate: [ |
| | | dayjs().format("YYYY-MM-DD"), |
| | | dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | ], // å½å
¥æ¥æ |
| | | ], |
| | | entryDateStart: dayjs().format("YYYY-MM-DD"), |
| | | entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | }, |
| | |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å¨è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatData: (params) => (params == 0 ? "离è" : params == 1 ? "å¨è" : null), |
| | | formatType: (params) => (params == 0 ? "danger" : params == 1 ? "primary" : null), |
| | | }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "ç±è´¯", |
| | | prop: "nativePlace", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postJob", |
| | | }, |
| | | { |
| | | label: "å®¶åºä½å", |
| | | prop: "adress", |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width:100 |
| | | }, |
| | | { |
| | | label: "身份è¯å·", |
| | | prop: "identityCard", |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | // { |
| | | // label: "ååå¼å§æ¥æ", |
| | | // prop: "contractStartTime", |
| | | // width: 120 |
| | | // }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractExpireTime", |
| | | width: 120 |
| | | }, |
| | | { label: "åå·¥ç¼å·", prop: "staffNo" }, |
| | | { label: "å§å", prop: "staffName" }, |
| | | { label: "æ§å«", prop: "sex" }, |
| | | { label: "ç±è´¯", prop: "nativePlace" }, |
| | | { label: "å²ä½", prop: "postJob" }, |
| | | { label: "å
¥èæ¶é´", prop: "entryDate", width: 120 }, |
| | | { label: "èªèµ", prop: "salary" }, |
| | | { label: "å®¶åºä½å", prop: "adress", width: 200 }, |
| | | { label: "第ä¸å¦å", prop: "firstStudy" }, |
| | | { label: "ä¸ä¸", prop: "profession", width: 100 }, |
| | | { label: "身份è¯å·", prop: "identityCard", width: 200 }, |
| | | { label: "å¹´é¾", prop: "age" }, |
| | | { label: "èç³»çµè¯", prop: "phone", width: 150 }, |
| | | { label: "ååå¹´é", prop: "contractTerm" }, |
| | | { label: "ååç»ææ¥æ", prop: "contractExpireTime", width: 120 }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { name: "详æ
", type: "text", clickFun: (row) => openForm("edit", row) }, |
| | | { name: "ç¼è¾", type: "text", clickFun: (row) => openEdit("edit", row) }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0 |
| | | }); |
| | | const formDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | const page = reactive({ current: 1, size: 100, total: 0 }); |
| | | const formDia = ref(); |
| | | |
| | | // ç»è®¡ |
| | | const stats = reactive({ onJobCount: 0, salaryTotal: 0 }); |
| | | const getStats = () => { |
| | | staffOnJobStatistics().then((res) => { |
| | | stats.onJobCount = res.data.onJobCount; |
| | | stats.salaryTotal = res.data.salaryTotal; |
| | | }); |
| | | }; |
| | | |
| | | // ç¼è¾/æ°å¢ |
| | | const editVisible = ref(false); |
| | | const editType = ref("edit"); |
| | | const editFormRef = ref(); |
| | | const editForm = ref({}); |
| | | const editRules = { |
| | | staffName: [{ required: true, message: "请è¾å
¥å§å", trigger: "blur" }], |
| | | staffNo: [{ required: true, message: "请è¾å
¥åå·¥ç¼å·", trigger: "blur" }], |
| | | }; |
| | | const openEdit = (type, row) => { |
| | | editType.value = type; |
| | | if (type === "add") { |
| | | editForm.value = { staffState: 1, salary: 0 }; |
| | | } else { |
| | | editForm.value = { ...row }; |
| | | } |
| | | editVisible.value = true; |
| | | }; |
| | | const closeEdit = () => { |
| | | proxy.resetForm("editFormRef"); |
| | | editVisible.value = false; |
| | | }; |
| | | const submitEdit = () => { |
| | | editFormRef.value.validate((valid) => { |
| | | if (!valid) return; |
| | | const payload = { ...editForm.value }; |
| | | if (editType.value === "add") { |
| | | payload.staffState = 1; |
| | | delete payload.id; |
| | | createStaffOnJob(payload).then(() => { |
| | | proxy.$modal.msgSuccess("æ°å¢æå"); |
| | | closeEdit(); |
| | | handleQuery(); |
| | | getStats(); |
| | | }); |
| | | } else { |
| | | const id = payload.id; |
| | | updateStaffOnJob(id, payload).then(() => { |
| | | proxy.$modal.msgSuccess("ä¿åæå"); |
| | | closeEdit(); |
| | | handleQuery(); |
| | | getStats(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | |
| | | } |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const handleQuery = () => { page.current = 1; getList(); }; |
| | | const pagination = (obj) => { page.current = obj.page; page.size = obj.limit; getList(); }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined |
| | | staffOnJobListPage({...params, staffState: 1}).then(res => { |
| | | params.entryDate = undefined; |
| | | staffOnJobListPage({ ...params, staffState: 1 }).then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }).catch(() => { tableLoading.value = false; }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const handleSelectionChange = (selection) => { selectedRows.value = selection; }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | nextTick(() => { formDia.value?.openDialog(type, row); }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffOnJob/export", {staffState: 1}, "å¨èåå·¥å°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", |
| | | }).then(() => { |
| | | proxy.download("/staff/staffOnJob/export", { staffState: 1 }, "å¨èåå·¥å°è´¦.xlsx"); |
| | | }).catch(() => { proxy.$modal.msg("已忶"); }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | getStats(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | | <style scoped> |
| | | .stat_bar { |
| | | margin: 6px 0 10px; |
| | | padding: 8px 12px; |
| | | background: #f0f1f5; |
| | | border-radius: 4px; |
| | | font-size: 14px; |
| | | } |
| | | .stat_bar b { |
| | | color: #409eff; |
| | | font-size: 16px; |
| | | } |
| | | </style> |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"><el-form-item label="å票代ç ï¼"><el-input v-model="form.invoiceCode" clearable /></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="å票类åï¼"><el-select v-model="form.invoiceType" style="width:100%"><el-option v-for="v in ['å¢å¼ç¨ä¸ç¨å票','å¢å¼ç¨æ®éå票','çµåä¸ç¥¨','çµåæ®ç¥¨','å
¶ä»']" :key="v" :label="v" :value="v" /></el-select></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="ç¨çï¼"><el-input-number v-model="form.taxRate" :min="0" :max="100" :precision="2" style="width:100%" /></el-form-item></el-col> |
| | | <el-col :span="12"><el-form-item label="ç¨é¢ï¼"><el-input-number v-model="form.taxAmount" :min="0" :precision="2" style="width:100%" placeholder="ç空èªå¨è®¡ç®" /></el-form-item></el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥äººï¼" prop="issUer"> |
| | | <el-input |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="夿³¨ï¼"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item> |
| | | <el-form-item label="产åä¿¡æ¯ï¼"> </el-form-item> |
| | | <PIMTable |
| | | rowKey="id" |
| | |
| | | supplierName: undefined, // ä¾åºååç§° |
| | | projectName: undefined, // 项ç®åç§° |
| | | invoiceNumber: undefined, // åç¥¨å· |
| | | invoiceCode: undefined, |
| | | invoiceType: "å¢å¼ç¨ä¸ç¨å票", |
| | | taxRate: 13, |
| | | taxAmount: undefined, |
| | | remark: undefined, |
| | | invoiceAmount: undefined, // å票éé¢(å
) |
| | | issUerId: userStore.id, // å½å
¥äºº |
| | | issUer: userStore.nickName, // å½å
¥äºº |
| | |
| | | const submitForm = () => { |
| | | formRef.value.validate(async (valid, fields) => { |
| | | if (valid) { |
| | | // modalLoading.value = true; |
| | | modalLoading.value = true; |
| | | const { code } = await addOrUpdateRegistration({ |
| | | purchaseLedgerId: id.value, |
| | | purchaseContractNumber: form.purchaseLedgerNo, |
| | | invoiceNumber: form.invoiceNumber, |
| | | invoiceAmount: form.invoiceAmount, |
| | | invoiceCode: form.invoiceCode, |
| | | invoiceType: form.invoiceType, |
| | | taxRate: form.taxRate, |
| | | taxAmount: form.taxAmount, |
| | | remark: form.remark, |
| | | salesContractNo: form.salesContractNo, |
| | | projectName: form.projectName, |
| | | productData: form.productData, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <SupplierArchive /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import SupplierArchive from '@/views/basicData/supplierManage/index.vue' |
| | | |
| | | defineOptions({ name: 'SupplierArchive' }) |
| | | </script> |