index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <template>
  2. <div class="app-container">
  3. <div class="header-container box-card">
  4. <div class="left-title">
  5. <span class="title-text">订单申诉管理</span>
  6. </div>
  7. <div class="right-search">
  8. <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
  9. <el-form-item prop="orderCode">
  10. <el-input v-model="queryParams.orderCode" placeholder="订单号" clearable style="width: 200px"
  11. @keyup.enter="handleQuery">
  12. <template #prefix>
  13. <el-icon>
  14. <Search />
  15. </el-icon>
  16. </template>
  17. </el-input>
  18. </el-form-item>
  19. <el-form-item prop="fulfillerName">
  20. <el-input v-model="queryParams.fulfillerName" placeholder="履约者姓名" clearable style="width: 150px"
  21. @keyup.enter="handleQuery">
  22. <template #prefix>
  23. <el-icon>
  24. <Search />
  25. </el-icon>
  26. </template>
  27. </el-input>
  28. </el-form-item>
  29. <el-form-item prop="service">
  30. <el-select v-model="queryParams.service" placeholder="实际服务" clearable style="width: 160px"
  31. @change="handleQuery">
  32. <el-option v-for="item in serviceOptions" :key="item.id" :label="item.name" :value="item.id" />
  33. </el-select>
  34. </el-form-item>
  35. <el-form-item>
  36. <el-button type="primary" @click="handleQuery">查询</el-button>
  37. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  38. </el-form-item>
  39. </el-form>
  40. </div>
  41. </div>
  42. <div class="content-container box-card">
  43. <el-table v-loading="loading" :data="subOrderAppealList"
  44. :header-cell-style="{ background: '#f8f9fa', color: '#606266', fontWeight: 'bold' }">
  45. <el-table-column label="关联订单号" align="left" prop="orderCode" min-width="150" />
  46. <el-table-column label="实际服务" align="center" prop="serviceName" min-width="120" />
  47. <el-table-column label="履约者" align="center" prop="fulfillerName" min-width="100" />
  48. <el-table-column label="上报图片" align="center" prop="photoUrls" min-width="120">
  49. <template #default="scope">
  50. <div style="display: flex; gap: 5px; justify-content: center; flex-wrap: wrap;">
  51. <template v-if="scope.row.photoUrls">
  52. <image-preview v-for="(url, index) in scope.row.photoUrls.split(',')" :key="index" :src="url"
  53. :width="40" :height="40" />
  54. </template>
  55. </div>
  56. </template>
  57. </el-table-column>
  58. <el-table-column label="履约佣金(元)" align="right" prop="fulfillmentCommission" width="120">
  59. <template #default="scope">
  60. <span>{{ (scope.row.fulfillmentCommission / 100).toFixed(2) }}</span>
  61. </template>
  62. </el-table-column>
  63. <el-table-column label="提交内容" align="left" prop="reason" :show-overflow-tooltip="true" min-width="200" />
  64. <el-table-column label="提交时间" align="center" prop="createTime" width="180" sortable>
  65. <template #default="scope">
  66. <span>{{ parseTime(scope.row.createTime) }}</span>
  67. </template>
  68. </el-table-column>
  69. <el-table-column label="审核状态" align="center" prop="auditStatus" min-width="100">
  70. <template #default="scope">
  71. <el-tag v-if="scope.row.auditStatus !== undefined"
  72. :type="subOrderAppealDict.AuditStatus[scope.row.auditStatus]?.tagType || 'info'">
  73. {{ subOrderAppealDict.AuditStatus[scope.row.auditStatus]?.label || scope.row.auditStatus }}
  74. </el-tag>
  75. </template>
  76. </el-table-column>
  77. <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
  78. <template #default="scope">
  79. <el-button v-if="scope.row.auditStatus === 0 && checkPermi(['order:appeal:audit'])" link type="primary"
  80. @click="handleAudit(scope.row)">审核</el-button>
  81. <el-button link type="danger" v-hasPermi="['order:appeal:remove']"
  82. @click="handleDelete(scope.row)">删除</el-button>
  83. </template>
  84. </el-table-column>
  85. </el-table>
  86. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
  87. v-model:limit="queryParams.pageSize" @pagination="getList" />
  88. </div>
  89. <!-- 删除确认详情展示 -->
  90. <el-dialog title="确认删除申诉" v-model="openView" width="600px" append-to-body custom-class="detail-dialog">
  91. <div class="detail-container">
  92. <div class="detail-item">
  93. <span class="label">订单号:</span>
  94. <span class="value">{{ form.orderCode }}</span>
  95. </div>
  96. <div class="detail-item">
  97. <span class="label">实际服务:</span>
  98. <span class="value">{{ form.serviceName }}</span>
  99. </div>
  100. <div class="detail-item">
  101. <span class="label">履约者:</span>
  102. <span class="value">{{ form.fulfillerName }}</span>
  103. </div>
  104. <div class="detail-item">
  105. <span class="label">履约佣金:</span>
  106. <span class="value highlighting">{{ (form.fulfillmentCommission / 100).toFixed(2) }} 元</span>
  107. </div>
  108. <div class="detail-item full-width">
  109. <span class="label">提交时间:</span>
  110. <span class="value">{{ parseTime(form.createTime) }}</span>
  111. </div>
  112. <div class="detail-item full-width">
  113. <span class="label">申诉理由:</span>
  114. <div class="reason-content">{{ form.reason }}</div>
  115. </div>
  116. <div class="detail-item full-width">
  117. <span class="label">图片展示:</span>
  118. <div class="photo-list" v-if="form.photoUrls">
  119. <image-preview v-for="(url, index) in form.photoUrls.split(',')" :key="index" :src="url" :width="120"
  120. :height="120" style="margin-right: 10px; margin-bottom: 10px;" />
  121. </div>
  122. </div>
  123. <div class="detail-item">
  124. <span class="label">审核状态:</span>
  125. <span class="value">
  126. <el-tag v-if="form.auditStatus !== undefined"
  127. :type="subOrderAppealDict.AuditStatus[form.auditStatus]?.tagType || 'info'">
  128. {{ subOrderAppealDict.AuditStatus[form.auditStatus]?.label || form.auditStatus }}
  129. </el-tag>
  130. </span>
  131. </div>
  132. <div class="detail-item">
  133. <span class="label">审核人:</span>
  134. <span class="value">{{ form.auditorName || '-' }}</span>
  135. </div>
  136. <div class="detail-item">
  137. <span class="label">审核时间:</span>
  138. <span class="value">{{ parseTime(form.auditTime) || '-' }}</span>
  139. </div>
  140. <div class="detail-item full-width" v-if="form.rejectReason">
  141. <span class="label">驳回理由:</span>
  142. <div class="reason-content">{{ form.rejectReason }}</div>
  143. </div>
  144. </div>
  145. <template #footer>
  146. <div class="dialog-footer">
  147. <el-button type="danger" @click="submitDelete">确认删除</el-button>
  148. <el-button @click="openView = false">取 消</el-button>
  149. </div>
  150. </template>
  151. </el-dialog>
  152. <!-- 审核对话框 -->
  153. <el-dialog title="审核操作" v-model="openAudit" width="500px" append-to-body>
  154. <el-form :model="auditForm" :rules="auditRules" ref="auditRef" label-width="100px">
  155. <el-form-item label="审核结果" prop="result">
  156. <el-radio-group v-model="auditForm.result">
  157. <el-radio :label="1">通过</el-radio>
  158. <el-radio :label="2">驳回</el-radio>
  159. </el-radio-group>
  160. </el-form-item>
  161. <el-form-item label="驳回原因" prop="reason" v-if="auditForm.result === 2">
  162. <el-input v-model="auditForm.reason" type="textarea" placeholder="请输入驳回原因" />
  163. </el-form-item>
  164. </el-form>
  165. <template #footer>
  166. <div class="dialog-footer">
  167. <el-button type="primary" @click="submitAudit">确 定</el-button>
  168. <el-button @click="openAudit = false">取 消</el-button>
  169. </div>
  170. </template>
  171. </el-dialog>
  172. </div>
  173. </template>
  174. <script setup name="SubOrderAppeal">
  175. import { listSubOrderAppeal, delSubOrderAppeal, auditSubOrderAppeal } from "@/api/order/subOrderAppeal";
  176. import { listAllService } from "@/api/service/list";
  177. import { parseTime } from "@/utils/ruoyi";
  178. import { checkPermi } from "@/utils/permission";
  179. import subOrderAppealDict from "@/json/subOrderAppeal.json";
  180. const { proxy } = getCurrentInstance();
  181. const subOrderAppealList = ref([]);
  182. const loading = ref(true);
  183. const showSearch = ref(true);
  184. const total = ref(0);
  185. const openView = ref(false);
  186. const openAudit = ref(false);
  187. const serviceOptions = ref([]);
  188. const auditForm = ref({
  189. id: undefined,
  190. result: 1,
  191. reason: undefined
  192. });
  193. const auditRules = ref({
  194. result: [{ required: true, message: "请选择审核结果", trigger: "change" }],
  195. reason: [{ required: true, message: "请输入驳回原因", trigger: "blur" }]
  196. });
  197. const data = reactive({
  198. form: {},
  199. queryParams: {
  200. pageNum: 1,
  201. pageSize: 10,
  202. orderCode: undefined, // 改为 orderCode
  203. fulfillerName: undefined, // 履约者名称
  204. service: undefined,
  205. reason: undefined,
  206. },
  207. });
  208. const { queryParams, form } = toRefs(data);
  209. /** 查询服务列表用于下拉框 */
  210. function getServiceList() {
  211. listAllService().then(response => {
  212. serviceOptions.value = response.data;
  213. // 数据记载后重新渲染列表,确保 serviceName 正确映射
  214. getList();
  215. });
  216. }
  217. /** 查询列表 */
  218. function getList() {
  219. loading.value = true;
  220. listSubOrderAppeal(queryParams.value).then(response => {
  221. subOrderAppealList.value = response.rows.map(item => {
  222. // 匹配服务名称
  223. const serviceObj = serviceOptions.value.find(s => s.id === item.service);
  224. return {
  225. ...item,
  226. serviceName: serviceObj ? serviceObj.name : item.service
  227. };
  228. });
  229. total.value = response.total;
  230. loading.value = false;
  231. });
  232. }
  233. /** 搜索按钮操作 */
  234. function handleQuery() {
  235. queryParams.value.pageNum = 1;
  236. getList();
  237. }
  238. /** 重置按钮操作 */
  239. function resetQuery() {
  240. proxy.resetForm("queryRef");
  241. handleQuery();
  242. }
  243. /** 删除获取详情操作 */
  244. function handleDelete(row) {
  245. openView.value = true;
  246. form.value = { ...row };
  247. }
  248. /** 确认删除请求 */
  249. function submitDelete() {
  250. delSubOrderAppeal(form.value.id).then(() => {
  251. proxy.$modal.msgSuccess("删除成功");
  252. openView.value = false;
  253. getList();
  254. });
  255. }
  256. /** 审核按钮操作 */
  257. function handleAudit(row) {
  258. auditForm.value = {
  259. id: row.id,
  260. result: 1,
  261. reason: undefined
  262. };
  263. openAudit.value = true;
  264. if (proxy.$refs["auditRef"]) {
  265. proxy.$refs["auditRef"].resetFields();
  266. }
  267. }
  268. /** 提交审核 */
  269. function submitAudit() {
  270. proxy.$refs["auditRef"].validate(valid => {
  271. if (valid) {
  272. auditSubOrderAppeal(auditForm.value).then(response => {
  273. proxy.$modal.msgSuccess("审核成功");
  274. openAudit.value = false;
  275. getList();
  276. });
  277. }
  278. });
  279. }
  280. // 初始化加载
  281. getServiceList();
  282. </script>
  283. <style lang="scss" scoped>
  284. .app-container {
  285. padding: 20px;
  286. background-color: #f5f7fa;
  287. min-height: calc(100vh - 84px);
  288. }
  289. .box-card {
  290. background: #fff;
  291. border-radius: 8px;
  292. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
  293. margin-bottom: 20px;
  294. padding: 20px;
  295. }
  296. .header-container {
  297. display: flex;
  298. justify-content: space-between;
  299. align-items: center;
  300. padding: 15px 25px;
  301. .left-title {
  302. .title-text {
  303. font-size: 18px;
  304. font-weight: 600;
  305. color: #303133;
  306. }
  307. }
  308. .right-search {
  309. :deep(.el-form-item) {
  310. margin-bottom: 0;
  311. margin-right: 12px;
  312. }
  313. :deep(.el-input__wrapper) {
  314. border-radius: 20px;
  315. padding-left: 15px;
  316. }
  317. :deep(.el-select .el-input__wrapper) {
  318. border-radius: 20px;
  319. }
  320. }
  321. }
  322. .content-container {
  323. padding: 0;
  324. overflow: hidden;
  325. :deep(.el-table) {
  326. --el-table-header-bg-color: #f8f9fa;
  327. border-radius: 8px 8px 0 0;
  328. }
  329. }
  330. .detail-container {
  331. display: flex;
  332. flex-wrap: wrap;
  333. gap: 20px;
  334. padding: 10px;
  335. .detail-item {
  336. width: 45%;
  337. display: flex;
  338. align-items: center;
  339. &.full-width {
  340. width: 100%;
  341. align-items: flex-start;
  342. flex-direction: column;
  343. }
  344. .label {
  345. color: #909399;
  346. font-weight: bold;
  347. margin-right: 10px;
  348. white-space: nowrap;
  349. }
  350. .value {
  351. color: #303133;
  352. &.highlighting {
  353. color: #f56c6c;
  354. font-weight: bold;
  355. }
  356. }
  357. .reason-content {
  358. margin-top: 8px;
  359. padding: 12px;
  360. background: #f8f9fa;
  361. border-radius: 4px;
  362. width: 100%;
  363. color: #606266;
  364. line-height: 1.6;
  365. }
  366. .photo-list {
  367. margin-top: 10px;
  368. }
  369. }
  370. }
  371. :deep(.el-button--primary) {
  372. border-radius: 20px;
  373. padding: 8px 20px;
  374. }
  375. :deep(.dialog-footer) {
  376. text-align: center;
  377. padding-top: 20px;
  378. }
  379. </style>