Merge branch 'dev_tide' into dev_tide_gybjyhglxt
# Conflicts:
# .env.production
# src/store/modules/user.js
# src/utils/auth.js
# src/views/environmentAccess/accessManagement/index.vue
# src/views/environmentAccess/intelligentInspectionManagement/index.vue
# src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue
# src/views/environmentAccess/vehicleInformationCollection/index.vue
# src/views/financialManagement/expenseManagement/index.vue
# src/views/financialManagement/financialStatements/index.vue
# src/views/financialManagement/revenueManagement/index.vue
# src/views/index.vue
# src/views/inventoryManagement/dispatchLog/index.vue
# src/views/inventoryManagement/issueManagement/index.vue
# src/views/inventoryManagement/receiptManagement/index.vue
# src/views/inventoryManagement/stockManagement/index.vue
# src/views/inventoryManagement/stockWarning/index.vue
# src/views/procurementManagement/paymentEntry/index.vue
# src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
# src/views/procurementManagement/procurementLedger/index.vue
# src/views/productionManagement/operationScheduling/components/formDia.vue
# src/views/productionManagement/operationScheduling/index.vue
# src/views/productionManagement/productionCosting/index.vue
# src/views/productionManagement/productionDispatching/components/formDia.vue
# src/views/productionManagement/productionDispatching/index.vue
# src/views/productionManagement/productionOrder/index.vue
# src/views/productionManagement/productionReporting/index.vue
# src/views/salesManagement/receiptPayment/index.vue
# src/views/salesManagement/receiptPaymentHistory/index.vue
# src/views/salesManagement/salesLedger/index.vue
# src/views/tideLogin.vue
# vite.config.js
| | |
| | | <meta name="renderer" content="webkit">
|
| | | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
| | | <link rel="icon" href="/favicon.ico">
|
| | | <title>MISï¼ç®¡çä¿¡æ¯ç³»ç»ï¼</title>
|
| | | <title>%VITE_APP_TITLE%</title>
|
| | | <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
| | | <style>
|
| | | html,
|
| | |
| | | }); |
| | | }; |
| | | |
| | | // æ¥è¯¢ç产å
¥åºä¿¡æ¯å表 |
| | | export const getStockInPageByProduction = (params) => { |
| | | return request({ |
| | | url: "/stockin/listPageByProduction", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // åºåºå°è´¦-æ¥è¯¢èªå®ä¹å
¥åºä¿¡æ¯å表 |
| | | export const getStockInPageByCustom = (params) => { |
| | | return request({ |
| | | url: "/stockmanagement/listPageByCustom", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | // å
¥åºç®¡ç-æ¥è¯¢èªå®ä¹å
¥åºä¿¡æ¯å表 |
| | | export const getInPageByCustom = (params) => { |
| | | return request({ |
| | | url: "/stockin/listPageByCustom", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // åºåºå°è´¦-æ¥è¯¢ç产åºåºä¿¡æ¯å表 |
| | | export const getStockInPageByProduct = (params) => { |
| | | return request({ |
| | | url: "/stockmanagement/listPageByProduct", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // ä¿®æ¹å
¥åºåä¿¡æ¯ |
| | | export const updateStockIn = (data) => { |
| | | return request({ |
| | |
| | | data, |
| | | }); |
| | | }; |
| | | // ä¿®æ¹ææåºåä¿¡æ¯ |
| | | export const updateManagementByCustom = (data) => { |
| | | return request({ |
| | | url: "/stockin/updateManagementByCustom ", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | // æ°å¢ååå
¥åºä¿¡æ¯ |
| | | export function addSutockIn(data) { |
| | |
| | | }) |
| | | } |
| | | |
| | | // æ°å¢èªå®ä¹å
¥åºä¿¡æ¯ |
| | | export function addStockInCustom(data) { |
| | | return request({ |
| | | url: '/stockin/addCustom', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | // ç¼è¾èªå®ä¹å
¥åºä¿¡æ¯ |
| | | export function updateStockInCustom(data) { |
| | | return request({ |
| | | url: '/stockin/updateCustom', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | // ç¼è¾æåå
¥åºä¿¡æ¯ |
| | | export function updateProduct(data) { |
| | | return request({ |
| | | url: '/stockin/update', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | // å é¤å
¥åºä¿¡æ¯ |
| | | export function delStockIn(ids) { |
| | | return request({ |
| | |
| | | }) |
| | | } |
| | | |
| | | // å é¤èªå®ä¹å
¥åºä¿¡æ¯ |
| | | export function delStockInCustom(ids) { |
| | | return request({ |
| | | url: '/stockin/delteCustom', |
| | | method: 'post', |
| | | data: ids |
| | | }) |
| | | } |
| | | |
| | | // 导åºå
¥åºä¿¡æ¯ |
| | | export function exportStockIn(query) { |
| | | return request({ |
| | |
| | | }); |
| | | }; |
| | | |
| | | // æ¥è¯¢ç产å
¥åºåºåä¿¡æ¯å表 |
| | | export const getStockManagePageByProduction = (params) => { |
| | | return request({ |
| | | url: "/stockin/listPageCopyByProduction", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // æ¥è¯¢èªå®ä¹å
¥åºåºåä¿¡æ¯å表 |
| | | export const getStockManagePageByCustom = (params) => { |
| | | return request({ |
| | | url: "/stockin/listPageCopyByCustom", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | |
| | | // ä¿®æ¹åºåä¿¡æ¯ |
| | | export const updateStockManage = (data) => { |
| | |
| | | }) |
| | | } |
| | | |
| | | //åºåºæ¥å£ |
| | | // åºåºç®¡ç-é¢ç¨æ¥å£ |
| | | export const stockOut = (data) => { |
| | | return request({ |
| | | url: '/stockmanagement/stockout', |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | //æ¥è¯¢åºåºå表 |
| | | // åºåºå°è´¦-éè´åºåºæ¥è¯¢åºåºå表 |
| | | export const getStockOutPage = (params) => { |
| | | return request({ |
| | | url: "/stockmanagement/listPage", |
| | |
| | | data: ids |
| | | }) |
| | | } |
| | | |
| | | //导åºåºåºä¿¡æ¯ |
| | | export const exportStockOut = (query) => { |
| | | return request({ |
| | | url: '/stockmanagement/export', |
| | | method: 'get', |
| | | params: query, |
| | | responseType: 'blob' |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // è·ååºåæ¥æ¥ç»è®¡ |
| | | export const getStockDailyReport = (params) => { |
| | | return request({ |
| | | url: "/stockin/getReportList", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | // è·ååºåææ¥ç»è®¡ |
| | | export const getStockMonthlyReport = (params) => { |
| | | return request({ |
| | | url: "/stockin/getReportList", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // è·åä½ä¸æ¥è¡¨ç»è®¡ |
| | | export const getWorkReport = (params) => { |
| | | return request({ |
| | | url: "/stockin/getReportList", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // è·ååºåè¿åºåç»è®¡ |
| | | export const getStockInOutReport = (params) => { |
| | | return request({ |
| | | url: "/stockin/getReportList", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // 导åºåºåæ¥è¡¨ |
| | | export const exportStockReport = (params) => { |
| | | return request({ |
| | | url: "/stockin/exportCopy", |
| | | method: "post", |
| | | params, |
| | | responseType: 'blob' |
| | | }); |
| | | }; |
| | | |
| | | // è·ååºåè¶å¿æ°æ® |
| | | export const getStockTrendData = (params) => { |
| | | return request({ |
| | | url: "/stockreport/trend", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢å¨æ°ç½é¢è¦å表 |
| | | export const getStockWarningPage = (params) => { |
| | | export const getStockWarningPage = (page, params) => { |
| | | return request({ |
| | | url: "/gasTankWarning/listPage", |
| | | method: "get", |
| | | params, |
| | | params: { |
| | | ...page, |
| | | ...params |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | |
| | | return request({ |
| | | url: "/gasTankWarning/add", |
| | | method: "post", |
| | | data, |
| | | data: data, |
| | | }); |
| | | }; |
| | | |
| | |
| | | export const updateStockWarning = (data) => { |
| | | return request({ |
| | | url: "/gasTankWarning/update", |
| | | method: "put", |
| | | data, |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | }; |
| | | |
| | |
| | | return request({ |
| | | url: "/gasTankWarning/delete", |
| | | method: "delete", |
| | | data: { ids }, |
| | | data: ids, |
| | | }); |
| | | }; |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: '/lavorIssue/listPage', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function statistics(params) { |
| | | return request({ |
| | | url: '/lavorIssue/statistics', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | export function statisticsList(params) { |
| | | return request({ |
| | | url: '/lavorIssue/statisticsList', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // æ·»å |
| | | export function add(data) { |
| | | return request({ |
| | | url: '/lavorIssue/add', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // ä¿®æ¹ |
| | | export function update(data) { |
| | | return request({ |
| | | url: '/lavorIssue/update', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // å é¤ |
| | | export function deleteLedger(data) { |
| | | return request({ |
| | | url: '/lavorIssue/delete', |
| | | method: 'delete', |
| | | data |
| | | }) |
| | | } |
| | | |
| | |
| | | // å页æ¥è¯¢ |
| | | export function schedulingListPage(query) { |
| | | return request({ |
| | | url: "/salesLedger/scheduling/listPage", |
| | | url: "/productionOrder/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢ç产订å |
| | | export function addProductionOrder(query) { |
| | | return request({ |
| | | url: "/productionOrder/addProductionOrder", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // ä¿®æ¹ç产订å |
| | | export function updateProductionOrder(query) { |
| | | return request({ |
| | | url: "/productionOrder/updateProductionOrder", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // å é¤ç产订å |
| | | export function deleteProductionOrder(query) { |
| | | return request({ |
| | | url: "/productionOrder/deleteProductionOrder", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
| | |
| | | export function isEqual(obj1, obj2) {
|
| | | return JSON.stringify(obj1) === JSON.stringify(obj2);
|
| | | }
|
| | |
|
| | | /**
|
| | | * è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD
|
| | | * @returns {string} æ ¼å¼åçæ¥æå符串
|
| | | */
|
| | | export function getCurrentDate() {
|
| | | const today = new Date();
|
| | | const year = today.getFullYear();
|
| | | const month = String(today.getMonth() + 1).padStart(2, '0'); // æä»½ä»0å¼å§
|
| | | const day = String(today.getDate()).padStart(2, '0');
|
| | | return `${year}-${month}-${day}`;
|
| | | }
|
| | |
|
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div style="padding: 20px;"> |
| | | <!-- 页颿 é¢åç鿡件 --> |
| | | <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;"> |
| | | <el-button |
| | | type="primary" |
| | | icon="Refresh" |
| | | @click="resetFilters" |
| | | size="default" |
| | | > |
| | | æ¥è¯¢ |
| | | </el-button> |
| | | </div> |
| | | |
| | | <main class="container mx-auto px-4 pb-10"> |
| | | <!-- åºå®èµäº§ææ å¡ç --> |
| | | <div class="grid-container"> |
| | | <!-- è®¾å¤æ»æ° --> |
| | | <el-card class="bg2"> |
| | | <p>è®¾å¤æ»æ°</p> |
| | | <h3> |
| | | {{ assetInfo.totalEquipment }} |
| | | </h3> |
| | | </el-card> |
| | | |
| | | <!-- èµäº§åå¼ --> |
| | | <el-card class="bg3"> |
| | | <p>èµäº§åå¼</p> |
| | | <h3> |
| | | ¥{{ assetInfo.totalOriginalValue }} |
| | | </h3> |
| | | </el-card> |
| | | |
| | | <!-- ç´¯è®¡ææ§ --> |
| | | <el-card class="bg4"> |
| | | <p>ç´¯è®¡ææ§</p> |
| | | <h3> |
| | | ¥{{ assetInfo.totalDepreciation }} |
| | | </h3> |
| | | </el-card> |
| | | |
| | | <!-- åå¼ --> |
| | | <el-card class="bg5"> |
| | | <p>åå¼</p> |
| | | <h3> |
| | | ¥{{ assetInfo.totalNetValue }} |
| | | </h3> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- åºå®èµäº§ç»è®¡å¾è¡¨ --> |
| | | <div class="grid-layout"> |
| | | <!-- æè®¾å¤ç±»åç»è®¡ --> |
| | | <el-card style="margin-bottom: 20px;"> |
| | | <h2 class="section-title">设å¤ç±»ååå¸</h2> |
| | | <div class="echarts"> |
| | | <Echarts |
| | | :legend="typeDistributionLegend" |
| | | :chartStyle="chartStylePie" |
| | | :series="typeDistributionSeries" |
| | | :tooltip="pieTooltip" |
| | | style="height: 260px; width: 35%;"> |
| | | <div class="chart-num"> |
| | | <span style="font-size: 22px;">设å¤ç±»å</span> |
| | | <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ assetInfo.totalEquipment }}</span> |
| | | </div> |
| | | </Echarts> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="lineLegend" |
| | | :series="typeDistributionLineSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | style="height: 260px; width: 64%;"></Echarts> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | <!-- 设å¤å°è´¦è¡¨æ ¼ --> |
| | | <el-card style="margin-bottom: 20px;"> |
| | | <el-table |
| | | :data="equipmentList" |
| | | stripe |
| | | style="width: 100%" |
| | | :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" |
| | | > |
| | | <el-table-column prop="id" label="èµäº§ç¼å·" width="120" /> |
| | | <el-table-column prop="deviceName" label="设å¤åç§°" width="250" /> |
| | | <el-table-column prop="deviceModel" label="åå·è§æ ¼" min-width="150" /> |
| | | <el-table-column prop="supplierName" label="ä¾åºå" min-width="120" /> |
| | | <el-table-column prop="unit" label="åä½" width="120" /> |
| | | <el-table-column prop="number" label="æ°é" width="120" /> |
| | | <el-table-column prop="originalValue" label="åå¼(å
)" width="120"> |
| | | <template #default="{ row }"> |
| | | ¥{{ formatCurrency(row.taxIncludingPriceTotal) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="depreciation" label="ç´¯è®¡ææ§(å
)" width="140"> |
| | | <template #default="{ row }"> |
| | | ¥{{ formatCurrency(row.taxIncludingPriceTotal-row.unTaxIncludingPriceTotal) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="netValue" label="åå¼(å
)" width="120"> |
| | | <template #default="{ row }"> |
| | | ¥{{ formatCurrency(row.unTaxIncludingPriceTotal) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag |
| | | :type="getStatusTagType(row.status)" |
| | | size="small" |
| | | > |
| | | {{ row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <div class="pagination-container"> |
| | | <el-pagination |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | :current-page="pagination.currentPage" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | :page-size="pagination.pageSize" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="pagination.total" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | </main> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, reactive } from 'vue'; |
| | | import 'element-plus/dist/index.css'; |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import { getLedgerPage } from "@/api/equipmentManagement/ledger"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // ç鿡件 |
| | | const dateRange = ref(null); |
| | | const equipmentType = ref(''); |
| | | |
| | | |
| | | // åºå®èµäº§ä¿¡æ¯ |
| | | const assetInfo = ref({ |
| | | totalEquipment: 0, |
| | | totalOriginalValue: 0, |
| | | totalDepreciation: 0, |
| | | totalNetValue: 0 |
| | | }); |
| | | |
| | | // 设å¤å表 |
| | | const equipmentList = ref([]); |
| | | const pagination = ref({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0 |
| | | }); |
| | | |
| | | // å¾è¡¨é
ç½® |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', |
| | | position: 'relative', |
| | | }; |
| | | |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }; |
| | | |
| | | const lineLegend = { |
| | | show: false, |
| | | }; |
| | | |
| | | // æçº¿å¾æç¤ºæ¡ |
| | | const tooltip = reactive({ |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'line', |
| | | lineStyle: { color: '#aaa' } |
| | | }, |
| | | // èªå®ä¹å
容 |
| | | formatter: function (params) { |
| | | if (!params || !params.length) return ''; |
| | | const axisLabel = params[0].axisValueLabel || params[0].axisValue || ''; |
| | | const rows = params |
| | | .map(p => { |
| | | const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`; |
| | | return `${colorDot}${p.seriesName}: ${p.value}`; |
| | | }) |
| | | .join('<br/>'); |
| | | return `<div>${axisLabel}</div><div>${rows}</div>`; |
| | | } |
| | | }); |
| | | |
| | | const xAxis = ref([ |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: true, alignWithLabel: true }, |
| | | data: [], |
| | | }, |
| | | ]); |
| | | |
| | | const yAxis = [ |
| | | { |
| | | type: 'value', |
| | | name: 'æ°é/éé¢', // 左侧yè½´ |
| | | position: 'left', |
| | | min: 0, |
| | | // åæ è½´åç§°æ ·å¼ |
| | | nameTextStyle: { |
| | | color: '#000', |
| | | fontSize: 14, |
| | | }, |
| | | } |
| | | ]; |
| | | |
| | | const chartStylePie = { |
| | | width: '100%', |
| | | height: '100%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | }; |
| | | |
| | | const pieColors = ['#F04864', '#FACC14', '#8543E0', '#1890FF', '#13C2C2', '#2FC25B']; // 坿 ¹æ®å®é
è°æ´ |
| | | |
| | | // 饼徿°æ® |
| | | const typeDistributionData = ref([]); |
| | | const departmentDistributionData = ref([]); |
| | | |
| | | // 饼å¾å¾ä¾ |
| | | const typeDistributionLegend = computed(() => ({ |
| | | show: true, |
| | | top: 'center', |
| | | left: '60%', |
| | | orient: 'vertical', |
| | | icon: 'circle', |
| | | data: typeDistributionData.value.map(item => item.name), |
| | | formatter: function(name) { |
| | | const item = typeDistributionData.value.find(i => i.name === name); |
| | | if (!item) return name; |
| | | return `${name} | ${item.count} å° | ${item.amount}`; |
| | | }, |
| | | textStyle: { |
| | | color: '#333', |
| | | fontSize: 14, |
| | | lineHeight: 26, |
| | | } |
| | | })); |
| | | |
| | | |
| | | // 饼å¾ç³»å |
| | | const typeDistributionSeries = computed(() => [ |
| | | { |
| | | type: 'pie', |
| | | radius: ['50%', '65%'], |
| | | center: ['25%', '50%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | data: typeDistributionData.value, |
| | | color: pieColors |
| | | } |
| | | ]); |
| | | |
| | | // æçº¿å¾æ°æ® |
| | | const typeDistributionLineSeries = ref([]); |
| | | |
| | | |
| | | // é¥¼å¾æç¤ºæ¡ |
| | | const pieTooltip = reactive({ |
| | | trigger: 'item', |
| | | formatter: function(params) { |
| | | // æ£æ¥æ°æ®æ¯å¦åå¨ |
| | | if (!params.data) return params.name; |
| | | // æ¼æ¥å®æ´å
容 |
| | | return ` |
| | | <div> |
| | | <div style="color:${params.color};font-size:16px;">â</div> |
| | | <div>${params.name}</div> |
| | | <div>æ°éï¼${params.data.count} å°</div> |
| | | <div>éé¢ï¼${params.data.amount}</div> |
| | | </div> |
| | | `; |
| | | } |
| | | }); |
| | | |
| | | // éé¡¹æ°æ® |
| | | const equipmentTypeOptions = ref([]); |
| | | |
| | | // è·åæ°æ® |
| | | const fetchData = async () => { |
| | | try { |
| | | // è·ååºå®èµäº§æ±æ»ä¿¡æ¯ |
| | | const assetInfoRes = await getAssetInfo({ |
| | | startDate: dateRange.value ? dateRange.value[0] : null, |
| | | endDate: dateRange.value ? dateRange.value[1] : null, |
| | | equipmentType: equipmentType.value |
| | | }); |
| | | |
| | | if (assetInfoRes.code === 200) { |
| | | assetInfo.value = assetInfoRes.data; |
| | | } |
| | | |
| | | // è·å设å¤å表 |
| | | const equipmentListRes = await getLedgerPage({ |
| | | current: pagination.value.currentPage, |
| | | size: pagination.value.pageSize, |
| | | startDate: dateRange.value ? dateRange.value[0] : null, |
| | | endDate: dateRange.value ? dateRange.value[1] : null, |
| | | equipmentType: equipmentType.value |
| | | }); |
| | | |
| | | if (equipmentListRes.code === 200) { |
| | | equipmentList.value = equipmentListRes.data.records; |
| | | pagination.value.total = equipmentListRes.data.total; |
| | | |
| | | // æ ¹æ® equipmentList æ deviceName è¿è¡åç±»ç»è®¡ |
| | | const deviceNameMap = {}; |
| | | equipmentList.value.forEach(item => { |
| | | const deviceName = item.deviceName; |
| | | if (!deviceNameMap[deviceName]) { |
| | | deviceNameMap[deviceName] = { |
| | | name: deviceName, |
| | | count: 0, |
| | | totalValue: 0 |
| | | }; |
| | | } |
| | | deviceNameMap[deviceName].count += item.number || 1; // å设 number ä¸ºè®¾å¤æ°é |
| | | deviceNameMap[deviceName].totalValue += item.taxIncludingPriceTotal || 0; // ç´¯å å«ç¨æ»ä»· |
| | | }); |
| | | |
| | | // 转æ¢ä¸º typeDistributionData æ ¼å¼ |
| | | typeDistributionData.value = Object.values(deviceNameMap).map(item => ({ |
| | | name: item.name, |
| | | value: item.count, |
| | | count: item.count, |
| | | amount: `Â¥${formatCurrency(item.totalValue)}` |
| | | })); |
| | | |
| | | // æ´æ°xè½´æ°æ® |
| | | xAxis.value[0].data = typeDistributionData.value.map(item => item.name); |
| | | |
| | | // æå»ºæçº¿å¾æ°æ® |
| | | typeDistributionLineSeries.value = [ |
| | | { |
| | | name: 'è®¾å¤æ°é', |
| | | type: 'line', |
| | | data: typeDistributionData.value.map(item => item.count) |
| | | } |
| | | ]; |
| | | } |
| | | } catch (error) { |
| | | console.error('è·ååºå®èµäº§æ°æ®å¤±è´¥ï¼', error); |
| | | } |
| | | }; |
| | | |
| | | // åå§å |
| | | onMounted(() => { |
| | | // è·ååè¡¨æ°æ® |
| | | fetchData(); |
| | | }); |
| | | |
| | | // æ ¼å¼åè´§å¸ |
| | | const formatCurrency = (value) => { |
| | | if (!value) return '0.00'; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); |
| | | }; |
| | | |
| | | // è·åç¶ææ ç¾ç±»å |
| | | const getStatusTagType = (status) => { |
| | | switch (status) { |
| | | case 'å¨ç¨': |
| | | return 'success'; |
| | | case 'é²ç½®': |
| | | return 'info'; |
| | | case 'ç»´ä¿®ä¸': |
| | | return 'warning'; |
| | | case 'æ¥åº': |
| | | return 'danger'; |
| | | default: |
| | | return 'info'; |
| | | } |
| | | }; |
| | | |
| | | // éç½®ç鿡件 |
| | | const resetFilters = () => { |
| | | dateRange.value = null; |
| | | equipmentType.value = ''; |
| | | fetchData(); |
| | | }; |
| | | |
| | | // å页å¤ç |
| | | const handleSizeChange = (size) => { |
| | | pagination.value.pageSize = size; |
| | | fetchData(); |
| | | }; |
| | | |
| | | const handleCurrentChange = (page) => { |
| | | pagination.value.currentPage = page; |
| | | fetchData(); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | /* åºç¡æ ·å¼è¡¥å
*/ |
| | | :root { |
| | | --el-color-primary: #4f46e5; |
| | | } |
| | | |
| | | .el-card { |
| | | position: relative; |
| | | border-radius: 12px; |
| | | padding: 14px 10px 10px 10px; |
| | | box-shadow: 0 2px 8px #eee; |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 10px 20px !important; |
| | | } |
| | | |
| | | &.bg1 { |
| | | background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important; |
| | | } |
| | | |
| | | &.bg2 { |
| | | background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important; |
| | | } |
| | | |
| | | &.bg3 { |
| | | background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important; |
| | | } |
| | | |
| | | &.bg4 { |
| | | background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important; |
| | | } |
| | | |
| | | &.bg5 { |
| | | background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important; |
| | | } |
| | | } |
| | | |
| | | .grid-container { |
| | | /* grid 容å¨åºç¡æ ·å¼ */ |
| | | display: grid; |
| | | gap: 1rem; /* gap-4 å¯¹åº 1rem (16px) */ |
| | | margin-bottom: 2rem; /* mb-8 å¯¹åº 2rem (32px) */ |
| | | |
| | | p { |
| | | font-size: 22px; |
| | | margin-top: 0px; |
| | | color: #fff; |
| | | } |
| | | |
| | | h3 { |
| | | font-size: 36px; |
| | | font-weight: 500; |
| | | font-family: 'MyCustomFont', sans-serif; |
| | | margin: 10px 0; |
| | | color: #fff; |
| | | } |
| | | } |
| | | |
| | | /* ç§»å¨ç«¯é»è®¤æ ·å¼ (grid-cols-1) */ |
| | | .grid-container { |
| | | grid-template-columns: repeat(1, minmax(0, 1fr)); |
| | | } |
| | | |
| | | /* å°å±å¹åä»¥ä¸ (sm:grid-cols-2) */ |
| | | @media (min-width: 640px) { |
| | | .grid-container { |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | } |
| | | } |
| | | |
| | | /* 大å±å¹åä»¥ä¸ (lg:grid-cols-5) */ |
| | | @media (min-width: 1024px) { |
| | | .grid-container { |
| | | grid-template-columns: repeat(5, minmax(0, 1fr)); |
| | | } |
| | | } |
| | | |
| | | /* å¡çæ¬åææå¢å¼º */ |
| | | .el-card:hover { |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .echarts { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | /* å¾è¡¨å®¹å¨æ ·å¼ */ |
| | | .el-chart { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .section-title { |
| | | position: relative; |
| | | font-size: 18px; |
| | | color: #333; |
| | | padding-left: 10px; |
| | | margin-bottom: 10px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .section-title::before { |
| | | position: absolute; |
| | | left: 0; |
| | | top: 0px; |
| | | content: ''; |
| | | width: 4px; |
| | | height: 18px; |
| | | background-color: #002FA7; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .chart-num { |
| | | position: absolute; |
| | | z-index: 3; |
| | | top: 92px; |
| | | left: 92px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .pagination-container { |
| | | margin-top: 20px; |
| | | display: flex; |
| | | justify-content: center; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="inventory-statistics"> |
| | | <!-- çé表å --> |
| | | <div class="filter-form"> |
| | | <el-form :model="filterForm" inline> |
| | | <!-- <el-form-item label="æ¶é´èå´">--> |
| | | <!-- <el-date-picker--> |
| | | <!-- v-model="filterForm.dateRange"--> |
| | | <!-- type="daterange"--> |
| | | <!-- range-separator="è³"--> |
| | | <!-- start-placeholder="å¼å§æ¥æ"--> |
| | | <!-- end-placeholder="ç»ææ¥æ"--> |
| | | <!-- />--> |
| | | <!-- </el-form-item>--> |
| | | <!-- <el-form-item label="ä¾åºååç§°">--> |
| | | <!-- <el-input v-model="filterForm.supplierName" style="width: 240px" placeholder="请è¾å
¥" clearable prefix-icon="Search" />--> |
| | | <!-- </el-form-item>--> |
| | | <!-- <el-form-item label="产ååç§°">--> |
| | | <!-- <el-input v-model="filterForm.productCategory" style="width: 240px" placeholder="请è¾å
¥" clearable prefix-icon="Search" />--> |
| | | <!-- </el-form-item>--> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æ¥è¯¢</el-button> |
| | | <!-- <el-button @click="handleReset">éç½®</el-button>--> |
| | | <!-- <el-button type="success" @click="handleExport">导åº</el-button>--> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- ç»è®¡æ±æ»å¡ç --> |
| | | <div class="summary-cards"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="summary-card"> |
| | | <div class="summary-item"> |
| | | <p class="summary-title">æ»åºåæ°é</p> |
| | | <p class="summary-value">{{ summaryData.totalInventoryCount }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="summary-card"> |
| | | <div class="summary-item"> |
| | | <p class="summary-title">æ»åºåéé¢</p> |
| | | <p class="summary-value">Â¥{{ summaryData.totalInventoryValue }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="summary-card"> |
| | | <div class="summary-item"> |
| | | <p class="summary-title">åºåå卿°é</p> |
| | | <p class="summary-value">{{ summaryData.inventoryChangeCount }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="summary-card"> |
| | | <div class="summary-item"> |
| | | <p class="summary-title">åºååå¨éé¢</p> |
| | | <p class="summary-value">Â¥{{ summaryData.inventoryChangeValue }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <div class="chart-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åºååç±»å æ¯</span> |
| | | </div> |
| | | </template> |
| | | <div id="category-pie-chart" style="height: 400px;"></div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åºåéé¢è¶å¿</span> |
| | | </div> |
| | | </template> |
| | | <div id="amount-trend-chart" style="height: 400px;"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- æ°æ®è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | v-loading="loading" |
| | | border |
| | | style="width: 100%" |
| | | :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" width="240" show-overflow-tooltip /> |
| | | <el-table-column label="产å" prop="productCategory" min-width="100" show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" width="70" show-overflow-tooltip /> |
| | | <el-table-column label="å
¥åºæ°é" prop="inboundNum" width="90" show-overflow-tooltip /> |
| | | <el-table-column label="åºåæ°é" prop="inboundNum0" width="90" show-overflow-tooltip /> |
| | | <el-table-column label="å«ç¨åä»·" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="å«ç¨æ»ä»·" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" width="80" show-overflow-tooltip /> |
| | | <el-table-column label="ä¸å«ç¨æ»ä»·" prop="taxExclusiveTotalPrice" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="å
¥åºäºº" prop="createBy" width="100" show-overflow-tooltip /> |
| | | </el-table> |
| | | <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import {getStockInPage} from "@/api/inventoryManagement/stockIn.js"; |
| | | |
| | | // ç¶æåé |
| | | const loading = ref(false) |
| | | const total = ref(0) |
| | | const tableData = ref([]) |
| | | const summaryData = ref({}) |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | const categoryPieChart = ref(null) |
| | | const amountTrendChart = ref(null) |
| | | |
| | | // çé表å |
| | | const filterForm = reactive({ |
| | | dateRange: [], |
| | | supplierName: '', |
| | | productCategory: '' |
| | | }) |
| | | |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | loadData() |
| | | } |
| | | |
| | | // åå§åæ°æ® |
| | | onMounted(() => { |
| | | loadSummaryData() |
| | | loadData() |
| | | }) |
| | | |
| | | // å è½½ç»è®¡æ±æ»æ°æ® |
| | | const loadSummaryData = () => { |
| | | getStockInChartData().then(res => { |
| | | summaryData.value = res.data |
| | | }) |
| | | } |
| | | |
| | | // å è½½åºåæ°æ® |
| | | const loadData = () => { |
| | | loading.value = true |
| | | getStockInPage({ ...filterForm, ...page }).then(res => { |
| | | loading.value = false |
| | | tableData.value = res.data.records |
| | | total.value = res.data.total |
| | | console.log('res', res.data.records) |
| | | |
| | | // æ°æ®å è½½å®æåæ¸²æå¾è¡¨ |
| | | nextTick(() => { |
| | | renderCategoryPieChart() |
| | | renderAmountTrendChart() |
| | | }) |
| | | }).catch(() => { |
| | | loading.value = false |
| | | }) |
| | | } |
| | | |
| | | // 渲æåç±»å æ¯é¥¼å¾ |
| | | const renderCategoryPieChart = () => { |
| | | if (!categoryPieChart.value) { |
| | | categoryPieChart.value = echarts.init(document.getElementById('category-pie-chart')) |
| | | } |
| | | // æ ¹æ® tableData æ productCategory åç±»å¹¶è®¡ç® inboundNum0 æ°éæ»å |
| | | const categoryMap = tableData.value.reduce((acc, cur) => { |
| | | acc[cur.productCategory] = (acc[cur.productCategory] || 0) + cur.inboundNum0 |
| | | return acc |
| | | }, {}) |
| | | |
| | | // å°åç±»ç»æè½¬æ¢ä¸º ECharts 饼徿éçæ°æ®æ ¼å¼ |
| | | const categoryData = Object.entries(categoryMap).map(([name, value]) => ({ |
| | | name: name, |
| | | value: value |
| | | })) |
| | | const option = { |
| | | title: { |
| | | text: 'åºååç±»å æ¯', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}: {c} ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'åºååç±»', |
| | | type: 'pie', |
| | | radius: ['40%', '70%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderRadius: 10, |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | }, |
| | | label: { |
| | | show: true, |
| | | formatter: '{b}: {d}%' |
| | | }, |
| | | emphasis: { |
| | | label: { |
| | | show: true, |
| | | fontSize: '16', |
| | | fontWeight: 'bold' |
| | | } |
| | | }, |
| | | data: categoryData |
| | | } |
| | | ] |
| | | } |
| | | |
| | | categoryPieChart.value.setOption(option) |
| | | } |
| | | // 渲æéé¢è¶å¿æçº¿å¾ |
| | | const renderAmountTrendChart = () => { |
| | | if (!amountTrendChart.value) { |
| | | amountTrendChart.value = echarts.init(document.getElementById('amount-trend-chart')) |
| | | } |
| | | // ææä»½åç»å¹¶è®¡ç®taxInclusiveTotalPriceæ»å |
| | | const monthlyAmounts = tableData.value.reduce((acc, cur) => { |
| | | const date = new Date(cur.createTime); |
| | | const month = date.getMonth() + 1; |
| | | |
| | | // ç¡®ä¿monthå¨1-12èå´å
|
| | | if (month >= 1 && month <= 12) { |
| | | acc[month] = (acc[month] || 0) + cur.taxInclusiveTotalPrice; |
| | | } |
| | | return acc; |
| | | }, {}); |
| | | |
| | | // çæ12个æçæ°æ®ï¼ç¼ºå¤±æä»½ç¨0ä»£æ¿ |
| | | const amounts = []; |
| | | for (let i = 1; i <= 12; i++) { |
| | | amounts.push(monthlyAmounts[i] || 0); |
| | | } |
| | | const dates = ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'] |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'åºåéé¢è¶å¿', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | formatter: '{b}: ¥{c}' |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: dates |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { |
| | | formatter: 'Â¥{value}' |
| | | } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'åºåéé¢', |
| | | type: 'line', |
| | | data: amounts, |
| | | smooth: true, |
| | | areaStyle: {} |
| | | } |
| | | ] |
| | | } |
| | | |
| | | amountTrendChart.value.setOption(option) |
| | | } |
| | | |
| | | // æ¥è¯¢æä½ |
| | | const handleSearch = () => { |
| | | loadData() |
| | | } |
| | | |
| | | // éç½®æä½ |
| | | const handleReset = () => { |
| | | filterForm.dateRange = [] |
| | | filterForm.supplierName = '' |
| | | filterForm.productCategory = '' |
| | | loadData() |
| | | } |
| | | |
| | | // å¯¼åºæä½ |
| | | const handleExport = () => { |
| | | console.log('å¯¼åºæ°æ®') |
| | | } |
| | | |
| | | // çªå£å¤§å°æ¹åæ¶ï¼éæ°è°æ´å¾è¡¨å¤§å° |
| | | window.addEventListener('resize', () => { |
| | | if (categoryPieChart.value) categoryPieChart.value.resize() |
| | | if (amountTrendChart.value) amountTrendChart.value.resize() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .inventory-statistics { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .filter-form { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .summary-cards { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .summary-card { |
| | | text-align: center; |
| | | height: 100px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .summary-item { |
| | | width: 100%; |
| | | } |
| | | |
| | | .summary-title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .summary-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .summary-value.warning { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .summary-value.danger { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .chart-section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-card { |
| | | height: 460px; |
| | | } |
| | | |
| | | .card-header { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .table_list { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .pagination { |
| | | text-align: right; |
| | | margin-top: 20px; |
| | | } |
| | | </style> |
| | |
| | | <div class="dashboard"> |
| | | <!-- 顶鍿¨ªå两æ --> |
| | | <div class="dashboard-top"> |
| | | <!-- å·¦ï¼ç³»ç»æ¦è§+æ°æ®å¡ç --> |
| | | <!-- å·¦ï¼ä¼ä¸ä¿¡æ¯+ä¸å¤§æ°æ®å¡çï¼ä¸ä¸æåï¼ --> |
| | | <div class="top-left"> |
| | | <div class="system-info"> |
| | | <div class="section-title">å·¥èºæ¥è¦ä¼å管çç³»ç»</div> |
| | | <div class="company-info"> |
| | | <div class="section-title">ç»éä¿¡æ¯</div> |
| | | <div style="display: flex;align-items: center;gap: 20px"> |
| | | <div class="system-card"> |
| | | <div class="system-name">å·¥èºæ¥è¦ä¼å管çç³»ç»</div> |
| | | <div class="system-meta">宿¶çæ§ Â· æºè½åæ · 髿å¤ç</div> |
| | | <img :src="userStore.avatar" class="avatar" alt=""/> |
| | | <div class="company-card"> |
| | | <div class="company-name">{{userStore.name}}</div> |
| | | <div class="company-meta">{{userStore.roleName}}</div> |
| | | </div> |
| | | <div style="display: flex;align-items: center;gap: 8px"> |
| | | <el-icon color="#5053B5" size="22"><Clock /></el-icon> |
| | | <span>å½åæ¶é´ï¼{{ currentTime }}</span> |
| | | <span>ç»éæ¥æï¼{{userStore.currentLoginTime}}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="data-cards"> |
| | | <div class="data-card total"> |
| | | <div class="data-title">æ»æ¥è¦æ°</div> |
| | | <div class="data-value">{{ alarmStats.total }}</div> |
| | | <div class="data-desc">å·²å¤ç {{ alarmStats.handled }} | æªå¤ç {{ alarmStats.pending }}</div> |
| | | </div> |
| | | <div class="data-card pending"> |
| | | <div class="data-title">æªå¤çæ¥è¦</div> |
| | | <div class="data-value">{{ alarmStats.pending }}</div> |
| | | <div class="data-desc">严é {{ alarmStats.severePending }} | ä¸ç {{ alarmStats.moderatePending }} | 轻微 {{ alarmStats.minorPending }}</div> |
| | | </div> |
| | | <div class="data-card today"> |
| | | <div class="data-title">仿¥æ¥è¦</div> |
| | | <div class="data-value">{{ alarmStats.today }}</div> |
| | | <div class="data-desc">忝 {{ alarmStats.today忝 }}% | ç¯æ¯ {{ alarmStats.todayç¯æ¯ }}%</div> |
| | | </div> |
| | | </div> |
| | | <!-- <div class="data-cards">--> |
| | | <!-- <div class="data-card sales">--> |
| | | <!-- <div class="data-title">é宿°æ®</div>--> |
| | | <!-- <div class="data-num">--> |
| | | <!-- <div>--> |
| | | <!-- <div class="data-desc">æ¬æéå®é¢/å
</div>--> |
| | | <!-- <div class="data-value">{{businessInfo.monthSaleMoney}}</div>--> |
| | | <!-- </div>--> |
| | | <!-- <div>--> |
| | | <!-- <div class="data-desc">æªå¼ç¥¨éé¢/å
</div>--> |
| | | <!-- <div class="data-value">{{businessInfo.monthSaleHaveMoney}}</div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- --> |
| | | <!-- </div>--> |
| | | <!-- <div class="data-card purchase">--> |
| | | <!-- <div class="data-title">éè´æ°æ®</div>--> |
| | | <!-- <div class="data-num">--> |
| | | <!-- <div>--> |
| | | <!-- <div class="data-desc">æ¬æéè´é¢/å
</div>--> |
| | | <!-- <div class="data-value">{{businessInfo.monthPurchaseMoney}}</div>--> |
| | | <!-- </div>--> |
| | | <!-- <div>--> |
| | | <!-- <div class="data-desc">å¾
仿¬¾éé¢/å
</div>--> |
| | | <!-- <div class="data-value">{{businessInfo.monthPurchaseHaveMoney}}</div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="data-card inventory">--> |
| | | <!-- <div class="data-title">åºåæ°æ®</div>--> |
| | | <!-- <div class="data-num">--> |
| | | <!-- <div>--> |
| | | <!-- <div class="data-desc">å½ååºåæ»é/ä»¶</div>--> |
| | | <!-- <div class="data-value">{{businessInfo.inventoryNum}}</div>--> |
| | | <!-- </div>--> |
| | | <!-- <div>--> |
| | | <!-- <div class="data-desc">仿¥å
¥åº/ä»¶</div>--> |
| | | <!-- <div class="data-value">{{businessInfo.todayInventoryNum}}</div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | </div> |
| | | <!-- å³ï¼å¾
å¤çæ¥è¦å表 --> |
| | | <div class="alarm-panel"> |
| | | <div class="section-title">å¾
å¤çæ¥è¦</div> |
| | | <ul class="alarm-list" v-if="pendingAlarms.length > 0"> |
| | | <li v-for="item in pendingAlarms" :key="item.id"> |
| | | <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 10px"> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;"> |
| | | <div class="alarm-title">{{ item.equipmentName }} - {{ item.parameter }}</div> |
| | | <el-tag :type="getAlarmLevelType(item.alarmLevel)">{{ item.alarmLevel }}</el-tag> |
| | | </div> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;"> |
| | | <div class="alarm-value">æ¥è¦å¼: {{ item.alarmValue }} | éå¼: {{ item.threshold }}</div> |
| | | <div class="alarm-time">{{ item.alarmTime }}</div> |
| | | </div> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <div v-else style="text-align: center; color: #909399; padding: 20px;"> |
| | | ææ å¾
å¤çæ¥è¦ |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ä¸é¨æ¨ªå两æ --> |
| | | <div class="dashboard-row"> |
| | | <div class="main-panel"> |
| | | <div class="section-title">æ¥è¦è¶å¿åæ</div> |
| | | <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries" :tooltip="tooltipLine" :xAxis="xAxis" :yAxis="yAxis" style="height: 300px;"></Echarts> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="section-title">æ¥è¦çº§å«åå¸</div> |
| | | <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 300px;"> |
| | | <div style="width: 50%;"> |
| | | <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie" :series="levelPieSeries" :tooltip="pieTooltip"></Echarts> |
| | | </div> |
| | | <ul class="level-list" style="width: 50%;"> |
| | | <li v-for="item in levelPieSeries[0].data" :key="item.name"> |
| | | <div style="display: flex;align-items: center;justify-content: space-between;width: 100%"> |
| | | <div class="line" :style="{color: item.itemStyle.color}">â{{ item.name }}</div> |
| | | <div style="width: 60px;">{{ item.value }}次</div> |
| | | <div style="width: 60px;">{{ item.rate }}%</div> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å³ï¼å¾
åäºé¡¹ --> |
| | | <!-- <div class="todo-panel">--> |
| | | <!-- <div class="section-title">å¾
åäºé¡¹</div>--> |
| | | <!-- <ul class="todo-list" v-if="todoList.length > 0">--> |
| | | <!-- <li v-for="item in todoList" :key="item.id">--> |
| | | <!-- <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">--> |
| | | <!-- <div style="display: flex;justify-content: space-between;align-items: center;">--> |
| | | <!-- <div class="todo-title">æµç¨ç¼å·ï¼{{item.approveId}}</div>--> |
| | | <!-- <div class="todo-division">ç³è¯·é¨é¨ï¼{{item.approveDeptName}}</div>--> |
| | | <!-- <div class="todo-time">{{item.approveTime}}</div>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="todo-division">审æ¹äºç±ï¼{{item.approveReason}}</div>--> |
| | | <!-- </div>--> |
| | | <!-- </li>--> |
| | | <!-- </ul>--> |
| | | <!-- <div v-else style="text-align: center">--> |
| | | <!-- ææ æ°æ®--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- --> |
| | | <!-- <!– ä¸é¨æ¨ªåä¸¤æ –>--> |
| | | <!-- <div class="dashboard-row">--> |
| | | <!-- <div class="main-panel">--> |
| | | <!-- <div class="section-title">客æ·ååéé¢åæ</div>--> |
| | | <!-- <div class="contract-summary">--> |
| | | <!-- <div class="contract-info">--> |
| | | <!-- <img src="../assets/images/khtitle.png" alt="" style="width: 42px"/>--> |
| | | <!-- <div class="contract-card">--> |
| | | <!-- <div class="contract-name">æ»ååéé¢(å
)</div>--> |
| | | <!-- <div class="contract-meta">--> |
| | | <!-- <div class="main-amount">{{sum}}</div>--> |
| | | <!-- <div>å¨åæ¯: <span class="up">{{yny}}% </span> æ¥ç¯æ¯: <span class="up">{{chain}}% </span></div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px">--> |
| | | <!-- <div>--> |
| | | <!-- <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie"--> |
| | | <!-- :series="materialPieSeries"--> |
| | | <!-- :tooltip="pieTooltip"></Echarts>--> |
| | | <!-- </div>--> |
| | | <!-- <ul class="contract-list">--> |
| | | <!-- <li v-for="item in materialPieSeries[0].data" :key="item.name">--> |
| | | <!-- <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">--> |
| | | <!-- <div class="line" :style="{color: item.itemStyle.color}">â{{item.name}}</div>--> |
| | | <!-- <div style="width: 70px">{{item.rate}}%</div>--> |
| | | <!-- <div>ï¿¥{{item.value}}</div>--> |
| | | <!-- </div>--> |
| | | <!-- </li>--> |
| | | <!-- </ul>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="main-panel">--> |
| | | <!-- <div style="display: flex;justify-content: space-between;">--> |
| | | <!-- <div class="section-title">åºæ¶åºä»ç»è®¡</div>--> |
| | | <!--<!– <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">–>--> |
| | | <!--<!– <el-radio-button label="æå¨" :value="1" />–>--> |
| | | <!--<!– <el-radio-button label="ææ" :value="2" />–>--> |
| | | <!--<!– <el-radio-button label="æå£åº¦" :value="3" />–>--> |
| | | <!--<!– </el-radio-group>–>--> |
| | | <!-- </div>--> |
| | | <!-- <Echarts ref="chart"--> |
| | | <!-- :color="barColors2"--> |
| | | <!-- :chartStyle="chartStyle"--> |
| | | <!-- :grid="grid"--> |
| | | <!-- :series="barSeries"--> |
| | | <!-- :tooltip="tooltip"--> |
| | | <!-- :xAxis="xAxis"--> |
| | | <!-- :yAxis="yAxis"--> |
| | | <!-- style="height: 260px"></Echarts>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | |
| | | <!-- åºé¨æ¨ªå两æ --> |
| | | <div class="dashboard-row"> |
| | | <div class="main-panel"> |
| | | <div class="section-title">è®¾å¤æ¥è¦ç»è®¡</div> |
| | | <Echarts ref="chart" :color="barColors" :chartStyle="chartStyle" :grid="grid" :series="equipmentBarSeries" :tooltip="tooltip" :xAxis="equipmentXAxis" :yAxis="yAxis" style="height: 300px;"></Echarts> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="section-title">æ¥è¦å¤çæ¶æåæ</div> |
| | | <Echarts ref="chart" :color="barColors" :chartStyle="chartStyle" :grid="grid" :series="handlingTimeSeries" :tooltip="tooltip" :xAxis="handlingTimeXAxis" :yAxis="yAxis" style="height: 300px;"></Echarts> |
| | | </div> |
| | | <!-- <div class="dashboard-row">--> |
| | | <!-- <div class="main-panel">--> |
| | | <!-- <div class="section-title">è´¨éç»è®¡</div>--> |
| | | <!-- <div class="quality-cards">--> |
| | | <!-- <div class="quality-card one">åææå·²æ£æµæ° <span>{{qualityStatisticsObject.supplierNum}}ä»¶</span></div>--> |
| | | <!-- <div class="quality-card two">è¿ç¨æ£éªæ°é <span>{{qualityStatisticsObject.processNum}}ä»¶</span></div>--> |
| | | <!-- <div class="quality-card three">åºåå·²æ£æ°é <span>{{qualityStatisticsObject.factoryNum}}ä»¶</span></div>--> |
| | | <!-- </div>--> |
| | | <!-- <Echarts ref="chart"--> |
| | | <!-- :chartStyle="chartStyle"--> |
| | | <!-- :grid="grid"--> |
| | | <!-- :legend="barLegend"--> |
| | | <!-- :series="barSeries1"--> |
| | | <!-- :tooltip="tooltip"--> |
| | | <!-- :xAxis="xAxis1"--> |
| | | <!-- :yAxis="yAxis1"--> |
| | | <!-- style="height: 260px"></Echarts>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="main-panel">--> |
| | | <!-- <div class="section-title">忬¾ä¸å¼ç¥¨åæ</div>--> |
| | | <!-- <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"--> |
| | | <!-- :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;"></Echarts>--> |
| | | <!-- </div>--> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, reactive, computed } from 'vue' |
| | | import { ref, onMounted } from 'vue' |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import { Clock } from '@element-plus/icons-vue'; |
| | | import * as echarts from 'echarts'; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import { |
| | | analysisCustomerContractAmounts, getAmountHalfYear, |
| | | getBusiness, |
| | | homeTodos, |
| | | qualityStatistics, |
| | | statisticsReceivablePayable |
| | | } from "@/api/viewIndex.js"; |
| | | |
| | | // å½åæ¶é´ |
| | | const currentTime = computed(() => { |
| | | const now = new Date(); |
| | | return now.toLocaleString('zh-CN'); |
| | | }); |
| | | const userStore = useUserStore() |
| | | |
| | | // æ¥è¦ç»è®¡æ°æ® |
| | | const alarmStats = reactive({ |
| | | total: 425, |
| | | handled: 398, |
| | | pending: 27, |
| | | severePending: 8, |
| | | moderatePending: 12, |
| | | minorPending: 7, |
| | | today: 35, |
| | | today忝: '+12.5', |
| | | todayç¯æ¯: '-8.3' |
| | | }); |
| | | const businessInfo = ref({ |
| | | inventoryNum: 0, |
| | | monthPurchaseHaveMoney: 0, |
| | | monthPurchaseMoney: 0, |
| | | monthSaleHaveMoney: 0, |
| | | monthSaleMoney: 0, |
| | | todayInventoryNum: 0, |
| | | }) |
| | | const qualityStatisticsObject = ref({ |
| | | supplierNum: 0, |
| | | processNum: 0, |
| | | factoryNum: 0, |
| | | }) |
| | | const sum = ref(0) |
| | | const yny = ref(0) |
| | | const chain = ref(0) |
| | | |
| | | // å¾
å¤çæ¥è¦å表 |
| | | const pendingAlarms = ref([ |
| | | const pieLegend = reactive({ |
| | | show: false, |
| | | }) |
| | | const barSeries = ref([ |
| | | { |
| | | id: 1, |
| | | equipmentName: 'ååºéA', |
| | | parameter: '温度', |
| | | alarmValue: '185°C', |
| | | threshold: 'â¤180°C', |
| | | alarmLevel: '严é', |
| | | alarmTime: '2025-12-16 14:30:23' |
| | | type: 'bar', |
| | | data: [], |
| | | label: { |
| | | show: true, |
| | | } |
| | | }, |
| | | ]) |
| | | const barSeries1 = ref([ |
| | | { |
| | | name: 'åææä¸åæ ¼æ°', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | id: 2, |
| | | equipmentName: 'ç¦»å¿æºB', |
| | | parameter: 'æ¯å¨', |
| | | alarmValue: '0.8mm/s', |
| | | threshold: 'â¤0.5mm/s', |
| | | alarmLevel: 'ä¸ç', |
| | | alarmTime: '2025-12-16 14:28:15' |
| | | name: 'è¿ç¨ä¸åæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | id: 3, |
| | | equipmentName: 'ååºéD', |
| | | parameter: 'pHå¼', |
| | | alarmValue: '4.2', |
| | | threshold: '5.0-7.0', |
| | | alarmLevel: 'ä¸ç', |
| | | alarmTime: '2025-12-16 14:22:18' |
| | | name: 'åºåä¸åæ ¼æ°', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | id: 4, |
| | | equipmentName: 'å¹²ç¥æºE', |
| | | parameter: '湿度', |
| | | alarmValue: '15%', |
| | | threshold: 'â¤10%', |
| | | alarmLevel: '轻微', |
| | | alarmTime: '2025-12-16 14:18:55' |
| | | } |
| | | ]); |
| | | |
| | | // å¾è¡¨æ ·å¼ |
| | | ]) |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%' |
| | | }; |
| | | |
| | | height: '100%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | } |
| | | const chartStylePie = { |
| | | width: '100%', |
| | | height: '100%' |
| | | }; |
| | | |
| | | width: '140%', |
| | | height: '140%' // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | | } |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }; |
| | | |
| | | // æ¥è¦è¶å¿åæ - æçº¿å¾ |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | } |
| | | const barLegend = { |
| | | show: true, |
| | | data: ['åææä¸åæ ¼æ°', 'è¿ç¨ä¸åæ ¼æ°', 'åºåä¸åæ ¼æ°'] |
| | | } |
| | | const barLegend1 = { |
| | | show: true, |
| | | data: ['é¢ä»è´¦æ¬¾', 'åºä»è´¦æ¬¾', '颿¶è´¦æ¬¾', 'åºæ¶è´¦æ¬¾'] |
| | | } |
| | | const lineLegend = { |
| | | show: true, |
| | | data: ['严é', 'ä¸ç', '轻微'] |
| | | }; |
| | | |
| | | const tooltipLine = { |
| | | data: ['å¼ç¥¨', '忬¾'] |
| | | } |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'cross' |
| | | type: 'shadow' |
| | | } |
| | | }; |
| | | |
| | | const xAxis = ref({ |
| | | type: 'category', |
| | | data: ['12-01', '12-02', '12-03', '12-04', '12-05', '12-06', '12-07'] |
| | | }); |
| | | |
| | | const yAxis = ref({ |
| | | } |
| | | const xAxis = [{ |
| | | type: 'value', |
| | | name: 'æ¥è¦æ°é' |
| | | }); |
| | | |
| | | const lineSeries = ref([ |
| | | { |
| | | name: '严é', |
| | | type: 'line', |
| | | data: [8, 12, 9, 15, 11, 13, 10], |
| | | itemStyle: { |
| | | color: '#f56c6c' |
| | | }, |
| | | lineStyle: { |
| | | width: 2 |
| | | }, |
| | | showSymbol: true |
| | | }, |
| | | { |
| | | name: 'ä¸ç', |
| | | type: 'line', |
| | | data: [22, 25, 20, 28, 24, 26, 23], |
| | | itemStyle: { |
| | | color: '#e6a23c' |
| | | }, |
| | | lineStyle: { |
| | | width: 2 |
| | | }, |
| | | showSymbol: true |
| | | }, |
| | | { |
| | | name: '轻微', |
| | | type: 'line', |
| | | data: [35, 38, 32, 40, 36, 39, 34], |
| | | itemStyle: { |
| | | color: '#67c23a' |
| | | }, |
| | | lineStyle: { |
| | | width: 2 |
| | | }, |
| | | showSymbol: true |
| | | } |
| | | ]); |
| | | |
| | | // æ¥è¦çº§å«åå¸ - é¥¼å¾ |
| | | const pieLegend = { |
| | | show: false |
| | | }; |
| | | |
| | | const pieTooltip = { |
| | | }] |
| | | const xAxis1 = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | data: [] |
| | | }]) |
| | | const yAxis = [{ |
| | | type: 'category', |
| | | data: [ 'åºä»è´¦æ¬¾', 'åºæ¶è´¦æ¬¾',] |
| | | }] |
| | | const yAxis1 = [{ |
| | | type: 'value' |
| | | }] |
| | | const pieTooltip = reactive({ |
| | | trigger: 'item', |
| | | formatter: '{b}: {c}次 ({d}%)' |
| | | }; |
| | | |
| | | const levelPieSeries = ref([ |
| | | formatter: function (params) { |
| | | // å¨æçææç¤ºä¿¡æ¯ï¼åºäºæ°æ®é¡¹ç name 屿§ |
| | | const description = params.name === 'æ¬æåæ¬¾éé¢' ? 'æ¬æåæ¬¾éé¢' : 'åºæ¶æ¬¾éé¢'; |
| | | return `${description} ${formatNumber(params.value)}å
${params.percent}%`; |
| | | }, |
| | | position: 'right' |
| | | }) |
| | | const materialPieSeries = ref([ |
| | | { |
| | | type: 'pie', |
| | | radius: ['60%', '80%'], |
| | | radius: ['66%', '90%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderColor: '#fff', |
| | |
| | | label: { |
| | | show: false |
| | | }, |
| | | data: [ |
| | | { |
| | | name: '严é', |
| | | value: 78, |
| | | rate: 18.35, |
| | | itemStyle: { color: '#f56c6c' } |
| | | }, |
| | | { |
| | | name: 'ä¸ç', |
| | | value: 178, |
| | | rate: 41.88, |
| | | itemStyle: { color: '#e6a23c' } |
| | | }, |
| | | { |
| | | name: '轻微', |
| | | value: 169, |
| | | rate: 39.76, |
| | | itemStyle: { color: '#67c23a' } |
| | | } |
| | | ] |
| | | data: [] |
| | | } |
| | | ]); |
| | | |
| | | // è®¾å¤æ¥è¦ç»è®¡ - æ±ç¶å¾ |
| | | const barColors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399']; |
| | | |
| | | const tooltip = { |
| | | ]) |
| | | const lineSeries = ref([ |
| | | { |
| | | type: 'line', |
| | | data: [], |
| | | label: { |
| | | show: true |
| | | }, |
| | | showSymbol: true, // æ¾ç¤ºåç¹ |
| | | }, |
| | | ]) |
| | | const tooltipLine = { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }; |
| | | |
| | | const equipmentXAxis = ref({ |
| | | type: 'category', |
| | | data: ['ååºé', 'ç¦»å¿æº', 'è¾éæ³µ', 'å¹²ç¥æº', 'å缩æº'] |
| | | }); |
| | | |
| | | const equipmentBarSeries = ref([ |
| | | } |
| | | const yAxis2 = ref([ |
| | | { |
| | | name: 'æ¥è¦æ°é', |
| | | type: 'bar', |
| | | data: [125, 85, 78, 65, 72], |
| | | itemStyle: { |
| | | color: function(params) { |
| | | return barColors[params.dataIndex % barColors.length]; |
| | | } |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'top' |
| | | type: 'value', |
| | | } |
| | | ]) |
| | | const xAxis2 = ref([ |
| | | { |
| | | type: 'category', |
| | | data: [], |
| | | axisLabel: { |
| | | interval: 0, |
| | | formatter: function(value) { |
| | | return value.replace(/~/g, '\n'); |
| | | }, |
| | | } |
| | | } |
| | | ]); |
| | | ]) |
| | | |
| | | // æ¥è¦å¤çæ¶æåæ - æ±ç¶å¾ |
| | | const handlingTimeXAxis = ref({ |
| | | type: 'category', |
| | | data: ['0-30åé', '30-60åé', '1-2å°æ¶', '2å°æ¶ä»¥ä¸'] |
| | | }); |
| | | // å¾
åäºé¡¹ |
| | | const todoList = ref([]) |
| | | const radio1 = ref(1) |
| | | |
| | | const handlingTimeSeries = ref([ |
| | | { |
| | | name: 'å¤çæ°é', |
| | | type: 'bar', |
| | | data: [280, 75, 45, 25], |
| | | itemStyle: { |
| | | color: function(params) { |
| | | return barColors[params.dataIndex % barColors.length]; |
| | | } |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'top' |
| | | } |
| | | } |
| | | ]); |
| | | // å¾è¡¨å¼ç¨ |
| | | const barChart = ref(null) |
| | | const lineChart = ref(null) |
| | | const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8'] |
| | | |
| | | // è·åæ¥è¦çº§å«æ ·å¼ |
| | | const getAlarmLevelType = (level) => { |
| | | switch(level) { |
| | | case '严é': return 'danger'; |
| | | case 'ä¸ç': return 'warning'; |
| | | case '轻微': return 'info'; |
| | | default: return 'info'; |
| | | } |
| | | }; |
| | | // éæºé¢è²çæå½æ° |
| | | const getRandomColor = () => { |
| | | return '#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0'); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // 页é¢å è½½å®æåçåå§åæä½ |
| | | }); |
| | | getBusinessData() |
| | | analysisCustomer() |
| | | todoInfoS() |
| | | statisticsReceivable() |
| | | qualityStatisticsInfo() |
| | | getAmountHalfYearNum() |
| | | }) |
| | | // æ°æ®ç»è®¡ |
| | | const getBusinessData = () => { |
| | | getBusiness().then((res) => { |
| | | businessInfo.value = {...res.data} |
| | | }) |
| | | } |
| | | // ååéé¢ |
| | | const analysisCustomer = () => { |
| | | analysisCustomerContractAmounts().then((res) => { |
| | | sum.value = res.data.sum |
| | | yny.value = res.data.yny |
| | | chain.value = res.data.chain |
| | | // 为æ¯ä¸ªæ°æ®é¡¹åé
éæºé¢è² |
| | | materialPieSeries.value[0].data = res.data.item.map(item => ({ |
| | | ...item, |
| | | itemStyle: { color: getRandomColor() } |
| | | })) |
| | | }) |
| | | } |
| | | // å¾
åäºé¡¹ |
| | | const todoInfoS = () => { |
| | | homeTodos().then((res) => { |
| | | todoList.value = res.data |
| | | }) |
| | | } |
| | | // åºä»åºæ¶ç»è®¡ |
| | | const statisticsReceivable = (type) => { |
| | | console.log(type) |
| | | statisticsReceivablePayable({type: radio1.value}).then((res) => { |
| | | barSeries.value[0].data = [ |
| | | // { value: res.data.prepayMoney, itemStyle: { color: barColors2[0] } }, |
| | | { value: res.data.payableMoney, itemStyle: { color: barColors2[0] } }, |
| | | // { value: res.data.advanceMoney, itemStyle: { color: barColors2[2] } }, |
| | | { value: res.data.receivableMoney, itemStyle: { color: barColors2[1] } } |
| | | ] |
| | | }) |
| | | } |
| | | // è´¨æ£ç»è®¡ |
| | | const qualityStatisticsInfo = () => { |
| | | qualityStatistics().then((res) => { |
| | | res.data.item.forEach(item => { |
| | | xAxis1.value[0].data.push(item.date) |
| | | barSeries1.value[0].data.push(item.supplierNum) |
| | | barSeries1.value[1].data.push(item.processNum) |
| | | barSeries1.value[2].data.push(item.factoryNum) |
| | | }) |
| | | qualityStatisticsObject.value.supplierNum = res.data.supplierNum |
| | | qualityStatisticsObject.value.processNum = res.data.processNum |
| | | qualityStatisticsObject.value.factoryNum = res.data.factoryNum |
| | | }) |
| | | } |
| | | const getAmountHalfYearNum = async () => { |
| | | const res = await getAmountHalfYear() |
| | | console.log(res) |
| | | const monthName = [] |
| | | const receiptAmount = [] |
| | | const invoiceAmount = [] |
| | | res.data.forEach(item => { |
| | | monthName.push(item.month) |
| | | receiptAmount.push(item.receiptAmount) |
| | | invoiceAmount.push(item.invoiceAmount) |
| | | }) |
| | | // æ£ç¡®ååºå¼èµå¼ï¼å建æ°ç xAxis å series 对象 |
| | | xAxis2.value[0].data = monthName |
| | | xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~')); |
| | | lineSeries.value = [ |
| | | { |
| | | name: 'å¼ç¥¨', |
| | | type: 'line', |
| | | data: receiptAmount, |
| | | stack: 'Total', |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(131, 207, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(186, 228, 255, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | itemStyle: { |
| | | color: '#2D99FF', |
| | | borderColor: '#2D99FF' |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | showSymbol: true, |
| | | }, |
| | | { |
| | | name: '忬¾', |
| | | type: 'line', |
| | | data: invoiceAmount, |
| | | stack: 'Total', |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | itemStyle: { |
| | | color: '#83CFFF', |
| | | borderColor: '#83CFFF' |
| | | }, |
| | | showSymbol: true, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(54, 153, 255, 1)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(89, 169, 254, 1)' |
| | | } |
| | | ]) |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | } |
| | | ] |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | padding: 20px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .dashboard-top { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .system-info { |
| | | .company-info { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | padding: 20px; |
| | | min-width: 0; |
| | | background-color: #EFF2FB; |
| | | background-color: #EFF2FB; /* ä½¿ç¨æå®çèæ¯é¢è² */ |
| | | background-image: url("../assets/images/denglu.png"); |
| | | background-size: 100% 260%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | border-radius: 12px; |
| | | height: 138px; |
| | | } |
| | | |
| | | .system-card { |
| | | .avatar { |
| | | width: 60px; |
| | | height: 60px; |
| | | border-radius: 50%; |
| | | object-fit: contain; |
| | | background: #fff; |
| | | border: 1px solid #eee; |
| | | } |
| | | .company-card { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | |
| | | padding-right: 15px; |
| | | } |
| | | |
| | | .system-name { |
| | | font-weight: 600; |
| | | font-size: 18px; |
| | | .company-card::after { |
| | | content: ''; |
| | | position: absolute; |
| | | right: 0; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 1px; |
| | | background-color: #C9C5C5; |
| | | border-radius: 2px; |
| | | } |
| | | .company-name { |
| | | font-weight: 400; |
| | | font-size: 16px; |
| | | color: #161A9A; |
| | | } |
| | | |
| | | .system-meta { |
| | | .company-meta { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #818185; |
| | | } |
| | | |
| | | .data-cards { |
| | | display: flex; |
| | | gap: 16px; |
| | |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .data-title { |
| | | font-weight: 700; |
| | | font-size: 26px; |
| | | color: #FFFFFF; |
| | | } |
| | | .data-num { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-top: 20px; |
| | | } |
| | | .data-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | padding: 14px 10px 10px 10px; |
| | | min-width: 160px; |
| | | box-shadow: 0 2px 8px #eee; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 32%; |
| | | height: 140px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .data-card:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
| | | .data-card.sales { |
| | | background-image: url("../assets/images/xioashoushuju.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .data-card.total { |
| | | background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%); |
| | | color: #fff; |
| | | .data-card.purchase { |
| | | background-image: url("../assets/images/caigou.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .data-card.pending { |
| | | background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%); |
| | | color: #fff; |
| | | .data-card.inventory { |
| | | background-image: url("../assets/images/kucun.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .data-card.today { |
| | | background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | .data-title { |
| | | font-weight: 600; |
| | | font-size: 16px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .data-value { |
| | | font-size: 32px; |
| | | font-weight: bold; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .data-desc { |
| | | font-size: 12px; |
| | | opacity: 0.9; |
| | | font-weight: 500; |
| | | font-size: 13px; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .data-value { |
| | | font-size: 18px; |
| | | font-weight: 500; |
| | | margin: 10px 0; |
| | | color: #FFFFFF; |
| | | } |
| | | .top-left { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | width: 60%; |
| | | width: 50%; |
| | | } |
| | | |
| | | .alarm-panel { |
| | | .todo-panel { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | width: 40%; |
| | | width: 50%; |
| | | } |
| | | |
| | | .alarm-list { |
| | | .todo-list { |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | font-size: 14px; |
| | | font-size: 15px; |
| | | overflow-y: auto; |
| | | height: 260px; |
| | | } |
| | | |
| | | .alarm-list li { |
| | | .todo-list li { |
| | | border-radius: 8px; |
| | | margin-bottom: 12px; |
| | | padding: 12px 20px; |
| | | padding: 8px 20px; |
| | | height: 74px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | background: #f8f9fa; |
| | | border-left: 4px solid #409eff; |
| | | transition: all 0.3s ease; |
| | | background: rgba(225,227,250,0.62); |
| | | } |
| | | |
| | | .alarm-list li:hover { |
| | | background: #e9ecef; |
| | | transform: translateX(4px); |
| | | } |
| | | |
| | | .alarm-title { |
| | | font-weight: 600; |
| | | font-size: 14px; |
| | | color: #303133; |
| | | } |
| | | |
| | | .alarm-value { |
| | | .todo-title { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #606266; |
| | | color: #000000; |
| | | position: relative; |
| | | } |
| | | |
| | | .alarm-time { |
| | | .todo-title::before { |
| | | content: ''; /* å¿
éï¼è¡¨ç¤ºè¿éæä¸ä¸ªå
容 */ |
| | | position: absolute; |
| | | left: -10px; /* å®ä½å°å·¦ä¾§ */ |
| | | top: 50%; /* åç´å±
ä¸ */ |
| | | transform: translateY(-50%); /* å¾®è°åç´å±
ä¸ */ |
| | | width: 6px; /* åçç´å¾ */ |
| | | height: 6px; /* åçç´å¾ */ |
| | | background: #498CEB; |
| | | border-radius: 50%; /* 让å
¶åæåå½¢ */ |
| | | } |
| | | .todo-division { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | color: #000000; |
| | | } |
| | | |
| | | .todo-time { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | } |
| | | .todo-meta { |
| | | color: #888; |
| | | font-size: 13px; |
| | | } |
| | | .dashboard-row { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .main-panel { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .section-title { |
| | | position: relative; |
| | | font-size: 18px; |
| | | color: #333; |
| | | padding-left: 10px; |
| | | margin-bottom: 20px; |
| | | margin-bottom: 10px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | |
| | | content: ''; |
| | | width: 4px; |
| | | height: 18px; |
| | | background-color: #409eff; |
| | | background-color: #002FA7; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .level-list { |
| | | margin: 0; |
| | | padding: 0; |
| | | list-style: none; |
| | | height: 200px; |
| | | overflow-y: auto; |
| | | .contract-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 20px; |
| | | height: 90px; |
| | | background: rgba(245,245,245,0.59); |
| | | width: 100%; |
| | | border-radius: 10px; |
| | | padding: 10px 30px; |
| | | } |
| | | |
| | | .level-list li { |
| | | margin-bottom: 15px; |
| | | padding: 10px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | transition: all 0.3s ease; |
| | | .contract-summary { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .level-list li:hover { |
| | | background: #e9ecef; |
| | | .contract-card { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .contract-name { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #050505; |
| | | } |
| | | .contract-meta { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 100%; |
| | | gap: 80px; |
| | | } |
| | | .main-amount { |
| | | font-size: 24px; |
| | | color: rgba(51,50,50,0.85); |
| | | } |
| | | .up { color: #e57373; } |
| | | .contract-list { |
| | | margin-top: 16px; |
| | | font-size: 14px; |
| | | color: #666; |
| | | list-style: none; |
| | | padding: 0; |
| | | height: 190px; |
| | | overflow-y: auto; |
| | | width: 460px; |
| | | } |
| | | .line { |
| | | position: relative; |
| | | width: 80px; |
| | | font-weight: 500; |
| | | width: 230px; |
| | | } |
| | | </style> |
| | | .line::after { |
| | | content: ''; |
| | | position: absolute; |
| | | right: 2px; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 1px; |
| | | background-color: #C9C5C5; |
| | | border-radius: 2px; |
| | | } |
| | | .contract-list li { |
| | | margin-top: 10px; |
| | | } |
| | | .quality-cards { |
| | | display: flex; |
| | | gap: 12px; |
| | | margin-bottom: 12px; |
| | | } |
| | | .quality-card { |
| | | border-radius: 8px; |
| | | padding: 15px 10px 10px 50px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: rgba(0,0,0,0.67); |
| | | width: 236px; |
| | | height: 49px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | .quality-card.one { |
| | | background-image: url("../assets/images/yuancailiao.png"); |
| | | } |
| | | .quality-card.two { |
| | | background-image: url("../assets/images/guocheng.png"); |
| | | } |
| | | .quality-card.three { |
| | | background-image: url("../assets/images/chuchang.png"); |
| | | |
| | | } |
| | | .quality-card span { |
| | | color: #4fc3f7; |
| | | font-weight: bold; |
| | | margin-left: 6px; |
| | | } |
| | | .chart { |
| | | width: 100%; |
| | | height: 220px; |
| | | margin-top: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">åæ¾å£åº¦ï¼</span> |
| | | <el-select |
| | | style="width: 200px;" |
| | | @change="handleQuery" |
| | | v-model="searchForm.season" |
| | | placeholder="è¯·éæ©" |
| | | :clearable="false" |
| | | > |
| | | <el-option :label="item.label" :value="item.value" v-for="(item,index) in jidu" :key="item.value" /> |
| | | </el-select> |
| | | <span class="search_title ml10">åå·¥åç§°ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.staffName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | prefix-icon="Search" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus"> æ°å¢ </el-button> |
| | | <el-button @click="handleOut" icon="download">导åº</el-button> |
| | | <el-button |
| | | type="danger" |
| | | icon="Delete" |
| | | :disabled="multipleList.length <= 0" |
| | | @click="deleteRow(multipleList.map((item) => item.id))" |
| | | > |
| | | æ¹éå é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <el-table |
| | | ref="tableRef" |
| | | v-loading="tableLoading" |
| | | :data="tableData" |
| | | border |
| | | height="calc(100vh - 21em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | style="width: 100%" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <!-- éæ©å --> |
| | | <el-table-column |
| | | align="center" |
| | | type="selection" |
| | | width="55" |
| | | fixed="left" |
| | | /> |
| | | |
| | | <!-- åºå·å --> |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | fixed="left" |
| | | /> |
| | | |
| | | <!-- åºå®åï¼å§å --> |
| | | <el-table-column |
| | | label="å§å" |
| | | prop="staffName" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | align="center" |
| | | fixed="left" |
| | | /> |
| | | |
| | | <!-- åºå®åï¼å·¥å· --> |
| | | <el-table-column |
| | | label="å·¥å·" |
| | | prop="staffNo" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | align="center" |
| | | fixed="left" |
| | | /> |
| | | |
| | | <!-- 卿åï¼æ ¹æ®åå
¸æ¸²æ --> |
| | | <el-table-column |
| | | v-for="(dictItem, index) in sys_lavor_issue" |
| | | :key="dictItem.value" |
| | | :label="dictItem.label" |
| | | :prop="dictItem.value" |
| | | show-overflow-tooltip |
| | | > |
| | | </el-table-column> |
| | | |
| | | <!-- æä½å --> |
| | | <el-table-column |
| | | label="æä½" |
| | | width="150" |
| | | align="center" |
| | | fixed="right" |
| | | > |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="edit(scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | :disabled="!!scope.row.adoptedDate" |
| | | @click="adopted(scope.row)" |
| | | > |
| | | é¢ç¨ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination :total="total" layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | </div> |
| | | <!-- <Modal ref="modalRef" @success="handleQuery"></Modal> --> |
| | | <!-- <files-dia ref="filesDia"></files-dia> --> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, reactive, toRefs, nextTick, getCurrentInstance } from 'vue' |
| | | import dayjs from "dayjs"; |
| | | // import Modal from "./Modal.vue"; |
| | | // import FilesDia from "./filesDia.vue"; |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | 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, |
| | | size: 100, |
| | | }) |
| | | const total = ref(0) |
| | | // ååºå¼æ°æ® |
| | | const tableRef = ref(null) |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const { sys_lavor_issue } = proxy.useDict("sys_lavor_issue") |
| | | const data = reactive({ |
| | | searchForm: { |
| | | season: "", |
| | | staffName: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const modalRef = ref(); |
| | | // const filesDia = ref(); |
| | | const multipleList = ref([]); |
| | | const jidu = ref([ |
| | | { |
| | | value: '1', |
| | | label: '第ä¸å£åº¦' |
| | | }, |
| | | { |
| | | value: '2', |
| | | label: '第äºå£åº¦' |
| | | }, |
| | | { |
| | | value: '3', |
| | | label: '第ä¸å£åº¦' |
| | | }, |
| | | { |
| | | value: '4', |
| | | label: '第åå£åº¦' |
| | | } |
| | | ]) |
| | | |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | // è·ååå
¸æ°æ® |
| | | const getList = async () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page.value }; |
| | | listPage(params).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | } |
| | | const add = () => { |
| | | modalRef.value.openModal(); |
| | | }; |
| | | const edit = (row) => { |
| | | modalRef.value.loadForm(row); |
| | | }; |
| | | const deleteRow = (id) => { |
| | | ElMessageBox.confirm("æ¤æä½å°æ°¸ä¹
å é¤è¯¥æ°æ®, æ¯å¦ç»§ç»?", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(async () => { |
| | | const { code } = await deleteLedger(id); |
| | | if (code == 200) { |
| | | ElMessage({ |
| | | type: "success", |
| | | message: "å 餿å", |
| | | }); |
| | | await getList(); |
| | | } |
| | | }); |
| | | }; |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download(`/lavorIssue/exportCopy`, {season: searchForm.value.season}, "å³ä¿å°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.info("已忶"); |
| | | }); |
| | | }; |
| | | const adopted = (row) => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤é¢ç¨?", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(async () => { |
| | | const params = { |
| | | id: row.id, |
| | | adoptedDate: dayjs().format("YYYY-MM-DD") |
| | | } |
| | | const { code } = await update(params); |
| | | if (code == 200) { |
| | | ElMessage({ |
| | | type: "success", |
| | | message: "é¢ç¨æå", |
| | | }); |
| | | await getList(); |
| | | } |
| | | }) |
| | | } |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | // const openFilesFormDia = (row) => { |
| | | // nextTick(() => { |
| | | // filesDia.value?.openDialog( row,'æ¶å
¥') |
| | | // }) |
| | | // }; |
| | | // äºä»¶å¤ç彿° |
| | | const handleSelectionChange = (selection) => { |
| | | multipleList.value = selection; |
| | | } |
| | | |
| | | const paginationChange = (pagination) => { |
| | | page.value.current = pagination.page; |
| | | page.value.size = pagination.limit; |
| | | getList(); |
| | | } |
| | | |
| | | // ç»ä»¶æè½½æ¶å è½½åå
¸æ°æ® |
| | | onMounted(() => { |
| | | handleQuery() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .dynamic-table-container { |
| | | width: 100%; |
| | | } |
| | | |
| | | .pagination-container { |
| | | margin-top: 20px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | :deep(.el-table .el-table__header-wrapper th) { |
| | | background-color: #F0F1F5 !important; |
| | | color: #333333; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | :deep(.el-table .el-table__body-wrapper td) { |
| | | padding: 8px 0; |
| | | } |
| | | |
| | | :deep(.el-select) { |
| | | width: 100%; |
| | | } |
| | | |
| | | :deep(.el-input) { |
| | | width: 100%; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogFormVisible" :title="getDialogTitle()" width="70%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-form-item label="éè´è®¢åå·" prop="purchaseContractNumber"> |
| | | <el-select |
| | | v-model="form.purchaseContractNumber" |
| | | placeholder="è¯·éæ©éè´è®¢åå·" |
| | | clearable |
| | | filterable |
| | | :loading="loadingPurchaseOptions" |
| | | @change="handlePurchaseChange" |
| | | :disabled="operationType === 'edit'" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in purchaseOptions" |
| | | :key="item.purchaseContractNumber" |
| | | :label="formatPurchaseOption(item)" |
| | | :value="item.purchaseContractNumber" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-table |
| | | :data="productList" |
| | | border |
| | | v-loading="loadingProducts" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column label="产å大类" prop="productCategory" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> |
| | | <el-table-column label="åä½" prop="unit" width="70" /> |
| | | <!-- <el-table-column label="ä¾åºå" prop="supplierName" width="100" /> --> |
| | | <el-table-column label="éè´æ°é" prop="quantity" width="100" /> |
| | | <el-table-column label="å¾
å
¥åºæ°é" prop="quantity0" width="100" /> |
| | | <el-table-column label="æ¬æ¬¡å
¥åºæ°é" prop="quantityStock" width="150"> |
| | | <template #default="scope"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.quantityStock" @change="() => calculateTotalPrice(scope.row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" width="120" /> |
| | | <el-table-column label="åä»·(å
)" prop="taxInclusiveUnitPrice" width="150"> |
| | | <template #default="scope"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveUnitPrice" @change="() => calculateTotalPrice(scope.row)" :disabled="operationType === 'edit'"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="æ»ä»·(å
)" |
| | | :formatter="formattedNumber" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="150" |
| | | > |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance } from 'vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import { |
| | | updateStockIn, |
| | | addSutockIn, |
| | | selectProductRecordListByPuechaserId |
| | | } from "@/api/inventoryManagement/stockIn.js"; |
| | | import { purchaseListPage } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | |
| | | const userStore = useUserStore() |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close', 'success']) |
| | | |
| | | const operationType = ref('')// æä½ç±»å: 'add' æ 'edit' |
| | | const dialogFormVisible = ref(false)// å¼¹æ¡æ¾ç¤ºç¶æ |
| | | const productList = ref([]);// 产ååè¡¨æ°æ® |
| | | const loadingProducts = ref(false);// 产åå è½½ç¶æ |
| | | const selectedRows = ref([]) // 产åè¡¨æ ¼éä¸è¡ |
| | | const purchaseOptions = ref([]) |
| | | const loadingPurchaseOptions = ref(false) |
| | | const loading = ref(false); |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | id: null, |
| | | purchaseContractNumber: '', // éè´è®¢åå· |
| | | supplierId: null, // ä¾åºåID |
| | | supplierName: '', // ä¾åºååç§° |
| | | inboundTime: '', // å
¥åºæ¶é´ |
| | | inboundBatch: '', // å
¥åºæ¹æ¬¡ |
| | | recorderId: userStore.userId, // å½å
¥äººID |
| | | recorderName: userStore.name, // å½å
¥äººå§å |
| | | entryDate: getCurrentDate(), // å½å
¥æ¥æ |
| | | remark: '', // 夿³¨ |
| | | }, |
| | | rules: { |
| | | purchaseContractNumber: [{ required: true, message: "请è¾å
¥éè´ååå·", trigger: "blur" }], |
| | | supplierId: [{ required: true, message: "è¯·éæ©ä¾åºå", trigger: "change" }], |
| | | inboundTime: [{ required: true, message: "è¯·éæ©å
¥åºæ¶é´", trigger: "change" }], |
| | | inboundBatch: [{ required: true, message: "请è¾å
¥å
¥åºæ¹æ¬¡", trigger: "blur" }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | | |
| | | // å¨æè®¡ç®å¯¹è¯æ¡æ é¢ |
| | | const getDialogTitle = () => { |
| | | return operationType.value === 'add' ? 'æ°å¢å
¥åº' : 'ç¼è¾å
¥åº' |
| | | } |
| | | |
| | | const formatPurchaseOption = (item = {}) => { |
| | | const contract = item.purchaseContractNumber || '--'; |
| | | const supplier = item.supplierName ? ` · ${item.supplierName}` : ''; |
| | | return `${contract}${supplier}`; |
| | | }; |
| | | |
| | | const loadPurchaseOptions = async (keyword = '') => { |
| | | try { |
| | | loadingPurchaseOptions.value = true; |
| | | const res = await purchaseListPage({ |
| | | current: -1, |
| | | size: -1, |
| | | purchaseContractNumber: keyword, |
| | | }); |
| | | const records = res.data?.records || []; |
| | | purchaseOptions.value = records; |
| | | if ( |
| | | form.value.purchaseContractNumber && |
| | | !purchaseOptions.value.find( |
| | | (item) => item.purchaseContractNumber === form.value.purchaseContractNumber |
| | | ) |
| | | ) { |
| | | purchaseOptions.value.push({ |
| | | purchaseContractNumber: form.value.purchaseContractNumber, |
| | | supplierName: form.value.supplierName, |
| | | supplierId: form.value.supplierId, |
| | | }); |
| | | } |
| | | } finally { |
| | | loadingPurchaseOptions.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handlePurchaseChange = (value) => { |
| | | form.value.purchaseContractNumber = value || ''; |
| | | const matched = purchaseOptions.value.find( |
| | | (item) => item.purchaseContractNumber === value |
| | | ); |
| | | if (matched) { |
| | | form.value.supplierName = matched.supplierName || form.value.supplierName; |
| | | form.value.supplierId = matched.supplierId || form.value.supplierId; |
| | | } |
| | | if (!value) { |
| | | productList.value = []; |
| | | return; |
| | | } |
| | | fetchProductsByContract(); |
| | | }; |
| | | |
| | | const exceedsAddLimit = (product) => { |
| | | const stock = Number(product?.quantityStock ?? 0); |
| | | const waiting = Number(product?.quantity0 ?? 0); |
| | | if (!Number.isFinite(stock) || !Number.isFinite(waiting)) { |
| | | return false; |
| | | } |
| | | return stock > waiting; |
| | | }; |
| | | |
| | | const exceedsEditLimit = (product) => { |
| | | const stock = Number(product?.quantityStock ?? 0); |
| | | const waiting = Number(product?.quantity0 ?? 0); |
| | | const original = Number(product?.originalQuantityStock ?? 0); |
| | | if (!Number.isFinite(stock) || !Number.isFinite(waiting) || !Number.isFinite(original)) { |
| | | return false; |
| | | } |
| | | return stock > waiting + original; |
| | | }; |
| | | |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | |
| | | // è®¡ç®æ»ä»· |
| | | const calculateTotalPrice = (row) => { |
| | | const quantityStock = Number(row?.quantityStock ?? 0); |
| | | const taxInclusiveUnitPrice = Number(row?.taxInclusiveUnitPrice ?? 0); |
| | | |
| | | if (Number.isFinite(quantityStock) && Number.isFinite(taxInclusiveUnitPrice)) { |
| | | row.taxInclusiveTotalPrice = quantityStock * taxInclusiveUnitPrice; |
| | | } else { |
| | | row.taxInclusiveTotalPrice = 0; |
| | | } |
| | | }; |
| | | |
| | | const fetchProductsByContract = async () => { |
| | | if (!form.value.purchaseContractNumber) { |
| | | proxy.$modal.msgWarning('è¯·éæ©ååå·') |
| | | return |
| | | } |
| | | try { |
| | | loadingProducts.value = true |
| | | const productRes = await selectProductRecordListByPuechaserId({ |
| | | purchaseContractNumber: form.value.purchaseContractNumber |
| | | }); |
| | | if (!productRes.data || productRes.data.length === 0) { |
| | | proxy.$modal.msgWarning('该åå䏿²¡æäº§åè®°å½') |
| | | productList.value = []; |
| | | return |
| | | } |
| | | productList.value = productRes.data.map(item => { |
| | | const quantityStock = Number(item?.quantity0 ?? 0); |
| | | const taxInclusiveUnitPrice = Number(item?.taxInclusiveUnitPrice ?? 0); |
| | | return { |
| | | ...item, |
| | | quantityStock, |
| | | taxInclusiveUnitPrice, |
| | | taxInclusiveTotalPrice: quantityStock * taxInclusiveUnitPrice, |
| | | originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? 0), |
| | | }; |
| | | }) |
| | | } catch (error) { |
| | | console.error('æ¥è¯¢äº§åè®°å½å¤±è´¥:', error) |
| | | proxy.$modal.msgError('æ¥è¯¢äº§åè®°å½å¤±è´¥') |
| | | productList.value = []; |
| | | } finally { |
| | | loadingProducts.value = false |
| | | } |
| | | } |
| | | |
| | | const updatePro = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning('请å
éæ©äº§å'); |
| | | return; |
| | | } |
| | | const target = selectedRows.value[0]; |
| | | const stock = Number(target?.quantityStock ?? 0); |
| | | if (!Number.isFinite(stock) || stock <= 0) { |
| | | proxy.$modal.msgWarning('è¯·å¡«åææçå
¥åºæ°é'); |
| | | return; |
| | | } |
| | | if (exceedsEditLimit(target)) { |
| | | proxy.$modal.msgError('æ¬æ¬¡å
¥åºæ°éä¸è½è¶
è¿åå
¥åºæ°éä¸å¾
å
¥åºæ°éä¹å'); |
| | | return; |
| | | } |
| | | const stockInData = { |
| | | id: selectedRows.value[0].recordId, |
| | | quantityStock: Number(selectedRows.value[0].quantityStock), |
| | | }; |
| | | await updateStockIn(stockInData) |
| | | proxy.$modal.msgSuccess('ä¿®æ¹å
¥åºæå') |
| | | closeDia() |
| | | emit('success') |
| | | } |
| | | |
| | | const submitForm = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning('请å
éæ©éè´ååå¹¶éæ©äº§å') |
| | | return |
| | | } |
| | | if(operationType.value !== 'add'){ |
| | | await updatePro() |
| | | return |
| | | } |
| | | try { |
| | | await proxy.$refs.formRef.validate() |
| | | const invalidProducts = selectedRows.value.filter((product) => { |
| | | const stock = Number(product?.quantityStock ?? 0); |
| | | if (!Number.isFinite(stock) || stock <= 0) { |
| | | return true; |
| | | } |
| | | return exceedsAddLimit(product); |
| | | }) |
| | | |
| | | if (invalidProducts.length > 0) { |
| | | proxy.$modal.msgError('æ¬æ¬¡å
¥åºæ°éé大äº0ï¼ä¸ä¸è½è¶
è¿å¾
å
¥åºæ°é') |
| | | return |
| | | } |
| | | |
| | | const stockInData = { |
| | | ...form.value, |
| | | inboundTime: formatDateTime(form.value.inboundTime), |
| | | nickName: userStore.nickName, |
| | | details: selectedRows.value.map(product => ({ |
| | | id: product.id, |
| | | inboundQuantity: Number(product.quantityStock), |
| | | taxInclusiveUnitPrice: Number(product.taxInclusiveUnitPrice), |
| | | taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice) |
| | | })), |
| | | }; |
| | | loading.value = true |
| | | await addSutockIn(stockInData) |
| | | |
| | | proxy.$modal.msgSuccess('æ°å¢å
¥åºæå') |
| | | closeDia() |
| | | emit('success') |
| | | |
| | | } catch (error) { |
| | | console.error('æäº¤å¤±è´¥:', error) |
| | | if (!error.errors) { |
| | | proxy.$modal.msgError('æä½å¤±è´¥ï¼è¯·éè¯') |
| | | } |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | const closeDia = () => { |
| | | proxy.$refs.formRef.resetFields() |
| | | dialogFormVisible.value = false |
| | | emit('close') |
| | | } |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection.filter(item => item.id); |
| | | } |
| | | |
| | | function formatDateTime(date = new Date(), includeTime = true) { |
| | | const d = new Date(date); |
| | | const year = d.getFullYear(); |
| | | const month = String(d.getMonth() + 1).padStart(2, '0'); |
| | | const day = String(d.getDate()).padStart(2, '0'); |
| | | |
| | | if (!includeTime) { |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | |
| | | const hours = String(d.getHours()).padStart(2, '0'); |
| | | const minutes = String(d.getMinutes()).padStart(2, '0'); |
| | | const seconds = String(d.getSeconds()).padStart(2, '0'); |
| | | |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | } |
| | | |
| | | |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type |
| | | dialogFormVisible.value = true |
| | | selectedRows.value = [] |
| | | await loadPurchaseOptions(); |
| | | |
| | | if (type === 'add') { |
| | | form.value = { |
| | | id: null, |
| | | purchaseContractNumber: '', |
| | | supplierId: null, |
| | | supplierName: '', |
| | | inboundTime: '', |
| | | inboundBatch: '', |
| | | recorderId: userStore.userId, |
| | | recorderName: userStore.name, |
| | | entryDate: getCurrentDate(), |
| | | remark: '' |
| | | } |
| | | productList.value = [] |
| | | } else { |
| | | form.value = JSON.parse(JSON.stringify(row)) |
| | | try { |
| | | loadingProducts.value = true |
| | | const res = await selectProductRecordListByPuechaserId({ |
| | | purchaseContractNumber: form.value.purchaseContractNumber, |
| | | id: row.id |
| | | }); |
| | | productList.value = res.data.map(item => ({ |
| | | ...item, |
| | | quantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0), |
| | | taxInclusiveUnitPrice: Number(item?.taxInclusiveUnitPrice ?? 0), |
| | | taxInclusiveTotalPrice: Number(item?.quantityStock ?? 0) * Number(item?.taxInclusiveUnitPrice ?? 0), |
| | | originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0), |
| | | })) |
| | | selectedRows.value = productList.value |
| | | } catch (error) { |
| | | console.error('å 载产å失败:', error) |
| | | proxy.$modal.msgError('å 载产å失败') |
| | | productList.value = [] |
| | | } finally { |
| | | loadingProducts.value = false |
| | | } |
| | | } |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| | | |
| | | |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢èªå®ä¹å
¥åº' : 'ç¼è¾èªå®ä¹å
¥åº'" width="70%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <div style="margin-bottom: 10px;" v-if="operationType === 'add'"> |
| | | <el-button type="primary" @click="addProductRow">æ°å¢</el-button> |
| | | </div> |
| | | <el-table |
| | | :data="productList" |
| | | border |
| | | v-loading="loadingProducts" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column label="产å大类" prop="productCategory" width="200"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.productCategory" placeholder="请è¾å
¥äº§å大类" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" width="200"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.specificationModel" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åä½" prop="unit" width="100"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.unit" placeholder="请è¾å
¥åä½" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä¾åºå" prop="supplierName" width="200"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.supplierName" placeholder="请è¾å
¥ä¾åºå" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç©åç±»å" prop="itemType" width="150"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.itemType" placeholder="请è¾å
¥ç©åç±»å" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å
¥åºæ°é" prop="inboundNum" width="150"> |
| | | <template #default="scope"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.inboundNum" @change="() => calculateTotalPrice(scope.row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å
¥åºæ¥æ" prop="inboundDate" width="180"> |
| | | <template #default="scope"> |
| | | <el-date-picker |
| | | v-model="scope.row.inboundDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©å
¥åºæ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åä»·(å
)" prop="taxInclusiveUnitPrice" width="150"> |
| | | <template #default="scope"> |
| | | <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="taxInclusiveTotalPrice" |
| | | width="150" |
| | | > |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="80" v-if="operationType === 'add'"> |
| | | <template #default="scope"> |
| | | <el-button type="danger" size="small" @click="removeProductRow(scope.$index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance } from 'vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import { |
| | | addStockInCustom, |
| | | updateStockInCustom, |
| | | } from "@/api/inventoryManagement/stockIn.js"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | |
| | | const userStore = useUserStore() |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close', 'success']) |
| | | |
| | | const operationType = ref('')// æä½ç±»å: 'add' æ 'edit' |
| | | const dialogFormVisible = ref(false)// å¼¹æ¡æ¾ç¤ºç¶æ |
| | | const productList = ref([]);// 产ååè¡¨æ°æ® |
| | | const loadingProducts = ref(false);// 产åå è½½ç¶æ |
| | | const loading = ref(false); |
| | | |
| | | function formatDateTime(date = new Date(), includeTime = true) { |
| | | const d = new Date(date); |
| | | const year = d.getFullYear(); |
| | | const month = String(d.getMonth() + 1).padStart(2, '0'); |
| | | const day = String(d.getDate()).padStart(2, '0'); |
| | | |
| | | if (!includeTime) { |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | |
| | | const hours = String(d.getHours()).padStart(2, '0'); |
| | | const minutes = String(d.getMinutes()).padStart(2, '0'); |
| | | const seconds = String(d.getSeconds()).padStart(2, '0'); |
| | | |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | } |
| | | |
| | | |
| | | const taxRateOptions = [ |
| | | { label: '1', value: 1 }, |
| | | { label: '6', value: 6 }, |
| | | { label: '13', value: 13 }, |
| | | ] |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | id: null, |
| | | supplierId: null, // ä¾åºåID |
| | | supplierName: '', // ä¾åºååç§° |
| | | recorderId: userStore.userId, // å½å
¥äººID |
| | | recorderName: userStore.name, // å½å
¥äººå§å |
| | | entryDate: getCurrentDate(), // å½å
¥æ¥æ |
| | | remark: '', // 夿³¨ |
| | | }, |
| | | rules: { |
| | | supplierName: [{ required: true, message: "请è¾å
¥ä¾åºååç§°", trigger: "blur" }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | | |
| | | // æ°å¢äº§åè¡ |
| | | const addProductRow = () => { |
| | | productList.value.push({ |
| | | id: null, |
| | | productCategory: '', |
| | | specificationModel: '', |
| | | unit: '', |
| | | supplierName: form.value.supplierName || '', |
| | | itemType: '', |
| | | inboundNum: 0, |
| | | inboundDate: getCurrentDate(), // é»è®¤å½å¤©æ¥æ |
| | | // quantityStock: 0, |
| | | taxInclusiveUnitPrice: 0, |
| | | taxInclusiveTotalPrice: 0, |
| | | taxRate: null, |
| | | taxExclusiveTotalPrice: 0, |
| | | }); |
| | | }; |
| | | |
| | | // å é¤äº§åè¡ |
| | | const removeProductRow = (index) => { |
| | | productList.value.splice(index, 1); |
| | | }; |
| | | |
| | | // è®¡ç®æ»ä»·ï¼æ ¹æ®æ°éãåä»·åå«ç¨åä»·ï¼ |
| | | const calculateTotalPrice = (row) => { |
| | | // è®¡ç®æ®éæ»ä»·ï¼inboundNum * taxInclusiveUnitPrice |
| | | const quantity = Number(row.inboundNum || 0); |
| | | const taxInclusiveUnitPrice = Number(row.taxInclusiveUnitPrice || 0); |
| | | row.taxInclusiveTotalPrice = quantity * taxInclusiveUnitPrice; |
| | | calculateExclusivePrice(row); |
| | | }; |
| | | |
| | | // 计ç®ä¸å«ç¨æ»ä»·ï¼æ ¹æ®å«ç¨æ»ä»·åç¨çï¼ |
| | | const calculateExclusivePrice = (row) => { |
| | | const taxInclusiveTotalPrice = Number(row.taxInclusiveTotalPrice || 0); |
| | | const taxRate = Number(row.taxRate || 0); |
| | | row.taxExclusiveTotalPrice = taxInclusiveTotalPrice / (1 + taxRate / 100); |
| | | }; |
| | | |
| | | const submitForm = async () => { |
| | | try { |
| | | await proxy.$refs.formRef.validate() |
| | | |
| | | if (!productList.value.length) { |
| | | proxy.$modal.msgError('请è³å°æ·»å 䏿¡äº§åæ°æ®') |
| | | return |
| | | } |
| | | |
| | | // éªè¯èªå®ä¹æ·»å çæ°æ®å¿
å¡«åæ®µ |
| | | for (let i = 0; i < productList.value.length; i++) { |
| | | const product = productList.value[i]; |
| | | if (!product.productCategory || !product.specificationModel || !product.unit) { |
| | | proxy.$modal.msgError(`第${i + 1}è¡äº§åæ°æ®æªå¡«å宿´ï¼äº§å大类ãè§æ ¼åå·ãåä½ä¸ºå¿
å¡«ï¼`) |
| | | return |
| | | } |
| | | if (!product.itemType) { |
| | | proxy.$modal.msgError(`第${i + 1}è¡è¯·éæ©ç©åç±»å`) |
| | | return |
| | | } |
| | | if (!product.inboundDate) { |
| | | proxy.$modal.msgError(`第${i + 1}è¡è¯·éæ©å
¥åºæ¥æ`) |
| | | return |
| | | } |
| | | const stock = Number(product?.inboundNum ?? 0); |
| | | if (!Number.isFinite(stock) || stock <= 0) { |
| | | proxy.$modal.msgError(`第${i + 1}è¡æ¬æ¬¡å
¥åºæ°éé大äº0`) |
| | | return |
| | | } |
| | | } |
| | | |
| | | const payloadList = productList.value.map(product => ({ |
| | | id: product.id ?? null, |
| | | inboundNum: Number(product.inboundNum), |
| | | productCategory: product.productCategory, |
| | | specificationModel: product.specificationModel, |
| | | unit: product.unit, |
| | | supplierName: product.supplierName || form.value.supplierName, |
| | | itemType: product.itemType, |
| | | inboundDate: formatDateTime(product.inboundDate, false), |
| | | taxRate: Number(product.taxRate || 0), |
| | | taxExclusiveTotalPrice: Number(product.taxExclusiveTotalPrice || 0), |
| | | taxInclusiveUnitPrice: Number(product.taxInclusiveUnitPrice || 0), |
| | | taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice || 0), |
| | | })); |
| | | loading.value = true |
| | | if (operationType.value === 'edit') { |
| | | const editPayload = payloadList[0] |
| | | await updateStockInCustom(editPayload) |
| | | } else { |
| | | await addStockInCustom(payloadList) |
| | | } |
| | | |
| | | proxy.$modal.msgSuccess(operationType.value === 'edit' ? 'ç¼è¾èªå®ä¹å
¥åºæå' : 'æ°å¢èªå®ä¹å
¥åºæå') |
| | | closeDia() |
| | | emit('success') |
| | | |
| | | } catch (error) { |
| | | console.error('æäº¤å¤±è´¥:', error) |
| | | if (!error.errors) { |
| | | proxy.$modal.msgError('æä½å¤±è´¥ï¼è¯·éè¯') |
| | | } |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | const closeDia = () => { |
| | | proxy.$refs.formRef.resetFields() |
| | | dialogFormVisible.value = false |
| | | productList.value = [] |
| | | emit('close') |
| | | } |
| | | |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type |
| | | dialogFormVisible.value = true |
| | | |
| | | if (type === 'add') { |
| | | form.value = { |
| | | id: null, |
| | | supplierId: null, |
| | | supplierName: '', |
| | | recorderId: userStore.userId, |
| | | recorderName: userStore.name, |
| | | entryDate: getCurrentDate(), |
| | | remark: '' |
| | | } |
| | | productList.value = [] |
| | | } else { |
| | | // ç¼è¾æ¨¡å¼ï¼å°è¡æ°æ®å¡«å
å°è¡¨æ ¼ä¸ä»¥æ¯æä¿®æ¹ |
| | | form.value = { |
| | | id: row?.id ?? null, |
| | | supplierId: row?.supplierId ?? null, |
| | | supplierName: row?.supplierName ?? '', |
| | | recorderId: userStore.userId, |
| | | recorderName: userStore.name, |
| | | entryDate: getCurrentDate(), |
| | | remark: row?.remark ?? '' |
| | | } |
| | | productList.value = [{ |
| | | id: row?.id ?? null, |
| | | productCategory: row?.productCategory ?? '', |
| | | specificationModel: row?.specificationModel ?? '', |
| | | unit: row?.unit ?? '', |
| | | supplierName: row?.supplierName ?? '', |
| | | itemType: row?.itemType ?? '', |
| | | inboundNum: Number(row?.inboundNum ?? row?.inboundQuantity ?? 0), |
| | | inboundDate: row?.inboundDate ?? row?.createTime ?? '', |
| | | taxRate: Number(row?.taxRate ?? 0), |
| | | taxInclusiveUnitPrice: Number(row?.taxInclusiveUnitPrice ?? 0), |
| | | taxInclusiveTotalPrice: Number(row?.taxInclusiveTotalPrice ?? 0), |
| | | taxExclusiveTotalPrice: Number(row?.taxExclusiveTotalPrice ?? 0), |
| | | }] |
| | | } |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢èªå®ä¹å
¥åº' : 'ç¼è¾èªå®ä¹å
¥åº'" width="70%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <div style="margin-bottom: 10px;" v-if="operationType === 'add'"> |
| | | <el-button type="primary" @click="addProductRow">æ°å¢</el-button> |
| | | </div> |
| | | <el-table |
| | | :data="productList" |
| | | border |
| | | v-loading="loadingProducts" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column label="产å大类" prop="productCategory" width="200"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.productCategory" placeholder="请è¾å
¥äº§å大类" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" width="200"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.specificationModel" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åä½" prop="unit" width="100"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.unit" placeholder="请è¾å
¥åä½" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å
¥åºæ°é" prop="inboundNum" width="150"> |
| | | <template #default="scope"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.inboundNum" @change="() => calculateTotalPrice(scope.row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å
¥åºæ¥æ" prop="inboundDate" width="180"> |
| | | <template #default="scope"> |
| | | <el-date-picker |
| | | v-model="scope.row.inboundDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©å
¥åºæ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åä»·(å
)" prop="unitPrice" width="150"> |
| | | <template #default="scope"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.unitPrice" @change="() => calculateTotalPrice(scope.row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="æ»ä»·(å
)" |
| | | prop="totalPrice" |
| | | width="150" |
| | | > |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="80" v-if="operationType === 'add'"> |
| | | <template #default="scope"> |
| | | <el-button type="danger" size="small" @click="removeProductRow(scope.$index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance } from 'vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import { |
| | | addStockInCustom, updateProduct |
| | | } from "@/api/inventoryManagement/stockIn.js"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | |
| | | const userStore = useUserStore() |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close', 'success']) |
| | | |
| | | const operationType = ref('')// æä½ç±»å: 'add' æ 'edit' |
| | | const dialogFormVisible = ref(false)// å¼¹æ¡æ¾ç¤ºç¶æ |
| | | const productList = ref([]);// 产ååè¡¨æ°æ® |
| | | const loadingProducts = ref(false);// 产åå è½½ç¶æ |
| | | const loading = ref(false); |
| | | |
| | | function formatDateTime(date = new Date(), includeTime = true) { |
| | | const d = new Date(date); |
| | | const year = d.getFullYear(); |
| | | const month = String(d.getMonth() + 1).padStart(2, '0'); |
| | | const day = String(d.getDate()).padStart(2, '0'); |
| | | |
| | | if (!includeTime) { |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | |
| | | const hours = String(d.getHours()).padStart(2, '0'); |
| | | const minutes = String(d.getMinutes()).padStart(2, '0'); |
| | | const seconds = String(d.getSeconds()).padStart(2, '0'); |
| | | |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | } |
| | | |
| | | |
| | | const itemTypeOptions = [ |
| | | { label: 'ç©æ', value: 'ç©æ' }, |
| | | { label: 'åæ', value: 'åæ' }, |
| | | { label: 'æå', value: 'æå' }, |
| | | { label: 'å
¶ä»', value: 'å
¶ä»' }, |
| | | ] |
| | | |
| | | const taxRateOptions = [ |
| | | { label: '1', value: 1 }, |
| | | { label: '6', value: 6 }, |
| | | { label: '13', value: 13 }, |
| | | ] |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | id: null, |
| | | supplierId: null, // ä¾åºåID |
| | | supplierName: '', // ä¾åºååç§° |
| | | recorderId: userStore.userId, // å½å
¥äººID |
| | | recorderName: userStore.name, // å½å
¥äººå§å |
| | | entryDate: getCurrentDate(), // å½å
¥æ¥æ |
| | | remark: '', // 夿³¨ |
| | | }, |
| | | rules: { |
| | | supplierName: [{ required: true, message: "请è¾å
¥ä¾åºååç§°", trigger: "blur" }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | | |
| | | // æ°å¢äº§åè¡ |
| | | const addProductRow = () => { |
| | | productList.value.push({ |
| | | id: null, |
| | | productCategory: '', |
| | | specificationModel: '', |
| | | unit: '', |
| | | supplierName: form.value.supplierName || '', |
| | | itemType: '', |
| | | inboundNum: 0, |
| | | inboundDate: '', |
| | | quantityStock: 0, |
| | | unitPrice: 0, |
| | | totalPrice: 0, |
| | | taxRate: null, |
| | | taxExclusiveTotalPrice: 0, |
| | | }); |
| | | }; |
| | | |
| | | // å é¤äº§åè¡ |
| | | const removeProductRow = (index) => { |
| | | productList.value.splice(index, 1); |
| | | }; |
| | | |
| | | // è®¡ç®æ»ä»·ï¼æ ¹æ®æ°éãåä»·åå«ç¨åä»·ï¼ |
| | | const calculateTotalPrice = (row) => { |
| | | // è®¡ç®æ®éæ»ä»·ï¼inboundNum * unitPrice |
| | | const quantity = Number(row.inboundNum || 0); |
| | | const unitPrice = Number(row.unitPrice || 0); |
| | | row.totalPrice = quantity * unitPrice; |
| | | calculateExclusivePrice(row); |
| | | }; |
| | | |
| | | // 计ç®ä¸å«ç¨æ»ä»·ï¼æ ¹æ®å«ç¨æ»ä»·åç¨çï¼ |
| | | const calculateExclusivePrice = (row) => { |
| | | const totalPrice = Number(row.totalPrice || 0); |
| | | const taxRate = Number(row.taxRate || 0); |
| | | row.taxExclusiveTotalPrice = totalPrice / (1 + taxRate / 100); |
| | | }; |
| | | |
| | | const submitForm = async () => { |
| | | try { |
| | | await proxy.$refs.formRef.validate() |
| | | |
| | | if (!productList.value.length) { |
| | | proxy.$modal.msgError('请è³å°æ·»å 䏿¡äº§åæ°æ®') |
| | | return |
| | | } |
| | | |
| | | // éªè¯èªå®ä¹æ·»å çæ°æ®å¿
å¡«åæ®µ |
| | | for (let i = 0; i < productList.value.length; i++) { |
| | | const product = productList.value[i]; |
| | | if (!product.productCategory || !product.specificationModel || !product.unit) { |
| | | proxy.$modal.msgError(`第${i + 1}è¡äº§åæ°æ®æªå¡«å宿´ï¼äº§å大类ãè§æ ¼åå·ãåä½ä¸ºå¿
å¡«ï¼`) |
| | | return |
| | | } |
| | | if (!product.inboundDate) { |
| | | proxy.$modal.msgError(`第${i + 1}è¡è¯·éæ©å
¥åºæ¥æ`) |
| | | return |
| | | } |
| | | const stock = Number(product?.inboundNum ?? 0); |
| | | if (!Number.isFinite(stock) || stock <= 0) { |
| | | proxy.$modal.msgError(`第${i + 1}è¡æ¬æ¬¡å
¥åºæ°éé大äº0`) |
| | | return |
| | | } |
| | | } |
| | | |
| | | const payloadList = productList.value.map(product => ({ |
| | | id: product.id ?? null, |
| | | inboundNum: Number(product.inboundNum), |
| | | productCategory: product.productCategory, |
| | | specificationModel: product.specificationModel, |
| | | unit: product.unit, |
| | | supplierName: product.supplierName || form.value.supplierName, |
| | | itemType: product.itemType, |
| | | inboundDate: formatDateTime(product.inboundDate, false), |
| | | taxRate: Number(product.taxRate || 0), |
| | | taxExclusiveTotalPrice: Number(product.taxExclusiveTotalPrice || 0), |
| | | unitPrice: Number(product.unitPrice || 0), |
| | | })); |
| | | loading.value = true |
| | | if (operationType.value === 'edit') { |
| | | const editPayload = payloadList[0] |
| | | await updateProduct(editPayload) |
| | | } else { |
| | | await addStockInCustom(payloadList) |
| | | } |
| | | |
| | | proxy.$modal.msgSuccess(operationType.value === 'edit' ? 'ç¼è¾èªå®ä¹å
¥åºæå' : 'æ°å¢èªå®ä¹å
¥åºæå') |
| | | closeDia() |
| | | emit('success') |
| | | |
| | | } catch (error) { |
| | | console.error('æäº¤å¤±è´¥:', error) |
| | | if (!error.errors) { |
| | | proxy.$modal.msgError('æä½å¤±è´¥ï¼è¯·éè¯') |
| | | } |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | const closeDia = () => { |
| | | proxy.$refs.formRef.resetFields() |
| | | dialogFormVisible.value = false |
| | | productList.value = [] |
| | | emit('close') |
| | | } |
| | | |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type |
| | | dialogFormVisible.value = true |
| | | |
| | | if (type === 'add') { |
| | | form.value = { |
| | | id: null, |
| | | supplierId: null, |
| | | supplierName: '', |
| | | recorderId: userStore.userId, |
| | | recorderName: userStore.name, |
| | | entryDate: getCurrentDate(), |
| | | remark: '' |
| | | } |
| | | productList.value = [] |
| | | } else { |
| | | // ç¼è¾æ¨¡å¼ï¼å°è¡æ°æ®å¡«å
å°è¡¨æ ¼ä¸ä»¥æ¯æä¿®æ¹ |
| | | form.value = { |
| | | id: row?.id ?? null, |
| | | supplierId: row?.supplierId ?? null, |
| | | supplierName: row?.supplierName ?? '', |
| | | recorderId: userStore.userId, |
| | | recorderName: userStore.name, |
| | | entryDate: getCurrentDate(), |
| | | remark: row?.remark ?? '' |
| | | } |
| | | productList.value = [{ |
| | | id: row?.id ?? null, |
| | | productCategory: row?.productCategory ?? '', |
| | | specificationModel: row?.specificationModel ?? '', |
| | | unit: row?.unit ?? '', |
| | | supplierName: row?.supplierName ?? '', |
| | | itemType: row?.itemType ?? '', |
| | | inboundNum: Number(row?.inboundNum ?? row?.inboundQuantity ?? 0), |
| | | inboundDate: row?.inboundDate ?? row?.createTime ?? '', |
| | | taxRate: Number(row?.taxRate ?? 0), |
| | | unitPrice: Number(row?.unitPrice ?? 0), |
| | | taxExclusiveTotalPrice: Number(row?.taxExclusiveTotalPrice ?? 0), |
| | | }] |
| | | } |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢ææåºå' : 'ç¼è¾ææåºå'" width="70%" |
| | | @update:model-value="$emit('update:dialogFormVisible', $event)" @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产å大类ï¼" prop="productCategory"> |
| | | <el-input disabled v-model="form.productCategory" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="specificationModel"> |
| | | <el-input disabled v-model="form.specificationModel" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½ï¼" prop="unit"> |
| | | <el-input disabled v-model="form.unit" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç©åç±»åï¼" prop="itemType"> |
| | | <el-input disabled v-model="form.itemType" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¥åºæ¶é´ï¼" prop="createTime"> |
| | | <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD" |
| | | type="date" placeholder="è¯·éæ©" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåæ°éï¼" prop="inboundNum"> |
| | | <el-input v-model="form.inboundNum" placeholder="请è¾å
¥" clearable @input="calculateTotalPrice" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å·²åºåºæ°éï¼" prop="totalInboundNum"> |
| | | <el-input disabled v-model="form.totalInboundNum" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¾
åºåºæ°éï¼" prop="inboundNum0"> |
| | | <el-input disabled v-model="form.inboundNum0" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä»·(å
)ï¼" prop="taxInclusiveUnitPrice"> |
| | | <el-input v-model="form.taxInclusiveUnitPrice" placeholder="请è¾å
¥" clearable @input="calculateTotalPrice" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ»ä»·(å
)ï¼" prop="taxInclusiveTotalPrice"> |
| | | <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="èªå¨è®¡ç®" clearable /> |
| | | </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> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, watch } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | dialogFormVisible: Boolean, |
| | | operationType: String, |
| | | formData: Object |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close']) |
| | | |
| | | const formRef = ref() |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | productCategory: '', |
| | | specificationModel: '', |
| | | unit: '', |
| | | itemType: '', |
| | | createTime: '', |
| | | inboundNum: '', |
| | | totalInboundNum: '', |
| | | inboundNum0: '', |
| | | taxInclusiveUnitPrice: '', |
| | | taxInclusiveTotalPrice: '' |
| | | }, |
| | | rules: { |
| | | productCategory: [{ required: true, message: '请è¾å
¥äº§å大类', trigger: 'blur' }], |
| | | specificationModel: [{ required: true, message: '请è¾å
¥è§æ ¼åå·', trigger: 'blur' }], |
| | | unit: [{ required: true, message: '请è¾å
¥åä½', trigger: 'blur' }], |
| | | itemType: [{ required: true, message: '请è¾å
¥ç©åç±»å', trigger: 'blur' }], |
| | | createTime: [{ required: true, message: 'è¯·éæ©å
¥åºæ¶é´', trigger: 'change' }], |
| | | inboundNum: [{ required: true, message: '请è¾å
¥åºåæ°é', trigger: 'blur' }], |
| | | taxInclusiveUnitPrice: [{ required: true, message: '请è¾å
¥åä»·', trigger: 'blur' }] |
| | | } |
| | | }) |
| | | |
| | | const { form, rules } = toRefs(data) |
| | | |
| | | // è®¡ç®æ»ä»·ï¼æ»ä»· = åä»· à å©ä½åºå |
| | | const calculateTotalPrice = () => { |
| | | const unitPrice = parseFloat(form.value.taxInclusiveUnitPrice) || 0 |
| | | const stockQuantity = parseFloat(form.value.inboundNum) || 0 // åºåæ°é |
| | | const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // å·²åºåºæ°é |
| | | const remainingStock = stockQuantity - outboundQuantity // å©ä½åºå |
| | | form.value.taxInclusiveTotalPrice = (unitPrice * remainingStock).toFixed(2) |
| | | } |
| | | |
| | | // çå¬formDataåå |
| | | watch(() => props.formData, (newVal) => { |
| | | if (newVal) { |
| | | form.value = { ...newVal } |
| | | // æ°æ®åååéæ°è®¡ç®æ»ä»· |
| | | calculateTotalPrice() |
| | | } |
| | | }, { immediate: true }) |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | emit('submit', form.value) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | emit('close') |
| | | emit('update:dialogFormVisible', false) |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dialog-footer { |
| | | text-align: center; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢æååºå' : 'ç¼è¾æååºå'" width="70%" |
| | | @update:model-value="$emit('update:dialogFormVisible', $event)" @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产å大类ï¼" prop="productCategory"> |
| | | <el-input disabled v-model="form.productCategory" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="specificationModel"> |
| | | <el-input disabled v-model="form.specificationModel" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½ï¼" prop="unit"> |
| | | <el-input disabled v-model="form.unit" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¥åºæ¶é´ï¼" prop="createTime"> |
| | | <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD" |
| | | type="date" placeholder="è¯·éæ©" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåæ°éï¼" prop="inboundNum"> |
| | | <el-input v-model="form.inboundNum" placeholder="请è¾å
¥" clearable @input="calculateTotalPrice" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å·²åºåºæ°éï¼" prop="totalInboundNum"> |
| | | <el-input disabled v-model="form.totalInboundNum" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¾
åºåºæ°éï¼" prop="inboundNum0"> |
| | | <el-input disabled v-model="form.inboundNum0" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä»·(å
)ï¼" prop="unitPrice"> |
| | | <el-input v-model="form.unitPrice" placeholder="请è¾å
¥" clearable @input="calculateTotalPrice" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ»ä»·(å
)ï¼" prop="totalPrice"> |
| | | <el-input disabled v-model="form.totalPrice" placeholder="èªå¨è®¡ç®" clearable /> |
| | | </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> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, watch } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | dialogFormVisible: Boolean, |
| | | operationType: String, |
| | | formData: Object |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close']) |
| | | |
| | | const formRef = ref() |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | productCategory: '', |
| | | specificationModel: '', |
| | | unit: '', |
| | | createTime: '', |
| | | inboundNum: '', |
| | | totalInboundNum: '', |
| | | inboundNum0: '', |
| | | unitPrice: '', |
| | | totalPrice: '' |
| | | }, |
| | | rules: { |
| | | productCategory: [{ required: true, message: '请è¾å
¥äº§å大类', trigger: 'blur' }], |
| | | specificationModel: [{ required: true, message: '请è¾å
¥è§æ ¼åå·', trigger: 'blur' }], |
| | | unit: [{ required: true, message: '请è¾å
¥åä½', trigger: 'blur' }], |
| | | createTime: [{ required: true, message: 'è¯·éæ©å
¥åºæ¶é´', trigger: 'change' }], |
| | | inboundNum: [{ required: true, message: '请è¾å
¥åºåæ°é', trigger: 'blur' }], |
| | | unitPrice: [{ required: true, message: '请è¾å
¥åä»·', trigger: 'blur' }] |
| | | } |
| | | }) |
| | | |
| | | const { form, rules } = toRefs(data) |
| | | |
| | | // è®¡ç®æ»ä»·ï¼æ»ä»· = åä»· à å©ä½åºå |
| | | const calculateTotalPrice = () => { |
| | | const unitPrice = parseFloat(form.value.unitPrice) || 0 |
| | | const stockQuantity = parseFloat(form.value.inboundNum) || 0 // åºåæ°é |
| | | const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // å·²åºåºæ°é |
| | | const remainingStock = stockQuantity - outboundQuantity // å©ä½åºå |
| | | form.value.totalPrice = (unitPrice * remainingStock).toFixed(2) |
| | | } |
| | | |
| | | // çå¬formDataåå |
| | | watch(() => props.formData, (newVal) => { |
| | | if (newVal) { |
| | | form.value = { ...newVal } |
| | | // æ°æ®åååéæ°è®¡ç®æ»ä»· |
| | | calculateTotalPrice() |
| | | } |
| | | }, { immediate: true }) |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | emit('submit', form.value) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | emit('close') |
| | | emit('update:dialogFormVisible', false) |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dialog-footer { |
| | | text-align: center; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢åæåºå' : 'ç¼è¾åæåºå'" width="70%" |
| | | @update:model-value="$emit('update:dialogFormVisible', $event)" @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产å大类ï¼" prop="productCategory"> |
| | | <el-input disabled v-model="form.productCategory" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="specificationModel"> |
| | | <el-input disabled v-model="form.specificationModel" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½ï¼" prop="unit"> |
| | | <el-input disabled v-model="form.unit" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¥åºæ¶é´ï¼" prop="createTime"> |
| | | <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD" |
| | | type="date" placeholder="è¯·éæ©" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåæ°éï¼" prop="inboundNum"> |
| | | <el-input v-model="form.inboundNum" placeholder="请è¾å
¥" clearable @input="calculateTotalPrice" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å·²åºåºæ°éï¼" prop="totalInboundNum"> |
| | | <el-input disabled v-model="form.totalInboundNum" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¾
åºåºæ°éï¼" prop="inboundNum0"> |
| | | <el-input disabled v-model="form.inboundNum0" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å«ç¨åä»·(å
)ï¼" prop="taxInclusiveUnitPrice"> |
| | | <el-input v-model="form.taxInclusiveUnitPrice" placeholder="请è¾å
¥" clearable @input="calculateTotalPrice" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å«ç¨æ»ä»·(å
)ï¼" prop="taxInclusiveTotalPrice"> |
| | | <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="èªå¨è®¡ç®" clearable /> |
| | | </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> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, watch } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | dialogFormVisible: Boolean, |
| | | operationType: String, |
| | | formData: Object |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close']) |
| | | |
| | | const formRef = ref() |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | productCategory: '', |
| | | specificationModel: '', |
| | | unit: '', |
| | | createTime: '', |
| | | inboundNum: '', |
| | | totalInboundNum: '', |
| | | inboundNum0: '', |
| | | taxInclusiveUnitPrice: '', |
| | | taxInclusiveTotalPrice: '' |
| | | }, |
| | | rules: { |
| | | productCategory: [{ required: true, message: '请è¾å
¥äº§å大类', trigger: 'blur' }], |
| | | specificationModel: [{ required: true, message: '请è¾å
¥è§æ ¼åå·', trigger: 'blur' }], |
| | | unit: [{ required: true, message: '请è¾å
¥åä½', trigger: 'blur' }], |
| | | createTime: [{ required: true, message: 'è¯·éæ©å
¥åºæ¶é´', trigger: 'change' }], |
| | | inboundNum: [{ required: true, message: '请è¾å
¥åºåæ°é', trigger: 'blur' }], |
| | | taxInclusiveUnitPrice: [{ required: true, message: '请è¾å
¥å«ç¨åä»·', trigger: 'blur' }] |
| | | } |
| | | }) |
| | | |
| | | const { form, rules } = toRefs(data) |
| | | |
| | | // è®¡ç®æ»ä»·ï¼å«ç¨æ»ä»· = å«ç¨åä»· à å©ä½åºå |
| | | const calculateTotalPrice = () => { |
| | | const unitPrice = parseFloat(form.value.taxInclusiveUnitPrice) || 0 |
| | | const stockQuantity = parseFloat(form.value.inboundNum) || 0 // åºåæ°é |
| | | const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // å·²åºåºæ°é |
| | | const remainingStock = stockQuantity - outboundQuantity // å©ä½åºå |
| | | form.value.taxInclusiveTotalPrice = (unitPrice * remainingStock).toFixed(2) |
| | | } |
| | | |
| | | // çå¬formDataåå |
| | | watch(() => props.formData, (newVal) => { |
| | | if (newVal) { |
| | | form.value = { ...newVal } |
| | | // æ°æ®åååéæ°è®¡ç®æ»ä»· |
| | | calculateTotalPrice() |
| | | } |
| | | }, { immediate: true }) |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | emit('submit', form.value) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | emit('close') |
| | | emit('update:dialogFormVisible', false) |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dialog-footer { |
| | | text-align: center; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æç´¢è¡¨å --> |
| | | <div class="search_form"> |
| | | <div class="search_left"> |
| | | <span class="search_title">æ¥è¡¨ç±»åï¼</span> |
| | | <el-select |
| | | v-model="searchForm.reportType" |
| | | style="width: 150px;" |
| | | placeholder="è¯·éæ©" |
| | | @change="handleReportTypeChange" |
| | | > |
| | | <el-option label="æ¥æ¥" value="daily" /> |
| | | <el-option label="ææ¥" value="monthly" /> |
| | | <el-option label="ä½ä¸æ¥è¡¨" value="work" /> |
| | | <el-option label="è¿åºåæ¥è¡¨" value="inout" /> |
| | | </el-select> |
| | | |
| | | <span class="search_title ml10">æ¶é´èå´ï¼</span> |
| | | <el-date-picker |
| | | v-if="searchForm.reportType === 'daily'" |
| | | v-model="searchForm.singleDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 200px;" |
| | | /> |
| | | <el-date-picker |
| | | v-else-if="searchForm.reportType === 'monthly'" |
| | | v-model="searchForm.monthRange" |
| | | type="monthrange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æä»½" |
| | | end-placeholder="ç»ææä»½" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 240px;" |
| | | /> |
| | | <el-date-picker |
| | | v-else |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 240px;" |
| | | /> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æ¥è¯¢ |
| | | </el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | </div> |
| | | |
| | | <div class="search_right"> |
| | | <el-button type="success" @click="handleExport" icon="Download"> |
| | | å¯¼åºæ¥è¡¨ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç»è®¡å¡ç --> |
| | | <div class="stats_cards" v-if="reportData.summary"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="stats_card"> |
| | | <div class="stats_content"> |
| | | <div class="stats_icon in"> |
| | | <el-icon><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="stats_info"> |
| | | <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div> |
| | | <div class="stats_label">æ»å
¥åºé</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stats_card"> |
| | | <div class="stats_content"> |
| | | <div class="stats_icon out"> |
| | | <el-icon><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="stats_info"> |
| | | <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div> |
| | | <div class="stats_label">æ»åºåºé</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stats_card"> |
| | | <div class="stats_content"> |
| | | <div class="stats_icon stock"> |
| | | <el-icon><Box /></el-icon> |
| | | </div> |
| | | <div class="stats_info"> |
| | | <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div> |
| | | <div class="stats_label">å½ååºå</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stats_card"> |
| | | <div class="stats_content"> |
| | | <div class="stats_icon turnover"> |
| | | <el-icon><Refresh /></el-icon> |
| | | </div> |
| | | <div class="stats_info"> |
| | | <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div> |
| | | <div class="stats_label">å¨è½¬ç</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <div class="chart_section" v-if="reportData.chartData"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card> |
| | | <template #header> |
| | | <span>åºåè¶å¿å¾</span> |
| | | </template> |
| | | <div ref="trendChart" style="height: 300px;"></div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card> |
| | | <template #header> |
| | | <span>è¿åºåºå¯¹æ¯</span> |
| | | </template> |
| | | <div ref="comparisonChart" style="height: 300px;"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- è¯¦ç»æ°æ®è¡¨æ ¼ --> |
| | | <div class="table_section"> |
| | | <el-card> |
| | | <template #header> |
| | | <span>{{ getTableTitle() }}</span> |
| | | </template> |
| | | <el-table |
| | | v-loading="tableLoading" |
| | | :data="reportData.tableData" |
| | | border |
| | | height="400" |
| | | style="width: 100%" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column |
| | | v-if="searchForm.reportType === 'daily'" |
| | | label="æ¥æ" |
| | | prop="createTime" |
| | | width="100" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | v-if="searchForm.reportType === 'monthly'" |
| | | label="æä»½" |
| | | prop="createTime" |
| | | width="100" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | label="å
¥åºæ¶é´" |
| | | prop="createTime" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å
¥åºæ¹æ¬¡" |
| | | prop="inboundBatches" |
| | | width="160" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="产å大类" |
| | | prop="productCategory" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="è§æ ¼åå·" |
| | | prop="specificationModel" |
| | | min-width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åä½" |
| | | prop="unit" |
| | | width="70" |
| | | show-overflow-tooltip |
| | | /> |
| | | <!-- <el-table-column |
| | | label="æååºå" |
| | | prop="beginStock" |
| | | width="100" |
| | | align="center" |
| | | /> --> |
| | | <el-table-column |
| | | label="å
¥åºæ°é" |
| | | prop="inboundNum" |
| | | width="100" |
| | | align="center" |
| | | /> |
| | | <!-- <el-table-column |
| | | label="åºåºæ°é" |
| | | prop="" |
| | | width="100" |
| | | align="center" |
| | | /> --> |
| | | <el-table-column |
| | | label="ç°å¨åºå" |
| | | prop="inboundNum0" |
| | | width="100" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨åä»·" |
| | | prop="taxInclusiveUnitPrice" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ç¨ç(%)" |
| | | prop="taxRate" |
| | | width="80" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¸å«ç¨æ»ä»·" |
| | | prop="taxExclusiveTotalPrice" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å
¥åºäºº" |
| | | prop="createBy" |
| | | width="80" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | v-if="searchForm.reportType === 'work'" |
| | | label="æä½äººå" |
| | | prop="operator" |
| | | width="80" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | v-if="searchForm.reportType === 'work'" |
| | | label="æä½æ¶é´" |
| | | prop="operateTime" |
| | | width="150" |
| | | align="center" |
| | | /> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import * as echarts from 'echarts' |
| | | import { |
| | | getStockDailyReport, |
| | | getStockMonthlyReport, |
| | | getWorkReport, |
| | | getStockInOutReport, |
| | | exportStockReport |
| | | } from '@/api/inventoryManagement/stockReport' |
| | | |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | // ååºå¼æ°æ® |
| | | const tableLoading = ref(false) |
| | | const trendChart = ref(null) |
| | | const comparisonChart = ref(null) |
| | | |
| | | const searchForm = reactive({ |
| | | reportType: 'daily', |
| | | singleDate: '', |
| | | dateRange: [], |
| | | monthRange: [] |
| | | }) |
| | | |
| | | const reportData = ref({ |
| | | summary: null, |
| | | chartData: null, |
| | | tableData: [] |
| | | }) |
| | | |
| | | // è·åè¡¨æ ¼æ é¢ |
| | | const getTableTitle = () => { |
| | | const typeMap = { |
| | | daily: 'æ¥æ¥è¯¦ç»æ°æ®', |
| | | monthly: 'ææ¥è¯¦ç»æ°æ®', |
| | | work: 'ä½ä¸æ¥è¡¨è¯¦ç»æ°æ®', |
| | | inout: 'è¿åºåæ¥è¡¨è¯¦ç»æ°æ®' |
| | | } |
| | | return typeMap[searchForm.reportType] || 'æ¥è¡¨è¯¦ç»æ°æ®' |
| | | } |
| | | |
| | | // æ¥è¡¨ç±»åæ¹å |
| | | const handleReportTypeChange = () => { |
| | | reportData.value = { |
| | | summary: null, |
| | | chartData: null, |
| | | tableData: [] |
| | | } |
| | | } |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleQuery = async () => { |
| | | if (!validateSearchForm()) { |
| | | return |
| | | } |
| | | |
| | | tableLoading.value = true |
| | | try { |
| | | const params = getQueryParams() |
| | | let response |
| | | |
| | | switch (searchForm.reportType) { |
| | | case 'daily': |
| | | response = await getStockDailyReport(params) |
| | | break |
| | | case 'monthly': |
| | | response = await getStockMonthlyReport(params) |
| | | break |
| | | case 'work': |
| | | response = await getWorkReport(params) |
| | | break |
| | | case 'inout': |
| | | response = await getStockInOutReport(params) |
| | | break |
| | | default: |
| | | throw new Error('æªç¥çæ¥è¡¨ç±»å') |
| | | } |
| | | |
| | | if (response.code === 200) { |
| | | // generateMockData() |
| | | reportData.value.tableData = response.data.tableData |
| | | reportData.value.summary = response.data.summary |
| | | reportData.value.chartData = response.data.chartData |
| | | nextTick(() => { |
| | | initCharts() |
| | | }) |
| | | |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('æ¥è¯¢å¤±è´¥ï¼' + error.message) |
| | | } finally { |
| | | tableLoading.value = false |
| | | } |
| | | } |
| | | // // çæåæ°æ® |
| | | // const generateMockData = () => { |
| | | // // çæç»è®¡å¡çåæ°æ® |
| | | // const summary = { |
| | | // totalIn: 1000, |
| | | // totalOut: 600, |
| | | // currentStock: 400, |
| | | // turnoverRate: 30 |
| | | // } |
| | | |
| | | // // çæå¾è¡¨åæ°æ® |
| | | // const trendDates = ['2025-09-15', '2025-09-16', '2025-09-17', '2025-09-18', '2025-09-19'] |
| | | // const trendValues = [300, 350, 400, 380, 420] |
| | | // const comparisonDates = ['2025-09-15', '2025-09-16', '2025-09-17'] |
| | | // const inValues = [100, 150, 200] |
| | | // const outValues = [80, 120, 100] |
| | | |
| | | // const chartData = { |
| | | // trendDates, |
| | | // trendValues, |
| | | // comparisonDates, |
| | | // inValues, |
| | | // outValues |
| | | // } |
| | | |
| | | // reportData.value = { |
| | | // summary, |
| | | // chartData, |
| | | // tableData: [] |
| | | // } |
| | | // } |
| | | // éªè¯æç´¢è¡¨å |
| | | const validateSearchForm = () => { |
| | | if (searchForm.reportType === 'daily') { |
| | | if (!searchForm.singleDate) { |
| | | ElMessage.warning('è¯·éæ©æ¥æ') |
| | | return false |
| | | } |
| | | } else if (searchForm.reportType === 'work' || searchForm.reportType === 'inout') { |
| | | if (!searchForm.dateRange || searchForm.dateRange.length !== 2) { |
| | | ElMessage.warning('è¯·éæ©æ¥æèå´') |
| | | return false |
| | | } |
| | | } else if (searchForm.reportType === 'monthly') { |
| | | if (!searchForm.monthRange || searchForm.monthRange.length !== 2) { |
| | | ElMessage.warning('è¯·éæ©æä»½èå´') |
| | | return false |
| | | } |
| | | } |
| | | return true |
| | | } |
| | | |
| | | // è·åæ¥è¯¢åæ° |
| | | const getQueryParams = () => { |
| | | const params = { |
| | | reportType: searchForm.reportType, |
| | | reportDate: "", |
| | | startMonth: "", |
| | | endMonth: "", |
| | | startDate: "", |
| | | endDate: "" |
| | | } |
| | | |
| | | if (searchForm.reportType === 'daily') { |
| | | params.reportDate = searchForm.singleDate |
| | | } else if (searchForm.reportType === 'monthly') { |
| | | params.startMonth = searchForm.monthRange[0] |
| | | params.endMonth = searchForm.monthRange[1] |
| | | } else { |
| | | params.startDate = searchForm.dateRange[0] |
| | | params.endDate = searchForm.dateRange[1] |
| | | } |
| | | |
| | | return params |
| | | } |
| | | |
| | | // éç½®æç´¢ |
| | | const handleReset = () => { |
| | | searchForm.reportType = 'daily' |
| | | searchForm.singleDate = '' |
| | | searchForm.dateRange = [] |
| | | searchForm.monthRange = [] |
| | | reportData.value = { |
| | | summary: null, |
| | | chartData: null, |
| | | tableData: [] |
| | | } |
| | | } |
| | | |
| | | // å¯¼åºæ¥è¡¨ |
| | | const handleExport = async () => { |
| | | if (!validateSearchForm()) { |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const params = getQueryParams() |
| | | // const response = await exportStockReport(params) |
| | | proxy.download("/stockin/exportCopy", params, 'åºåæ¥è¡¨.xlsx') |
| | | // å建ä¸è½½é¾æ¥ |
| | | // const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) |
| | | // const url = window.URL.createObjectURL(blob) |
| | | // const link = document.createElement('a') |
| | | // link.href = url |
| | | // link.download = `${getTableTitle()}_${new Date().getTime()}.xlsx` |
| | | // document.body.appendChild(link) |
| | | // link.click() |
| | | // document.body.removeChild(link) |
| | | // window.URL.revokeObjectURL(url) |
| | | |
| | | // ElMessage.success('å¯¼åºæå') |
| | | } catch (error) { |
| | | ElMessage.error('导åºå¤±è´¥ï¼' + error.message) |
| | | } |
| | | } |
| | | |
| | | // åå§åå¾è¡¨ |
| | | const initCharts = () => { |
| | | if (!reportData.value.chartData) return |
| | | |
| | | initTrendChart() |
| | | initComparisonChart() |
| | | } |
| | | |
| | | // åå§åè¶å¿å¾ |
| | | const initTrendChart = () => { |
| | | if (!trendChart.value) return |
| | | |
| | | const chart = echarts.init(trendChart.value) |
| | | const option = { |
| | | title: { |
| | | text: 'åºåååè¶å¿', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | legend: { |
| | | data: ['åºåé'], |
| | | top: 30 |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: reportData.value.chartData.trendDates || [] |
| | | }, |
| | | yAxis: { |
| | | type: 'value' |
| | | }, |
| | | series: [{ |
| | | name: 'åºåé', |
| | | type: 'line', |
| | | data: reportData.value.chartData.trendValues || [], |
| | | smooth: true, |
| | | itemStyle: { |
| | | color: '#409EFF' |
| | | } |
| | | }] |
| | | } |
| | | chart.setOption(option) |
| | | } |
| | | |
| | | // åå§å对æ¯å¾ |
| | | const initComparisonChart = () => { |
| | | if (!comparisonChart.value) return |
| | | |
| | | const chart = echarts.init(comparisonChart.value) |
| | | const option = { |
| | | title: { |
| | | text: 'è¿åºåºå¯¹æ¯', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | legend: { |
| | | data: ['å
¥åº', 'åºåº'], |
| | | top: 30 |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: reportData.value.chartData.comparisonDates || [] |
| | | }, |
| | | yAxis: { |
| | | type: 'value' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'å
¥åº', |
| | | type: 'bar', |
| | | data: reportData.value.chartData.inValues || [], |
| | | itemStyle: { |
| | | color: '#67C23A' |
| | | } |
| | | }, |
| | | { |
| | | name: 'åºåº', |
| | | type: 'bar', |
| | | data: reportData.value.chartData.outValues || [], |
| | | itemStyle: { |
| | | color: '#F56C6C' |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | chart.setOption(option) |
| | | } |
| | | |
| | | // ç»ä»¶æè½½æ¶è®¾ç½®é»è®¤æ¶é´ |
| | | onMounted(() => { |
| | | const today = new Date() |
| | | searchForm.singleDate = today.toISOString().split('T')[0] |
| | | |
| | | const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000) |
| | | searchForm.dateRange = [ |
| | | yesterday.toISOString().split('T')[0], |
| | | today.toISOString().split('T')[0] |
| | | ] |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .search_form { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background: #fff; |
| | | border-radius: 4px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .search_left { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .search_title { |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .ml10 { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .stats_cards { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .stats_card { |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .stats_content { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .stats_icon { |
| | | width: 50px; |
| | | height: 50px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 15px; |
| | | font-size: 24px; |
| | | color: #fff; |
| | | } |
| | | |
| | | .stats_icon.in { |
| | | background: linear-gradient(135deg, #67C23A, #85CE61); |
| | | } |
| | | |
| | | .stats_icon.out { |
| | | background: linear-gradient(135deg, #F56C6C, #F78989); |
| | | } |
| | | |
| | | .stats_icon.stock { |
| | | background: linear-gradient(135deg, #409EFF, #66B1FF); |
| | | } |
| | | |
| | | .stats_icon.turnover { |
| | | background: linear-gradient(135deg, #E6A23C, #EEBE77); |
| | | } |
| | | |
| | | .stats_info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .stats_value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #333; |
| | | line-height: 1; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .stats_label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | |
| | | .chart_section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .table_section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | :deep(.el-card__header) { |
| | | background: #f8f9fa; |
| | | border-bottom: 1px solid #e9ecef; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | :deep(.el-table .el-table__header-wrapper th) { |
| | | background-color: #F0F1F5 !important; |
| | | color: #333333; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | :deep(.el-table .el-table__body-wrapper td) { |
| | | padding: 8px 0; |
| | | } |
| | | </style> |