Browse Source

新增预算组件

余思翰 3 weeks ago
parent
commit
ea60ee013d

+ 60
- 0
oa-ui/src/views/flowable/form/budget/adjust/components/CarCost.vue View File

@@ -0,0 +1,60 @@
1
+<template>
2
+  <div class="car-cost">
3
+    <table border="1" style="width:100%;">
4
+      <tr style="background-color:#f8f8f9">
5
+        <td>车牌号</td>
6
+        <td>折旧成本(天)</td>
7
+        <td>预算天数</td>
8
+        <td>金额</td>
9
+        <td class="adjust">核算天数</td>
10
+        <td class="adjust">核算金额</td>
11
+      </tr>
12
+      <tr v-for="car in carList" :key="'car' + car.carId">
13
+        <td>{{ car.car ? car.car.licensePlate : '' }}</td>
14
+        <td>{{ car.car ? car.car.dayCost : '' }}</td>
15
+        <td>{{ car.days }}</td>
16
+        <td>{{ car.amount }}</td>
17
+        <td>
18
+          <el-input-number :controls="false" style="width:100%;" v-model="car.daysAdjust" @change="handleChange"></el-input-number>
19
+        </td>
20
+        <td>
21
+          <el-input-number :controls="false" style="width:100%;" v-model="car.amountAdjust" @change="handleChange"></el-input-number>
22
+        </td>
23
+      </tr>
24
+    </table>
25
+  </div>
26
+</template>
27
+
28
+<script>
29
+export default {
30
+  name: 'CarCost',
31
+  props: {
32
+    carList: {
33
+      type: Array,
34
+      default: () => []
35
+    }
36
+  },
37
+  methods: {
38
+    handleChange() {
39
+      this.$emit('update:carList', this.carList);
40
+    }
41
+  }
42
+}
43
+</script>
44
+
45
+<style lang="scss" scoped>
46
+.car-cost {
47
+  table {
48
+    text-align: center;
49
+    border-collapse: collapse;
50
+    margin: 0 auto;
51
+    td {
52
+      padding: 5px;
53
+    }
54
+  }
55
+  .adjust {
56
+    color: #F56C6C;
57
+    width: 120px;
58
+  }
59
+}
60
+</style> 

+ 64
- 0
oa-ui/src/views/flowable/form/budget/adjust/components/DeviceCost.vue View File

@@ -0,0 +1,64 @@
1
+<template>
2
+  <div class="device-cost">
3
+    <table border="1" style="width:100%;">
4
+      <tr style="background-color:#f8f8f9">
5
+        <td>设备名称</td>
6
+        <td>规格型号</td>
7
+        <td>品牌</td>
8
+        <td>折旧成本(天)</td>
9
+        <td>预算天数</td>
10
+        <td>金额</td>
11
+        <td class="adjust">核算天数</td>
12
+        <td class="adjust">核算金额</td>
13
+      </tr>
14
+      <tr v-for="device in deviceList" :key="'device' + device.deviceId">
15
+        <td>{{ device.device ? device.device.name : '' }}</td>
16
+        <td>{{ device.device ? device.device.series : '' }}</td>
17
+        <td>{{ device.device ? device.device.brand : '' }}</td>
18
+        <td>{{ device.device ? device.device.dayCost : '' }}</td>
19
+        <td>{{ device.days }}</td>
20
+        <td>{{ device.amount }}</td>
21
+        <td>
22
+          <el-input-number :controls="false" style="width:100%;" v-model="device.daysAdjust" @change="handleChange"></el-input-number>
23
+        </td>
24
+        <td>
25
+          <el-input-number :controls="false" style="width:100%;" v-model="device.amountAdjust" @change="handleChange"></el-input-number>
26
+        </td>
27
+      </tr>
28
+    </table>
29
+  </div>
30
+</template>
31
+
32
+<script>
33
+export default {
34
+  name: 'DeviceCost',
35
+  props: {
36
+    deviceList: {
37
+      type: Array,
38
+      default: () => []
39
+    }
40
+  },
41
+  methods: {
42
+    handleChange() {
43
+      this.$emit('update:deviceList', this.deviceList);
44
+    }
45
+  }
46
+}
47
+</script>
48
+
49
+<style lang="scss" scoped>
50
+.device-cost {
51
+  table {
52
+    text-align: center;
53
+    border-collapse: collapse;
54
+    margin: 0 auto;
55
+    td {
56
+      padding: 5px;
57
+    }
58
+  }
59
+  .adjust {
60
+    color: #F56C6C;
61
+    width: 120px;
62
+  }
63
+}
64
+</style> 

+ 147
- 0
oa-ui/src/views/flowable/form/budget/adjust/components/InnerStaffCost.vue View File

@@ -0,0 +1,147 @@
1
+<template>
2
+  <div class="inner-staff-cost">
3
+    <table border="1" style="width:100%">
4
+      <tr style="background-color:#f8f8f9">
5
+        <td></td>
6
+        <td>序号</td>
7
+        <td>姓名</td>
8
+        <td>人员成本(天)</td>
9
+        <td style="width:120px">预算天数</td>
10
+        <td>预算绩效</td>
11
+        <td style="width:120px">金额</td>
12
+        <td class="adjust" style="width:120px">核算天数</td>
13
+        <td class="adjust" style="width:120px">核算绩效</td>
14
+        <td class="adjust" style="width:120px">核算金额</td>
15
+      </tr>
16
+      <tr v-for="staff, index in staffList" :key="'user' + staff.userId">
17
+        <td>
18
+          <!-- <el-button type="danger" icon="el-icon-minus" circle plain></el-button> -->
19
+          <div class="delete-btn" v-if="index + 1 > lens" @click="removeStaff(index)"><i class="el-icon-remove-outline"
20
+              style="color:#F56C6C"></i>
21
+          </div>
22
+        </td>
23
+        <td>{{ index + 1 }}</td>
24
+        <td>{{ staff.user ? staff.user.nickName : '' }}</td>
25
+        <td>{{ staff.staffCost }}</td>
26
+        <td>{{ staff.days }}</td>
27
+        <td>{{ staff.performance }}</td>
28
+        <td>{{ staff.amount }}</td>
29
+        <td>
30
+          <el-input-number :controls="false" style="width:100%;" v-model="staff.daysAdjust"
31
+            @change="handleChange(staff)"></el-input-number>
32
+        </td>
33
+        <td>
34
+          <el-input-number :controls="false" style="width:100%;" v-model="staff.performanceAdjust"
35
+            @change="handleChange(staff)"></el-input-number>
36
+        </td>
37
+        <td>
38
+          {{ staff.amountAdjust }}
39
+        </td>
40
+      </tr>
41
+    </table>
42
+    <div class="add-staff mt10">
43
+      <el-button type="success" plain icon="el-icon-plus" @click="openPeople" size="mini">新增人员</el-button>
44
+    </div>
45
+    <!-- 选择人员对话框 -->
46
+    <el-dialog title="选择人员" :visible.sync="isOpenPeople" width="700px" append-to-body>
47
+      <choosePeople @chooseUser="getChooseUser"></choosePeople>
48
+    </el-dialog>
49
+  </div>
50
+</template>
51
+
52
+<script>
53
+import choosePeople from '@/views/flowable/form/budget/components/choosePeople.vue'
54
+
55
+export default {
56
+  name: 'InnerStaffCost',
57
+  components: {
58
+    choosePeople
59
+  },
60
+  props: {
61
+    staffList: {
62
+      type: Array,
63
+      default: () => []
64
+    }
65
+  },
66
+  data() {
67
+    return {
68
+      isOpenPeople: false,
69
+      lens: 0,
70
+    }
71
+  },
72
+  watch: {
73
+    staffList(newval, oldval) {
74
+      if (oldval.length == 0) {
75
+        this.lens = newval.length;
76
+        console.log(this.lens)
77
+      }
78
+    }
79
+  },
80
+  mounted() {
81
+
82
+  },
83
+  methods: {
84
+    handleChange(staff) {
85
+      staff.amountAdjust = ((Number(staff.staffCost) * Number(staff.daysAdjust)) + Number(staff.settleAdjust)).toFixed(2)
86
+      this.$emit('update:staffList', this.staffList);
87
+    },
88
+    openPeople() {
89
+      this.isOpenPeople = true;
90
+    },
91
+    getChooseUser(val) {
92
+      for (let v of val) {
93
+        v.days = 0;
94
+        v.settle = 0;
95
+        v.amount = 0;
96
+        v.user = v
97
+      }
98
+      this.staffList.push(...val);
99
+      this.isOpenPeople = false;
100
+      this.$emit('update:staffList', this.staffList);
101
+    },
102
+    removeStaff(index) {
103
+      this.$confirm('确实移除该人员吗', '提示', {
104
+        confirmButtonText: '确定',
105
+        cancelButtonText: '取消',
106
+        type: 'warning'
107
+      }).then(() => {
108
+        let arr = this.staffList;
109
+        if (arr.length == 1) {
110
+          return;
111
+        }
112
+        if (index >= 0 && index < arr.length) {
113
+          arr.splice(index, 1);
114
+        }
115
+      }).catch(() => { });
116
+    }
117
+  }
118
+}
119
+</script>
120
+
121
+<style lang="scss" scoped>
122
+.inner-staff-cost {
123
+  .add-staff {
124
+    margin-bottom: 10px;
125
+  }
126
+
127
+  table {
128
+    text-align: center;
129
+    border-collapse: collapse;
130
+    margin: 0 auto;
131
+
132
+    td {
133
+      padding: 5px;
134
+    }
135
+  }
136
+
137
+  .adjust {
138
+    color: #F56C6C;
139
+    width: 120px;
140
+  }
141
+}
142
+
143
+.delete-btn {
144
+  cursor: pointer;
145
+  font-size: 18px;
146
+}
147
+</style>

