x
This commit is contained in:
Generated
+87
-154
@@ -8,11 +8,11 @@
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"air-datepicker": "^3.6.0",
|
||||
"chart.js": "^4.5.1",
|
||||
"element-plus": "^2.14.1",
|
||||
"pinia": "^3.0.4",
|
||||
"tdesign-icons-vue-next": "^0.4.4",
|
||||
"tdesign-vue-next": "^1.20.1",
|
||||
"vue": "^3.5.34",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
@@ -58,6 +58,15 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
|
||||
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
||||
@@ -71,24 +80,6 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ctrl/tinycolor": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
|
||||
"integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@element-plus/icons-vue": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
|
||||
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
@@ -123,31 +114,6 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
|
||||
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
|
||||
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.7.5",
|
||||
"@floating-ui/utils": "^0.2.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
|
||||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
@@ -518,10 +484,22 @@
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
|
||||
"node_modules/@types/sortablejs": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.9.tgz",
|
||||
"integrity": "sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/tinycolor2": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz",
|
||||
"integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/validator": {
|
||||
"version": "13.15.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz",
|
||||
"integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
@@ -738,44 +716,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "14.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz",
|
||||
"integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.21",
|
||||
"@vueuse/metadata": "14.3.0",
|
||||
"@vueuse/shared": "14.3.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "14.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz",
|
||||
"integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "14.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz",
|
||||
"integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/air-datepicker": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/air-datepicker/-/air-datepicker-3.6.0.tgz",
|
||||
@@ -789,12 +729,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
||||
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/birpc": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
|
||||
@@ -853,32 +787,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/element-plus": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.14.1.tgz",
|
||||
"integrity": "sha512-UFnm1+BckNi+azkKJ7L32q1uXs9ekr99Z9pWTQPeDR05jqEWUwQq51ro4kZMVrANbjknX3Z7ukCZwTi2T6Tr9A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.2.0",
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@floating-ui/dom": "^1.7.6",
|
||||
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@vueuse/core": "14.3.0",
|
||||
"async-validator": "^4.2.5",
|
||||
"dayjs": "^1.11.20",
|
||||
"lodash": "^4.18.1",
|
||||
"lodash-es": "^4.18.1",
|
||||
"lodash-unified": "^1.0.3",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-wheel-es": "^1.2.0",
|
||||
"vue-component-type-helpers": "^3.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.7"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||
@@ -1221,29 +1129,12 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
|
||||
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-unified": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz",
|
||||
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/lodash-es": "*",
|
||||
"lodash": "*",
|
||||
"lodash-es": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -1253,12 +1144,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
@@ -1290,12 +1175,6 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-wheel-es": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
|
||||
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
@@ -1417,6 +1296,12 @@
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.15.7",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz",
|
||||
"integrity": "sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -1447,6 +1332,51 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/tdesign-icons-vue-next": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/tdesign-icons-vue-next/-/tdesign-icons-vue-next-0.4.4.tgz",
|
||||
"integrity": "sha512-UPKyR5pea840aeI5p/k8VLVgfyVzndSyE4t1yRBD8uYWiuV0p29jZbDn8hMnes9Tz5JPgywCGzbk4QJzt684NA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tdesign-vue-next": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/tdesign-vue-next/-/tdesign-vue-next-1.20.1.tgz",
|
||||
"integrity": "sha512-E8S6pSAiOvq20wxkMuXScVTVJPvoYgoUl1FLCemdrcvEbDWni+ZsT8F5y2Y2LP4FqRvdCNmDwC1LiZ8ecKifDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.22.6",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/validator": "^13.7.17",
|
||||
"dayjs": "^1.11.10",
|
||||
"lodash-es": "^4.18.1",
|
||||
"mitt": "^3.0.1",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tdesign-icons-vue-next": "~0.4.4",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"validator": "^13.15.23"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinycolor2": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
|
||||
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
@@ -1493,6 +1423,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/validator": {
|
||||
"version": "13.15.35",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz",
|
||||
"integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.14",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz",
|
||||
@@ -1599,12 +1538,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-component-type-helpers": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.3.tgz",
|
||||
"integrity": "sha512-x4nsFpy5Pe8fqPzp/5vkTPeTTDBpAx4WVtV47Ejt0+2FQrq4pRRsJs7JmYRqMFzTu/LW+pCWEjQ3YVCkPV7f9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.6.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"air-datepicker": "^3.6.0",
|
||||
"chart.js": "^4.5.1",
|
||||
"element-plus": "^2.14.1",
|
||||
"pinia": "^3.0.4",
|
||||
"tdesign-icons-vue-next": "^0.4.4",
|
||||
"tdesign-vue-next": "^1.20.1",
|
||||
"vue": "^3.5.34",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<div class="user-avatar">{{ (store.user.full_name || store.user.username).charAt(0).toUpperCase() }}</div>
|
||||
<span class="user-name">{{ store.user.full_name || store.user.username }}</span>
|
||||
</div>
|
||||
<button class="btn btn-secondary" @click="handleLogout">退出</button>
|
||||
<t-button theme="default" variant="outline" size="small" @click="handleLogout">退出</t-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link to="/login" class="btn btn-primary">登录</router-link>
|
||||
|
||||
@@ -3,12 +3,11 @@ import { createPinia } from 'pinia'
|
||||
import router from './router'
|
||||
import App from './App.vue'
|
||||
import './assets/styles/main.css'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import TDesign from 'tdesign-vue-next'
|
||||
import 'tdesign-vue-next/es/style/index.css'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus, { locale: zhCn })
|
||||
app.use(TDesign)
|
||||
app.mount('#app')
|
||||
|
||||
@@ -67,7 +67,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
<t-alert
|
||||
v-if="!state.backendDbReady"
|
||||
:title="state.backendDbMessage"
|
||||
type="warning"
|
||||
@@ -76,7 +76,7 @@ onUnmounted(() => {
|
||||
style="margin-bottom: 16px;"
|
||||
/>
|
||||
|
||||
<div v-loading="state.loading">
|
||||
<t-loading :loading="state.loading">
|
||||
<DashboardTab v-if="state.activeTab === 'dashboard'" />
|
||||
<ProductsTab v-else-if="state.activeTab === 'products'" />
|
||||
<MaterialsTab v-else-if="state.activeTab === 'materials'" />
|
||||
@@ -87,7 +87,7 @@ onUnmounted(() => {
|
||||
<CustomersTab v-else-if="state.activeTab === 'customers'" />
|
||||
<FinanceTab v-else-if="state.activeTab === 'finance'" />
|
||||
<MovementsTab v-else-if="state.activeTab === 'movements'" />
|
||||
</div>
|
||||
</t-loading>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@ onUnmounted(() => {
|
||||
.inventory-breadcrumb .sep { margin: 0 var(--space-2); }
|
||||
.inventory-breadcrumb .current { color: var(--text-primary); font-weight: var(--font-medium); }
|
||||
|
||||
.inventory-sidebar-wrapper :deep(.el-menu) {
|
||||
.inventory-sidebar-wrapper :deep(.t-menu) {
|
||||
border-right: none;
|
||||
background: transparent;
|
||||
}
|
||||
@@ -124,7 +124,7 @@ onUnmounted(() => {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.inventory-sidebar-wrapper :deep(.el-menu-item) {
|
||||
.inventory-sidebar-wrapper :deep(.t-menu__item) {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
font-size: var(--text-sm);
|
||||
@@ -132,12 +132,12 @@ onUnmounted(() => {
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0 var(--space-4);
|
||||
}
|
||||
.inventory-sidebar-wrapper :deep(.el-menu-item.is-active) {
|
||||
.inventory-sidebar-wrapper :deep(.t-is-active) {
|
||||
background: var(--primary-50);
|
||||
color: var(--primary-600);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
.inventory-sidebar-wrapper :deep(.el-menu-item:hover) {
|
||||
.inventory-sidebar-wrapper :deep(.t-menu__item:hover) {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -81,57 +81,57 @@ onMounted(() => { loadCustomers() })
|
||||
<template>
|
||||
<div class="customers-tab">
|
||||
<div class="table-header">
|
||||
<el-button type="primary" @click="openCreateCustomer">+ 新增客户</el-button>
|
||||
<t-button type="primary" @click="openCreateCustomer">+ 新增客户</t-button>
|
||||
</div>
|
||||
<el-table :data="state.customers" style="width: 100%" v-loading="state.loading" empty-text="暂无客户数据">
|
||||
<el-table-column prop="code" label="编码" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="contact_person" label="联系人">
|
||||
<t-table :data="state.customers" style="width: 100%" :loading="state.loading" empty-text="暂无客户数据">
|
||||
<t-table-column prop="code" label="编码" />
|
||||
<t-table-column prop="name" label="名称" />
|
||||
<t-table-column prop="contact_person" label="联系人">
|
||||
<template #default="{ row }">
|
||||
{{ row.contact_person || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="phone" label="电话">
|
||||
</t-table-column>
|
||||
<t-table-column prop="phone" label="电话">
|
||||
<template #default="{ row }">
|
||||
{{ row.phone || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="email" label="邮箱">
|
||||
</t-table-column>
|
||||
<t-table-column prop="email" label="邮箱">
|
||||
<template #default="{ row }">
|
||||
{{ row.email || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editCustomer(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteCustomer(row.id)">删除</el-button>
|
||||
<t-button size="small" @click="editCustomer(row)">编辑</t-button>
|
||||
<t-button size="small" type="danger" @click="deleteCustomer(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
|
||||
<el-dialog v-model="showModal" :title="editingItem ? '编辑客户' : '新增客户'" width="520px" :close-on-click-modal="false">
|
||||
<el-form :model="form" label-width="80px">
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="请输入客户名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input v-model="form.contact_person" placeholder="请输入联系人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="电话">
|
||||
<el-input v-model="form.phone" placeholder="请输入电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱">
|
||||
<el-input v-model="form.email" type="email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="form.address" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<t-dialog v-model="showModal" :title="editingItem ? '编辑客户' : '新增客户'" width="520px" :close-on-click-modal="false">
|
||||
<t-form :model="form" label-width="80px">
|
||||
<t-form-item label="名称" required>
|
||||
<t-input v-model="form.name" placeholder="请输入客户名称" />
|
||||
</t-form-item>
|
||||
<t-form-item label="联系人">
|
||||
<t-input v-model="form.contact_person" placeholder="请输入联系人" />
|
||||
</t-form-item>
|
||||
<t-form-item label="电话">
|
||||
<t-input v-model="form.phone" placeholder="请输入电话" />
|
||||
</t-form-item>
|
||||
<t-form-item label="邮箱">
|
||||
<t-input v-model="form.email" type="email" placeholder="请输入邮箱" />
|
||||
</t-form-item>
|
||||
<t-form-item label="地址">
|
||||
<t-input v-model="form.address" placeholder="请输入地址" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="save">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ const { state, formatCurrency } = useInventory()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8" v-for="card in [
|
||||
<t-row :gutter="16">
|
||||
<t-col :span="8" v-for="card in [
|
||||
{ icon: '📦', value: state.dashboard?.product_count || 0, label: '成品数量' },
|
||||
{ icon: '📊', value: state.dashboard?.total_stock || 0, label: '物料库存总量' },
|
||||
{ icon: '💰', value: formatCurrency(state.dashboard?.total_value || 0), label: '库存价值' },
|
||||
@@ -13,7 +13,7 @@ const { state, formatCurrency } = useInventory()
|
||||
{ icon: '👥', value: state.dashboard?.customer_count || 0, label: '客户' },
|
||||
{ icon: '🏪', value: state.dashboard?.warehouse_count || 0, label: '仓库' }
|
||||
]" :key="card.label" style="margin-bottom: 16px">
|
||||
<el-card shadow="hover">
|
||||
<t-card shadow="hover">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<span style="font-size: 32px;">{{ card.icon }}</span>
|
||||
<div>
|
||||
@@ -21,9 +21,9 @@ const { state, formatCurrency } = useInventory()
|
||||
<div style="color: var(--text-tertiary); font-size: 13px;">{{ card.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</t-card>
|
||||
</t-col>
|
||||
</t-row>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -74,192 +74,192 @@ function getTransactionStatusLabel(status: string): string {
|
||||
<div>
|
||||
<div style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
||||
<span style="font-weight: 600;">统计周期:</span>
|
||||
<el-input-number
|
||||
<t-input-number
|
||||
v-model="periodYear"
|
||||
:min="2020"
|
||||
:max="2099"
|
||||
style="width: 120px;"
|
||||
/>
|
||||
<span>年</span>
|
||||
<el-select v-model="periodQuarter" placeholder="选择季度" style="width: 120px;">
|
||||
<el-option
|
||||
<t-select v-model="periodQuarter" placeholder="选择季度" style="width: 120px;">
|
||||
<t-option
|
||||
v-for="q in quarterOptions"
|
||||
:key="q.value"
|
||||
:label="q.label"
|
||||
:value="q.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="refreshFinance">刷新统计</el-button>
|
||||
<el-tag type="info" size="large">{{ periodLabel }}</el-tag>
|
||||
</t-select>
|
||||
<t-button type="primary" @click="refreshFinance">刷新统计</t-button>
|
||||
<t-tag type="info" size="large">{{ periodLabel }}</t-tag>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16" style="margin-bottom: 16px;">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<t-row :gutter="16" style="margin-bottom: 16px;">
|
||||
<t-col :span="6">
|
||||
<t-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">应收总额</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #409eff;">
|
||||
{{ formatCurrency(totalReceivable) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
</t-card>
|
||||
</t-col>
|
||||
<t-col :span="6">
|
||||
<t-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">应付总额</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #e6a23c;">
|
||||
{{ formatCurrency(totalPayable) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
</t-card>
|
||||
</t-col>
|
||||
<t-col :span="6">
|
||||
<t-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">周期收款</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #67c23a;">
|
||||
{{ formatCurrency(periodReceived) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
</t-card>
|
||||
</t-col>
|
||||
<t-col :span="6">
|
||||
<t-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">周期付款</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #f56c6c;">
|
||||
{{ formatCurrency(periodPaid) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</t-card>
|
||||
</t-col>
|
||||
</t-row>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<t-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">客户账款(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.customerFinanceStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="客户" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="transaction_count" label="流水数" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.customerFinanceStatement" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="partner_name" label="客户" />
|
||||
<t-table-column prop="order_count" label="订单数" />
|
||||
<t-table-column prop="transaction_count" label="流水数" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单已收">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单已收">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_received) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实收流水">
|
||||
</t-table-column>
|
||||
<t-table-column label="实收流水">
|
||||
<template #default="{ row }">{{ formatCurrency(row.transaction_received) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应收余额">
|
||||
</t-table-column>
|
||||
<t-table-column label="应收余额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.receivable_balance) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.customerFinanceStatement.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.customerFinanceStatement.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<t-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">供应商账款(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.supplierFinanceStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="供应商" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="transaction_count" label="流水数" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.supplierFinanceStatement" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="partner_name" label="供应商" />
|
||||
<t-table-column prop="order_count" label="订单数" />
|
||||
<t-table-column prop="transaction_count" label="流水数" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单已付">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单已付">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_paid) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实付流水">
|
||||
</t-table-column>
|
||||
<t-table-column label="实付流水">
|
||||
<template #default="{ row }">{{ formatCurrency(row.transaction_paid) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付余额">
|
||||
</t-table-column>
|
||||
<t-table-column label="应付余额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.payable_balance) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.supplierFinanceStatement.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.supplierFinanceStatement.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<t-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">客户-商品追溯(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.customerProductStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="客户" />
|
||||
<el-table-column prop="product_sku" label="SKU" />
|
||||
<el-table-column prop="product_name" label="商品" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="quantity" label="数量" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.customerProductStatement" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="partner_name" label="客户" />
|
||||
<t-table-column prop="product_sku" label="SKU" />
|
||||
<t-table-column prop="product_name" label="商品" />
|
||||
<t-table-column prop="order_count" label="订单数" />
|
||||
<t-table-column prop="quantity" label="数量" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已结款">
|
||||
</t-table-column>
|
||||
<t-table-column label="已结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.settled_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未结款">
|
||||
</t-table-column>
|
||||
<t-table-column label="未结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.unsettled_amount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.customerProductStatement.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.customerProductStatement.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<t-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">供应商-商品追溯(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.supplierProductStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="供应商" />
|
||||
<el-table-column prop="product_sku" label="SKU" />
|
||||
<el-table-column prop="product_name" label="商品" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="quantity" label="数量" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.supplierProductStatement" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="partner_name" label="供应商" />
|
||||
<t-table-column prop="product_sku" label="SKU" />
|
||||
<t-table-column prop="product_name" label="商品" />
|
||||
<t-table-column prop="order_count" label="订单数" />
|
||||
<t-table-column prop="quantity" label="数量" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已结款">
|
||||
</t-table-column>
|
||||
<t-table-column label="已结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.settled_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未结款">
|
||||
</t-table-column>
|
||||
<t-table-column label="未结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.unsettled_amount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.supplierProductStatement.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.supplierProductStatement.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
|
||||
<el-card>
|
||||
<t-card>
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">最近财务流水</span>
|
||||
</template>
|
||||
<el-table :data="state.financeTransactions" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="order_no" label="单号" />
|
||||
<el-table-column label="类型">
|
||||
<t-table :data="state.financeTransactions" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="order_no" label="单号" />
|
||||
<t-table-column label="类型">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getTransactionTypeTag(row.type)" size="small">
|
||||
<t-tag :type="getTransactionTypeTag(row.type)" size="small">
|
||||
{{ getTransactionTypeLabel(row.type) }}
|
||||
</el-tag>
|
||||
</t-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="partner_name" label="往来方" />
|
||||
<el-table-column label="金额">
|
||||
</t-table-column>
|
||||
<t-table-column prop="partner_name" label="往来方" />
|
||||
<t-table-column label="金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态">
|
||||
</t-table-column>
|
||||
<t-table-column label="状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'confirmed' ? 'success' : 'warning'" size="small">
|
||||
<t-tag :type="row.status === 'confirmed' ? 'success' : 'warning'" size="small">
|
||||
{{ getTransactionStatusLabel(row.status) }}
|
||||
</el-tag>
|
||||
</t-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="日期">
|
||||
</t-table-column>
|
||||
<t-table-column label="日期">
|
||||
<template #default="{ row }">{{ formatDate(row.transaction_date) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.financeTransactions.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.financeTransactions.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const { state, menuGroups, openGroups, toggleGroup, handleMenuClick } = useInven
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu
|
||||
<t-menu
|
||||
:default-active="state.activeTab"
|
||||
class="inventory-sidebar"
|
||||
@select="(key: string) => handleMenuClick(key)"
|
||||
@@ -13,17 +13,17 @@ const { state, menuGroups, openGroups, toggleGroup, handleMenuClick } = useInven
|
||||
<div v-for="group in menuGroups" :key="group.key" class="sidebar-group">
|
||||
<div class="sidebar-group-title" @click="toggleGroup(group.key)">
|
||||
<span>{{ group.title }}</span>
|
||||
<el-icon><component :is="openGroups[group.key] ? 'ArrowDown' : 'ArrowRight'" /></el-icon>
|
||||
<t-icon><component :is="openGroups[group.key] ? 'ArrowDown' : 'ArrowRight'" /></t-icon>
|
||||
</div>
|
||||
<template v-if="openGroups[group.key]">
|
||||
<el-menu-item
|
||||
<t-menu-item
|
||||
v-for="item in group.items"
|
||||
:key="group.key + '-' + item.key"
|
||||
:index="item.key"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-menu-item>
|
||||
</t-menu-item>
|
||||
</template>
|
||||
</div>
|
||||
</el-menu>
|
||||
</t-menu>
|
||||
</template>
|
||||
|
||||
@@ -87,62 +87,62 @@ onMounted(() => { loadInventory() })
|
||||
<template>
|
||||
<div class="inventory-tab">
|
||||
<div class="table-header">
|
||||
<el-button type="primary" @click="openCreate">+ 新增物料库存</el-button>
|
||||
<t-button type="primary" @click="openCreate">+ 新增物料库存</t-button>
|
||||
</div>
|
||||
<el-table :data="state.inventory" style="width: 100%" v-loading="state.loading" empty-text="暂无库存数据">
|
||||
<el-table-column prop="product_sku" label="SKU" />
|
||||
<el-table-column prop="product_name" label="物料" />
|
||||
<el-table-column prop="warehouse_name" label="仓库" />
|
||||
<el-table-column prop="quantity" label="数量" />
|
||||
<el-table-column prop="available_quantity" label="可用" />
|
||||
<el-table-column label="操作" width="180">
|
||||
<t-table :data="state.inventory" style="width: 100%" :loading="state.loading" empty-text="暂无库存数据">
|
||||
<t-table-column prop="product_sku" label="SKU" />
|
||||
<t-table-column prop="product_name" label="物料" />
|
||||
<t-table-column prop="warehouse_name" label="仓库" />
|
||||
<t-table-column prop="quantity" label="数量" />
|
||||
<t-table-column prop="available_quantity" label="可用" />
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteItem(row.id)">删除</el-button>
|
||||
<t-button size="small" @click="openEdit(row)">编辑</t-button>
|
||||
<t-button size="small" type="danger" @click="deleteItem(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
|
||||
<el-dialog v-model="showModal" :title="editingItem ? '编辑物料库存' : '新增物料库存'" width="520px" :close-on-click-modal="false">
|
||||
<el-form :model="form" label-width="80px">
|
||||
<el-form-item label="物料" required>
|
||||
<el-select v-model="form.product_id" placeholder="请选择物料" style="width: 100%">
|
||||
<el-option
|
||||
<t-dialog v-model="showModal" :title="editingItem ? '编辑物料库存' : '新增物料库存'" width="520px" :close-on-click-modal="false">
|
||||
<t-form :model="form" label-width="80px">
|
||||
<t-form-item label="物料" required>
|
||||
<t-select v-model="form.product_id" placeholder="请选择物料" style="width: 100%">
|
||||
<t-option
|
||||
v-for="m in state.materials"
|
||||
:key="m.id"
|
||||
:label="m.name + (m.sku ? ' (' + m.sku + ')' : '')"
|
||||
:value="m.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库" required>
|
||||
<el-select v-model="form.warehouse_id" placeholder="请选择仓库" style="width: 100%">
|
||||
<el-option
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="仓库" required>
|
||||
<t-select v-model="form.warehouse_id" placeholder="请选择仓库" style="width: 100%">
|
||||
<t-option
|
||||
v-for="w in state.warehouses"
|
||||
:key="w.id"
|
||||
:label="w.name"
|
||||
:value="w.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="数量" required>
|
||||
<el-input-number v-model="form.quantity" :min="0" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="锁定数量">
|
||||
<el-input-number v-model="form.locked_quantity" :min="0" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="批次号">
|
||||
<el-input v-model="form.batch_no" placeholder="请输入批次号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="库位">
|
||||
<el-input v-model="form.location" placeholder="请输入库位" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="数量" required>
|
||||
<t-input-number v-model="form.quantity" :min="0" style="width: 100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="锁定数量">
|
||||
<t-input-number v-model="form.locked_quantity" :min="0" style="width: 100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="批次号">
|
||||
<t-input v-model="form.batch_no" placeholder="请输入批次号" />
|
||||
</t-form-item>
|
||||
<t-form-item label="库位">
|
||||
<t-input v-model="form.location" placeholder="请输入库位" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="save">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -175,139 +175,139 @@ onMounted(() => { loadMaterials() })
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 12px; display: flex; gap: 8px;">
|
||||
<el-button type="primary" @click="openCreateMaterial">新增物料</el-button>
|
||||
<el-button type="success" @click="openRestockModal">物料补货</el-button>
|
||||
<t-button type="primary" @click="openCreateMaterial">新增物料</t-button>
|
||||
<t-button type="success" @click="openRestockModal">物料补货</t-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="state.materials" v-loading="state.loading" stripe>
|
||||
<el-table-column prop="sku" label="SKU" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="category" label="分类">
|
||||
<t-table :data="state.materials" :loading="state.loading" stripe>
|
||||
<t-table-column prop="sku" label="SKU" />
|
||||
<t-table-column prop="name" label="名称" />
|
||||
<t-table-column prop="category" label="分类">
|
||||
<template #default="{ row }">{{ row.category || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unit" label="单位" />
|
||||
<el-table-column label="成本价">
|
||||
</t-table-column>
|
||||
<t-table-column prop="unit" label="单位" />
|
||||
<t-table-column label="成本价">
|
||||
<template #default="{ row }">{{ formatCurrency(row.cost_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="min_stock" label="最低库存">
|
||||
</t-table-column>
|
||||
<t-table-column prop="min_stock" label="最低库存">
|
||||
<template #default="{ row }">{{ row.min_stock ?? '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" @click="editMaterial(row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteMaterial(row.id)">删除</el-button>
|
||||
<t-button type="primary" size="small" @click="editMaterial(row)">编辑</t-button>
|
||||
<t-button type="danger" size="small" @click="deleteMaterial(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.materials.length === 0" description="暂无物料数据" />
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.materials.length === 0" description="暂无物料数据" />
|
||||
|
||||
<el-dialog
|
||||
v-model="showModal"
|
||||
<t-dialog
|
||||
v-model:visible="showModal"
|
||||
:title="(editingItem ? '编辑' : '新增') + '物料'"
|
||||
width="560px"
|
||||
@closed="closeModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="类型">
|
||||
<el-input model-value="物料(纳入库存)" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="SKU" required>
|
||||
<el-input v-model="form.sku" placeholder="产品编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="产品名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-input v-model="form.category" placeholder="产品分类" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位">
|
||||
<el-input v-model="form.unit" placeholder="kg" />
|
||||
</el-form-item>
|
||||
<el-form-item label="成本价">
|
||||
<el-input-number v-model="form.cost_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最低库存">
|
||||
<el-input-number v-model="form.min_stock" :min="0" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最高库存">
|
||||
<el-input-number v-model="form.max_stock" :min="0" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="类型">
|
||||
<t-input model-value="物料(纳入库存)" disabled />
|
||||
</t-form-item>
|
||||
<t-form-item label="SKU" required>
|
||||
<t-input v-model="form.sku" placeholder="产品编码" />
|
||||
</t-form-item>
|
||||
<t-form-item label="名称" required>
|
||||
<t-input v-model="form.name" placeholder="产品名称" />
|
||||
</t-form-item>
|
||||
<t-form-item label="分类">
|
||||
<t-input v-model="form.category" placeholder="产品分类" />
|
||||
</t-form-item>
|
||||
<t-form-item label="单位">
|
||||
<t-input v-model="form.unit" placeholder="kg" />
|
||||
</t-form-item>
|
||||
<t-form-item label="成本价">
|
||||
<t-input-number v-model="form.cost_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="最低库存">
|
||||
<t-input-number v-model="form.min_stock" :min="0" style="width:100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="最高库存">
|
||||
<t-input-number v-model="form.max_stock" :min="0" style="width:100%" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveProduct">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveProduct">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showRestockModal"
|
||||
<t-dialog
|
||||
v-model:visible="showRestockModal"
|
||||
title="物料补货"
|
||||
width="900px"
|
||||
@closed="closeRestockModal"
|
||||
>
|
||||
<el-form label-width="100px" style="margin-bottom: 16px;">
|
||||
<el-form-item label="供应商">
|
||||
<el-select v-model="restockSupplierId" placeholder="请选择供应商" style="width:100%">
|
||||
<el-option
|
||||
<t-form label-width="100px" style="margin-bottom: 16px;">
|
||||
<t-form-item label="供应商">
|
||||
<t-select v-model="restockSupplierId" placeholder="请选择供应商" style="width:100%">
|
||||
<t-option
|
||||
v-for="supplier in state.suppliers"
|
||||
:key="supplier.id"
|
||||
:label="supplier.name"
|
||||
:value="supplier.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">补货物料明细</span>
|
||||
<el-button type="primary" size="small" @click="addRestockItem">+ 添加物料</el-button>
|
||||
<t-button type="primary" size="small" @click="addRestockItem">+ 添加物料</t-button>
|
||||
</div>
|
||||
<el-table :data="restockItems" border size="small">
|
||||
<el-table-column label="物料" min-width="200">
|
||||
<t-table :data="restockItems" border size="small">
|
||||
<t-table-column label="物料" min-width="200">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.material_id" placeholder="请选择物料" style="width:100%">
|
||||
<el-option
|
||||
<t-select v-model="line.material_id" placeholder="请选择物料" style="width:100%">
|
||||
<t-option
|
||||
v-for="material in state.materials"
|
||||
:key="material.id"
|
||||
:label="`${material.sku} - ${material.name}`"
|
||||
:value="material.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="1" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.quantity" :min="1" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.unit_price" :min="0" :precision="2" :step="0.01" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.unit_price" :min="0" :precision="2" :step="0.01" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="总价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
{{ formatCurrency((line.unit_price || 0) * (line.quantity || 0)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="150">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注" width="150">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
<t-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeRestockItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeRestockItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
补货总金额:{{ formatCurrency(restockTotalAmount) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showRestockModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveRestock">创建采购订单</el-button>
|
||||
<t-button @click="showRestockModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveRestock">创建采购订单</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -16,28 +16,28 @@ onMounted(() => { loadMovements() })
|
||||
|
||||
<template>
|
||||
<div class="movements-tab">
|
||||
<el-table :data="state.movements" style="width: 100%" v-loading="state.loading" empty-text="暂无库存变动记录">
|
||||
<el-table-column label="物料">
|
||||
<t-table :data="state.movements" style="width: 100%" :loading="state.loading" empty-text="暂无库存变动记录">
|
||||
<t-table-column label="物料">
|
||||
<template #default="{ row }">
|
||||
{{ row.product_name }}{{ row.product_sku ? ' (' + row.product_sku + ')' : '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="140">
|
||||
</t-table-column>
|
||||
<t-table-column label="类型" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getMovementTagType(row.movement_type)" size="small">
|
||||
<t-tag :type="getMovementTagType(row.movement_type)" size="small">
|
||||
{{ getMovementTypeLabel(row.movement_type) }}
|
||||
</el-tag>
|
||||
</t-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="quantity" label="数量" width="80" />
|
||||
<el-table-column prop="before_quantity" label="变动前" width="80" />
|
||||
<el-table-column prop="after_quantity" label="变动后" width="80" />
|
||||
<el-table-column label="时间" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column prop="quantity" label="数量" width="80" />
|
||||
<t-table-column prop="before_quantity" label="变动前" width="80" />
|
||||
<t-table-column prop="after_quantity" label="变动后" width="80" />
|
||||
<t-table-column label="时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -124,102 +124,102 @@ onMounted(() => { loadFinishedProducts() })
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<el-button type="primary" @click="openCreateProduct">新增成品</el-button>
|
||||
<t-button type="primary" @click="openCreateProduct">新增成品</t-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="state.finishedProducts" v-loading="state.loading" stripe>
|
||||
<el-table-column prop="sku" label="SKU" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="category" label="分类">
|
||||
<t-table :data="state.finishedProducts" :loading="state.loading" stripe>
|
||||
<t-table-column prop="sku" label="SKU" />
|
||||
<t-table-column prop="name" label="名称" />
|
||||
<t-table-column prop="category" label="分类">
|
||||
<template #default="{ row }">{{ row.category || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unit" label="单位" />
|
||||
<el-table-column label="成本价">
|
||||
</t-table-column>
|
||||
<t-table-column prop="unit" label="单位" />
|
||||
<t-table-column label="成本价">
|
||||
<template #default="{ row }">{{ formatCurrency(row.cost_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="销售价">
|
||||
</t-table-column>
|
||||
<t-table-column label="销售价">
|
||||
<template #default="{ row }">{{ formatCurrency(row.sale_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="基础物料成本">
|
||||
</t-table-column>
|
||||
<t-table-column label="基础物料成本">
|
||||
<template #default="{ row }">{{ formatCurrency(row.material_cost || 0) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" @click="editProduct(row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteProduct(row.id)">删除</el-button>
|
||||
<t-button type="primary" size="small" @click="editProduct(row)">编辑</t-button>
|
||||
<t-button type="danger" size="small" @click="deleteProduct(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.finishedProducts.length === 0" description="暂无成品数据" />
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.finishedProducts.length === 0" description="暂无成品数据" />
|
||||
|
||||
<el-dialog
|
||||
v-model="showModal"
|
||||
<t-dialog
|
||||
v-model:visible="showModal"
|
||||
:title="(editingItem ? '编辑' : '新增') + '成品'"
|
||||
width="560px"
|
||||
@closed="closeModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="类型">
|
||||
<el-input model-value="成品(按单生产,不做库存)" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="SKU" required>
|
||||
<el-input v-model="form.sku" placeholder="产品编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="产品名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-input v-model="form.category" placeholder="产品分类" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位">
|
||||
<el-input v-model="form.unit" placeholder="件" />
|
||||
</el-form-item>
|
||||
<el-form-item label="成本价">
|
||||
<el-input-number v-model="form.cost_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="销售价">
|
||||
<el-input-number v-model="form.sale_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="说明">
|
||||
<el-input model-value="成品不做库存,成本由下方BOM定义物料构成后自动计算" disabled />
|
||||
</el-form-item>
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="类型">
|
||||
<t-input model-value="成品(按单生产,不做库存)" disabled />
|
||||
</t-form-item>
|
||||
<t-form-item label="SKU" required>
|
||||
<t-input v-model="form.sku" placeholder="产品编码" />
|
||||
</t-form-item>
|
||||
<t-form-item label="名称" required>
|
||||
<t-input v-model="form.name" placeholder="产品名称" />
|
||||
</t-form-item>
|
||||
<t-form-item label="分类">
|
||||
<t-input v-model="form.category" placeholder="产品分类" />
|
||||
</t-form-item>
|
||||
<t-form-item label="单位">
|
||||
<t-input v-model="form.unit" placeholder="件" />
|
||||
</t-form-item>
|
||||
<t-form-item label="成本价">
|
||||
<t-input-number v-model="form.cost_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="销售价">
|
||||
<t-input-number v-model="form.sale_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="说明">
|
||||
<t-input model-value="成品不做库存,成本由下方BOM定义物料构成后自动计算" disabled />
|
||||
</t-form-item>
|
||||
|
||||
<template v-if="editingItem">
|
||||
<el-divider content-position="left">BOM物料配置</el-divider>
|
||||
<t-divider content-position="left">BOM物料配置</t-divider>
|
||||
<div style="padding: 0 12px 12px 12px;">
|
||||
<el-button type="primary" size="small" @click="addBomItem" style="margin-bottom:8px">+ 添加物料</el-button>
|
||||
<el-table :data="bomItems" size="small" border>
|
||||
<el-table-column label="物料">
|
||||
<t-button type="primary" size="small" @click="addBomItem" style="margin-bottom:8px">+ 添加物料</t-button>
|
||||
<t-table :data="bomItems" size="small" border>
|
||||
<t-table-column label="物料">
|
||||
<template #default="{ row: bomItem }">
|
||||
<el-select v-model="bomItem.material_id" placeholder="选择物料" style="width:100%">
|
||||
<el-option
|
||||
<t-select v-model="bomItem.material_id" placeholder="选择物料" style="width:100%">
|
||||
<t-option
|
||||
v-for="material in state.materials"
|
||||
:key="material.id"
|
||||
:label="`${material.sku} - ${material.name}`"
|
||||
:value="material.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="120">
|
||||
<template #default="{ row: bomItem }">
|
||||
<el-input-number v-model="bomItem.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
<t-input-number v-model="bomItem.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeBomItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeBomItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</div>
|
||||
</template>
|
||||
</el-form>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveProduct">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveProduct">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { DialogPlugin } from 'tdesign-vue-next'
|
||||
import { useInventory } from '../composables/useInventory'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
import { addNotification, handleApiError } from '@/shared/notification'
|
||||
|
||||
function confirmDialog(header: string, body: string, theme: string, confirmText: string, cancelText: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const dlg = DialogPlugin.confirm({
|
||||
header, body, theme: theme as any,
|
||||
confirmBtn: confirmText, cancelBtn: cancelText,
|
||||
onConfirm: () => { dlg.hide(); resolve(true) },
|
||||
onCancel: () => { dlg.hide(); resolve(false) },
|
||||
onClose: () => { resolve(false) }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
state,
|
||||
formatCurrency,
|
||||
@@ -140,11 +152,7 @@ async function savePurchaseOrder() {
|
||||
}
|
||||
|
||||
async function deleteOrder(id: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这个采购订单吗?', '删除确认', {
|
||||
confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('删除确认', '确定要删除这个采购订单吗?', 'warning', '删除', '取消')) return
|
||||
try {
|
||||
await apiRequest(`/api/purchase-orders/${id}`, { method: 'DELETE' })
|
||||
addNotification('采购订单已删除', 'success')
|
||||
@@ -212,29 +220,15 @@ async function receivePurchaseOrder() {
|
||||
|
||||
async function markAsPaid(orderId: number, receiptStatus: string) {
|
||||
if (receiptStatus !== 'received') {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'该订单尚未全部收货,确定要提前付款吗?建议先完成收货后再付款。',
|
||||
'非标准流程提醒',
|
||||
{ confirmButtonText: '仍要付款', cancelButtonText: '取消', type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
if (!await confirmDialog('非标准流程提醒', '该订单尚未全部收货,确定要提前付款吗?建议先完成收货后再付款。', 'warning', '仍要付款', '取消')) return
|
||||
} else {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认已向供应商支付该订单款项?', '付款确认', {
|
||||
confirmButtonText: '确认付款', cancelButtonText: '取消', type: 'success'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('付款确认', '确认已向供应商支付该订单款项?', 'success', '确认付款', '取消')) return
|
||||
}
|
||||
await doUpdateStatus(orderId, 'paid', '已付款')
|
||||
}
|
||||
|
||||
async function cancelOrder(orderId: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要作废该订单吗?此操作不可撤销。', '作废确认', {
|
||||
confirmButtonText: '确认作废', cancelButtonText: '取消', type: 'error'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('作废确认', '确定要作废该订单吗?此操作不可撤销。', 'error', '确认作废', '取消')) return
|
||||
await doUpdateStatus(orderId, 'cancelled', '已作废')
|
||||
}
|
||||
|
||||
@@ -280,223 +274,223 @@ onMounted(() => {
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 12px; display: flex; gap: 12px; align-items: center;">
|
||||
<el-select
|
||||
<t-select
|
||||
v-model="purchaseWarehouseId"
|
||||
placeholder="选择仓库"
|
||||
style="width: 200px;"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
<t-option
|
||||
v-for="warehouse in state.warehouses"
|
||||
:key="warehouse.id"
|
||||
:label="warehouse.name"
|
||||
:value="warehouse.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button @click="loadPurchaseOrders">刷新</el-button>
|
||||
<el-button type="primary" @click="openCreateOrder">新增采购订单</el-button>
|
||||
</t-select>
|
||||
<t-button @click="loadPurchaseOrders">刷新</t-button>
|
||||
<t-button type="primary" @click="openCreateOrder">新增采购订单</t-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="state.purchaseOrders" v-loading="state.loading" stripe>
|
||||
<el-table-column prop="order_no" label="采购单" />
|
||||
<el-table-column prop="supplier_name" label="供应商" />
|
||||
<el-table-column label="状态">
|
||||
<t-table :data="state.purchaseOrders" :loading="state.loading" stripe>
|
||||
<t-table-column prop="order_no" label="采购单" />
|
||||
<t-table-column prop="supplier_name" label="供应商" />
|
||||
<t-table-column label="状态">
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<el-tag
|
||||
<t-tag
|
||||
:type="row.receipt_status === 'received' ? 'success' : row.receipt_status === 'partial_received' ? 'warning' : row.receipt_status === 'cancelled' ? 'danger' : 'info'"
|
||||
size="small"
|
||||
>{{ getReceiptStatusLabel(row.receipt_status) }}</el-tag>
|
||||
<el-tag
|
||||
>{{ getReceiptStatusLabel(row.receipt_status) }}</t-tag>
|
||||
<t-tag
|
||||
:type="row.payment_status === 'paid' ? 'success' : 'info'"
|
||||
size="small"
|
||||
>{{ getPaymentStatusLabel(row.payment_status) }}</el-tag>
|
||||
>{{ getPaymentStatusLabel(row.payment_status) }}</t-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单创建">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单创建">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预计到货">
|
||||
</t-table-column>
|
||||
<t-table-column label="预计到货">
|
||||
<template #default="{ row }">{{ formatDate(row.expected_date) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际到货">
|
||||
</t-table-column>
|
||||
<t-table-column label="实际到货">
|
||||
<template #default="{ row }">{{ row.received_at ? formatDateTime(row.received_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际付款">
|
||||
</t-table-column>
|
||||
<t-table-column label="实际付款">
|
||||
<template #default="{ row }">{{ row.paid_at ? formatDateTime(row.paid_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总金额">
|
||||
</t-table-column>
|
||||
<t-table-column label="总金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.total_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已付款">
|
||||
</t-table-column>
|
||||
<t-table-column label="已付款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.paid_amount || 0) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="280">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
<t-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="isPurchaseOrderLocked(row.status)"
|
||||
@click="editOrder(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
</t-button>
|
||||
<t-button
|
||||
type="danger"
|
||||
size="small"
|
||||
:disabled="isPurchaseOrderLocked(row.status)"
|
||||
@click="deleteOrder(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
</t-button>
|
||||
<t-button
|
||||
v-if="row.receipt_status === 'pending' || row.receipt_status === 'partial_received'"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="openReceiveDialog(row)"
|
||||
>
|
||||
到货入库
|
||||
</el-button>
|
||||
<el-button
|
||||
</t-button>
|
||||
<t-button
|
||||
v-if="row.payment_status !== 'paid' && row.receipt_status !== 'cancelled'"
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="markAsPaid(row.id, row.receipt_status)"
|
||||
>
|
||||
标记已付款
|
||||
</el-button>
|
||||
<el-button
|
||||
</t-button>
|
||||
<t-button
|
||||
v-if="row.payment_status !== 'paid' && row.receipt_status !== 'cancelled'"
|
||||
type="info"
|
||||
size="small"
|
||||
@click="cancelOrder(row.id)"
|
||||
>
|
||||
作废
|
||||
</el-button>
|
||||
</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.purchaseOrders.length === 0" description="暂无采购订单" />
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.purchaseOrders.length === 0" description="暂无采购订单" />
|
||||
|
||||
<el-dialog
|
||||
v-model="showModal"
|
||||
<t-dialog
|
||||
v-model:visible="showModal"
|
||||
:title="(editingItem ? '编辑' : '新增') + '采购订单'"
|
||||
width="800px"
|
||||
@closed="closeModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="供应商" required>
|
||||
<el-select v-model="form.supplier_id" placeholder="请选择供应商" style="width:100%">
|
||||
<el-option
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="供应商" required>
|
||||
<t-select v-model="form.supplier_id" placeholder="请选择供应商" style="width:100%">
|
||||
<t-option
|
||||
v-for="supplier in state.suppliers"
|
||||
:key="supplier.id"
|
||||
:label="supplier.name"
|
||||
:value="supplier.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="预计到货">
|
||||
<el-date-picker
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="预计到货">
|
||||
<t-date-picker
|
||||
v-model="form.expected_date"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
style="width:100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</t-form-item>
|
||||
<t-form-item label="备注">
|
||||
<t-input v-model="form.remark" placeholder="备注信息" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">物料明细</span>
|
||||
<el-button type="primary" size="small" @click="addOrderItem">+ 添加物料</el-button>
|
||||
<t-button type="primary" size="small" @click="addOrderItem">+ 添加物料</t-button>
|
||||
</div>
|
||||
<el-table :data="orderItems" border size="small">
|
||||
<el-table-column label="物料" min-width="200">
|
||||
<t-table :data="orderItems" border size="small">
|
||||
<t-table-column label="物料" min-width="200">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.product_id" placeholder="请选择物料" style="width:100%">
|
||||
<el-option
|
||||
<t-select v-model="line.product_id" placeholder="请选择物料" style="width:100%">
|
||||
<t-option
|
||||
v-for="material in state.materials"
|
||||
:key="material.id"
|
||||
:label="`${material.sku} - ${material.name}`"
|
||||
:value="material.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number
|
||||
<t-input-number
|
||||
:model-value="(state.materials.find((m: any) => m.id === line.product_id)?.cost_price || 0)"
|
||||
disabled
|
||||
size="small"
|
||||
style="width:100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="总价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
{{ formatCurrency((state.materials.find((m: any) => m.id === line.product_id)?.cost_price || 0) * (line.quantity || 0)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="150">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注" width="150">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
<t-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeOrderItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeOrderItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
订单总金额:{{ formatCurrency(orderTotalAmount) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="savePurchaseOrder">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="savePurchaseOrder">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showReceiveModal"
|
||||
<t-dialog
|
||||
v-model:visible="showReceiveModal"
|
||||
title="采购到货入库"
|
||||
width="700px"
|
||||
@closed="closeReceiveModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="入库仓库" required>
|
||||
<el-select v-model="receiveWarehouseId" placeholder="请选择仓库" style="width:100%">
|
||||
<el-option
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="入库仓库" required>
|
||||
<t-select v-model="receiveWarehouseId" placeholder="请选择仓库" style="width:100%">
|
||||
<t-option
|
||||
v-for="warehouse in state.warehouses"
|
||||
:key="warehouse.id"
|
||||
:label="warehouse.name"
|
||||
:value="warehouse.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="receiveRemark" placeholder="入库备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="receiveItems" border size="small">
|
||||
<el-table-column prop="material_label" label="物料" />
|
||||
<el-table-column prop="id" label="明细ID" width="80" />
|
||||
<el-table-column label="剩余待入库" width="100">
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="备注">
|
||||
<t-input v-model="receiveRemark" placeholder="入库备注" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<t-table :data="receiveItems" border size="small">
|
||||
<t-table-column prop="material_label" label="物料" />
|
||||
<t-table-column prop="id" label="明细ID" width="80" />
|
||||
<t-table-column label="剩余待入库" width="100">
|
||||
<template #default="{ row: item }">{{ item.remaining }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本次入库" width="140">
|
||||
</t-table-column>
|
||||
<t-table-column label="本次入库" width="140">
|
||||
<template #default="{ row: item }">
|
||||
<el-input-number
|
||||
<t-input-number
|
||||
v-model="item.receive_quantity"
|
||||
:min="0"
|
||||
:max="item.remaining"
|
||||
@@ -504,13 +498,13 @@ onMounted(() => {
|
||||
style="width:100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<template #footer>
|
||||
<el-button @click="showReceiveModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="receivePurchaseOrder">确认入库</el-button>
|
||||
<t-button @click="showReceiveModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="receivePurchaseOrder">确认入库</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { DialogPlugin } from 'tdesign-vue-next'
|
||||
import { useInventory } from '../composables/useInventory'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
import { addNotification, handleApiError } from '@/shared/notification'
|
||||
|
||||
function confirmDialog(header: string, body: string, theme: string, confirmText: string, cancelText: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const dlg = DialogPlugin.confirm({
|
||||
header, body, theme: theme as any,
|
||||
confirmBtn: confirmText, cancelBtn: cancelText,
|
||||
onConfirm: () => { dlg.hide(); resolve(true) },
|
||||
onCancel: () => { dlg.hide(); resolve(false) },
|
||||
onClose: () => { resolve(false) }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
state,
|
||||
formatCurrency,
|
||||
@@ -156,11 +168,7 @@ async function saveSalesOrder() {
|
||||
}
|
||||
|
||||
async function deleteOrder(id: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这个销售订单吗?', '删除确认', {
|
||||
confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('删除确认', '确定要删除这个销售订单吗?', 'warning', '删除', '取消')) return
|
||||
try {
|
||||
await apiRequest(`/api/sales-orders/${id}`, { method: 'DELETE' })
|
||||
addNotification('销售订单已删除', 'success')
|
||||
@@ -173,39 +181,21 @@ async function deleteOrder(id: number) {
|
||||
}
|
||||
|
||||
async function markDelivered(orderId: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认该订单已交付?', '交付确认', {
|
||||
confirmButtonText: '确认交付', cancelButtonText: '取消', type: 'success'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('交付确认', '确认该订单已交付?', 'success', '确认交付', '取消')) return
|
||||
await doUpdateStatus(orderId, 'delivered', '已交付')
|
||||
}
|
||||
|
||||
async function markPaid(orderId: number, deliveryStatus: string) {
|
||||
if (deliveryStatus !== 'delivered') {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'当前订单尚未交付,确定要提前收款吗?建议先确认交付后再收款。',
|
||||
'非标准流程提醒',
|
||||
{ confirmButtonText: '仍要收款', cancelButtonText: '取消', type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
if (!await confirmDialog('非标准流程提醒', '当前订单尚未交付,确定要提前收款吗?建议先确认交付后再收款。', 'warning', '仍要收款', '取消')) return
|
||||
} else {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认已收到该订单款项?', '收款确认', {
|
||||
confirmButtonText: '确认收款', cancelButtonText: '取消', type: 'success'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('收款确认', '确认已收到该订单款项?', 'success', '确认收款', '取消')) return
|
||||
}
|
||||
await doUpdateStatus(orderId, 'paid', '已收款')
|
||||
}
|
||||
|
||||
async function cancelOrder(orderId: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要作废该订单吗?此操作不可撤销。', '作废确认', {
|
||||
confirmButtonText: '确认作废', cancelButtonText: '取消', type: 'error'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('作废确认', '确定要作废该订单吗?此操作不可撤销。', 'error', '确认作废', '取消')) return
|
||||
await doUpdateStatus(orderId, 'cancelled', '已作废')
|
||||
}
|
||||
|
||||
@@ -316,146 +306,146 @@ onMounted(() => {
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 12px; display: flex; gap: 12px; align-items: center;">
|
||||
<el-select
|
||||
<t-select
|
||||
v-model="productionWarehouseId"
|
||||
placeholder="选择仓库"
|
||||
style="width: 200px;"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
<t-option
|
||||
v-for="warehouse in state.warehouses"
|
||||
:key="warehouse.id"
|
||||
:label="warehouse.name"
|
||||
:value="warehouse.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button @click="loadProductionOrders">刷新</el-button>
|
||||
<el-button type="primary" @click="openCreateOrder">新增销售订单</el-button>
|
||||
</t-select>
|
||||
<t-button @click="loadProductionOrders">刷新</t-button>
|
||||
<t-button type="primary" @click="openCreateOrder">新增销售订单</t-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="state.productionOrders" v-loading="state.loading" stripe>
|
||||
<el-table-column prop="order_no" label="销售单" />
|
||||
<el-table-column prop="customer_name" label="客户" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.productionOrders" :loading="state.loading" stripe>
|
||||
<t-table-column prop="order_no" label="销售单" />
|
||||
<t-table-column prop="customer_name" label="客户" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.total_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交付日期">
|
||||
</t-table-column>
|
||||
<t-table-column label="交付日期">
|
||||
<template #default="{ row }">{{ formatDate(row.delivery_date) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单创建">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单创建">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际交付">
|
||||
</t-table-column>
|
||||
<t-table-column label="实际交付">
|
||||
<template #default="{ row }">{{ row.delivered_at ? formatDateTime(row.delivered_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际收款">
|
||||
</t-table-column>
|
||||
<t-table-column label="实际收款">
|
||||
<template #default="{ row }">{{ row.paid_at ? formatDateTime(row.paid_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" width="160">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单状态" width="160">
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<el-tag
|
||||
<t-tag
|
||||
:type="row.delivery_status === 'delivered' ? 'warning' : row.delivery_status === 'cancelled' ? 'danger' : 'info'"
|
||||
size="small"
|
||||
>{{ getDeliveryStatusLabel(row.delivery_status) }}</el-tag>
|
||||
<el-tag
|
||||
>{{ getDeliveryStatusLabel(row.delivery_status) }}</t-tag>
|
||||
<t-tag
|
||||
:type="row.payment_status === 'paid' ? 'success' : 'info'"
|
||||
size="small"
|
||||
>{{ getPaymentStatusLabel(row.payment_status) }}</el-tag>
|
||||
>{{ getPaymentStatusLabel(row.payment_status) }}</t-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="280">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
<t-button
|
||||
v-if="row.delivery_status === 'manufacturing'"
|
||||
type="success" size="small"
|
||||
@click="markDelivered(row.id)"
|
||||
>已交付</el-button>
|
||||
<el-button
|
||||
>已交付</t-button>
|
||||
<t-button
|
||||
v-if="row.payment_status !== 'paid' && row.delivery_status !== 'cancelled'"
|
||||
type="warning" size="small"
|
||||
@click="markPaid(row.id, row.delivery_status)"
|
||||
>已收款</el-button>
|
||||
<el-button
|
||||
>已收款</t-button>
|
||||
<t-button
|
||||
type="primary" size="small"
|
||||
:disabled="row.payment_status === 'paid' || row.delivery_status === 'cancelled'"
|
||||
@click="editOrder(row)"
|
||||
>编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteOrder(row.id)">删除</el-button>
|
||||
<el-button
|
||||
>编辑</t-button>
|
||||
<t-button type="danger" size="small" @click="deleteOrder(row.id)">删除</t-button>
|
||||
<t-button
|
||||
v-if="row.payment_status !== 'paid' && row.delivery_status !== 'cancelled'"
|
||||
type="info" size="small"
|
||||
@click="cancelOrder(row.id)"
|
||||
>作废</el-button>
|
||||
>作废</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.productionOrders.length === 0" description="暂无销售订单" />
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.productionOrders.length === 0" description="暂无销售订单" />
|
||||
|
||||
<el-dialog
|
||||
v-model="showModal"
|
||||
<t-dialog
|
||||
v-model:visible="showModal"
|
||||
:title="(editingItem ? '编辑' : '新增') + '销售订单'"
|
||||
width="900px"
|
||||
@closed="closeModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="客户" required>
|
||||
<el-select v-model="form.customer_id" placeholder="请选择客户" style="width:100%">
|
||||
<el-option
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="客户" required>
|
||||
<t-select v-model="form.customer_id" placeholder="请选择客户" style="width:100%">
|
||||
<t-option
|
||||
v-for="customer in state.customers"
|
||||
:key="customer.id"
|
||||
:label="customer.name"
|
||||
:value="customer.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="交付日期">
|
||||
<el-date-picker
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="交付日期">
|
||||
<t-date-picker
|
||||
v-model="form.delivery_date"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
style="width:100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</t-form-item>
|
||||
<t-form-item label="备注">
|
||||
<t-input v-model="form.remark" placeholder="备注信息" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">模具明细</span>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<el-button v-if="editingItem" type="warning" size="small" @click="openConsumptionModal">+ 消耗物料</el-button>
|
||||
<el-button type="primary" size="small" @click="addMoldItem">+ 添加模具</el-button>
|
||||
<t-button v-if="editingItem" type="warning" size="small" @click="openConsumptionModal">+ 消耗物料</t-button>
|
||||
<t-button type="primary" size="small" @click="addMoldItem">+ 添加模具</t-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="moldItems" border size="small">
|
||||
<el-table-column label="模式" width="80">
|
||||
<t-table :data="moldItems" border size="small">
|
||||
<t-table-column label="模式" width="80">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.mold_mode" size="small" style="width:100%">
|
||||
<el-option label="新模" value="new" />
|
||||
<el-option label="改模" value="existing" />
|
||||
</el-select>
|
||||
<t-select v-model="line.mold_mode" size="small" style="width:100%">
|
||||
<t-option label="新模" value="new" />
|
||||
<t-option label="改模" value="existing" />
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模具SKU" min-width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="模具SKU" min-width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-if="line.mold_mode === 'new'" v-model="line.mold_sku" size="small" placeholder="模具SKU" />
|
||||
<el-select v-else v-model="line.mold_id" placeholder="选择模具" size="small" style="width:100%">
|
||||
<el-option
|
||||
<t-input v-if="line.mold_mode === 'new'" v-model="line.mold_sku" size="small" placeholder="模具SKU" />
|
||||
<t-select v-else v-model="line.mold_id" placeholder="选择模具" size="small" style="width:100%">
|
||||
<t-option
|
||||
v-for="product in state.finishedProducts"
|
||||
:key="product.id"
|
||||
:label="`${product.sku} - ${product.name}`"
|
||||
:value="product.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模具名称" min-width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="模具名称" min-width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input
|
||||
<t-input
|
||||
v-if="line.mold_mode === 'new'"
|
||||
v-model="line.mold_name"
|
||||
size="small"
|
||||
@@ -465,118 +455,118 @@ onMounted(() => {
|
||||
{{ state.finishedProducts.find((p: any) => p.id === line.mold_id)?.name || '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="100">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="100">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="1" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.quantity" :min="1" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.unit_price" :min="0" :precision="2" :step="0.01" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.unit_price" :min="0" :precision="2" :step="0.01" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
<t-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeMoldItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeMoldItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
订单总金额:{{ formatCurrency(orderTotalAmount) }}
|
||||
</div>
|
||||
|
||||
<template v-if="editingItem && consumedMaterials.length > 0">
|
||||
<el-divider content-position="left">已消耗物料</el-divider>
|
||||
<el-table :data="consumedMaterials" border size="small">
|
||||
<el-table-column label="物料">
|
||||
<t-divider content-position="left">已消耗物料</t-divider>
|
||||
<t-table :data="consumedMaterials" border size="small">
|
||||
<t-table-column label="物料">
|
||||
<template #default="{ row: item }">{{ item.material_name }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量">
|
||||
<template #default="{ row: item }">{{ item.quantity }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价">
|
||||
<template #default="{ row: item }">{{ formatCurrency(item.unit_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注">
|
||||
<template #default="{ row: item }">{{ item.remark || '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveSalesOrder">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveSalesOrder">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showConsumptionModal"
|
||||
<t-dialog
|
||||
v-model:visible="showConsumptionModal"
|
||||
title="物料消耗"
|
||||
width="800px"
|
||||
@closed="closeConsumptionModal"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">消耗物料明细</span>
|
||||
<el-button type="primary" size="small" @click="addConsumptionItem">+ 添加物料</el-button>
|
||||
<t-button type="primary" size="small" @click="addConsumptionItem">+ 添加物料</t-button>
|
||||
</div>
|
||||
<el-table :data="consumptionItems" border size="small">
|
||||
<el-table-column label="物料" min-width="200">
|
||||
<t-table :data="consumptionItems" border size="small">
|
||||
<t-table-column label="物料" min-width="200">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.material_id" placeholder="请选择物料" style="width:100%">
|
||||
<el-option
|
||||
<t-select v-model="line.material_id" placeholder="请选择物料" style="width:100%">
|
||||
<t-option
|
||||
v-for="material in state.materials"
|
||||
:key="material.id"
|
||||
:label="`${material.sku} - ${material.name}`"
|
||||
:value="material.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number
|
||||
<t-input-number
|
||||
:model-value="state.materials.find((m: any) => m.id === line.material_id)?.cost_price || 0"
|
||||
disabled
|
||||
size="small"
|
||||
style="width:100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="总价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
{{ formatCurrency((state.materials.find((m: any) => m.id === line.material_id)?.cost_price || 0) * (line.quantity || 0)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="150">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注" width="150">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
<t-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeConsumptionItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeConsumptionItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
消耗总金额:{{ formatCurrency(consumptionTotalAmount) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showConsumptionModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveConsumption">保存消耗</el-button>
|
||||
<t-button @click="showConsumptionModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveConsumption">保存消耗</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -81,57 +81,57 @@ onMounted(() => { loadSuppliers() })
|
||||
<template>
|
||||
<div class="suppliers-tab">
|
||||
<div class="table-header">
|
||||
<el-button type="primary" @click="openCreateSupplier">+ 新增供应商</el-button>
|
||||
<t-button type="primary" @click="openCreateSupplier">+ 新增供应商</t-button>
|
||||
</div>
|
||||
<el-table :data="state.suppliers" style="width: 100%" v-loading="state.loading" empty-text="暂无供应商数据">
|
||||
<el-table-column prop="code" label="编码" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="contact_person" label="联系人">
|
||||
<t-table :data="state.suppliers" style="width: 100%" :loading="state.loading" empty-text="暂无供应商数据">
|
||||
<t-table-column prop="code" label="编码" />
|
||||
<t-table-column prop="name" label="名称" />
|
||||
<t-table-column prop="contact_person" label="联系人">
|
||||
<template #default="{ row }">
|
||||
{{ row.contact_person || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="phone" label="电话">
|
||||
</t-table-column>
|
||||
<t-table-column prop="phone" label="电话">
|
||||
<template #default="{ row }">
|
||||
{{ row.phone || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="email" label="邮箱">
|
||||
</t-table-column>
|
||||
<t-table-column prop="email" label="邮箱">
|
||||
<template #default="{ row }">
|
||||
{{ row.email || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editSupplier(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteSupplier(row.id)">删除</el-button>
|
||||
<t-button size="small" @click="editSupplier(row)">编辑</t-button>
|
||||
<t-button size="small" type="danger" @click="deleteSupplier(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
|
||||
<el-dialog v-model="showModal" :title="editingItem ? '编辑供应商' : '新增供应商'" width="520px" :close-on-click-modal="false">
|
||||
<el-form :model="form" label-width="80px">
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="请输入供应商名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input v-model="form.contact_person" placeholder="请输入联系人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="电话">
|
||||
<el-input v-model="form.phone" placeholder="请输入电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱">
|
||||
<el-input v-model="form.email" type="email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="form.address" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<t-dialog v-model="showModal" :title="editingItem ? '编辑供应商' : '新增供应商'" width="520px" :close-on-click-modal="false">
|
||||
<t-form :model="form" label-width="80px">
|
||||
<t-form-item label="名称" required>
|
||||
<t-input v-model="form.name" placeholder="请输入供应商名称" />
|
||||
</t-form-item>
|
||||
<t-form-item label="联系人">
|
||||
<t-input v-model="form.contact_person" placeholder="请输入联系人" />
|
||||
</t-form-item>
|
||||
<t-form-item label="电话">
|
||||
<t-input v-model="form.phone" placeholder="请输入电话" />
|
||||
</t-form-item>
|
||||
<t-form-item label="邮箱">
|
||||
<t-input v-model="form.email" type="email" placeholder="请输入邮箱" />
|
||||
</t-form-item>
|
||||
<t-form-item label="地址">
|
||||
<t-input v-model="form.address" placeholder="请输入地址" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="save">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,20 +6,18 @@
|
||||
<h1 class="login-title">Gemold</h1>
|
||||
<p class="login-subtitle">模具制造管理系统</p>
|
||||
</div>
|
||||
<form @submit.prevent="handleLogin">
|
||||
<div class="form-group">
|
||||
<label class="form-label">用户名</label>
|
||||
<input v-model="state.username" type="text" class="form-input" placeholder="请输入用户名" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">密码</label>
|
||||
<input v-model="state.password" type="password" class="form-input" placeholder="请输入密码" required />
|
||||
</div>
|
||||
<div class="form-error" v-if="state.error">{{ state.error }}</div>
|
||||
<button type="submit" class="btn btn-primary w-full mt-4" :disabled="state.loading">
|
||||
<t-form @submit="handleLogin">
|
||||
<t-form-item label="用户名">
|
||||
<t-input v-model="state.username" placeholder="请输入用户名" clearable />
|
||||
</t-form-item>
|
||||
<t-form-item label="密码">
|
||||
<t-input v-model="state.password" type="password" placeholder="请输入密码" clearable />
|
||||
</t-form-item>
|
||||
<t-alert v-if="state.error" theme="error" :message="state.error" style="margin-bottom:16px" />
|
||||
<t-button theme="primary" block type="submit" :loading="state.loading">
|
||||
{{ state.loading ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
</form>
|
||||
</t-button>
|
||||
</t-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -60,19 +60,18 @@
|
||||
<div class="upload-side-card">
|
||||
<h2 class="section-title">2. 注塑模参数</h2>
|
||||
<div class="material-panel compact-panel">
|
||||
<div class="form-group">
|
||||
<label class="form-label">产品材料</label>
|
||||
<select v-model="state.selectedMaterial" class="form-select">
|
||||
<option value="ABS">ABS (1.05 g/cm³)</option>
|
||||
<option value="PP">PP (0.90 g/cm³)</option>
|
||||
<option value="PE">PE (0.95 g/cm³)</option>
|
||||
<option value="PC">PC (1.20 g/cm³)</option>
|
||||
<option value="PA">PA (1.14 g/cm³)</option>
|
||||
<option value="POM">POM (1.41 g/cm³)</option>
|
||||
<option value="PMMA">PMMA (1.18 g/cm³)</option>
|
||||
<option value="PBT">PBT (1.31 g/cm³)</option>
|
||||
</select>
|
||||
</div>
|
||||
<t-form-item label="产品材料">
|
||||
<t-select v-model="state.selectedMaterial">
|
||||
<t-option value="ABS" label="ABS (1.05 g/cm³)" />
|
||||
<t-option value="PP" label="PP (0.90 g/cm³)" />
|
||||
<t-option value="PE" label="PE (0.95 g/cm³)" />
|
||||
<t-option value="PC" label="PC (1.20 g/cm³)" />
|
||||
<t-option value="PA" label="PA (1.14 g/cm³)" />
|
||||
<t-option value="POM" label="POM (1.41 g/cm³)" />
|
||||
<t-option value="PMMA" label="PMMA (1.18 g/cm³)" />
|
||||
<t-option value="PBT" label="PBT (1.31 g/cm³)" />
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
|
||||
<details class="advanced-params">
|
||||
<summary>高级工艺参数</summary>
|
||||
@@ -80,37 +79,35 @@
|
||||
<div class="inline-note">默认值适用于多数注塑件;仅在已知工艺约束时再调整。</div>
|
||||
<div class="advanced-param-grid">
|
||||
<div class="param-input-card" v-for="field in moldParamFields" :key="field.key">
|
||||
<label class="form-label">{{ field.label }}</label>
|
||||
<div class="param-input-row">
|
||||
<input
|
||||
type="number"
|
||||
class="form-input param-number-input"
|
||||
<t-form-item :label="field.label">
|
||||
<t-input-number
|
||||
v-model.number="state.moldParams[field.key as keyof typeof state.moldParams]"
|
||||
:min="field.min"
|
||||
:max="field.max"
|
||||
:step="field.step"
|
||||
:suffix="field.unit"
|
||||
/>
|
||||
<span class="param-unit">{{ field.unit }}</span>
|
||||
</div>
|
||||
<div class="param-meta-row">
|
||||
<span class="range-value">默认 {{ field.defaultValue }}{{ field.unit }}</span>
|
||||
<span class="range-value">范围 {{ field.min }} - {{ field.max }}{{ field.unit }}</span>
|
||||
</div>
|
||||
<div class="param-hint">{{ field.hint }}</div>
|
||||
<div class="param-meta-row">
|
||||
<span class="range-value">默认 {{ field.defaultValue }}{{ field.unit }}</span>
|
||||
<span class="range-value">范围 {{ field.min }} - {{ field.max }}{{ field.unit }}</span>
|
||||
</div>
|
||||
<div class="param-hint">{{ field.hint }}</div>
|
||||
</t-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<button
|
||||
<t-button
|
||||
v-if="state.selectedFile"
|
||||
class="btn-primary upload-submit-btn"
|
||||
theme="primary"
|
||||
class="upload-submit-btn"
|
||||
@click="uploadFile"
|
||||
:disabled="state.uploading || state.polling"
|
||||
>
|
||||
{{ state.uploading ? '上传中...' : state.polling ? '分析中...' : '3. 开始注塑模分析' }}
|
||||
</button>
|
||||
</t-button>
|
||||
<div v-else class="inline-note">先选择 STP 文件,再填写材料并开始分析。</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,90 +115,89 @@
|
||||
<details v-if="state.history?.files?.length" class="history-collapsible section">
|
||||
<summary class="history-summary">分析历史({{ state.history.files.length }} 个文件)</summary>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文件名</th>
|
||||
<th>上传次数</th>
|
||||
<th>文件大小</th>
|
||||
<th>最新状态</th>
|
||||
<th>最新分析时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="file in state.history.files" :key="file.filename">
|
||||
<tr>
|
||||
<td>{{ file.filename }}</td>
|
||||
<td>
|
||||
<span class="badge badge-info">{{ file.upload_count }} 次</span>
|
||||
</td>
|
||||
<td>{{ formatFileSize(file.file_size) }}</td>
|
||||
<td>
|
||||
<span :class="['badge', file.latest_status === 'completed' ? 'badge-success' : file.latest_status === 'failed' ? 'badge-error' : 'badge-warning']">
|
||||
{{ file.latest_status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatDateTime(file.latest_upload_time) }}</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button v-if="file.latest_status === 'completed'" class="btn-sm btn-primary" @click="viewResult({ task_id: file.latest_task_id })">
|
||||
查看最新
|
||||
</button>
|
||||
<button class="btn-sm btn-icon" @click="toggleFileHistory(file.filename)" :title="state.expandedFiles[file.filename] ? '收起' : '展开历史记录'">
|
||||
<span class="dropdown-icon" :class="{ expanded: state.expandedFiles[file.filename] }">▼</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="state.expandedFiles[file.filename]" class="history-detail-row">
|
||||
<td colspan="6">
|
||||
<div class="history-dropdown">
|
||||
<table class="data-table inner-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>上传时间</th>
|
||||
<th>文件大小</th>
|
||||
<th>状态</th>
|
||||
<th>体积 (mm³)</th>
|
||||
<th>表面积 (mm²)</th>
|
||||
<th>重量 (g)</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="record in state.expandedFiles[file.filename]" :key="record.id">
|
||||
<td>{{ formatDateTime(record.upload_time) }}</td>
|
||||
<td>{{ formatFileSize(record.file_size) }}</td>
|
||||
<td>
|
||||
<span :class="['badge', record.status === 'completed' ? 'badge-success' : record.status === 'failed' ? 'badge-error' : 'badge-warning']">
|
||||
{{ record.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ record.volume ? formatNumber(record.volume) : '-' }}</td>
|
||||
<td>{{ record.surface_area ? formatNumber(record.surface_area) : '-' }}</td>
|
||||
<td>{{ record.product_weight ? record.product_weight.toFixed(2) : '-' }}</td>
|
||||
<td>
|
||||
<button v-if="record.has_analysis" class="btn-sm btn-primary" @click="viewResult(record)">
|
||||
查看详情
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<t-table
|
||||
row-key="filename"
|
||||
:data="state.history.files"
|
||||
:expanded-row-keys="historyExpandedKeys"
|
||||
@expand-change="onHistoryExpand"
|
||||
>
|
||||
<t-table-column key="filename" title="文件名">
|
||||
<template #cell="{ row }">{{ row.filename }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="upload_count" title="上传次数">
|
||||
<template #cell="{ row }">
|
||||
<t-tag theme="primary" variant="light">{{ row.upload_count }} 次</t-tag>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</t-table-column>
|
||||
<t-table-column key="file_size" title="文件大小">
|
||||
<template #cell="{ row }">{{ formatFileSize(row.file_size) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="latest_status" title="最新状态">
|
||||
<template #cell="{ row }">
|
||||
<t-tag :theme="statusTagTheme(row.latest_status)" variant="light">{{ row.latest_status }}</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="latest_upload_time" title="最新分析时间">
|
||||
<template #cell="{ row }">{{ formatDateTime(row.latest_upload_time) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="actions" title="操作">
|
||||
<template #cell="{ row }">
|
||||
<div class="action-buttons">
|
||||
<t-button v-if="row.latest_status === 'completed'" theme="primary" size="small" @click="viewResult({ task_id: row.latest_task_id })">
|
||||
查看最新
|
||||
</t-button>
|
||||
<t-button variant="text" size="small" @click="toggleFileHistory(row.filename)" :title="state.expandedFiles[row.filename] ? '收起' : '展开历史记录'">
|
||||
<template #icon><span class="dropdown-icon" :class="{ expanded: state.expandedFiles[row.filename] }">▼</span></template>
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<template #expandedRow="{ row }">
|
||||
<div class="history-dropdown">
|
||||
<t-table
|
||||
v-if="state.expandedFiles[row.filename]"
|
||||
row-key="id"
|
||||
:data="state.expandedFiles[row.filename]"
|
||||
>
|
||||
<t-table-column key="upload_time" title="上传时间">
|
||||
<template #cell="{ row: r }">{{ formatDateTime(r.upload_time) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="file_size" title="文件大小">
|
||||
<template #cell="{ row: r }">{{ formatFileSize(r.file_size) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="status" title="状态">
|
||||
<template #cell="{ row: r }">
|
||||
<t-tag :theme="statusTagTheme(r.status)" variant="light">{{ r.status }}</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="volume" title="体积 (mm³)">
|
||||
<template #cell="{ row: r }">{{ r.volume ? formatNumber(r.volume) : '-' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="surface_area" title="表面积 (mm²)">
|
||||
<template #cell="{ row: r }">{{ r.surface_area ? formatNumber(r.surface_area) : '-' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="product_weight" title="重量 (g)">
|
||||
<template #cell="{ row: r }">{{ r.product_weight ? r.product_weight.toFixed(2) : '-' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="detail_actions" title="操作">
|
||||
<template #cell="{ row: r }">
|
||||
<t-button v-if="r.has_analysis" theme="primary" size="small" @click="viewResult(r)">
|
||||
查看详情
|
||||
</t-button>
|
||||
</template>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div v-else class="inline-note">加载中...</div>
|
||||
</div>
|
||||
</template>
|
||||
</t-table>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
@@ -273,6 +269,20 @@ const state = reactive({
|
||||
expandedFiles: {} as Record<string, HistoryRecord[] | null>,
|
||||
})
|
||||
|
||||
const historyExpandedKeys = computed(() => {
|
||||
return Object.keys(state.expandedFiles).filter(k => state.expandedFiles[k] !== null)
|
||||
})
|
||||
|
||||
const statusTagTheme = (status: string): 'success' | 'warning' | 'danger' => {
|
||||
if (status === 'completed') return 'success'
|
||||
if (status === 'failed') return 'danger'
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
const onHistoryExpand = async (expandedKeys: (string | number)[]) => {
|
||||
// TDesign expand-change fires when keys change; we rely on toggleFileHistory instead
|
||||
}
|
||||
|
||||
const loadHistory = async () => {
|
||||
try {
|
||||
state.history = await apiRequest<HistoryData>('/api/history')
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="page-header">
|
||||
<button class="btn-back" @click="router.back()">
|
||||
<t-button variant="text" @click="router.back()">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
||||
返回
|
||||
</button>
|
||||
</t-button>
|
||||
<h1>分析结果</h1>
|
||||
</div>
|
||||
<div v-if="state.loading" class="loading-state">
|
||||
<div class="spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<t-loading :loading="state.loading" text="加载中...">
|
||||
<t-alert v-if="state.error" theme="error" :message="state.error" />
|
||||
|
||||
<div v-else-if="state.error" class="error-state">
|
||||
<p>{{ state.error }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="state.task" class="result-container">
|
||||
<div v-else-if="state.task" class="result-container">
|
||||
<div class="result-header">
|
||||
<h2>{{ state.task.filename }}</h2>
|
||||
<span :class="['badge', state.task.status === 'completed' ? 'badge-success' : 'badge-error']">
|
||||
<t-tag :type="state.task.status === 'completed' ? 'success' : 'danger'">
|
||||
{{ state.task.status }}
|
||||
</span>
|
||||
</t-tag>
|
||||
</div>
|
||||
|
||||
<div class="result-anchor-nav">
|
||||
@@ -39,7 +33,7 @@
|
||||
<div class="result-card result-card-highlight" style="cursor: pointer;" @click="selectScheme(selectedScheme?.scheme_id)">
|
||||
<div class="summary-header">
|
||||
<h3>推荐方案</h3>
|
||||
<span class="badge badge-info">{{ selectedScheme?.title || selectedScheme?.scheme_id || '方案待定' }}</span>
|
||||
<t-tag theme="primary">{{ selectedScheme?.title || selectedScheme?.scheme_id || '方案待定' }}</t-tag>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
@@ -72,7 +66,7 @@
|
||||
<div class="result-card result-card-highlight" v-if="candidateSchemes.length > 1" style="cursor: pointer;" @click="selectScheme(candidateSchemes[1]?.scheme_id)">
|
||||
<div class="summary-header">
|
||||
<h3>备选方案</h3>
|
||||
<span class="badge badge-warning">1 个备选</span>
|
||||
<t-tag theme="warning">1 个备选</t-tag>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<template v-for="scheme in candidateSchemes.slice(1, 2)" :key="'alt-' + scheme.scheme_id">
|
||||
@@ -102,7 +96,7 @@
|
||||
<div class="result-card result-card-highlight" v-else>
|
||||
<div class="summary-header">
|
||||
<h3>方案概览</h3>
|
||||
<span class="badge badge-success">单一方案</span>
|
||||
<t-tag theme="success">单一方案</t-tag>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
@@ -114,29 +108,26 @@
|
||||
</div>
|
||||
|
||||
<div v-if="state.task.status === 'completed'" class="export-buttons-bar" style="display: flex; gap: var(--space-2); flex-wrap: wrap; justify-content: center; padding: var(--space-3) 0;">
|
||||
<button class="btn-sm btn-primary" @click="exportCAD('step')" title="导出装配体STEP格式(UG/FreeCAD/SolidWorks通用)">
|
||||
<t-button type="primary" size="small" @click="exportCAD('step')" title="导出装配体STEP格式(UG/FreeCAD/SolidWorks通用)">
|
||||
📦 导出 STEP
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('iges')" title="导出IGES格式(兼容旧系统)">
|
||||
</t-button>
|
||||
<t-button type="default" size="small" @click="exportCAD('iges')" title="导出IGES格式(兼容旧系统)">
|
||||
导出 IGES
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('stl')" title="导出STL网格格式(3D打印预览)">
|
||||
</t-button>
|
||||
<t-button type="default" size="small" @click="exportCAD('stl')" title="导出STL网格格式(3D打印预览)">
|
||||
导出 STL
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('brep')" title="导出BRep格式(FreeCAD原生)">
|
||||
</t-button>
|
||||
<t-button type="default" size="small" @click="exportCAD('brep')" title="导出BRep格式(FreeCAD原生)">
|
||||
导出 BRep
|
||||
</button>
|
||||
</t-button>
|
||||
</div>
|
||||
|
||||
<div id="preview-3d" v-if="selectedHtmlFile" class="viewer-section viewer-section-hero">
|
||||
<div class="summary-header">
|
||||
<h3>3D 预览</h3>
|
||||
<span class="badge badge-info">重点区域</span>
|
||||
</div>
|
||||
<div v-if="state.previewStatus === 'error'" class="inline-alert inline-alert-warning" style="margin-bottom: var(--space-3);">
|
||||
<div class="inline-alert-title">预览加载失败</div>
|
||||
<div class="inline-alert-message">HTML 已生成但加载异常,请检查该链接是否可访问:{{ selectedHtmlFile }}</div>
|
||||
<t-tag theme="primary">重点区域</t-tag>
|
||||
</div>
|
||||
<t-alert v-if="state.previewStatus === 'error'" theme="warning" title="预览加载失败" :message="'HTML 已生成但加载异常,请检查该链接是否可访问:' + selectedHtmlFile" style="margin-bottom: var(--space-3);" />
|
||||
<iframe
|
||||
:key="selectedHtmlFile"
|
||||
:src="selectedHtmlFile"
|
||||
@@ -149,66 +140,66 @@
|
||||
<div id="preview-3d-placeholder" v-else class="viewer-section viewer-section-hero">
|
||||
<div class="summary-header">
|
||||
<h3>3D 预览</h3>
|
||||
<span class="badge badge-warning">未生成</span>
|
||||
</div>
|
||||
<div class="inline-alert inline-alert-warning">
|
||||
<div class="inline-alert-title">预览未生成</div>
|
||||
<div class="inline-alert-message">当前任务没有返回 HTML 预览链接,属于未生成状态。</div>
|
||||
<t-tag theme="warning">未生成</t-tag>
|
||||
</div>
|
||||
<t-alert theme="warning" title="预览未生成" message="当前任务没有返回 HTML 预览链接,属于未生成状态。" />
|
||||
</div>
|
||||
|
||||
<div id="candidate-schemes" v-if="candidateSchemes.length > 1" class="viewer-section diagnostic-panel" style="margin-top: var(--space-3);">
|
||||
<summary>📊 方案对比</summary>
|
||||
<div class="result-card full-width diagnostic-panel-body">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>方案</th>
|
||||
<th>方向</th>
|
||||
<th>位置</th>
|
||||
<th>总分</th>
|
||||
<th>可制造性</th>
|
||||
<th>分型质量</th>
|
||||
<th>倒扣数</th>
|
||||
<th>锁模力</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(scheme, idx) in candidateSchemes.slice(0, 2)" :key="'cmp-' + scheme.scheme_id" :style="state.selectedSchemeId === scheme.scheme_id ? 'background: rgba(var(--primary-rgb), 0.06); font-weight: 600;cursor:pointer' : 'cursor:pointer'" @click="selectScheme(scheme.scheme_id)">
|
||||
<td>
|
||||
<span v-if="idx === 0" style="color: var(--primary-color); font-weight: 600;">★ 推荐方案</span>
|
||||
<span v-else style="color: var(--warning-color); font-weight: 600;">备选方案</span>
|
||||
</td>
|
||||
<td>{{ formatSchemeDirection(scheme) }}</td>
|
||||
<td>{{ scheme.offset_label || '中面' }}</td>
|
||||
<td>{{ formatNumber(scheme.score) }}</td>
|
||||
<td>{{ formatNumber(scheme.score_breakdown?.manufacturability || 0) }}</td>
|
||||
<td>{{ formatNumber(scheme.score_breakdown?.parting_quality || 0) }}</td>
|
||||
<td>{{ scheme.key_info?.quality_considerations?.undercut_count || 0 }}</td>
|
||||
<td>{{ scheme.cavity_data?.manufacturing_info?.estimated_clamping_force || 'N/A' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<t-table
|
||||
:data="candidateSchemes.slice(0, 2)"
|
||||
row-key="scheme_id"
|
||||
stripe
|
||||
hover
|
||||
@row-click="(e: any) => selectScheme(e.row.scheme_id)"
|
||||
>
|
||||
<t-table-column title="方案">
|
||||
<template #default="{ rowIndex }">
|
||||
<span v-if="rowIndex === 0" style="color: var(--primary-color); font-weight: 600;">★ 推荐方案</span>
|
||||
<span v-else style="color: var(--warning-color); font-weight: 600;">备选方案</span>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="方向">
|
||||
<template #default="{ row }">{{ formatSchemeDirection(row) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="位置">
|
||||
<template #default="{ row }">{{ row.offset_label || '中面' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="总分">
|
||||
<template #default="{ row }">{{ formatNumber(row.score) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="可制造性">
|
||||
<template #default="{ row }">{{ formatNumber(row.score_breakdown?.manufacturability || 0) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="分型质量">
|
||||
<template #default="{ row }">{{ formatNumber(row.score_breakdown?.parting_quality || 0) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="倒扣数">
|
||||
<template #default="{ row }">{{ row.key_info?.quality_considerations?.undercut_count || 0 }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="锁模力">
|
||||
<template #default="{ row }">{{ row.cavity_data?.manufacturing_info?.estimated_clamping_force || 'N/A' }}</template>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ai-side-action" class="viewer-section">
|
||||
<div class="summary-header">
|
||||
<h3>AI 倒扣与抽芯分析</h3>
|
||||
<span
|
||||
<t-tag
|
||||
v-if="sideActionAiAdvice"
|
||||
:class="[
|
||||
'badge',
|
||||
sideActionAiAdvice.status === 'required'
|
||||
? 'badge-error'
|
||||
: sideActionAiAdvice.status === 'not_required'
|
||||
? 'badge-success'
|
||||
: 'badge-warning'
|
||||
]"
|
||||
:type="sideActionAiAdvice.status === 'required'
|
||||
? 'danger'
|
||||
: sideActionAiAdvice.status === 'not_required'
|
||||
? 'success'
|
||||
: 'warning'"
|
||||
>
|
||||
{{ sideActionAiAdvice.statusLabel }}
|
||||
</span>
|
||||
<span v-else class="badge badge-warning">未生成</span>
|
||||
</t-tag>
|
||||
<t-tag v-else theme="warning">未生成</t-tag>
|
||||
</div>
|
||||
|
||||
<div v-if="sideActionAiAdvice" class="result-grid">
|
||||
@@ -283,38 +274,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="inline-alert inline-alert-warning">
|
||||
<div class="inline-alert-title">AI 结论未生成</div>
|
||||
<div class="inline-alert-message">当前任务未生成倒扣与抽芯 AI 分析,请检查模型配置或重新运行分析。</div>
|
||||
</div>
|
||||
<t-alert v-else theme="warning" title="AI 结论未生成" message="当前任务未生成倒扣与抽芯 AI 分析,请检查模型配置或重新运行分析。" />
|
||||
</div>
|
||||
|
||||
|
||||
<div id="export-cam" class="viewer-section">
|
||||
<div class="summary-header">
|
||||
<h3>CAM 与 CNC 加工</h3>
|
||||
<span class="badge badge-info">工艺规划</span>
|
||||
<t-tag theme="primary">工艺规划</t-tag>
|
||||
</div>
|
||||
<div class="result-card" style="margin-bottom: var(--space-4);">
|
||||
<h4>CAM 参数</h4>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="info-label">模具钢</span>
|
||||
<select v-model="state.camForm.mold_steel" class="form-select">
|
||||
<option v-for="opt in camSteelOptions" :key="'steel-' + opt.value" :value="opt.value">{{ opt.label }}</option>
|
||||
</select>
|
||||
<t-select v-model="state.camForm.mold_steel" style="width:200px">
|
||||
<t-option v-for="opt in camSteelOptions" :key="'steel-' + opt.value" :value="opt.value" :label="opt.label" />
|
||||
</t-select>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">表面质量</span>
|
||||
<select v-model="state.camForm.surface_quality" class="form-select">
|
||||
<option v-for="opt in camSurfaceOptions" :key="'surf-' + opt.value" :value="opt.value">{{ opt.label }}</option>
|
||||
</select>
|
||||
<t-select v-model="state.camForm.surface_quality" style="width:200px">
|
||||
<t-option v-for="opt in camSurfaceOptions" :key="'surf-' + opt.value" :value="opt.value" :label="opt.label" />
|
||||
</t-select>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">控制器</span>
|
||||
<select v-model="state.camForm.controller" class="form-select">
|
||||
<option v-for="opt in camControllerOptions" :key="'ctrl-' + opt.value" :value="opt.value">{{ opt.label }}</option>
|
||||
</select>
|
||||
<t-select v-model="state.camForm.controller" style="width:200px">
|
||||
<t-option v-for="opt in camControllerOptions" :key="'ctrl-' + opt.value" :value="opt.value" :label="opt.label" />
|
||||
</t-select>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">包含G代码</span>
|
||||
@@ -324,18 +312,12 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-sm btn-primary" :disabled="state.camLoading" @click="generateCamPlan()" style="margin-top: var(--space-2);" title="基于当前选中方案生成CAM工艺计划">
|
||||
<t-button type="primary" size="small" :disabled="state.camLoading" @click="generateCamPlan()" style="margin-top: var(--space-2);" title="基于当前选中方案生成CAM工艺计划">
|
||||
{{ state.camLoading ? '⏳ 生成中...' : '🔧 生成 CAM 计划' }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="state.camError" class="inline-alert inline-alert-warning">
|
||||
<div class="inline-alert-title">CAM 计划生成失败</div>
|
||||
<div class="inline-alert-message">{{ state.camError }}</div>
|
||||
</div>
|
||||
<div v-else-if="!state.camPlan" class="inline-alert inline-alert-warning">
|
||||
<div class="inline-alert-title">尚未生成 CAM 计划</div>
|
||||
<div class="inline-alert-message">点击上方"生成 CAM 计划"后,将返回工序计划、刀具建议与制造风险提示。</div>
|
||||
</t-button>
|
||||
</div>
|
||||
<t-alert v-if="state.camError" theme="warning" title="CAM 计划生成失败" :message="state.camError" />
|
||||
<t-alert v-else-if="!state.camPlan" theme="warning" title="尚未生成 CAM 计划" message="点击上方"生成 CAM 计划"后,将返回工序计划、刀具建议与制造风险提示。" />
|
||||
<template v-else>
|
||||
<div class="result-grid">
|
||||
<div class="result-card">
|
||||
@@ -375,28 +357,22 @@
|
||||
</div>
|
||||
<div class="result-card full-width">
|
||||
<h4>工序计划</h4>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>工序</th>
|
||||
<th>刀具</th>
|
||||
<th>主轴转速</th>
|
||||
<th>进给率</th>
|
||||
<th>预计时长(min)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="op in (state.camPlan.process_plan || [])" :key="'cam-op-' + op.seq">
|
||||
<td>{{ op.seq }}</td>
|
||||
<td>{{ op.operation }}</td>
|
||||
<td>{{ op.tool_id || 'N/A' }}</td>
|
||||
<td>{{ formatNumber(op.spindle_speed_rpm || 0) }}</td>
|
||||
<td>{{ formatNumber(op.feed_rate_mm_min || 0) }}</td>
|
||||
<td>{{ formatNumber(op.estimated_time_min || 0) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<t-table :data="state.camPlan.process_plan || []" row-key="seq" stripe>
|
||||
<t-table-column colKey="seq" title="序号" />
|
||||
<t-table-column colKey="operation" title="工序" />
|
||||
<t-table-column title="刀具">
|
||||
<template #default="{ row }">{{ row.tool_id || 'N/A' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="主轴转速">
|
||||
<template #default="{ row }">{{ formatNumber(row.spindle_speed_rpm || 0) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="进给率">
|
||||
<template #default="{ row }">{{ formatNumber(row.feed_rate_mm_min || 0) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="预计时长(min)">
|
||||
<template #default="{ row }">{{ formatNumber(row.estimated_time_min || 0) }}</template>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</div>
|
||||
<div class="result-card full-width">
|
||||
<h4>制造风险提示</h4>
|
||||
@@ -412,9 +388,9 @@
|
||||
<div id="llm-report" class="viewer-section" v-if="hasVisibleDesignReport">
|
||||
<div class="summary-header">
|
||||
<h3>LLM 设计报告</h3>
|
||||
<span class="badge" :class="(designReport?.overall_score || 0) >= 7 ? 'badge-success' : (designReport?.overall_score || 0) >= 5 ? 'badge-warning' : 'badge-error'">
|
||||
<t-tag :type="(designReport?.overall_score || 0) >= 7 ? 'success' : (designReport?.overall_score || 0) >= 5 ? 'warning' : 'danger'">
|
||||
评分 {{ designReport?.overall_score || 'N/A' }} / 10
|
||||
</span>
|
||||
</t-tag>
|
||||
</div>
|
||||
<div v-if="designReport?.overview" class="inline-note" style="margin-bottom: var(--space-4); font-size: 0.95rem; color: var(--text-primary);">
|
||||
{{ designReport?.overview }}
|
||||
@@ -438,18 +414,13 @@
|
||||
</template>
|
||||
|
||||
<template v-else-if="sec.type === 'params_table'">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(h, hi) in sec.headers" :key="'dr-h-' + hi">{{ h }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, ri) in sec.rows" :key="'dr-r-' + ri">
|
||||
<td v-for="(cell, ci) in row" :key="'dr-c-' + ci">{{ cell }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<t-table
|
||||
:columns="sec.headers!.map((h: string, i: number) => ({ colKey: 'col_' + i, title: h }))"
|
||||
:data="buildDynamicTableData(sec.rows!)"
|
||||
row-key="_rowId"
|
||||
stripe
|
||||
bordered
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="sec.type === 'recommendations'">
|
||||
@@ -465,7 +436,8 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -837,7 +809,7 @@ const sideActionAiAdvice = computed(() => {
|
||||
})
|
||||
|
||||
const ruleActionSummary = computed(() => {
|
||||
const summary = selectedScheme.value?.side_actions?.summary || {}
|
||||
const summary: any = selectedScheme.value?.side_actions?.summary || {}
|
||||
return {
|
||||
total_slider_count: summary.total_slider_count ?? 0,
|
||||
total_lifter_count: summary.total_lifter_count ?? 0,
|
||||
@@ -903,6 +875,14 @@ const resultAnchorLinks = [
|
||||
{ id: 'llm-report', label: '设计报告' }
|
||||
]
|
||||
|
||||
const buildDynamicTableData = (rows: unknown[][]) => {
|
||||
return rows.map((row, ri) => {
|
||||
const obj: Record<string, any> = { _rowId: ri }
|
||||
row.forEach((cell, ci) => { obj['col_' + ci] = cell })
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
const selectScheme = (schemeId: string | undefined) => {
|
||||
if (schemeId) {
|
||||
state.selectedSchemeId = schemeId
|
||||
|
||||
@@ -5,100 +5,86 @@
|
||||
<h1>用户管理</h1>
|
||||
<p>管理系统用户和权限</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="openUserModal()">+ 添加用户</button>
|
||||
<t-button theme="primary" @click="openUserModal()">+ 添加用户</t-button>
|
||||
</div>
|
||||
|
||||
<div v-if="state.loading" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<t-loading :loading="state.loading" size="large">
|
||||
<t-table :data="state.users" row-key="id" stripe>
|
||||
<t-table-column colKey="username" title="用户名" />
|
||||
<t-table-column colKey="email" title="邮箱" />
|
||||
<t-table-column colKey="full_name" title="姓名">
|
||||
<template #cell="{ row }">{{ row.full_name || '-' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="is_active" title="状态">
|
||||
<template #cell="{ row }">
|
||||
<t-tag :theme="row.is_active ? 'success' : 'danger'">
|
||||
{{ row.is_active ? '正常' : '禁用' }}
|
||||
</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="roles" title="角色">
|
||||
<template #cell="{ row }">
|
||||
<t-tag v-for="role in row.roles" :key="role" theme="primary" variant="light" style="margin-right: 4px;">
|
||||
{{ role }}
|
||||
</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="created_at" title="注册时间">
|
||||
<template #cell="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="actions" title="操作">
|
||||
<template #cell="{ row }">
|
||||
<t-space :size="4">
|
||||
<t-button theme="primary" size="small" @click="openUserModal(row)">编辑</t-button>
|
||||
<t-button theme="warning" size="small" @click="resetPassword(row)">重置密码</t-button>
|
||||
<t-button v-if="row.id !== storeUser?.id" theme="danger" size="small" @click="deleteUser(row)">删除</t-button>
|
||||
</t-space>
|
||||
</template>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.users.length === 0" description="暂无用户数据" />
|
||||
</t-loading>
|
||||
|
||||
<div v-else class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>邮箱</th>
|
||||
<th>姓名</th>
|
||||
<th>状态</th>
|
||||
<th>角色</th>
|
||||
<th>注册时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in state.users" :key="user.id">
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>{{ user.full_name || '-' }}</td>
|
||||
<td>
|
||||
<span :class="['badge', user.is_active ? 'badge-success' : 'badge-error']">
|
||||
{{ user.is_active ? '正常' : '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="role in (user as any).roles" :key="role" class="badge badge-info" style="margin-right: 4px;">
|
||||
{{ role }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatDateTime(user.created_at) }}</td>
|
||||
<td>
|
||||
<div class="action-buttons" style="display:flex; gap:4px;">
|
||||
<button class="btn btn-sm" style="background:var(--primary-500);color:white;" @click="openUserModal(user)">编辑</button>
|
||||
<button class="btn btn-sm" style="background:#eab308;color:white;" @click="resetPassword(user)">重置密码</button>
|
||||
<button v-if="(user as any).id !== storeUser?.id" class="btn btn-sm" style="background:var(--error);color:white;" @click="deleteUser(user)">删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="state.showUserModal" class="modal-overlay" @click.self="state.showUserModal = false">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>{{ state.editingUser ? '编辑用户' : '添加用户' }}</h2>
|
||||
<button class="modal-close" @click="state.showUserModal = false">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">用户名</label>
|
||||
<input v-model="state.userForm.username" type="text" class="form-input" :disabled="!!state.editingUser" />
|
||||
<t-dialog
|
||||
v-model:visible="state.showUserModal"
|
||||
:header="state.editingUser ? '编辑用户' : '添加用户'"
|
||||
:close-on-overlay-click="true"
|
||||
width="520px"
|
||||
>
|
||||
<t-form label-width="80px">
|
||||
<t-form-item label="用户名">
|
||||
<t-input v-model="state.userForm.username" :disabled="!!state.editingUser" />
|
||||
</t-form-item>
|
||||
<t-form-item label="邮箱">
|
||||
<t-input v-model="state.userForm.email" type="email" />
|
||||
</t-form-item>
|
||||
<t-form-item v-if="!state.editingUser" label="密码">
|
||||
<t-input v-model="state.userForm.password" type="password" />
|
||||
</t-form-item>
|
||||
<t-form-item label="姓名">
|
||||
<t-input v-model="state.userForm.full_name" />
|
||||
</t-form-item>
|
||||
<t-form-item label="角色">
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label v-for="role in state.roles" :key="role.id" style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
||||
<input type="checkbox" :value="role.id" v-model="state.userForm.role_ids" />
|
||||
{{ role.name }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">邮箱</label>
|
||||
<input v-model="state.userForm.email" type="email" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group" v-if="!state.editingUser">
|
||||
<label class="form-label">密码</label>
|
||||
<input v-model="state.userForm.password" type="password" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">姓名</label>
|
||||
<input v-model="state.userForm.full_name" type="text" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">角色</label>
|
||||
<div class="checkbox-group" style="flex-direction: column; gap: 8px;">
|
||||
<label v-for="role in state.roles" :key="(role as any).id" class="checkbox-label">
|
||||
<input type="checkbox" :value="(role as any).id" v-model="state.userForm.role_ids" class="checkbox-input" />
|
||||
{{ (role as any).name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @click="state.showUserModal = false">取消</button>
|
||||
<button class="btn btn-primary" @click="saveUser">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<t-button theme="default" @click="state.showUserModal = false">取消</t-button>
|
||||
<t-button theme="primary" @click="saveUser">保存</t-button>
|
||||
</template>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { DialogPlugin } from 'tdesign-vue-next'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { addNotification } from '@/shared/notification'
|
||||
@@ -139,6 +125,21 @@ const state = reactive({
|
||||
},
|
||||
})
|
||||
|
||||
function confirmDialog(header: string, body: string, theme: string, confirmText: string, cancelText: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const dlg = DialogPlugin.confirm({
|
||||
header,
|
||||
body,
|
||||
theme: theme as any,
|
||||
confirmBtn: confirmText,
|
||||
cancelBtn: cancelText,
|
||||
onConfirm: () => { dlg.hide(); resolve(true) },
|
||||
onCancel: () => { dlg.hide(); resolve(false) },
|
||||
onClose: () => { resolve(false) },
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
state.users = await apiRequest<UserItem[]>('/api/auth/users')
|
||||
@@ -217,7 +218,7 @@ const saveUser = async () => {
|
||||
}
|
||||
|
||||
const deleteUser = async (user: UserItem) => {
|
||||
if (!confirm(`确定要删除用户 ${user.username} 吗?`)) return
|
||||
if (!await confirmDialog('删除确认', `确定要删除用户 ${user.username} 吗?`, 'warning', '删除', '取消')) return
|
||||
|
||||
try {
|
||||
await apiRequest(`/api/auth/users/${user.id}`, { method: 'DELETE' })
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import{E as e,h as t,n,v as r}from"./index-CpL0XiXx.js";var i={};function a(n,i){return e(),r(`div`,null,[...i[0]||=[t(`div`,{class:`page-header`},[t(`h1`,{class:`page-title`},`设计体系`),t(`p`,{class:`page-subtitle`},`迁移中...`)],-1)]])}var o=n(i,[[`render`,a]]);export{o as default};
|
||||
@@ -1 +0,0 @@
|
||||
import{O as e,_ as t,b as n,n as r}from"./index-Dwzy8ysZ.js";var i={};function a(r,i){return e(),n(`div`,null,[...i[0]||=[t(`div`,{class:`page-header`},[t(`h1`,{class:`page-title`},`设计体系`),t(`p`,{class:`page-subtitle`},`迁移中...`)],-1)]])}var o=r(i,[[`render`,a]]);export{o as default};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.inventory-tab[data-v-b02ca093]{padding:0}.table-header[data-v-b02ca093]{margin-bottom:16px}.suppliers-tab[data-v-46312435]{padding:0}.table-header[data-v-46312435]{margin-bottom:16px}.customers-tab[data-v-910d60b4]{padding:0}.table-header[data-v-910d60b4]{margin-bottom:16px}.movements-tab[data-v-79f8d5e3],.page-container[data-v-de14c186]{padding:0}.page-header[data-v-de14c186]{margin-bottom:var(--space-6)}.page-header h1[data-v-de14c186]{font-size:var(--text-2xl);font-weight:var(--font-bold);color:var(--text-primary);margin:0}.page-header p[data-v-de14c186]{color:var(--text-tertiary);margin:var(--space-1) 0 0;font-size:var(--text-sm)}.inventory-layout[data-v-de14c186]{gap:var(--space-6);min-height:calc(100vh - 200px);display:flex}.inventory-sidebar-wrapper[data-v-de14c186]{flex-shrink:0;width:200px}.inventory-content[data-v-de14c186]{flex:1;min-width:0}.inventory-content-header[data-v-de14c186]{margin-bottom:var(--space-4)}.inventory-breadcrumb[data-v-de14c186]{font-size:var(--text-sm);color:var(--text-tertiary)}.inventory-breadcrumb .sep[data-v-de14c186]{margin:0 var(--space-2)}.inventory-breadcrumb .current[data-v-de14c186]{color:var(--text-primary);font-weight:var(--font-medium)}.inventory-sidebar-wrapper[data-v-de14c186] .t-menu{background:0 0;border-right:none}.inventory-sidebar-wrapper[data-v-de14c186] .sidebar-group-title{padding:var(--space-3) var(--space-4);font-size:var(--text-xs);font-weight:var(--font-semibold);color:var(--text-tertiary);text-transform:uppercase;cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:space-between;align-items:center;display:flex}.inventory-sidebar-wrapper[data-v-de14c186] .t-menu__item{height:36px;line-height:36px;font-size:var(--text-sm);margin:2px var(--space-2);border-radius:var(--radius-md);padding:0 var(--space-4)}.inventory-sidebar-wrapper[data-v-de14c186] .t-is-active{background:var(--primary-50);color:var(--primary-600);font-weight:var(--font-medium)}.inventory-sidebar-wrapper[data-v-de14c186] .t-menu__item:hover{background:var(--bg-tertiary)}
|
||||
@@ -1 +0,0 @@
|
||||
.inventory-tab[data-v-ac629843]{padding:0}.table-header[data-v-ac629843]{margin-bottom:16px}.suppliers-tab[data-v-9c9827fc]{padding:0}.table-header[data-v-9c9827fc]{margin-bottom:16px}.customers-tab[data-v-f6d9461f]{padding:0}.table-header[data-v-f6d9461f]{margin-bottom:16px}.movements-tab[data-v-d9f61c89],.page-container[data-v-c02cf723]{padding:0}.page-header[data-v-c02cf723]{margin-bottom:var(--space-6)}.page-header h1[data-v-c02cf723]{font-size:var(--text-2xl);font-weight:var(--font-bold);color:var(--text-primary);margin:0}.page-header p[data-v-c02cf723]{color:var(--text-tertiary);margin:var(--space-1) 0 0;font-size:var(--text-sm)}.inventory-layout[data-v-c02cf723]{gap:var(--space-6);min-height:calc(100vh - 200px);display:flex}.inventory-sidebar-wrapper[data-v-c02cf723]{flex-shrink:0;width:200px}.inventory-content[data-v-c02cf723]{flex:1;min-width:0}.inventory-content-header[data-v-c02cf723]{margin-bottom:var(--space-4)}.inventory-breadcrumb[data-v-c02cf723]{font-size:var(--text-sm);color:var(--text-tertiary)}.inventory-breadcrumb .sep[data-v-c02cf723]{margin:0 var(--space-2)}.inventory-breadcrumb .current[data-v-c02cf723]{color:var(--text-primary);font-weight:var(--font-medium)}.inventory-sidebar-wrapper[data-v-c02cf723] .el-menu{background:0 0;border-right:none}.inventory-sidebar-wrapper[data-v-c02cf723] .sidebar-group-title{padding:var(--space-3) var(--space-4);font-size:var(--text-xs);font-weight:var(--font-semibold);color:var(--text-tertiary);text-transform:uppercase;cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:space-between;align-items:center;display:flex}.inventory-sidebar-wrapper[data-v-c02cf723] .el-menu-item{height:36px;line-height:36px;font-size:var(--text-sm);margin:2px var(--space-2);border-radius:var(--radius-md);padding:0 var(--space-4)}.inventory-sidebar-wrapper[data-v-c02cf723] .el-menu-item.is-active{background:var(--primary-50);color:var(--primary-600);font-weight:var(--font-medium)}.inventory-sidebar-wrapper[data-v-c02cf723] .el-menu-item:hover{background:var(--bg-tertiary)}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
.login-container[data-v-01da5b35]{background:var(--bg-secondary);min-height:100vh;padding:var(--space-6);justify-content:center;align-items:center;display:flex}.login-card[data-v-01da5b35]{background:var(--bg-primary);border:1px solid var(--border-light);border-radius:var(--radius-xl);width:100%;max-width:400px;padding:var(--space-8)}.login-header[data-v-01da5b35]{text-align:center;margin-bottom:var(--space-8)}.login-logo[data-v-01da5b35]{background:var(--primary-500);border-radius:var(--radius-lg);color:#fff;width:48px;height:48px;font-size:var(--text-xl);font-weight:var(--font-bold);margin:0 auto var(--space-4);justify-content:center;align-items:center;display:flex}.login-title[data-v-01da5b35]{font-size:var(--text-2xl);font-weight:var(--font-bold);color:var(--text-primary);margin-bottom:var(--space-2)}.login-subtitle[data-v-01da5b35]{font-size:var(--text-sm);color:var(--text-tertiary)}
|
||||
.login-container[data-v-c91bfbf2]{background:var(--bg-secondary);min-height:100vh;padding:var(--space-6);justify-content:center;align-items:center;display:flex}.login-card[data-v-c91bfbf2]{background:var(--bg-primary);border:1px solid var(--border-light);border-radius:var(--radius-xl);width:100%;max-width:400px;padding:var(--space-8)}.login-header[data-v-c91bfbf2]{text-align:center;margin-bottom:var(--space-8)}.login-logo[data-v-c91bfbf2]{background:var(--primary-500);border-radius:var(--radius-lg);color:#fff;width:48px;height:48px;font-size:var(--text-xl);font-weight:var(--font-bold);margin:0 auto var(--space-4);justify-content:center;align-items:center;display:flex}.login-title[data-v-c91bfbf2]{font-size:var(--text-2xl);font-weight:var(--font-bold);color:var(--text-primary);margin-bottom:var(--space-2)}.login-subtitle[data-v-c91bfbf2]{font-size:var(--text-sm);color:var(--text-tertiary)}
|
||||
@@ -1 +0,0 @@
|
||||
import{B as e,F as t,O as n,P as r,_ as i,b as a,m as o,n as s,o as c,p as l,r as u,s as d,u as f,w as p,y as m}from"./index-Dwzy8ysZ.js";var h={class:`login-container`},g={class:`login-card`},_={class:`form-group`},v={class:`form-group`},y={key:0,class:`form-error`},b=[`disabled`],x=s(p({__name:`LoginView`,setup(s){let p=f(),x=t({username:``,password:``,error:``,loading:!1}),S=async()=>{x.error=``,x.loading=!0;try{let e=await d(`/api/auth/login/json`,{method:`POST`,body:JSON.stringify({username:x.username,password:x.password})});c(e.access_token,e.user),u(`登录成功`,`success`),p.push(`/`)}catch(e){x.error=e.message||`登录失败`}finally{x.loading=!1}};return(t,s)=>(n(),a(`div`,h,[i(`div`,g,[s[4]||=i(`div`,{class:`login-header`},[i(`div`,{class:`login-logo`},`G`),i(`h1`,{class:`login-title`},`Gemold`),i(`p`,{class:`login-subtitle`},`模具制造管理系统`)],-1),i(`form`,{onSubmit:o(S,[`prevent`])},[i(`div`,_,[s[2]||=i(`label`,{class:`form-label`},`用户名`,-1),r(i(`input`,{"onUpdate:modelValue":s[0]||=e=>x.username=e,type:`text`,class:`form-input`,placeholder:`请输入用户名`,required:``},null,512),[[l,x.username]])]),i(`div`,v,[s[3]||=i(`label`,{class:`form-label`},`密码`,-1),r(i(`input`,{"onUpdate:modelValue":s[1]||=e=>x.password=e,type:`password`,class:`form-input`,placeholder:`请输入密码`,required:``},null,512),[[l,x.password]])]),x.error?(n(),a(`div`,y,e(x.error),1)):m(``,!0),i(`button`,{type:`submit`,class:`btn btn-primary w-full mt-4`,disabled:x.loading},e(x.loading?`登录中...`:`登录`),9,b)],32)])]))}}),[[`__scopeId`,`data-v-01da5b35`]]);export{x as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{A as e,E as t,L as n,M as r,O as i,S as a,_ as o,b as s,g as c,h as l,n as u,o as d,r as f,s as p,u as m,v as h,x as g}from"./index-CpL0XiXx.js";var _={class:`login-container`},v={class:`login-card`},y=u(a({__name:`LoginView`,setup(a){let u=m(),y=r({username:``,password:``,error:``,loading:!1}),b=async()=>{y.error=``,y.loading=!0;try{let e=await p(`/api/auth/login/json`,{method:`POST`,body:JSON.stringify({username:y.username,password:y.password})});d(e.access_token,e.user),f(`登录成功`,`success`),u.push(`/`)}catch(e){y.error=e.message||`登录失败`}finally{y.loading=!1}};return(r,a)=>{let u=i(`t-input`),d=i(`t-form-item`),f=i(`t-alert`),p=i(`t-button`),m=i(`t-form`);return t(),h(`div`,_,[l(`div`,v,[a[2]||=l(`div`,{class:`login-header`},[l(`div`,{class:`login-logo`},`G`),l(`h1`,{class:`login-title`},`Gemold`),l(`p`,{class:`login-subtitle`},`模具制造管理系统`)],-1),g(m,{onSubmit:b},{default:e(()=>[g(d,{label:`用户名`},{default:e(()=>[g(u,{modelValue:y.username,"onUpdate:modelValue":a[0]||=e=>y.username=e,placeholder:`请输入用户名`,clearable:``},null,8,[`modelValue`])]),_:1}),g(d,{label:`密码`},{default:e(()=>[g(u,{modelValue:y.password,"onUpdate:modelValue":a[1]||=e=>y.password=e,type:`password`,placeholder:`请输入密码`,clearable:``},null,8,[`modelValue`])]),_:1}),y.error?(t(),c(f,{key:0,theme:`error`,message:y.error,style:{"margin-bottom":`16px`}},null,8,[`message`])):o(``,!0),g(p,{theme:`primary`,block:``,type:`submit`,loading:y.loading},{default:e(()=>[s(n(y.loading?`登录中...`:`登录`),1)]),_:1},8,[`loading`])]),_:1})])])}}}),[[`__scopeId`,`data-v-c91bfbf2`]]);export{y as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{O as e,_ as t,b as n,n as r}from"./index-Dwzy8ysZ.js";var i={};function a(r,i){return e(),n(`div`,null,[...i[0]||=[t(`div`,{class:`page-header`},[t(`h1`,{class:`page-title`},`灰度发布与回滚`),t(`p`,{class:`page-subtitle`},`迁移中...`)],-1)]])}var o=r(i,[[`render`,a]]);export{o as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{E as e,h as t,n,v as r}from"./index-CpL0XiXx.js";var i={};function a(n,i){return e(),r(`div`,null,[...i[0]||=[t(`div`,{class:`page-header`},[t(`h1`,{class:`page-title`},`灰度发布与回滚`),t(`p`,{class:`page-subtitle`},`迁移中...`)],-1)]])}var o=n(i,[[`render`,a]]);export{o as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -61,8 +61,8 @@
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/static/assets/index-Dwzy8ysZ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/assets/index-DBUUNurD.css">
|
||||
<script type="module" crossorigin src="/static/assets/index-CpL0XiXx.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/assets/index-9snwVMjk.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
|
||||
Reference in New Issue
Block a user