index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
  4. <el-form-item label="订单单号" prop="code">
  5. <el-input v-model="queryParams.code" placeholder="请输入订单单号" clearable style="width: 200px"
  6. @keyup.enter="handleQuery" />
  7. </el-form-item>
  8. <el-form-item label="下单人" prop="placer">
  9. <el-input v-model="queryParams.placer" placeholder="请输入下单人" clearable style="width: 200px"
  10. @keyup.enter="handleQuery" />
  11. </el-form-item>
  12. <el-form-item label="订单状态" prop="status">
  13. <el-select v-model="queryParams.status" placeholder="订单状态" clearable style="width: 200px">
  14. <el-option label="已撤销" :value="-1" />
  15. <el-option label="待审核" :value="0" />
  16. <el-option label="已驳回" :value="1" />
  17. <el-option label="待签批" :value="2" />
  18. <el-option label="生产中" :value="3" />
  19. <el-option label="已完成" :value="4" />
  20. </el-select>
  21. </el-form-item>
  22. <el-form-item>
  23. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  24. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  25. </el-form-item>
  26. </el-form>
  27. <el-row :gutter="10" class="mb8">
  28. <el-col :span="1.5">
  29. <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['erp:order:add']">新增</el-button>
  30. </el-col>
  31. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
  32. </el-row>
  33. <el-table v-loading="loading" :data="orderList">
  34. <el-table-column label="订单单号" align="center" prop="code" />
  35. <el-table-column label="下单人" align="center" prop="placerName" />
  36. <el-table-column label="下单时间" align="center" prop="placeTime" width="180">
  37. <template #default="scope">
  38. <span>{{ parseTime(scope.row.placeTime) }}</span>
  39. </template>
  40. </el-table-column>
  41. <el-table-column label="总支数" align="center" prop="totalCount" />
  42. <el-table-column label="状态" align="center" prop="status">
  43. <template #default="scope">
  44. <el-tag v-if="scope.row.status === -1" type="info">已撤销</el-tag>
  45. <el-tag v-else-if="scope.row.status === 0" type="warning">待审核</el-tag>
  46. <el-tag v-else-if="scope.row.status === 1" type="danger">已驳回</el-tag>
  47. <el-tag v-else-if="scope.row.status === 2" type="primary">待签批</el-tag>
  48. <el-tag v-else-if="scope.row.status === 3" type="info">生产中</el-tag>
  49. <el-tag v-else-if="scope.row.status === 4" type="success">已完成</el-tag>
  50. <el-tag v-else type="info">未知</el-tag>
  51. </template>
  52. </el-table-column>
  53. <el-table-column label="创建时间" align="center" prop="createTime" width="180">
  54. <template #default="scope">
  55. <span>{{ parseTime(scope.row.createTime) }}</span>
  56. </template>
  57. </el-table-column>
  58. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  59. <template #default="scope">
  60. <el-button link type="primary" icon="Stamp" @click="handleAudit(scope.row)" v-if="scope.row.status === 0"
  61. v-hasPermi="['erp:order:audit']">审核</el-button>
  62. <el-button link type="primary" icon="Timer" @click="handleViewAuditHistory(scope.row)">审核记录</el-button>
  63. </template>
  64. </el-table-column>
  65. </el-table>
  66. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
  67. v-model:limit="queryParams.pageSize" @pagination="getList" />
  68. <!-- 添加或修改订单对话框 -->
  69. <el-dialog :title="title" v-model="open" width="1000px" append-to-body>
  70. <el-form ref="orderRef" :model="form" :rules="rules" label-width="80px">
  71. <el-row>
  72. <el-col :span="12">
  73. <el-form-item label="订单单号" prop="code">
  74. <el-input v-model="form.code" placeholder="请输入订单单号" disabled />
  75. </el-form-item>
  76. </el-col>
  77. <el-col :span="12">
  78. <el-form-item label="下单人" prop="placer">
  79. <el-input v-model="form.placer" placeholder="请输入下单人" />
  80. </el-form-item>
  81. </el-col>
  82. </el-row>
  83. <el-divider content-position="center">订单明细信息</el-divider>
  84. <el-row :gutter="10" class="mb8">
  85. <el-col :span="1.5">
  86. <el-button type="primary" icon="Plus" @click="handleAddErpOrderDetail">添加</el-button>
  87. </el-col>
  88. <el-col :span="1.5">
  89. <el-button type="danger" icon="Delete" @click="handleDeleteErpOrderDetail">删除</el-button>
  90. </el-col>
  91. </el-row>
  92. <el-table :data="form.details" :row-class-name="rowErpOrderDetailIndex"
  93. @selection-change="handleErpOrderDetailSelectionChange" ref="erpOrderDetailTable">
  94. <el-table-column type="selection" width="50" align="center" />
  95. <el-table-column label="序号" align="center" prop="index" width="50" />
  96. <el-table-column label="型材" prop="modelId" width="200">
  97. <template #default="scope">
  98. <el-select v-model="scope.row.modelId" filterable placeholder="请选择型材">
  99. <el-option v-for="item in modelOptions" :key="item.rowId" :label="item.name" :value="item.rowId" />
  100. </el-select>
  101. </template>
  102. </el-table-column>
  103. <el-table-column label="颜色" prop="colorId" width="200">
  104. <template #default="scope">
  105. <el-select v-model="scope.row.colorId" filterable placeholder="请选择颜色">
  106. <el-option v-for="item in colorOptions" :key="item.rowId" :label="item.name" :value="item.rowId" />
  107. </el-select>
  108. </template>
  109. </el-table-column>
  110. <el-table-column label="数量" prop="count" width="150">
  111. <template #default="scope">
  112. <el-input-number v-model="scope.row.count" :min="1" controls-position="right" placeholder="请输入数量" />
  113. </template>
  114. </el-table-column>
  115. <el-table-column label="备注" prop="remark">
  116. <template #default="scope">
  117. <el-input v-model="scope.row.remark" placeholder="请输入备注" />
  118. </template>
  119. </el-table-column>
  120. </el-table>
  121. </el-form>
  122. <template #footer>
  123. <div class="dialog-footer">
  124. <el-button type="primary" @click="submitForm">确 定</el-button>
  125. <el-button @click="cancel">取 消</el-button>
  126. </div>
  127. </template>
  128. </el-dialog>
  129. <!-- 审核记录对话框 -->
  130. <el-dialog title="审核记录" v-model="auditHistoryOpen" width="600px" append-to-body>
  131. <el-timeline v-if="auditHistoryList.length > 0">
  132. <el-timeline-item
  133. v-for="(item, index) in auditHistoryList"
  134. :key="index"
  135. hide-timestamp
  136. :color="item.auditResult === 1 ? '#67c23a' : '#f56c6c'"
  137. >
  138. <div class="audit-card" :class="item.auditResult === 1 ? 'card-pass' : 'card-reject'">
  139. <div class="audit-card-header">
  140. <el-tag :type="item.auditResult === 1 ? 'success' : 'danger'" effect="dark" size="small">
  141. {{ item.auditResult === 1 ? '通过' : '驳回' }}
  142. </el-tag>
  143. <span class="audit-card-auditor">
  144. <el-icon><User /></el-icon> {{ item.auditorName || '未知' }}
  145. </span>
  146. </div>
  147. <div v-if="item.rejectReason" class="audit-card-reason">
  148. <span class="reason-label">驳回理由</span>
  149. <span class="reason-text">{{ item.rejectReason }}</span>
  150. </div>
  151. <div class="audit-card-time">
  152. <el-icon><Clock /></el-icon> {{ parseTime(item.auditTime) }}
  153. </div>
  154. </div>
  155. </el-timeline-item>
  156. </el-timeline>
  157. <el-empty v-else description="暂无审核记录" />
  158. </el-dialog>
  159. <!-- 订单审核对话框 -->
  160. <el-dialog title="订单审核" v-model="auditOpen" width="500px" append-to-body>
  161. <el-form ref="auditRef" :model="auditForm" :rules="auditRules" label-width="80px">
  162. <el-form-item label="审核结果" prop="auditResult">
  163. <el-radio-group v-model="auditForm.auditResult">
  164. <el-radio :label="1">通过</el-radio>
  165. <el-radio :label="2">驳回</el-radio>
  166. </el-radio-group>
  167. </el-form-item>
  168. <el-form-item label="驳回原因" prop="rejectReason" v-if="auditForm.auditResult === 2">
  169. <el-input v-model="auditForm.rejectReason" type="textarea" placeholder="请输入驳回原因" />
  170. </el-form-item>
  171. </el-form>
  172. <template #footer>
  173. <div class="dialog-footer">
  174. <el-button type="primary" @click="submitAudit">确 定</el-button>
  175. <el-button @click="auditOpen = false">取 消</el-button>
  176. </div>
  177. </template>
  178. </el-dialog>
  179. </div>
  180. </template>
  181. <script setup name="Order">
  182. import { listOrder, getOrder, addOrder, auditOrder } from "@/api/erp/order";
  183. import { listModel } from "@/api/erp/model";
  184. import { listColor } from "@/api/erp/color";
  185. import { getAuditHistory } from "@/api/erp/orderAudit";
  186. import { User, Clock } from '@element-plus/icons-vue';
  187. /** @Author: Antigravity */
  188. const { proxy } = getCurrentInstance();
  189. const orderList = ref([]);
  190. const open = ref(false);
  191. const loading = ref(true);
  192. const showSearch = ref(true);
  193. const total = ref(0);
  194. const title = ref("");
  195. const auditOpen = ref(false);
  196. const auditHistoryOpen = ref(false);
  197. const auditHistoryList = ref([]);
  198. const modelOptions = ref([]);
  199. const colorOptions = ref([]);
  200. const checkedErpOrderDetail = ref([]);
  201. const data = reactive({
  202. form: {},
  203. queryParams: {
  204. pageNum: 1,
  205. pageSize: 10,
  206. code: undefined,
  207. placer: undefined,
  208. status: undefined
  209. },
  210. rules: {
  211. placer: [{ required: true, message: "下单人不能为空", trigger: "blur" }]
  212. },
  213. auditForm: {},
  214. auditRules: {
  215. auditResult: [{ required: true, message: "请选择审核结果", trigger: "change" }],
  216. rejectReason: [{ required: true, message: "请输入驳回原因", trigger: "blur" }]
  217. }
  218. });
  219. const { queryParams, form, rules, auditForm, auditRules } = toRefs(data);
  220. /** 查询订单列表 */
  221. function getList() {
  222. loading.ref = true;
  223. listOrder(queryParams.value).then(response => {
  224. orderList.value = response.rows;
  225. total.value = response.total;
  226. loading.value = false;
  227. });
  228. }
  229. /** 查询型材和颜色选项 */
  230. function getOptions() {
  231. listModel({}).then(res => {
  232. modelOptions.value = res.data;
  233. });
  234. listColor().then(res => {
  235. colorOptions.value = res.data;
  236. });
  237. }
  238. // 取消按钮
  239. function cancel() {
  240. open.value = false;
  241. reset();
  242. }
  243. // 表单重置
  244. function reset() {
  245. form.value = {
  246. rowId: undefined,
  247. code: undefined,
  248. placer: undefined,
  249. status: 0,
  250. details: []
  251. };
  252. proxy.resetForm("orderRef");
  253. }
  254. /** 搜索按钮操作 */
  255. function handleQuery() {
  256. queryParams.value.pageNum = 1;
  257. getList();
  258. }
  259. /** 重置按钮操作 */
  260. function resetQuery() {
  261. proxy.resetForm("queryRef");
  262. handleQuery();
  263. }
  264. /** 新增按钮操作 */
  265. function handleAdd() {
  266. reset();
  267. open.value = true;
  268. title.value = "添加订单";
  269. }
  270. /** 审核按钮操作 */
  271. function handleAudit(row) {
  272. auditForm.value = {
  273. orderId: row.rowId,
  274. auditResult: 1,
  275. rejectReason: undefined
  276. };
  277. auditOpen.value = true;
  278. }
  279. /** 查看审核记录 */
  280. function handleViewAuditHistory(row) {
  281. auditHistoryList.value = [];
  282. auditHistoryOpen.value = true;
  283. getAuditHistory(row.rowId).then(res => {
  284. auditHistoryList.value = res.data || [];
  285. }).catch(() => {
  286. proxy.$modal.msgError("获取审核记录失败");
  287. });
  288. }
  289. /** 提交审核 */
  290. function submitAudit() {
  291. proxy.$refs["auditRef"].validate(valid => {
  292. if (valid) {
  293. auditOrder(auditForm.value).then(response => {
  294. proxy.$modal.msgSuccess("审核成功");
  295. auditOpen.value = false;
  296. getList();
  297. });
  298. }
  299. });
  300. }
  301. /** 提交按钮 */
  302. function submitForm() {
  303. proxy.$refs["orderRef"].validate(valid => {
  304. if (valid) {
  305. addOrder(form.value).then(response => {
  306. proxy.$modal.msgSuccess("新增成功");
  307. open.value = false;
  308. getList();
  309. });
  310. }
  311. });
  312. }
  313. /** 订单明细序号 */
  314. function rowErpOrderDetailIndex({ row, rowIndex }) {
  315. row.index = rowIndex + 1;
  316. }
  317. /** 订单明细添加按钮操作 */
  318. function handleAddErpOrderDetail() {
  319. let obj = {};
  320. obj.modelId = "";
  321. obj.colorId = "";
  322. obj.count = 1;
  323. obj.remark = "";
  324. form.value.details.push(obj);
  325. }
  326. /** 订单明细删除按钮操作 */
  327. function handleDeleteErpOrderDetail() {
  328. if (checkedErpOrderDetail.value.length == 0) {
  329. proxy.$modal.msgError("请选择要删除的订单明细数据");
  330. } else {
  331. const details = form.value.details;
  332. const checkedDetails = checkedErpOrderDetail.value;
  333. form.value.details = details.filter(function (item) {
  334. return checkedDetails.indexOf(item.index) == -1;
  335. });
  336. }
  337. }
  338. /** 复选框选中数据 */
  339. function handleErpOrderDetailSelectionChange(selection) {
  340. checkedErpOrderDetail.value = selection.map(item => item.index);
  341. }
  342. getOptions();
  343. getList();
  344. </script>
  345. <style scoped>
  346. .audit-card {
  347. background: #fff;
  348. border-radius: 8px;
  349. padding: 16px 20px;
  350. border-left: 4px solid #ebeef5;
  351. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  352. transition: box-shadow 0.2s;
  353. }
  354. .audit-card:hover {
  355. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  356. }
  357. .audit-card.card-pass {
  358. border-left-color: #67c23a;
  359. }
  360. .audit-card.card-reject {
  361. border-left-color: #f56c6c;
  362. }
  363. .audit-card-header {
  364. display: flex;
  365. align-items: center;
  366. justify-content: space-between;
  367. margin-bottom: 12px;
  368. }
  369. .audit-card-auditor {
  370. font-size: 14px;
  371. color: #606266;
  372. display: flex;
  373. align-items: center;
  374. gap: 4px;
  375. }
  376. .audit-card-reason {
  377. background: #fef0f0;
  378. border-radius: 6px;
  379. padding: 10px 14px;
  380. margin-bottom: 12px;
  381. }
  382. .reason-label {
  383. font-size: 12px;
  384. color: #f56c6c;
  385. font-weight: 500;
  386. display: block;
  387. margin-bottom: 4px;
  388. }
  389. .reason-text {
  390. font-size: 13px;
  391. color: #c03639;
  392. line-height: 1.5;
  393. }
  394. .audit-card-time {
  395. font-size: 13px;
  396. color: #909399;
  397. display: flex;
  398. align-items: center;
  399. gap: 4px;
  400. }
  401. </style>