+ 151
- 0
oa-ui/src/views/flowable/form/budget/adjust/components/OuterStaffCost.vue View File

@@ -0,0 +1,151 @@
1
+<template>
2
+  <div class="outer-staff-cost">
3
+    <table border="1" style="width:100%">
4
+      <tr style="background-color:#f8f8f9">
5
+        <td></td>
6
+        <td>序号</td>
7
+        <td>姓名</td>
8
+        <td>人员成本(天)</td>
9
+        <td style="width:120px">预算天数</td>
10
+        <td>预算系数</td>
11
+        <td>其他系数</td>
12
+        <td style="width:120px">金额</td>
13
+        <td class="adjust" style="width:120px">核算天数</td>
14
+        <td class="adjust" style="width:120px">核算系数</td>
15
+        <td class="adjust" style="width:120px">核算其他系数</td>
16
+        <td class="adjust" style="width:120px">核算金额</td>
17
+      </tr>
18
+      <tr v-for="staff, index in staffList" :key="'user' + staff.userId">
19
+        <td>
20
+          <div class="delete-btn" v-if="index + 1 > lens" @click="removeStaff(index)">
21
+            <i class="el-icon-remove-outline" style="color:#F56C6C"></i>
22
+          </div>
23
+        </td>
24
+        <td>{{ index + 1 }}</td>
25
+        <td>{{ staff.user ? staff.user.nickName : '' }}</td>
26
+        <td>{{ staff.staffCost }}</td>
27
+        <td>{{ staff.days }}</td>
28
+        <td>{{ staff.coefficient }}</td>
29
+        <td>{{ staff.otherCoefficient }}</td>
30
+        <td>{{ staff.amount }}</td>
31
+        <td>
32
+          <el-input-number :controls="false" style="width:100%;" v-model="staff.daysAdjust"
33
+            @change="handleChange(staff)"></el-input-number>
34
+        </td>
35
+        <td>
36
+          <el-input-number :controls="false" style="width:100%;" v-model="staff.coefficientAdjust"
37
+            @change="handleChange(staff)"></el-input-number>
38
+        </td>
39
+        <td>
40
+          <el-input-number :controls="false" style="width:100%;" v-model="staff.otherCoefficientAdjust"
41
+            @change="handleChange(staff)"></el-input-number>
42
+        </td>
43
+        <td>
44
+          {{ staff.amountAdjust }}
45
+        </td>
46
+      </tr>
47
+    </table>
48
+    <div class="add-staff mt10">
49
+      <el-button type="success" plain icon="el-icon-plus" @click="openPeople" size="mini">新增人员</el-button>
50
+    </div>
51
+    <!-- 选择人员对话框 -->
52
+    <el-dialog title="选择人员" :visible.sync="isOpenPeople" width="700px" append-to-body>
53
+      <choosePeople @chooseUser="getChooseUser"></choosePeople>
54
+    </el-dialog>
55
+  </div>
56
+</template>
57
+
58
+<script>
59
+import choosePeople from '@/views/flowable/form/budget/components/choosePeople.vue'
60
+
61
+export default {
62
+  name: 'OuterStaffCost',
63
+  components: {
64
+    choosePeople
65
+  },
66
+  props: {
67
+    staffList: {
68
+      type: Array,
69
+      default: () => []
70
+    }
71
+  },
72
+  data() {
73
+    return {
74
+      isOpenPeople: false,
75
+      lens: 0
76
+    }
77
+  },
78
+  watch: {
79
+    staffList(newval, oldval) {
80
+      if (oldval.length == 0) {
81
+        this.lens = newval.length;
82
+      }
83
+    }
84
+  },
85
+  methods: {
86
+    handleChange(staff) {
87
+      // 计算调整后的金额
88
+      staff.amountAdjust = (Number(staff.staffCost) * Number(staff.daysAdjust) * Number(staff.coefficientAdjust) * Number(staff.otherCoefficientAdjust)).toFixed(2)
89
+      this.$emit('update:staffList', this.staffList);
90
+    },
91
+    openPeople() {
92
+      this.isOpenPeople = true;
93
+    },
94
+    getChooseUser(val) {
95
+      for (let v of val) {
96
+        v.daysAdjust = 0;
97
+        v.coefficientAdjust = 1;
98
+        v.otherCoefficientAdjust = 1;
99
+        v.amountAdjust = 0;
100
+        v.user = v;
101
+      }
102
+      this.staffList.push(...val);
103
+      this.isOpenPeople = false;
104
+      this.$emit('update:staffList', this.staffList);
105
+    },
106
+    removeStaff(index) {
107
+      this.$confirm('确实移除该人员吗', '提示', {
108
+        confirmButtonText: '确定',
109
+        cancelButtonText: '取消',
110
+        type: 'warning'
111
+      }).then(() => {
112
+        let arr = this.staffList;
113
+        if (arr.length == 1) {
114
+          return;
115
+        }
116
+        if (index >= 0 && index < arr.length) {
117
+          arr.splice(index, 1);
118
+        }
119
+      }).catch(() => { });
120
+    }
121
+  }
122
+}
123
+</script>
124
+
125
+<style lang="scss" scoped>
126
+.outer-staff-cost {
127
+  .add-staff {
128
+    margin-bottom: 10px;
129
+  }
130
+
131
+  table {
132
+    text-align: center;
133
+    border-collapse: collapse;
134
+    margin: 0 auto;
135
+
136
+    td {
137
+      padding: 5px;
138
+    }
139
+  }
140
+
141
+  .adjust {
142
+    color: #F56C6C;
143
+    width: 120px;
144
+  }
145
+}
146
+
147
+.delete-btn {
148
+  cursor: pointer;
149
+  font-size: 18px;
150
+}
151
+</style>

