index.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <template>
  2. <div class="dashboard">
  3. <div class="dashboard-card" v-for="item in dashboardItems" :key="item.title">
  4. <span class="dashboard-card__title">{{ item.title }}</span>
  5. <div class="dashboard-card__value">
  6. <span class="dashboard-card__number">{{ item.value }}</span>
  7. <span class="dashboard-card__unit">{{ item.unit }}</span>
  8. </div>
  9. </div>
  10. </div>
  11. <div style="background: white; margin-top: 10px; padding: 10px">
  12. <a-button type="primary" style="margin: 10px 0 10px 0" @click="open = true"> 成本分析 </a-button>
  13. <a-table :columns="columns" :data-source="tableData" bordered :row-class-name="() => 'custom-row'" :scroll="{ y: 400 }">
  14. <template #money="{ text, record, index }">
  15. <div class="editable-cell">
  16. <div v-if="isEditable(record.age1) && editableData[index]" class="editable-cell-input-wrapper">
  17. <a-input-number v-model:value="editableData[index].phone1" :min="0" style="width: 100px" @pressEnter="save(index)" />
  18. <check-outlined class="editable-cell-icon-check" @click="save(index)" />
  19. </div>
  20. <div v-else class="editable-cell-text-wrapper">
  21. {{ text}}
  22. <edit-outlined v-if="isEditable(record.age1)" class="editable-cell-icon" @click="edit(index)" />
  23. </div>
  24. </div>
  25. </template>
  26. </a-table>
  27. <a-drawer v-model:open="open" width="1300" placement="right" title="成本分析">
  28. <CostAnalysis />
  29. <Echar />
  30. </a-drawer>
  31. </div>
  32. </template>
  33. <script setup>
  34. import { ref, reactive } from 'vue';
  35. import { cloneDeep } from 'lodash-es';
  36. import { CheckOutlined, EditOutlined } from '@ant-design/icons-vue';
  37. import CostAnalysis from './cost-analysis/index.vue';
  38. import Echar from './echar.vue';
  39. const dashboardItems = [
  40. { title: '任务总数', value: 12, unit: '个' },
  41. { title: '预计人天', value: 12, unit: '人天' },
  42. { title: '累计发生人天', value: 12, unit: '天' },
  43. { title: '超预计人天数', value: 12, unit: '天' },
  44. { title: '超期任务数', value: 12, unit: '个' },
  45. ];
  46. const open = ref(false);
  47. const tableData = ref([
  48. { key: '1', name: '人力成本', age1: '人力薪资', tel: '0571-22098909', phone1: 3000, phone: 5000 },
  49. { key: '2', name: '人力成本', age1: '人员补贴', tel: '0571-22098333', phone1: 1000, phone: 5000 },
  50. { key: '3', name: '人力成本', age1: '福利费', tel: '0575-22098909', phone1: 2000, phone: 5000 },
  51. { key: '4', name: '人力成本', age1: '劳务费', tel: '0575-22098909', phone1: 2000, phone: 5000 },
  52. { key: '5', name: '差旅成本', age1: '交通费', tel: '0575-22098909', phone1: 2000, phone: 2000 },
  53. { key: '6', name: '差旅成本', age1: '住宿费', tel: '0575-22098909', phone1: 1000, phone: 2000 },
  54. { key: '7', name: '销售成本', age1: '营销商务费用', tel: '0575-22098909', phone1: 1000, phone: 2000 },
  55. { key: '8', name: '经营成本', age1: '物料/设备采买', tel: '0575-22098909', phone1: 1000, phone: 2000 },
  56. { key: '9', name: '经营成本', age1: '房租', tel: '0575-22098909', phone1: 1000, phone: 2000 },
  57. { key: '10', name: '经营成本', age1: '管理费', tel: '0575-22098909', phone1: 1000, phone: 2000 },
  58. { key: '11', name: '经营成本', age1: '公司运营成本(分摊)', tel: '0575-22098909', phone1: 1000, phone: 2000 },
  59. { key: '12', name: '经营成本', age1: '税费', tel: '0575-22098909', phone1: 1000, phone: 2000 },
  60. ]);
  61. const editableData = reactive({});
  62. const isEditable = (costItem) => {
  63. return ['人力薪资', '人员补贴', '人员绩效', '劳务费', '公司运营成本(分摊)'].includes(costItem);
  64. };
  65. const edit = (index) => {
  66. if (isEditable(tableData.value[index].age1)) {
  67. editableData[index] = cloneDeep(tableData.value[index]);
  68. }
  69. };
  70. const save = (index) => {
  71. if (isEditable(tableData.value[index].age1)) {
  72. Object.assign(tableData.value[index], editableData[index]);
  73. delete editableData[index];
  74. }
  75. };
  76. /* 根据 index 返回 rowSpan(合并单元格用) */
  77. const getRowSpan = (_, index) => {
  78. if (index === 0) return { rowSpan: 4 };
  79. if (index === 1 || index === 2 || index === 3) return { rowSpan: 0 };
  80. if (index === 4) return { rowSpan: 2 };
  81. if (index === 5) return { rowSpan: 0 };
  82. if (index === 7) return { rowSpan: 3 };
  83. if (index === 8 || index === 9 || index === 10) return { rowSpan: 0 };
  84. return {};
  85. };
  86. const columns = [
  87. { title: '成本分类', dataIndex: 'name', align: 'center', customCell: getRowSpan },
  88. { title: '成本项', dataIndex: 'age1', align: 'center' },
  89. { title: '成本预算(元)', dataIndex: 'phone1', align: 'center' },
  90. {
  91. title: '实际成本(元)',
  92. dataIndex: 'phone1',
  93. align: 'center',
  94. slots: { customRender: 'money' },
  95. },
  96. { title: '实际成本(元)', dataIndex: 'phone', align: 'center', customCell: getRowSpan },
  97. { title: '成本偏差额(元)', dataIndex: 'age1', align: 'center' },
  98. { title: '总预算成本(元)', dataIndex: 'age1', align: 'center' },
  99. { title: '实际总成本(元)', dataIndex: 'age1', align: 'center' },
  100. { title: '实际总成本偏差(元)', dataIndex: 'age1', align: 'center' },
  101. ];
  102. </script>
  103. <style scoped lang="less">
  104. .dashboard {
  105. display: flex;
  106. gap: 70px;
  107. align-items: center;
  108. justify-content: center;
  109. padding: 10px;
  110. background: white;
  111. }
  112. .dashboard-card {
  113. width: 180px;
  114. height: 100px;
  115. background: #f5f8ff;
  116. border: 1px solid #d3dfff;
  117. border-radius: 10px;
  118. display: flex;
  119. flex-direction: column;
  120. align-items: center;
  121. justify-content: center;
  122. }
  123. .dashboard-card__title {
  124. font-size: 14px;
  125. color: #666;
  126. }
  127. .dashboard-card__value {
  128. margin-top: 10px;
  129. }
  130. .dashboard-card__number {
  131. font-weight: bold;
  132. color: #318586;
  133. font-size: 20px;
  134. margin-right: 8px;
  135. }
  136. .dashboard-card__unit {
  137. font-size: 14px;
  138. color: #666;
  139. }
  140. .custom-row td {
  141. padding: 16px !important;
  142. }
  143. .editable-cell {
  144. position: relative;
  145. .editable-cell-input-wrapper,
  146. .editable-cell-text-wrapper {
  147. padding-right: 24px;
  148. display: flex;
  149. align-items: center;
  150. justify-content: center;
  151. }
  152. .editable-cell-text-wrapper {
  153. padding: 5px 24px 5px 5px;
  154. }
  155. .editable-cell-icon,
  156. .editable-cell-icon-check {
  157. position: absolute;
  158. right: 0;
  159. width: 20px;
  160. cursor: pointer;
  161. }
  162. .editable-cell-icon {
  163. margin-top: 4px;
  164. display: none;
  165. }
  166. .editable-cell-icon-check {
  167. line-height: 28px;
  168. }
  169. .editable-cell-icon:hover,
  170. .editable-cell-icon-check:hover {
  171. color: #108ee9;
  172. }
  173. .editable-add-btn {
  174. margin-bottom: 8px;
  175. }
  176. }
  177. .editable-cell:hover .editable-cell-icon {
  178. display: inline-block;
  179. }
  180. </style>