|
|
@@ -0,0 +1,200 @@
|
|
|
+<template>
|
|
|
+ <div class="dashboard">
|
|
|
+ <div class="dashboard-card" v-for="item in dashboardItems" :key="item.title">
|
|
|
+ <span class="dashboard-card__title">{{ item.title }}</span>
|
|
|
+ <div class="dashboard-card__value">
|
|
|
+ <span class="dashboard-card__number">{{ item.value }}</span>
|
|
|
+ <span class="dashboard-card__unit">{{ item.unit }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="background: white; margin-top: 10px; padding: 10px">
|
|
|
+ <a-button type="primary" style="margin: 10px 0 10px 0" @click="open = true"> 成本分析 </a-button>
|
|
|
+ <a-table :columns="columns" :data-source="tableData" bordered :row-class-name="() => 'custom-row'" :scroll="{ y: 400 }">
|
|
|
+ <template #money="{ text, record, index }">
|
|
|
+ <div class="editable-cell">
|
|
|
+ <div v-if="isEditable(record.age1) && editableData[index]" class="editable-cell-input-wrapper">
|
|
|
+ <a-input-number v-model:value="editableData[index].phone1" :min="0" style="width: 100px" @pressEnter="save(index)" />
|
|
|
+ <check-outlined class="editable-cell-icon-check" @click="save(index)" />
|
|
|
+ </div>
|
|
|
+ <div v-else class="editable-cell-text-wrapper">
|
|
|
+ {{ text}}
|
|
|
+ <edit-outlined v-if="isEditable(record.age1)" class="editable-cell-icon" @click="edit(index)" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </a-table>
|
|
|
+ <a-drawer v-model:open="open" width="1300" placement="right" title="成本分析">
|
|
|
+ <CostAnalysis />
|
|
|
+ <Echar />
|
|
|
+ </a-drawer>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+ import { ref, reactive } from 'vue';
|
|
|
+ import { cloneDeep } from 'lodash-es';
|
|
|
+ import { CheckOutlined, EditOutlined } from '@ant-design/icons-vue';
|
|
|
+ import CostAnalysis from './cost-analysis/index.vue';
|
|
|
+ import Echar from './echar.vue';
|
|
|
+
|
|
|
+ const dashboardItems = [
|
|
|
+ { title: '任务总数', value: 12, unit: '个' },
|
|
|
+ { title: '预计人天', value: 12, unit: '人天' },
|
|
|
+ { title: '累计发生人天', value: 12, unit: '天' },
|
|
|
+ { title: '超预计人天数', value: 12, unit: '天' },
|
|
|
+ { title: '超期任务数', value: 12, unit: '个' },
|
|
|
+ ];
|
|
|
+
|
|
|
+ const open = ref(false);
|
|
|
+
|
|
|
+ const tableData = ref([
|
|
|
+ { key: '1', name: '人力成本', age1: '人力薪资', tel: '0571-22098909', phone1: 3000, phone: 5000 },
|
|
|
+ { key: '2', name: '人力成本', age1: '人员补贴', tel: '0571-22098333', phone1: 1000, phone: 5000 },
|
|
|
+ { key: '3', name: '人力成本', age1: '福利费', tel: '0575-22098909', phone1: 2000, phone: 5000 },
|
|
|
+ { key: '4', name: '人力成本', age1: '劳务费', tel: '0575-22098909', phone1: 2000, phone: 5000 },
|
|
|
+ { key: '5', name: '差旅成本', age1: '交通费', tel: '0575-22098909', phone1: 2000, phone: 2000 },
|
|
|
+ { key: '6', name: '差旅成本', age1: '住宿费', tel: '0575-22098909', phone1: 1000, phone: 2000 },
|
|
|
+ { key: '7', name: '销售成本', age1: '营销商务费用', tel: '0575-22098909', phone1: 1000, phone: 2000 },
|
|
|
+ { key: '8', name: '经营成本', age1: '物料/设备采买', tel: '0575-22098909', phone1: 1000, phone: 2000 },
|
|
|
+ { key: '9', name: '经营成本', age1: '房租', tel: '0575-22098909', phone1: 1000, phone: 2000 },
|
|
|
+ { key: '10', name: '经营成本', age1: '管理费', tel: '0575-22098909', phone1: 1000, phone: 2000 },
|
|
|
+ { key: '11', name: '经营成本', age1: '公司运营成本(分摊)', tel: '0575-22098909', phone1: 1000, phone: 2000 },
|
|
|
+ { key: '12', name: '经营成本', age1: '税费', tel: '0575-22098909', phone1: 1000, phone: 2000 },
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const editableData = reactive({});
|
|
|
+
|
|
|
+ const isEditable = (costItem) => {
|
|
|
+ return ['人力薪资', '人员补贴', '人员绩效', '劳务费', '公司运营成本(分摊)'].includes(costItem);
|
|
|
+ };
|
|
|
+
|
|
|
+ const edit = (index) => {
|
|
|
+ if (isEditable(tableData.value[index].age1)) {
|
|
|
+ editableData[index] = cloneDeep(tableData.value[index]);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const save = (index) => {
|
|
|
+ if (isEditable(tableData.value[index].age1)) {
|
|
|
+ Object.assign(tableData.value[index], editableData[index]);
|
|
|
+ delete editableData[index];
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /* 根据 index 返回 rowSpan(合并单元格用) */
|
|
|
+ const getRowSpan = (_, index) => {
|
|
|
+ if (index === 0) return { rowSpan: 4 };
|
|
|
+ if (index === 1 || index === 2 || index === 3) return { rowSpan: 0 };
|
|
|
+ if (index === 4) return { rowSpan: 2 };
|
|
|
+ if (index === 5) return { rowSpan: 0 };
|
|
|
+ if (index === 7) return { rowSpan: 3 };
|
|
|
+ if (index === 8 || index === 9 || index === 10) return { rowSpan: 0 };
|
|
|
+ return {};
|
|
|
+ };
|
|
|
+
|
|
|
+ const columns = [
|
|
|
+ { title: '成本分类', dataIndex: 'name', align: 'center', customCell: getRowSpan },
|
|
|
+ { title: '成本项', dataIndex: 'age1', align: 'center' },
|
|
|
+ { title: '成本预算(元)', dataIndex: 'phone1', align: 'center' },
|
|
|
+ {
|
|
|
+ title: '实际成本(元)',
|
|
|
+ dataIndex: 'phone1',
|
|
|
+ align: 'center',
|
|
|
+ slots: { customRender: 'money' },
|
|
|
+ },
|
|
|
+ { title: '实际成本(元)', dataIndex: 'phone', align: 'center', customCell: getRowSpan },
|
|
|
+ { title: '成本偏差额(元)', dataIndex: 'age1', align: 'center' },
|
|
|
+ { title: '总预算成本(元)', dataIndex: 'age1', align: 'center' },
|
|
|
+ { title: '实际总成本(元)', dataIndex: 'age1', align: 'center' },
|
|
|
+ { title: '实际总成本偏差(元)', dataIndex: 'age1', align: 'center' },
|
|
|
+ ];
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+ .dashboard {
|
|
|
+ display: flex;
|
|
|
+ gap: 70px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 10px;
|
|
|
+ background: white;
|
|
|
+ }
|
|
|
+ .dashboard-card {
|
|
|
+ width: 180px;
|
|
|
+ height: 100px;
|
|
|
+ background: #f5f8ff;
|
|
|
+ border: 1px solid #d3dfff;
|
|
|
+ border-radius: 10px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+ .dashboard-card__title {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+ .dashboard-card__value {
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+ .dashboard-card__number {
|
|
|
+ font-weight: bold;
|
|
|
+ color: #318586;
|
|
|
+ font-size: 20px;
|
|
|
+ margin-right: 8px;
|
|
|
+ }
|
|
|
+ .dashboard-card__unit {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+
|
|
|
+ .custom-row td {
|
|
|
+ padding: 16px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .editable-cell {
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .editable-cell-input-wrapper,
|
|
|
+ .editable-cell-text-wrapper {
|
|
|
+ padding-right: 24px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .editable-cell-text-wrapper {
|
|
|
+ padding: 5px 24px 5px 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .editable-cell-icon,
|
|
|
+ .editable-cell-icon-check {
|
|
|
+ position: absolute;
|
|
|
+ right: 0;
|
|
|
+ width: 20px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .editable-cell-icon {
|
|
|
+ margin-top: 4px;
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .editable-cell-icon-check {
|
|
|
+ line-height: 28px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .editable-cell-icon:hover,
|
|
|
+ .editable-cell-icon-check:hover {
|
|
|
+ color: #108ee9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .editable-add-btn {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.editable-cell:hover .editable-cell-icon {
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+</style>
|