+ 305
- 0
oa-ui/src/views/flowable/form/budget/adjust/index.vue View File

@@ -0,0 +1,305 @@
1
+<template>
2
+  <div class="main">
3
+    <h2 class="text-center">项目直接生产成本预算—核算表</h2>
4
+    <p style="text-align: center">编制人:{{ budgetForm.compilerUser ? budgetForm.compilerUser.nickName : '' }}</p>
5
+    <el-divider></el-divider>
6
+    <el-descriptions :column="3" border class="descriptions">
7
+      <el-descriptions-item label="项目编号">
8
+        {{ project.projectNumber }}
9
+      </el-descriptions-item>
10
+      <el-descriptions-item label="项目名称">
11
+        {{ project.projectName }}
12
+      </el-descriptions-item>
13
+      <el-descriptions-item label="项目负责人">
14
+        {{ getUserName(project.projectLeader) }}
15
+      </el-descriptions-item>
16
+      <el-descriptions-item label="承担部门">
17
+        {{ getDeptNames(project.undertakingDept) }}
18
+      </el-descriptions-item>
19
+      <el-descriptions-item label="项目备注" :span="3">
20
+        {{ project.remark }}
21
+      </el-descriptions-item>
22
+      <el-descriptions-item label="预算表单备注" :span="5">
23
+        {{ budgetForm.remark }}
24
+      </el-descriptions-item>
25
+      <el-descriptions-item label="项目计划工作量" :span="3">
26
+        <div>
27
+          <table border="1" style="width: 100%;">
28
+            <tr style="background-color:#f8f8f9">
29
+              <td style="width: 250px">工作内容</td>
30
+              <td style="width: 100px">比例尺/等级</td>
31
+              <td style="width: 100px">单位</td>
32
+              <td style="width: 100px">工作量</td>
33
+              <td style="width: 100px">要求完成时间</td>
34
+              <td style="width: 200px">备注</td>
35
+            </tr>
36
+            <tr v-for="(work, index) in workContentList" :key="index">
37
+              <td>
38
+                {{ work.content }}
39
+              </td>
40
+              <td>
41
+                {{ work.scale }}
42
+              </td>
43
+              <td>
44
+                {{ work.unit }}
45
+              </td>
46
+              <td>
47
+                {{ work.workload }}
48
+              </td>
49
+              <td>
50
+                {{ work.deadline }}
51
+              </td>
52
+              <td>
53
+                {{ work.remark }}
54
+              </td>
55
+            </tr>
56
+          </table>
57
+        </div>
58
+      </el-descriptions-item>
59
+      <el-descriptions-item label="内业绩效额" :span="3">
60
+        <table border="1" style="width:100%">
61
+          <tr style="background-color:#f8f8f9" v-if="workList.length != 0">
62
+            <td style="min-width:50px">工作简述</td>
63
+            <td style="min-width:50px">工作内容</td>
64
+            <td style="min-width:50px">比例尺/等级</td>
65
+            <td style="min-width:50px">数量</td>
66
+            <td style="min-width:50px">单价</td>
67
+            <td style="min-width:50px">单位</td>
68
+            <td style="min-width:50px">系数</td>
69
+            <td style="min-width:80px">金额</td>
70
+            <td style="min-width: 150px;">备注</td>
71
+          </tr>
72
+          <tr v-for="work in workList">
73
+            <td>{{ work.content }}</td>
74
+            <td>{{ work.cmcPrice ? work.cmcPrice.workItem : '' }}</td>
75
+            <td>
76
+              {{ work.scaleGrade }}
77
+            </td>
78
+            <td>
79
+              {{ work.workload }}
80
+            </td>
81
+            <td>{{ work.price }}</td>
82
+            <td>{{ work.unit }}</td>
83
+            <td>
84
+              {{ work.coefficient }}
85
+            </td>
86
+            <td style="text-align:right;">{{ work.settle ? work.settle.toFixed(2) : '0.00' }}</td>
87
+            <td>{{ work.remark ? work.remark : '' }}</td>
88
+          </tr>
89
+          <tr>
90
+            <td :colspan="7" class="head">内业绩效额合计</td>
91
+            <td :colspan="1" class="head" style="text-align:right;">
92
+              {{ budgetForm.settleExpense ? budgetForm.settleExpense.toFixed(2) : '0.00' }}
93
+            </td>
94
+            <td></td>
95
+          </tr>
96
+          <tr style="color:#F56C6C">
97
+            <td :colspan="7" class="head">内业核算绩效额</td>
98
+            <td :colspan="1" class="head">
99
+              <el-input-number v-model="budgetForm.settleAdjust" :controls="false" style="width:100%"></el-input-number>
100
+            </td>
101
+          </tr>
102
+        </table>
103
+      </el-descriptions-item>
104
+      <el-descriptions-item label="内业人员成本" :span="3">
105
+        <inner-staff-cost :staffList.sync="innerStaffList"
106
+          @update:staffList="handleInnerStaffChange"></inner-staff-cost>
107
+      </el-descriptions-item>
108
+      <el-descriptions-item label="外业人员成本" :span="3">
109
+        <outer-staff-cost :staffList.sync="outerStaffList"
110
+          @update:staffList="handleOuterStaffChange"></outer-staff-cost>
111
+      </el-descriptions-item>
112
+      <el-descriptions-item label="车辆成本" :span="3">
113
+        <car-cost :carList.sync="carList"></car-cost>
114
+      </el-descriptions-item>
115
+      <el-descriptions-item label="设备成本" :span="3">
116
+        <device-cost :deviceList.sync="deviceList"></device-cost>
117
+      </el-descriptions-item>
118
+      <el-descriptions-item label="现场开支" :span="3">
119
+        <table border="1" style="width:100%;">
120
+          <tr style="background-color:#f8f8f9">
121
+            <td>开支项</td>
122
+            <td>金额</td>
123
+            <td class="adjust">核算金额</td>
124
+          </tr>
125
+          <tr>
126
+
127
+          </tr>
128
+        </table>
129
+      </el-descriptions-item>
130
+      <el-descriptions-item label="经营相关" :span="3">
131
+
132
+      </el-descriptions-item>
133
+      <el-descriptions-item label="预算外开销" :span="3">
134
+
135
+      </el-descriptions-item>
136
+    </el-descriptions>
137
+  </div>
138
+</template>
139
+
140
+<script>
141
+import { listBudget, updateBudget } from "@/api/oa/budget/budget";
142
+import { listBudgetCar, updateBudgetCar } from "@/api/oa/budget/budgetCar";
143
+import { listBudgetDevice, updateBudgetDevice } from "@/api/oa/budget/budgetDevice";
144
+import { listBudgetSettle, updateBudgetSettle } from "@/api/oa/budget/budgetSettle";
145
+import { listBudgetStaff } from "@/api/oa/budget/budgetStaff";
146
+import { listProjectWork } from "@/api/oa/project/projectWork";
147
+import { getProject } from "@/api/oa/project/project";
148
+import InnerStaffCost from './components/InnerStaffCost.vue';
149
+import OuterStaffCost from './components/OuterStaffCost.vue';
150
+import CarCost from './components/CarCost.vue';
151
+import DeviceCost from './components/DeviceCost.vue';
152
+
153
+export default {
154
+  components: {
155
+    InnerStaffCost,
156
+    OuterStaffCost,
157
+    CarCost,
158
+    DeviceCost
159
+  },
160
+  data() {
161
+    return {
162
+      loading: true,
163
+      budgetForm: {},
164
+      project: {},
165
+      workContentList: [],
166
+      workList: [],
167
+      innerStaffList: [],
168
+      outerStaffList: [],
169
+      carList: [],
170
+      deviceList: [],
171
+    }
172
+  },
173
+  created() {
174
+    this.initBudgetForm();
175
+    this.getProjectWorkList();
176
+  },
177
+  methods: {
178
+    initBudgetForm() {
179
+      this.loading = true;
180
+      listBudget({ pageNum: 1, pageSize: 20, projectId: '1897087448425566208' }).then(async res => {
181
+        this.budgetForm = res.rows[0];
182
+        if (this.budgetForm) {
183
+          const budgetId = this.budgetForm.budgetId;
184
+          // 获取设备数据
185
+          let deviceRes = await listBudgetDevice({ pageSize: 100, budgetId });
186
+          this.deviceList = deviceRes.rows;
187
+
188
+          // 获取车辆数据
189
+          let carRes = await listBudgetCar({ pageSize: 100, budgetId });
190
+          this.carList = carRes.rows;
191
+
192
+          // 获取内业人员数据
193
+          let innerStaffRes = await listBudgetStaff({ pageSize: 100, budgetId });
194
+          this.innerStaffList = innerStaffRes.rows;
195
+
196
+          // 获取外业人员数据
197
+          let outerStaffRes = await listBudgetStaff({ pageSize: 100, budgetId });
198
+          this.outerStaffList = outerStaffRes.rows;
199
+
200
+          // 获取内业绩效额数据
201
+          let settleRes = await listBudgetSettle({ pageSize: 100, budgetId });
202
+          this.workList = settleRes.rows;
203
+          for (let work of this.workList) {
204
+            if (work.groundType == '0') {
205
+              work.price = work.cmcPrice.commonPrice
206
+              work.scaleGrade = work.cmcPrice.scaleGrade
207
+              work.unit = work.cmcPrice.unit
208
+            } else {
209
+              work.price = work.cmcPrice.complexPrice
210
+              work.scaleGrade = work.cmcPrice.scaleGrade
211
+              work.unit = work.cmcPrice.unit
212
+            }
213
+          }
214
+        }
215
+        this.loading = false;
216
+      }).catch(() => {
217
+        this.loading = false;
218
+      });
219
+    },
220
+    getProjectWorkList() {
221
+      getProject('1897087448425566208').then(res => {
222
+        this.project = res.data;
223
+      })
224
+      listProjectWork({ pageSize: 999, projectId: '1897087448425566208' }).then(res => {
225
+        this.workContentList = res.rows;
226
+      })
227
+    },
228
+    // 处理内业人员数据变化
229
+    handleInnerStaffChange(newList) {
230
+      this.innerStaffList = newList;
231
+      console.log(newList)
232
+      // 计算内业人员总成本
233
+      const totalAmount = this.innerStaffList.reduce((sum, staff) => {
234
+        return sum + (staff.amountAdjust || 0);
235
+      }, 0);
236
+
237
+      // 更新预算表单中的内业人员成本
238
+      if (this.budgetForm) {
239
+        this.budgetForm.innerStaffAmount = totalAmount;
240
+        // 可以在这里调用API保存数据
241
+        // updateBudget(this.budgetForm);
242
+      }
243
+    },
244
+
245
+    // 处理外业人员数据变化
246
+    handleOuterStaffChange(newList) {
247
+      this.outerStaffList = newList;
248
+      // 计算外业人员总成本
249
+      const totalAmount = this.outerStaffList.reduce((sum, staff) => {
250
+        return sum + (staff.amountAdjust || 0);
251
+      }, 0);
252
+
253
+      // 更新预算表单中的外业人员成本
254
+      if (this.budgetForm) {
255
+        this.budgetForm.outerStaffAmount = totalAmount;
256
+        // 可以在这里调用API保存数据
257
+        // updateBudget(this.budgetForm);
258
+      }
259
+    },
260
+  },
261
+}
262
+</script>
263
+
264
+<style lang="scss" scoped>
265
+.mylabel {
266
+  font-weight: bold;
267
+}
268
+
269
+.main {
270
+  width: 85%;
271
+  margin: 0 auto;
272
+  text-align: center;
273
+}
274
+
275
+.descriptions {
276
+  width: 100%;
277
+  margin: 0 auto;
278
+}
279
+
280
+table {
281
+  text-align: center;
282
+  border-collapse: collapse;
283
+  margin: 0 auto;
284
+
285
+  /*设置背景颜色*/
286
+  /* background-color: #bfa; */
287
+  td {
288
+    padding: 5px;
289
+  }
290
+}
291
+
292
+.adjust {
293
+  color: #F56C6C;
294
+  width: 120px;
295
+}
296
+
297
+::v-deep .el-descriptions .is-bordered .el-descriptions-item__cell {
298
+  border: 1px solid #838894;
299
+}
300
+
301
+::v-deep .el-descriptions-item__label.is-bordered-label {
302
+  color: #434141;
303
+  background: #eaeaea;
304
+}
305
+</style>

