PricesView.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <template>
  2. <SidebarLayout
  3. :menuItems="menuItems"
  4. :userName="authStore.user?.name"
  5. :activeKey="'prices'"
  6. @logout="handleLogout"
  7. >
  8. <div class="prices-page">
  9. <a-card>
  10. <a-space>
  11. <a-select v-model:value="filterWorkOrderId" placeholder="筛选工单" style="width: 250px" allowClear @change="loadPrices">
  12. <a-select-option v-for="wo in workOrders" :key="wo.id" :value="wo.id">
  13. {{ wo.order_no }} - {{ wo.style_name }}
  14. </a-select-option>
  15. </a-select>
  16. <a-button type="primary" @click="showCreateModal">设置工价</a-button>
  17. </a-space>
  18. </a-card>
  19. <a-card class="mt-4" title="工价列表">
  20. <a-table :columns="columns" :dataSource="prices" :loading="loading" rowKey="id">
  21. <template #price="{ record }">
  22. ¥{{ record.price.toFixed(2) }}
  23. </template>
  24. <template #action="{ record }">
  25. <a-space>
  26. <a-button size="small" @click="showEditModal(record)">编辑</a-button>
  27. <a-popconfirm title="删除此工价?" @confirm="deletePrice(record.id)">
  28. <a-button size="small" danger>删除</a-button>
  29. </a-popconfirm>
  30. </a-space>
  31. </template>
  32. </a-table>
  33. </a-card>
  34. <!-- 创建/编辑工价模态框 -->
  35. <a-modal v-model:open="modalVisible" :title="modalTitle" @ok="handleSubmit" :confirmLoading="submitting">
  36. <a-form ref="formRef" :model="formState" :rules="formRules" layout="vertical">
  37. <a-form-item label="款号" name="style" v-if="!isEdit">
  38. <a-input v-model:value="formState.style" placeholder="请输入款号" />
  39. </a-form-item>
  40. <a-form-item label="工序" name="process_id">
  41. <a-select v-model:value="formState.process_id" placeholder="请选择工序">
  42. <a-select-option v-for="p in processes" :key="p.id" :value="p.id">
  43. {{ p.name }} ({{ p.code }})
  44. </a-select-option>
  45. </a-select>
  46. </a-form-item>
  47. <a-form-item label="工价(元)" name="price">
  48. <a-input-number v-model:value="formState.price" :min="0" :max="10000" :step="0.1" style="width: 100%" />
  49. </a-form-item>
  50. <a-form-item label="备注" name="remark">
  51. <a-textarea v-model:value="formState.remark" :rows="2" placeholder="可选" />
  52. </a-form-item>
  53. </a-form>
  54. </a-modal>
  55. </div>
  56. </SidebarLayout>
  57. </template>
  58. <script setup lang="ts">
  59. import { ref, reactive, onMounted } from 'vue';
  60. import { useRouter } from 'vue-router';
  61. import { message } from 'ant-design-vue';
  62. import { useAuthStore } from '@/stores/auth';
  63. import { SidebarLayout } from '@smartcut/shared-components';
  64. import { WorkOrderApi, ProcessApi, createApiClient } from '@smartcut/api-client';
  65. import { STORAGE_KEYS } from '@smartcut/shared-utils';
  66. import type { WorkOrder, Process, PriceTemplate } from '@smartcut/types';
  67. const router = useRouter();
  68. const authStore = useAuthStore();
  69. const menuItems = [
  70. { key: 'dashboard', label: '主页看板', icon: 'DashboardOutlined', path: '/' },
  71. { key: 'work-orders', label: '工单管理', icon: 'FileOutlined', path: '/work-orders' },
  72. { key: 'processes', label: '工序管理', icon: 'AppstoreOutlined', path: '/processes' },
  73. { key: 'records', label: '计件记录', icon: 'UnorderedListOutlined', path: '/records' },
  74. { key: 'salary', label: '工资管理', icon: 'PayCircleOutlined', path: '/salary' },
  75. { key: 'users', label: '用户管理', icon: 'UserOutlined', path: '/users' }
  76. ];
  77. const apiClient = createApiClient({
  78. baseURL: window.location.origin,
  79. tokenKey: STORAGE_KEYS.FACTORY_TOKEN
  80. });
  81. const workOrderApi = new WorkOrderApi(apiClient);
  82. const processApi = new ProcessApi(apiClient);
  83. const prices = ref<PriceTemplate[]>([]);
  84. const workOrders = ref<WorkOrder[]>([]);
  85. const processes = ref<Process[]>([]);
  86. const loading = ref(false);
  87. const filterWorkOrderId = ref<number | null>(null);
  88. const columns = [
  89. { title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
  90. { title: '款号', dataIndex: 'style', key: 'style', width: 120 },
  91. { title: '工序', dataIndex: 'process_name', key: 'process_name', width: 150 },
  92. { title: '工价', key: 'price', width: 100, slots: { customRender: 'price' } },
  93. { title: '备注', dataIndex: 'remark', key: 'remark', width: 200 },
  94. { title: '操作', key: 'action', width: 150, slots: { customRender: 'action' } }
  95. ];
  96. const modalVisible = ref(false);
  97. const modalTitle = ref('设置工价');
  98. const isEdit = ref(false);
  99. const submitting = ref(false);
  100. const formRef = ref();
  101. const editingId = ref<number | null>(null);
  102. const formState = reactive({
  103. style: '',
  104. process_id: null as number | null,
  105. price: 0,
  106. remark: ''
  107. });
  108. const formRules = {
  109. style: [{ required: true, message: '请输入款号', trigger: 'blur' }],
  110. process_id: [{ required: true, message: '请选择工序', trigger: 'change' }],
  111. price: [{ required: true, message: '请输入工价', trigger: 'blur' }]
  112. };
  113. async function loadPrices() {
  114. loading.value = true;
  115. try {
  116. const params: any = {};
  117. if (filterWorkOrderId.value) params.work_order_id = filterWorkOrderId.value;
  118. const response = await apiClient.get('/prices', { params });
  119. prices.value = response.data.data || [];
  120. } catch (error) {
  121. message.error('加载工价列表失败');
  122. } finally {
  123. loading.value = false;
  124. }
  125. }
  126. async function loadWorkOrders() {
  127. try {
  128. const result = await workOrderApi.getWorkOrders({ page: 1, page_size: 100 });
  129. workOrders.value = result.list;
  130. } catch (error) {
  131. // ignore
  132. }
  133. }
  134. async function loadProcesses() {
  135. try {
  136. processes.value = await processApi.getProcesses();
  137. } catch (error) {
  138. // ignore
  139. }
  140. }
  141. function showCreateModal() {
  142. modalTitle.value = '设置工价';
  143. isEdit.value = false;
  144. modalVisible.value = true;
  145. formRef.value?.resetFields();
  146. formState.style = '';
  147. formState.process_id = null;
  148. formState.price = 0;
  149. formState.remark = '';
  150. }
  151. function showEditModal(record: PriceTemplate) {
  152. modalTitle.value = '编辑工价';
  153. isEdit.value = true;
  154. editingId.value = record.id;
  155. modalVisible.value = true;
  156. formState.style = record.style;
  157. formState.process_id = record.process_id;
  158. formState.price = record.price;
  159. formState.remark = record.remark;
  160. }
  161. async function handleSubmit() {
  162. try {
  163. await formRef.value.validate();
  164. submitting.value = true;
  165. if (isEdit.value && editingId.value) {
  166. await apiClient.put(`/prices/${editingId.value}`, {
  167. process_id: formState.process_id,
  168. price: formState.price,
  169. remark: formState.remark
  170. });
  171. message.success('工价已更新');
  172. } else {
  173. await apiClient.post('/prices', {
  174. style: formState.style,
  175. process_id: formState.process_id,
  176. price: formState.price,
  177. remark: formState.remark
  178. });
  179. message.success('工价已创建');
  180. }
  181. modalVisible.value = false;
  182. loadPrices();
  183. } catch (error: any) {
  184. message.error(error.response?.data?.msg || '操作失败');
  185. } finally {
  186. submitting.value = false;
  187. }
  188. }
  189. async function deletePrice(id: number) {
  190. try {
  191. await apiClient.delete(`/prices/${id}`);
  192. message.success('工价已删除');
  193. loadPrices();
  194. } catch (error) {
  195. message.error('删除失败');
  196. }
  197. }
  198. function handleLogout() {
  199. authStore.logout();
  200. message.success('已退出登录');
  201. router.push('/login');
  202. }
  203. onMounted(() => {
  204. loadPrices();
  205. loadWorkOrders();
  206. loadProcesses();
  207. });
  208. </script>
  209. <style scoped lang="postcss">
  210. .prices-page { padding: 0; }
  211. .mt-4 { margin-top: 16px; }
  212. </style>