DanExecution.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <template>
  2. <div class="dan-execution admin-page">
  3. <el-card>
  4. <div slot="header" class="admin-page-header">
  5. <span class="admin-page-title">DAN测评服务执行管理</span>
  6. <div class="filter-bar">
  7. <el-select v-model="statusFilter" placeholder="执行状态" clearable @change="handleSearch" style="width: 140px; margin-right: 10px;">
  8. <el-option label="全部状态" value="" />
  9. <el-option label="进行中" value="pending" />
  10. <el-option label="已完成" value="completed" />
  11. <el-option label="已确认" value="confirmed" />
  12. </el-select>
  13. <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
  14. </div>
  15. </div>
  16. <el-table :data="list" v-loading="loading" border stripe empty-text="暂无执行记录">
  17. <el-table-column label="ID" prop="id" width="80" />
  18. <el-table-column label="订单ID" prop="assessmentOrderId" width="100" />
  19. <el-table-column label="家庭ID" prop="familyId" width="100" />
  20. <el-table-column label="孩子ID" prop="childId" width="100" />
  21. <el-table-column label="评估师" width="120">
  22. <template slot-scope="{ row }">{{ row.assessorName || (row.assessorId ? 'ID:' + row.assessorId : '—') }}</template>
  23. </el-table-column>
  24. <el-table-column label="评估师状态" width="130">
  25. <template slot-scope="{ row }">
  26. <div class="status-cell">
  27. <i :class="executionIconClass(row.assessorStatus)" :style="{ color: executionIconColor(row.assessorStatus) }"></i>
  28. <el-tag :type="executionStatusType(row.assessorStatus)" size="mini">
  29. {{ executionStatusLabel(row.assessorStatus) }}
  30. </el-tag>
  31. </div>
  32. </template>
  33. </el-table-column>
  34. <el-table-column label="规划师" width="120">
  35. <template slot-scope="{ row }">{{ row.plannerName || (row.plannerId ? 'ID:' + row.plannerId : '—') }}</template>
  36. </el-table-column>
  37. <el-table-column label="规划师状态" width="130">
  38. <template slot-scope="{ row }">
  39. <div v-if="row.plannerId" class="status-cell">
  40. <i :class="executionIconClass(row.plannerStatus)" :style="{ color: executionIconColor(row.plannerStatus) }"></i>
  41. <el-tag :type="executionStatusType(row.plannerStatus)" size="mini">
  42. {{ executionStatusLabel(row.plannerStatus) }}
  43. </el-tag>
  44. </div>
  45. <span v-else style="color: #999;">—</span>
  46. </template>
  47. </el-table-column>
  48. <el-table-column label="用户确认" width="110">
  49. <template slot-scope="{ row }">
  50. <div class="status-cell">
  51. <i :class="row.userConfirmed === 1 ? 'el-icon-circle-check' : 'el-icon-time'"
  52. :style="{ color: row.userConfirmed === 1 ? '#67c23a' : '#909399' }"></i>
  53. <el-tag :type="row.userConfirmed === 1 ? 'success' : 'info'" size="mini">
  54. {{ row.userConfirmed === 1 ? '已确认' : '未确认' }}
  55. </el-tag>
  56. </div>
  57. </template>
  58. </el-table-column>
  59. <el-table-column label="创建时间" width="160">
  60. <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
  61. </el-table-column>
  62. <el-table-column label="操作" width="160" fixed="right">
  63. <template slot-scope="{ row }">
  64. <el-button size="mini" type="primary" icon="el-icon-view" plain @click="viewDetail(row)">详情</el-button>
  65. </template>
  66. </el-table-column>
  67. </el-table>
  68. <el-pagination
  69. @size-change="handleSizeChange"
  70. @current-change="handleCurrentChange"
  71. :current-page="page"
  72. :page-sizes="[10, 20, 50]"
  73. :page-size="size"
  74. :total="total"
  75. layout="total, sizes, prev, pager, next, jumper"
  76. class="pagination-wrap"
  77. />
  78. </el-card>
  79. <!-- 详情 Dialog -->
  80. <el-dialog title="执行记录详情" :visible.sync="detailDialogVisible" width="700px">
  81. <div v-if="currentRecord" v-loading="detailLoading">
  82. <el-descriptions :column="2" border size="small">
  83. <el-descriptions-item label="执行ID">{{ currentRecord.id }}</el-descriptions-item>
  84. <el-descriptions-item label="订单ID">{{ currentRecord.assessmentOrderId }}</el-descriptions-item>
  85. <el-descriptions-item label="家庭ID">{{ currentRecord.familyId }}</el-descriptions-item>
  86. <el-descriptions-item label="孩子ID">{{ currentRecord.childId }}</el-descriptions-item>
  87. <el-descriptions-item label="评估师">{{ currentRecord.assessorName || (currentRecord.assessorId ? 'ID:' + currentRecord.assessorId : '-') }}</el-descriptions-item>
  88. <el-descriptions-item label="评估师状态">
  89. <div class="status-cell">
  90. <i :class="executionIconClass(currentRecord.assessorStatus)" :style="{ color: executionIconColor(currentRecord.assessorStatus) }"></i>
  91. <el-tag :type="executionStatusType(currentRecord.assessorStatus)" size="mini">
  92. {{ executionStatusLabel(currentRecord.assessorStatus) }}
  93. </el-tag>
  94. </div>
  95. </el-descriptions-item>
  96. <el-descriptions-item label="评估师完成时间">{{ formatTime(currentRecord.assessorCompletedAt) }}</el-descriptions-item>
  97. <el-descriptions-item label="评估师备注">{{ currentRecord.assessorNotes || '-' }}</el-descriptions-item>
  98. <el-descriptions-item label="规划师">{{ currentRecord.plannerName || (currentRecord.plannerId ? 'ID:' + currentRecord.plannerId : '-') }}</el-descriptions-item>
  99. <el-descriptions-item label="规划师状态">
  100. <div v-if="currentRecord.plannerId" class="status-cell">
  101. <i :class="executionIconClass(currentRecord.plannerStatus)" :style="{ color: executionIconColor(currentRecord.plannerStatus) }"></i>
  102. <el-tag :type="executionStatusType(currentRecord.plannerStatus)" size="mini">
  103. {{ executionStatusLabel(currentRecord.plannerStatus) }}
  104. </el-tag>
  105. </div>
  106. <span v-else>无</span>
  107. </el-descriptions-item>
  108. <el-descriptions-item label="规划师完成时间">{{ formatTime(currentRecord.plannerCompletedAt) }}</el-descriptions-item>
  109. <el-descriptions-item label="规划师备注">{{ currentRecord.plannerNotes || '-' }}</el-descriptions-item>
  110. <el-descriptions-item label="用户确认">
  111. <div class="status-cell">
  112. <i :class="currentRecord.userConfirmed === 1 ? 'el-icon-circle-check' : 'el-icon-time'"
  113. :style="{ color: currentRecord.userConfirmed === 1 ? '#67c23a' : '#909399' }"></i>
  114. <el-tag :type="currentRecord.userConfirmed === 1 ? 'success' : 'info'" size="mini">
  115. {{ currentRecord.userConfirmed === 1 ? '已确认' : '未确认' }}
  116. </el-tag>
  117. </div>
  118. </el-descriptions-item>
  119. <el-descriptions-item label="确认时间">{{ formatTime(currentRecord.userConfirmedAt) }}</el-descriptions-item>
  120. <el-descriptions-item label="创建时间">{{ formatTime(currentRecord.createdAt) }}</el-descriptions-item>
  121. <el-descriptions-item label="更新时间">{{ formatTime(currentRecord.updatedAt) }}</el-descriptions-item>
  122. </el-descriptions>
  123. <div v-if="currentRecord.assessorSettlement || currentRecord.plannerSettlement" style="margin-top: 20px;">
  124. <h4>结算信息</h4>
  125. <el-descriptions :column="2" border size="small">
  126. <el-descriptions-item v-if="currentRecord.assessorSettlement" label="评估师结算金额">
  127. ¥{{ (currentRecord.assessorSettlement.commissionAmount / 100).toFixed(2) }}
  128. </el-descriptions-item>
  129. <el-descriptions-item v-if="currentRecord.assessorSettlement" label="评估师结算状态">
  130. <el-tag :type="settlementStatusType(currentRecord.assessorSettlement.status)" size="mini">
  131. {{ settlementStatusLabel(currentRecord.assessorSettlement.status) }}
  132. </el-tag>
  133. </el-descriptions-item>
  134. <el-descriptions-item v-if="currentRecord.plannerSettlement" label="规划师结算金额">
  135. ¥{{ (currentRecord.plannerSettlement.commissionAmount / 100).toFixed(2) }}
  136. </el-descriptions-item>
  137. <el-descriptions-item v-if="currentRecord.plannerSettlement" label="规划师结算状态">
  138. <el-tag :type="settlementStatusType(currentRecord.plannerSettlement.status)" size="mini">
  139. {{ settlementStatusLabel(currentRecord.plannerSettlement.status) }}
  140. </el-tag>
  141. </el-descriptions-item>
  142. </el-descriptions>
  143. </div>
  144. </div>
  145. </el-dialog>
  146. </div>
  147. </template>
  148. <script>
  149. import { searchUsers } from '@/api/admin'
  150. export default {
  151. name: 'DanExecution',
  152. data() {
  153. return {
  154. list: [],
  155. loading: false,
  156. page: 1,
  157. size: 10,
  158. total: 0,
  159. statusFilter: '',
  160. detailDialogVisible: false,
  161. detailLoading: false,
  162. currentRecord: null
  163. }
  164. },
  165. created() {
  166. this.loadData()
  167. },
  168. methods: {
  169. async loadData() {
  170. this.loading = true
  171. try {
  172. // TODO: 调用后端 API 获取执行记录列表
  173. // const res = await this.$axios.post('/api/admin/dan-execution/list', {
  174. // page: this.page,
  175. // size: this.size,
  176. // status: this.statusFilter
  177. // })
  178. // this.list = res.data.data.records
  179. // this.total = res.data.data.total
  180. this.list = []
  181. this.total = 0
  182. } catch (e) {
  183. console.error('加载执行记录失败', e)
  184. this.$message.error('加载失败')
  185. } finally {
  186. this.loading = false
  187. }
  188. },
  189. async resolveUserNames(items, idField) {
  190. if (!items || items.length === 0) return
  191. var ids = []
  192. items.forEach(function(item) {
  193. if (item[idField] && ids.indexOf(item[idField]) === -1) ids.push(item[idField])
  194. })
  195. items.forEach(function(item) {
  196. if (item.assessorId && ids.indexOf(item.assessorId) === -1) ids.push(item.assessorId)
  197. })
  198. var nameMap = {}
  199. for (var i = 0; i < ids.length; i++) {
  200. try {
  201. var res = await searchUsers({ keyword: String(ids[i]) })
  202. if (res.data && res.data.length > 0) {
  203. var u = res.data[0]
  204. nameMap[ids[i]] = u.nickname || u.realName || '用户' + ids[i]
  205. } else {
  206. nameMap[ids[i]] = '用户' + ids[i]
  207. }
  208. } catch (e) {
  209. nameMap[ids[i]] = '用户' + ids[i]
  210. }
  211. }
  212. items.forEach(function(item) {
  213. item.plannerName = nameMap[item.plannerId] || ''
  214. item.assessorName = nameMap[item.assessorId] || ''
  215. })
  216. },
  217. async viewDetail(row) {
  218. this.detailDialogVisible = true
  219. this.detailLoading = true
  220. try {
  221. const res = await this.$axios.post(`/api/dan-execution/detail/${row.id}`)
  222. if (res.data.code === 200) {
  223. this.currentRecord = res.data.data
  224. } else {
  225. this.$message.error(res.data.message || '加载失败')
  226. }
  227. } catch (e) {
  228. console.error('加载详情失败', e)
  229. this.$message.error('加载失败')
  230. } finally {
  231. this.detailLoading = false
  232. }
  233. },
  234. handleSearch() {
  235. this.page = 1
  236. this.loadData()
  237. },
  238. handleSizeChange(val) {
  239. this.size = val
  240. this.loadData()
  241. },
  242. handleCurrentChange(val) {
  243. this.page = val
  244. this.loadData()
  245. },
  246. formatTime(time) {
  247. if (!time) return '-'
  248. return new Date(time).toLocaleString('zh-CN')
  249. },
  250. executionStatusType(status) {
  251. if (status === 'completed') return 'success'
  252. if (status === 'pending') return 'warning'
  253. if (status === 'rejected') return 'danger'
  254. return 'info'
  255. },
  256. executionStatusLabel(status) {
  257. if (status === 'completed') return '已完成'
  258. if (status === 'pending') return '进行中'
  259. if (status === 'rejected') return '已拒绝'
  260. return status || '-'
  261. },
  262. executionIconClass(status) {
  263. if (status === 'completed') return 'el-icon-circle-check'
  264. if (status === 'pending') return 'el-icon-loading'
  265. if (status === 'rejected') return 'el-icon-circle-close'
  266. return 'el-icon-info'
  267. },
  268. executionIconColor(status) {
  269. if (status === 'completed') return '#67c23a'
  270. if (status === 'pending') return '#e6a23c'
  271. if (status === 'rejected') return '#f56c6c'
  272. return '#909399'
  273. },
  274. settlementStatusType(status) {
  275. if (status === 'settled') return 'success'
  276. if (status === 'pending') return 'warning'
  277. if (status === 'failed') return 'danger'
  278. return 'info'
  279. },
  280. settlementStatusLabel(status) {
  281. if (status === 'settled') return '已结算'
  282. if (status === 'pending') return '待结算'
  283. if (status === 'failed') return '失败'
  284. return status || '-'
  285. }
  286. }
  287. }
  288. </script>
  289. <style scoped>
  290. .admin-page {
  291. padding: 20px;
  292. }
  293. .admin-page-header {
  294. display: flex;
  295. justify-content: space-between;
  296. align-items: center;
  297. }
  298. .admin-page-title {
  299. font-size: 18px;
  300. font-weight: bold;
  301. }
  302. .filter-bar {
  303. display: flex;
  304. align-items: center;
  305. }
  306. .status-cell {
  307. display: flex;
  308. align-items: center;
  309. gap: 6px;
  310. }
  311. .status-cell i {
  312. font-size: 16px;
  313. }
  314. .pagination-wrap {
  315. margin-top: 20px;
  316. text-align: right;
  317. }
  318. </style>