+ 312
- 0
oa-ui/src/views/flowable/form/budget/components/calculatePerformance.vue View File

@@ -0,0 +1,312 @@
1
+<template>
2
+  <el-dialog title="计算绩效" :visible.sync="visible" width="800px" append-to-body>
3
+    <div class="performance-list">
4
+      <div v-for="(item, index) in performanceList" :key="index" class="performance-item">
5
+        <div class="item-header">
6
+          <span>工作项目 {{ index + 1 }}</span>
7
+          <el-button type="text" icon="el-icon-delete" @click="removeItem(index)"
8
+            v-if="performanceList.length > 1">删除</el-button>
9
+        </div>
10
+        <el-form ref="form" :model="item" label-width="100px">
11
+          <el-row>
12
+            <el-col :span="12">
13
+              <el-form-item label="工作类别">
14
+                <el-select v-model="item.workType" placeholder="请选择工作类别"
15
+                  @change="(val) => handleWorkTypeChange(val, index)">
16
+                  <el-option v-for="type in workTypeList" :key="type.value" :label="type.label" :value="type.value">
17
+                  </el-option>
18
+                </el-select>
19
+              </el-form-item>
20
+            </el-col>
21
+            <el-col :span="12">
22
+              <el-form-item label="工作项目">
23
+                <el-select v-model="item.workItem" placeholder="请选择工作项目"
24
+                  @change="(val) => handleWorkItemChange(val, index)">
25
+                  <el-option v-for="workItem in item.workItemList" :key="workItem.value" :label="workItem.label"
26
+                    :value="workItem.value">
27
+                  </el-option>
28
+                </el-select>
29
+              </el-form-item>
30
+            </el-col>
31
+          </el-row>
32
+          <el-row>
33
+            <el-col :span="12">
34
+              <el-form-item label="比例尺/等级">
35
+                <el-select v-model="item.scaleGrade" placeholder="请选择比例尺/等级"
36
+                  @change="(val) => handleScaleGradeChange(val, index)">
37
+                  <el-option v-for="scale in item.scaleGradeList" :key="scale.value" :label="scale.label"
38
+                    :value="scale.value">
39
+                  </el-option>
40
+                </el-select>
41
+              </el-form-item>
42
+            </el-col>
43
+            <el-col :span="12">
44
+              <el-form-item label="地类类型">
45
+                <el-select v-model="item.groundType" placeholder="请选择地类类型"
46
+                  @change="(val) => handleGroundTypeChange(val, index)">
47
+                  <el-option key="一般地类" label="一般地类" value="0"></el-option>
48
+                  <el-option key="复杂地类" label="复杂地类" value="1"></el-option>
49
+                </el-select>
50
+              </el-form-item></el-col>
51
+          </el-row>
52
+
53
+          <el-row>
54
+            <el-col :span="12">
55
+              <el-form-item label="系数">
56
+                <el-input-number v-model="item.coefficient" :precision="2" :step="0.1" :min="0"
57
+                  @change="() => calculateTotal(index)"></el-input-number>
58
+                <el-tooltip content="系数备注" placement="right-start">
59
+                  <el-button icon="el-icon-info" type="text"
60
+                    @click="() => getCoefficientRemark(item.workType)"></el-button>
61
+                </el-tooltip>
62
+              </el-form-item>
63
+            </el-col>
64
+            <el-col :span="12">
65
+              <el-form-item label="工作量">
66
+                <el-input-number v-model="item.workload" :precision="2" :step="0.1" :min="0"
67
+                  @change="() => calculateTotal(index)"></el-input-number>
68
+              </el-form-item></el-col>
69
+          </el-row>
70
+
71
+
72
+          <el-form-item label="单价">
73
+            <el-tag>{{ item.price }}/ {{ item.unit }}</el-tag>
74
+          </el-form-item>
75
+
76
+
77
+          <el-form-item label="单项总额">
78
+            <el-tag>{{ item.total }}</el-tag>
79
+          </el-form-item>
80
+        </el-form>
81
+      </div>
82
+    </div>
83
+
84
+    <div class="add-item">
85
+      <el-button type="primary" icon="el-icon-plus" @click="addItem">添加工作项目</el-button>
86
+    </div>
87
+
88
+    <div class="total-amount">
89
+      <span>绩效总额:</span>
90
+      <span class="amount">{{ totalAmount }}</span>
91
+    </div>
92
+
93
+    <div slot="footer" class="dialog-footer">
94
+      <el-button @click="visible = false">取 消</el-button>
95
+      <el-button type="primary" @click="handleConfirm">确 定</el-button>
96
+    </div>
97
+  </el-dialog>
98
+</template>
99
+
100
+<script>
101
+import { getWorkTypeList, getWorkItemList, getScaleGradeList, getUnitPrice, getPriceRemarkByWorkType } from '@/api/oa/price/price'
102
+
103
+export default {
104
+  name: 'CalculatePerformance',
105
+  data() {
106
+    return {
107
+      visible: false,
108
+      workTypeList: [],
109
+      performanceList: [this.getDefaultItem()]
110
+    }
111
+  },
112
+  computed: {
113
+    totalAmount() {
114
+      return this.performanceList.reduce((sum, item) => {
115
+        return sum + (parseFloat(item.total) || 0)
116
+      }, 0).toFixed(2)
117
+    }
118
+  },
119
+  methods: {
120
+    getDefaultItem() {
121
+      return {
122
+        workType: '',
123
+        workItem: '',
124
+        scaleGrade: '',
125
+        groundType: '0',
126
+        price: 0,
127
+        workload: 0,
128
+        coefficient: 1,
129
+        total: 0,
130
+        workItemList: [],
131
+        scaleGradeList: []
132
+      }
133
+    },
134
+    open() {
135
+      this.visible = true
136
+      this.resetForm()
137
+      this.initWorkTypeList()
138
+    },
139
+    resetForm() {
140
+      this.performanceList = [this.getDefaultItem()]
141
+    },
142
+    addItem() {
143
+      this.performanceList.push(this.getDefaultItem())
144
+    },
145
+    removeItem(index) {
146
+      this.performanceList.splice(index, 1)
147
+    },
148
+    initWorkTypeList() {
149
+      getWorkTypeList().then(res => {
150
+        if (res) {
151
+          this.workTypeList = this.setArray(res)
152
+        }
153
+      })
154
+    },
155
+    handleWorkTypeChange(value, index) {
156
+      const item = this.performanceList[index]
157
+      item.workItem = ''
158
+      item.scaleGrade = ''
159
+      item.price = 0
160
+      item.workItemList = []
161
+      item.scaleGradeList = []
162
+      this.calculateTotal(index)
163
+
164
+      if (value) {
165
+        getWorkItemList({ workType: value }).then(res => {
166
+          if (res) {
167
+            item.workItemList = this.setArray(res)
168
+          }
169
+        })
170
+      }
171
+    },
172
+    handleWorkItemChange(value, index) {
173
+      const item = this.performanceList[index]
174
+      item.scaleGrade = ''
175
+      item.price = 0
176
+      item.scaleGradeList = []
177
+      this.calculateTotal(index)
178
+
179
+      if (value) {
180
+        getScaleGradeList({ workItem: value }).then(res => {
181
+          if (res) {
182
+            item.scaleGradeList = this.setArray(res)
183
+            if (item.scaleGradeList.length > 0) {
184
+              item.scaleGrade = item.scaleGradeList[0].value
185
+              this.handleScaleGradeChange(item.scaleGrade, index)
186
+            }
187
+          }
188
+        })
189
+      }
190
+    },
191
+    handleScaleGradeChange(value, index) {
192
+      const item = this.performanceList[index]
193
+      item.price = 0
194
+      this.calculateTotal(index)
195
+
196
+      if (value && item.workItem) {
197
+        this.getUnitPrice(index)
198
+      }
199
+    },
200
+    handleGroundTypeChange(value, index) {
201
+      const item = this.performanceList[index]
202
+      item.price = 0
203
+      this.calculateTotal(index)
204
+
205
+      if (item.scaleGrade && item.workItem) {
206
+        this.getUnitPrice(index)
207
+      }
208
+    },
209
+    getUnitPrice(index) {
210
+      const item = this.performanceList[index]
211
+      getUnitPrice({
212
+        workItem: item.workItem,
213
+        scaleGrade: item.scaleGrade,
214
+        groundType: item.groundType
215
+      }).then(res => {
216
+        if (res.length != 0) {
217
+          item.price = res.data.price
218
+          item.unit = res.data.unit
219
+          this.calculateTotal(index)
220
+        }
221
+      })
222
+    },
223
+    calculateTotal(index) {
224
+      const item = this.performanceList[index]
225
+      item.total = ((parseFloat(item.price) * parseFloat(item.coefficient) * parseFloat(item.workload))).toFixed(2)
226
+    },
227
+    handleConfirm() {
228
+      this.$emit('confirm', this.totalAmount)
229
+      this.visible = false
230
+    },
231
+    getCoefficientRemark(workType) {
232
+      if (workType) {
233
+        getPriceRemarkByWorkType({ workType }).then(res => {
234
+          if (res.code == 200) {
235
+            this.$notify({
236
+              title: workType,
237
+              dangerouslyUseHTMLString: true,
238
+              message: res.msg,
239
+              duration: 0
240
+            })
241
+          }
242
+        })
243
+      }
244
+    },
245
+    setArray(arr) {
246
+      if (arr != [] && arr != undefined) {
247
+        let datalist = [...new Set(arr)]
248
+        let list = []
249
+        for (let i of datalist) {
250
+          let obj = new Object()
251
+          obj.label = i
252
+          obj.value = i
253
+          list.push(obj)
254
+        }
255
+        return list
256
+      } else {
257
+        return []
258
+      }
259
+    }
260
+  }
261
+}
262
+</script>
263
+
264
+<style lang="scss" scoped>
265
+.performance-list {
266
+  max-height: 600px;
267
+  overflow-y: auto;
268
+  padding: 10px;
269
+}
270
+
271
+.performance-item {
272
+  border: 1px solid #EBEEF5;
273
+  border-radius: 4px;
274
+  padding: 15px;
275
+  margin-bottom: 15px;
276
+  position: relative;
277
+
278
+  .item-header {
279
+    display: flex;
280
+    justify-content: space-between;
281
+    align-items: center;
282
+    margin-bottom: 15px;
283
+    padding-bottom: 10px;
284
+    border-bottom: 1px solid #EBEEF5;
285
+
286
+    span {
287
+      font-size: 16px;
288
+      font-weight: bold;
289
+    }
290
+  }
291
+}
292
+
293
+.add-item {
294
+  text-align: center;
295
+  margin: 15px 0;
296
+}
297
+
298
+.total-amount {
299
+  text-align: right;
300
+  padding: 15px;
301
+  font-size: 16px;
302
+  border-top: 1px solid #EBEEF5;
303
+  margin-top: 15px;
304
+
305
+  .amount {
306
+    color: #F56C6C;
307
+    font-size: 20px;
308
+    font-weight: bold;
309
+    margin-left: 10px;
310
+  }
311
+}
312
+</style>

+ 177
- 0
oa-ui/src/views/flowable/form/budget/siteExpenses.vue View File

@@ -0,0 +1,177 @@
1
+<template>
2
+  <div>
3
+    <el-form ref="form" :model="form" style="padding: 20px 100px 0">
4
+      <el-form-item label="现场开支:">
5
+        <div style="color:#F56C6C">现场开支与借款挂钩,请慎重填写</div>
6
+        <table border="1">
7
+          <tr>
8
+            <td colspan="5" class="head">现场开支表</td>
9
+          </tr>
10
+          <tr>
11
+            <td>序号</td>
12
+            <td>名称</td>
13
+            <td>金额</td>
14
+            <td>备注</td>
15
+            <td>操作</td>
16
+          </tr>
17
+          <tr v-for="item, index in expensesList">
18
+            <td>{{ index + 1 }}</td>
19
+            <td>
20
+              <el-select v-model="item.name" placeholder="请选择开支项" clearable @change="handleExpenseChange"
21
+                style="width: 100%;">
22
+                <el-option v-for="dict in dict.type.borrow_expenses" clearable :key="dict.value" :label="dict.label"
23
+                  :value="dict.value" />
24
+                <el-option label="+新增更多开支项..." value="new_expense" />
25
+              </el-select>
26
+            </td>
27
+            <td>
28
+              <el-input-number style="width: 100%;" :controls="false" v-model="item.amount"
29
+                placeholder="请输入金额"></el-input-number>
30
+            </td>
31
+            <td>
32
+              <el-input type="textarea" v-model="item.remark"></el-input>
33
+            </td>
34
+            <td>
35
+              <el-button icon="el-icon-delete" type="text" style="color:#F56C6C"
36
+                @click="deletExpensesList(index)">删除</el-button>
37
+            </td>
38
+          </tr>
39
+        </table>
40
+        <el-button type="primary" plain icon="el-icon-plus" @click="addExpensesList" size="mini"></el-button>
41
+      </el-form-item>
42
+    </el-form>
43
+    <el-dialog title="新增开支项" :visible.sync="open" width="400px" append-to-body>
44
+      <el-form ref="newExpenseForm" :model="newExpenseForm" :rules="newExpenseRules" label-width="120px">
45
+        <el-form-item label="开支项名称" prop="name">
46
+          <el-input v-model="newExpenseForm.name" placeholder="请输入开支项名称" />
47
+        </el-form-item>
48
+      </el-form>
49
+      <div slot="footer" class="dialog-footer">
50
+        <el-button type="primary" @click="submitNewExpense">确 定</el-button>
51
+        <el-button @click="open = false">取 消</el-button>
52
+      </div>
53
+    </el-dialog>
54
+  </div>
55
+</template>
56
+
57
+<script>
58
+import { listData, getData, delData, addData, updateData } from "@/api/system/dict/data";
59
+export default {
60
+  dicts: ['borrow_expenses', 'cmc_unit'],
61
+  props: {
62
+    outerUsers: {
63
+      type: Array,
64
+      default: () => []
65
+    }
66
+  },
67
+  watch: {
68
+    expensesList: {
69
+      handler(newVal) {
70
+        this.$emit('expensesList', [...newVal])
71
+      },
72
+    }
73
+  },
74
+  data() {
75
+    return {
76
+      open: false,
77
+      form: {},
78
+      expensesList: [{
79
+        name: '',
80
+        amount: 0,
81
+      }],
82
+      newExpenseForm: {
83
+        name: ''
84
+      },
85
+      newExpenseRules: {
86
+        name: [
87
+          { required: true, message: "开支项不能为空", trigger: "blur" }
88
+        ],
89
+      }
90
+    }
91
+  },
92
+  created() {
93
+    console.log(this.dict);
94
+  },
95
+  methods: {
96
+    addExpensesList() {
97
+      this.expensesList.push({
98
+        name: '',
99
+        amount: 0,
100
+      });
101
+    },
102
+    deletExpensesList(index) {
103
+      let arr = this.expensesList;
104
+      if (arr.length == 1) {
105
+        return;
106
+      }
107
+      if (index >= 0 && index < arr.length) {
108
+        arr.splice(index, 1);
109
+      }
110
+    },
111
+    handleExpenseChange(value) {
112
+      if (value === 'new_expense') {
113
+        this.open = true;
114
+      } else if (value === '食宿') {
115
+        const currentIndex = this.expensesList.findIndex(item => item.name === value);
116
+        if (currentIndex !== -1) {
117
+          const totalDays = this.outerUsers.reduce((sum, user) => sum + (user.days || 0), 0);
118
+          this.expensesList[currentIndex].amount = 130 * totalDays;
119
+          this.expensesList[currentIndex].remark = '130元/人天,共计' + totalDays + '人天';
120
+        }
121
+      }
122
+    },
123
+    submitNewExpense() {
124
+      this.$refs["newExpenseForm"].validate(valid => {
125
+        if (valid) {
126
+          this.newExpenseForm.dictType = 'borrow_expenses';
127
+          this.newExpenseForm.dictLabel = this.newExpenseForm.name;
128
+          this.newExpenseForm.dictValue = this.newExpenseForm.name;
129
+          this.newExpenseForm.listClass = 'default'
130
+          addData(this.newExpenseForm).then(response => {
131
+            this.$modal.msgSuccess("新增成功");
132
+            this.open = false;
133
+            this.newExpenseForm.name = '';
134
+            // 刷新字典数据
135
+            this.getDicts("borrow_expenses").then(response => {
136
+              console.log(response.data);
137
+              // 更新字典数据
138
+              this.dict.type.borrow_expenses = response.data;
139
+              // 重新加载字典数据后,更新当前选中项
140
+              if (this.expensesList.length > 0) {
141
+                const lastItem = this.expensesList[this.expensesList.length - 1];
142
+                lastItem.name = this.newExpenseForm.dictValue;
143
+              }
144
+            });
145
+          });
146
+        }
147
+      })
148
+    }
149
+  }
150
+}
151
+</script>
152
+
153
+<style lang="scss" scoped>
154
+table {
155
+  width: 100%;
156
+  /*居中*/
157
+  // margin: 0 auto;
158
+  /*边框*/
159
+  /* border: 1px solid black; */
160
+  text-align: center;
161
+  border-collapse: collapse;
162
+  border: 1px solid #DCDFE6;
163
+
164
+  /*设置背景颜色*/
165
+  /* background-color: #bfa; */
166
+  td {
167
+    padding: 1px;
168
+  }
169
+
170
+  .head {
171
+    background-color: #e7f6f3;
172
+    font-family: '微软雅黑';
173
+    font-size: 18;
174
+    font-weight: bold;
175
+  }
176
+}
177
+</style>

+ 21
- 0
oa-ui/src/views/oa/adjust/index.vue View File

@@ -0,0 +1,21 @@
1
+<!--
2
+ * @Author: ysh
3
+ * @Date: 2025-05-07 10:31:01
4
+ * @LastEditors: 
5
+ * @LastEditTime: 2025-05-07 10:31:07
6
+-->
7
+<template>
8
+  <div>
9
+    
10
+  </div>
11
+</template>
12
+
13
+<script>
14
+  export default {
15
+    
16
+  }
17
+</script>
18
+
19
+<style lang="scss" scoped>
20
+
21
+</style>

+ 358
- 0
oa-ui/src/views/oa/train/record.vue View File

@@ -0,0 +1,358 @@
1
+<template>
2
+  <div>
3
+    <el-form ref="form" :model="form" :rules="rules" label-width="80px">
4
+      <el-form-item label="培训资料" prop="resourceIds">
5
+        <el-button type="primary" size="mini" @click="showResourceDialog">选择培训资料</el-button>
6
+        <div v-if="selectedResources.length > 0" class="selected-resources mt10">
7
+          <el-tag v-for="resource in selectedResources" :key="resource.resourceId" closable @close="removeResource(resource.resourceId)"
8
+            class="mr10 mb10">
9
+            {{ resource.title }}
10
+          </el-tag>
11
+        </div>
12
+      </el-form-item>
13
+      <el-form-item label="参培成员" prop="userIds">
14
+        <el-button type="primary" size="mini" @click="showUserDialog" :disabled="!form.resourceIds || form.resourceIds.length === 0">选择参培成员</el-button>
15
+        <div v-if="selectedUsers.length > 0" class="selected-users mt10">
16
+          <el-tag v-for="user in selectedUsers" :key="user.userId" closable @close="removeUser(user.userId)"
17
+            class="mr10 mb10">
18
+            {{ user.nickName }}
19
+          </el-tag>
20
+        </div>
21
+      </el-form-item>
22
+      <el-form-item label="学习时间" prop="lastTime">
23
+        <el-date-picker v-model="form.lastTime" type="date" placeholder="选择学习时间" value-format="yyyy-MM-dd">
24
+        </el-date-picker>
25
+      </el-form-item>
26
+    </el-form>
27
+
28
+    <!-- 培训资料选择对话框 -->
29
+    <el-dialog title="选择培训资料" :visible.sync="resourceDialogVisible" width="800px" append-to-body>
30
+      <el-form :inline="true" :model="resourceQueryParams" class="demo-form-inline">
31
+        <el-form-item label="资料名称">
32
+          <el-input v-model="resourceQueryParams.title" placeholder="请输入资料名称" clearable
33
+            @keyup.enter.native="handleResourceQuery" />
34
+        </el-form-item>
35
+        <el-form-item>
36
+          <el-button type="primary" @click="handleResourceQuery">查询</el-button>
37
+          <el-button @click="resetResourceQuery">重置</el-button>
38
+        </el-form-item>
39
+      </el-form>
40
+      <el-table v-loading="resourceLoading" :data="resourceList" @selection-change="handleResourceSelectionChange" ref="resourceTable">
41
+        <el-table-column type="selection" width="55" reserve-selection />
42
+        <el-table-column type="index" label="序号" width="55" align="center" />
43
+        <el-table-column label="资料名称" align="center" prop="title" />
44
+        <el-table-column label="类型" align="center" prop="type" />
45
+        <el-table-column label="学时" align="center" prop="hours" />
46
+      </el-table>
47
+      <pagination v-show="resourceTotal > 0" :total="resourceTotal" :page.sync="resourceQueryParams.pageNum"
48
+        :limit.sync="resourceQueryParams.pageSize" @pagination="getResourceList" />
49
+      <div slot="footer" class="dialog-footer">
50
+        <el-button type="primary" @click="confirmResourceSelection">确 定</el-button>
51
+        <el-button @click="resourceDialogVisible = false">取 消</el-button>
52
+      </div>
53
+    </el-dialog>
54
+
55
+    <!-- 参培成员选择对话框 -->
56
+    <el-dialog title="选择参培成员" :visible.sync="userDialogVisible" width="800px" append-to-body>
57
+      <el-table v-loading="userLoading" :data="userList" @selection-change="handleUserSelectionChange" ref="userTable">
58
+        <el-table-column type="selection" width="55" :selectable="checkUserSelectable" reserve-selection />
59
+        <el-table-column type="index" label="序号" width="55" align="center" />
60
+        <el-table-column label="姓名" align="center" prop="nickName" />
61
+        <el-table-column label="部门" align="center" prop="dept.deptName" />
62
+      </el-table>
63
+      <pagination v-show="userTotal > 0" :total="userTotal" :page.sync="userQueryParams.pageNum"
64
+        :limit.sync="userQueryParams.pageSize" @pagination="getUserList" />
65
+      <div slot="footer" class="dialog-footer">
66
+        <el-button type="primary" @click="confirmUserSelection">确 定</el-button>
67
+        <el-button @click="userDialogVisible = false">取 消</el-button>
68
+      </div>
69
+    </el-dialog>
70
+  </div>
71
+</template>
72
+
73
+<script>
74
+import { listResource } from "@/api/oa/study/resource";
75
+import { listUser } from "@/api/system/user";
76
+import { listStudy } from "@/api/oa/study/myStudy";
77
+
78
+export default {
79
+  name: "TrainRecord",
80
+  props: {
81
+    trainId: {
82
+      type: String,
83
+    }
84
+  },
85
+  data() {
86
+    return {
87
+      // 表单数据
88
+      form: {
89
+        resourceIds: [],
90
+        userIds: [],
91
+        lastTime: null
92
+      },
93
+      // 表单校验规则
94
+      rules: {
95
+        resourceIds: [
96
+          { required: true, message: "请选择培训资料", trigger: "change" }
97
+        ],
98
+        userIds: [
99
+          { required: true, message: "请选择参培成员", trigger: "change" }
100
+        ],
101
+        lastTime: [
102
+          { required: true, message: "请选择学习时间", trigger: "change" }
103
+        ]
104
+      },
105
+      // 培训资料选择对话框
106
+      resourceDialogVisible: false,
107
+      resourceLoading: false,
108
+      resourceList: [],
109
+      resourceTotal: 0,
110
+      resourceQueryParams: {
111
+        pageNum: 1,
112
+        pageSize: 10,
113
+        title: undefined
114
+      },
115
+      selectedResources: [],
116
+      tempSelectedResources: [], // 临时存储选中的资源
117
+      // 参培成员选择对话框
118
+      userDialogVisible: false,
119
+      userLoading: false,
120
+      userList: [],
121
+      userTotal: 0,
122
+      userQueryParams: {
123
+        pageNum: 1,
124
+        pageSize: 10
125
+      },
126
+      selectedUsers: [],
127
+      existingUserIds: [],
128
+      tempSelectedUsers: [], // 临时存储选中的用户
129
+    };
130
+  },
131
+  methods: {
132
+    /** 显示培训资料选择对话框 */
133
+    showResourceDialog() {
134
+      this.resourceDialogVisible = true;
135
+      this.tempSelectedResources = [...this.selectedResources]; // 初始化临时选中资源
136
+      this.getResourceList();
137
+      // 在下一个tick中设置选中状态
138
+      this.$nextTick(() => {
139
+        if (this.$refs.resourceTable) {
140
+          this.resourceList.forEach(row => {
141
+            if (this.tempSelectedResources.some(resource => resource.resourceId === row.resourceId)) {
142
+              this.$refs.resourceTable.toggleRowSelection(row, true);
143
+            }
144
+          });
145
+        }
146
+      });
147
+    },
148
+    /** 查询培训资料列表 */
149
+    getResourceList() {
150
+      this.resourceLoading = true;
151
+      listResource(this.resourceQueryParams).then(response => {
152
+        this.resourceList = response.rows;
153
+        this.resourceTotal = response.total;
154
+        this.resourceLoading = false;
155
+        // 在数据加载后恢复选中状态
156
+        this.$nextTick(() => {
157
+          if (this.$refs.resourceTable) {
158
+            this.resourceList.forEach(row => {
159
+              if (this.tempSelectedResources.some(resource => resource.resourceId === row.resourceId)) {
160
+                this.$refs.resourceTable.toggleRowSelection(row, true);
161
+              }
162
+            });
163
+          }
164
+        });
165
+      });
166
+    },
167
+    /** 培训资料查询按钮操作 */
168
+    handleResourceQuery() {
169
+      this.resourceQueryParams.pageNum = 1;
170
+      this.getResourceList();
171
+    },
172
+    /** 重置培训资料查询 */
173
+    resetResourceQuery() {
174
+      this.resourceQueryParams = {
175
+        pageNum: 1,
176
+        pageSize: 10,
177
+        title: undefined
178
+      };
179
+      this.handleResourceQuery();
180
+    },
181
+    /** 培训资料选择变化 */
182
+    handleResourceSelectionChange(selection) {
183
+      this.tempSelectedResources = selection;
184
+    },
185
+    /** 确认培训资料选择 */
186
+    confirmResourceSelection() {
187
+      // 去重处理
188
+      const uniqueResources = this.tempSelectedResources.reduce((acc, current) => {
189
+        const x = acc.find(item => item.resourceId === current.resourceId);
190
+        if (!x) {
191
+          return acc.concat([current]);
192
+        } else {
193
+          return acc;
194
+        }
195
+      }, []);
196
+      
197
+      this.selectedResources = uniqueResources;
198
+      this.form.resourceIds = this.selectedResources.map(resource => resource.resourceId);
199
+      this.resourceDialogVisible = false;
200
+      // 获取所有选中资源的已学习记录
201
+      this.getExistingRecords(this.form.resourceIds);
202
+    },
203
+    /** 移除已选培训资料 */
204
+    removeResource(resourceId) {
205
+      this.selectedResources = this.selectedResources.filter(resource => resource.resourceId !== resourceId);
206
+      this.form.resourceIds = this.selectedResources.map(resource => resource.resourceId);
207
+      // 同时更新临时选中资源
208
+      this.tempSelectedResources = this.tempSelectedResources.filter(resource => resource.resourceId !== resourceId);
209
+      // 更新表格选中状态
210
+      this.$nextTick(() => {
211
+        if (this.$refs.resourceTable) {
212
+          this.resourceList.forEach(row => {
213
+            if (row.resourceId === resourceId) {
214
+              this.$refs.resourceTable.toggleRowSelection(row, false);
215
+            }
216
+          });
217
+        }
218
+      });
219
+      // 重新获取已学习记录
220
+      this.getExistingRecords(this.form.resourceIds);
221
+    },
222
+    /** 显示参培成员选择对话框 */
223
+    showUserDialog() {
224
+      this.userDialogVisible = true;
225
+      this.tempSelectedUsers = [...this.selectedUsers]; // 初始化临时选中用户
226
+      this.getUserList();
227
+      // 在下一个tick中设置选中状态
228
+      this.$nextTick(() => {
229
+        if (this.$refs.userTable) {
230
+          this.userList.forEach(row => {
231
+            if (this.tempSelectedUsers.some(user => user.userId === row.userId)) {
232
+              this.$refs.userTable.toggleRowSelection(row, true);
233
+            }
234
+          });
235
+        }
236
+      });
237
+    },
238
+    /** 查询用户列表 */
239
+    getUserList() {
240
+      this.userLoading = true;
241
+      listUser(this.userQueryParams).then(response => {
242
+        this.userList = response.rows;
243
+        this.userTotal = response.total;
244
+        this.userLoading = false;
245
+        // 在数据加载后恢复选中状态
246
+        this.$nextTick(() => {
247
+          if (this.$refs.userTable) {
248
+            this.userList.forEach(row => {
249
+              if (this.tempSelectedUsers.some(user => user.userId === row.userId)) {
250
+                this.$refs.userTable.toggleRowSelection(row, true);
251
+              }
252
+            });
253
+          }
254
+        });
255
+      });
256
+    },
257
+    /** 检查用户是否可选 */
258
+    checkUserSelectable(row) {
259
+      return !this.existingUserIds.includes(row.userId);
260
+    },
261
+    /** 用户选择变化 */
262
+    handleUserSelectionChange(selection) {
263
+      // 更新临时选中用户
264
+      this.tempSelectedUsers = selection;
265
+    },
266
+    /** 确认用户选择 */
267
+    confirmUserSelection() {
268
+      // 去重处理
269
+      const uniqueUsers = this.tempSelectedUsers.reduce((acc, current) => {
270
+        const x = acc.find(item => item.userId === current.userId);
271
+        if (!x) {
272
+          return acc.concat([current]);
273
+        } else {
274
+          return acc;
275
+        }
276
+      }, []);
277
+      
278
+      this.selectedUsers = uniqueUsers;
279
+      this.form.userIds = this.selectedUsers.map(user => user.userId);
280
+      this.userDialogVisible = false;
281
+    },
282
+    /** 移除已选用户 */
283
+    removeUser(userId) {
284
+      this.selectedUsers = this.selectedUsers.filter(user => user.userId !== userId);
285
+      this.form.userIds = this.selectedUsers.map(user => user.userId);
286
+      // 同时更新临时选中用户
287
+      this.tempSelectedUsers = this.tempSelectedUsers.filter(user => user.userId !== userId);
288
+      // 更新表格选中状态
289
+      this.$nextTick(() => {
290
+        if (this.$refs.userTable) {
291
+          this.userList.forEach(row => {
292
+            if (row.userId === userId) {
293
+              this.$refs.userTable.toggleRowSelection(row, false);
294
+            }
295
+          });
296
+        }
297
+      });
298
+    },
299
+    /** 获取已学习记录 */
300
+    getExistingRecords(resourceIds) {
301
+      if (!resourceIds || resourceIds.length === 0) return;
302
+      // 获取所有选中资源的已学习记录
303
+      const promises = resourceIds.map(resourceId => 
304
+        listStudy({
305
+          resourceId,
306
+          pageSize: 9999,
307
+          pageNum: 1
308
+        })
309
+      );
310
+      
311
+      Promise.all(promises).then(responses => {
312
+        // 合并所有资源的已学习用户ID
313
+        this.existingUserIds = responses.reduce((acc, response) => {
314
+          return [...acc, ...response.rows.map(record => record.userId)];
315
+        }, []);
316
+        
317
+        // 过滤掉已存在的用户
318
+        this.selectedUsers = this.selectedUsers.filter(user => !this.existingUserIds.includes(user.userId));
319
+        this.form.userIds = this.selectedUsers.map(user => user.userId);
320
+      });
321
+    }
322
+  }
323
+};
324
+</script>
325
+
326
+<style lang="scss" scoped>
327
+.mb8 {
328
+  margin-bottom: 8px;
329
+}
330
+
331
+.ml10 {
332
+  margin-left: 10px;
333
+}
334
+
335
+.mt10 {
336
+  margin-top: 10px;
337
+}
338
+
339
+.mr10 {
340
+  margin-right: 10px;
341
+}
342
+
343
+.mb10 {
344
+  margin-bottom: 10px;
345
+}
346
+
347
+.selected-resources,
348
+.selected-users {
349
+  display: flex;
350
+  flex-wrap: wrap;
351
+  gap: 10px;
352
+}
353
+
354
+.dialog-footer {
355
+  margin-top: 20px;
356
+  text-align: right;
357
+}
358
+</style>

Loading…
Cancel
Save