diff --git a/frontend/components.d.ts b/frontend/components.d.ts
new file mode 100644
index 0000000..88040b3
--- /dev/null
+++ b/frontend/components.d.ts
@@ -0,0 +1,34 @@
+/* eslint-disable */
+// @ts-nocheck
+// biome-ignore lint: disable
+// oxlint-disable
+// ------
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+
+export {}
+
+/* prettier-ignore */
+declare module 'vue' {
+ export interface GlobalComponents {
+ RouterLink: typeof import('vue-router')['RouterLink']
+ RouterView: typeof import('vue-router')['RouterView']
+ VanButton: typeof import('vant/es')['Button']
+ VanCell: typeof import('vant/es')['Cell']
+ VanCellGroup: typeof import('vant/es')['CellGroup']
+ VanCheckbox: typeof import('vant/es')['Checkbox']
+ VanDropdownItem: typeof import('vant/es')['DropdownItem']
+ VanDropdownMenu: typeof import('vant/es')['DropdownMenu']
+ VanEmpty: typeof import('vant/es')['Empty']
+ VanField: typeof import('vant/es')['Field']
+ VanGrid: typeof import('vant/es')['Grid']
+ VanGridItem: typeof import('vant/es')['GridItem']
+ VanIcon: typeof import('vant/es')['Icon']
+ VanImage: typeof import('vant/es')['Image']
+ VanLoading: typeof import('vant/es')['Loading']
+ VanNoticeBar: typeof import('vant/es')['NoticeBar']
+ VanSearch: typeof import('vant/es')['Search']
+ VanTabbar: typeof import('vant/es')['Tabbar']
+ VanTabbarItem: typeof import('vant/es')['TabbarItem']
+ }
+}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index e219384..feaec6b 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -17,9 +17,13 @@
},
"devDependencies": {
"@types/node": "^24.10.1",
+ "@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "^6.0.2",
"@vue/tsconfig": "^0.8.1",
"typescript": "~5.9.3",
+ "unplugin-auto-import": "^21.0.0",
+ "unplugin-vue-components": "^32.1.0",
+ "vant": "^4.9.24",
"vite": "^7.2.4",
"vue-tsc": "^3.1.5"
}
@@ -555,12 +559,55 @@
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
"node_modules/@popperjs/core": {
"name": "@sxzz/popperjs-es",
"version": "2.11.8",
@@ -1006,6 +1053,30 @@
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
"license": "MIT"
},
+ "node_modules/@vant/auto-import-resolver": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@vant/auto-import-resolver/-/auto-import-resolver-1.3.0.tgz",
+ "integrity": "sha512-lJyWtCyFizR4bHZvMiNMF3w+WTFTUWAvka1eqTnPK9ticUcKTCOx6qEmHcm8JPb3g1t3GaD2W3MnHkBp/nHamw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vant/popperjs": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@vant/popperjs/-/popperjs-1.3.0.tgz",
+ "integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vant/use": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vant/use/-/use-1.6.0.tgz",
+ "integrity": "sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
"node_modules/@vitejs/plugin-vue": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz",
@@ -1258,6 +1329,19 @@
"vue": "^3.5.0"
}
},
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -1323,6 +1407,22 @@
"node": ">= 0.4"
}
},
+ "node_modules/chokidar": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -1335,6 +1435,13 @@
"node": ">= 0.8"
}
},
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/copy-anything": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz",
@@ -1527,12 +1634,32 @@
"@esbuild/win32-x64": "0.27.7"
}
},
+ "node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -1730,6 +1857,31 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
+ "node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/local-pkg": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz",
+ "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.4",
+ "pkg-types": "^2.3.0",
+ "quansync": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
@@ -1804,6 +1956,38 @@
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
+ "node_modules/mlly": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
+ "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.3"
+ }
+ },
+ "node_modules/mlly/node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mlly/node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -1841,6 +2025,17 @@
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause"
},
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -1848,6 +2043,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -1894,6 +2096,18 @@
}
}
},
+ "node_modules/pkg-types": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz",
+ "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.4",
+ "exsolve": "^1.0.8",
+ "pathe": "^2.0.3"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
@@ -1931,6 +2145,37 @@
"node": ">=10"
}
},
+ "node_modules/quansync": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
+ "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/antfu"
+ },
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/readdirp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
+ "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
@@ -1982,6 +2227,13 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/scule": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
+ "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+ "dev": true,
+ "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",
@@ -2000,6 +2252,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/strip-literal": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
+ "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/superjson": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz",
@@ -2043,6 +2308,13 @@
"node": ">=14.17"
}
},
+ "node_modules/ufo": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz",
+ "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@@ -2050,6 +2322,171 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/unimport": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/unimport/-/unimport-5.7.0.tgz",
+ "integrity": "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "escape-string-regexp": "^5.0.0",
+ "estree-walker": "^3.0.3",
+ "local-pkg": "^1.1.2",
+ "magic-string": "^0.30.21",
+ "mlly": "^1.8.0",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "pkg-types": "^2.3.0",
+ "scule": "^1.3.0",
+ "strip-literal": "^3.1.0",
+ "tinyglobby": "^0.2.15",
+ "unplugin": "^2.3.11",
+ "unplugin-utils": "^0.3.1"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/unimport/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/unplugin": {
+ "version": "2.3.11",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
+ "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "acorn": "^8.15.0",
+ "picomatch": "^4.0.3",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/unplugin-auto-import": {
+ "version": "21.0.0",
+ "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-21.0.0.tgz",
+ "integrity": "sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "local-pkg": "^1.1.2",
+ "magic-string": "^0.30.21",
+ "picomatch": "^4.0.3",
+ "unimport": "^5.6.0",
+ "unplugin": "^2.3.11",
+ "unplugin-utils": "^0.3.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": "^4.0.0",
+ "@vueuse/core": "*"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "@vueuse/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unplugin-utils": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
+ "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ },
+ "node_modules/unplugin-vue-components": {
+ "version": "32.1.0",
+ "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-32.1.0.tgz",
+ "integrity": "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^5.0.0",
+ "local-pkg": "^1.2.0",
+ "magic-string": "^0.30.21",
+ "mlly": "^1.8.2",
+ "obug": "^2.1.1",
+ "picomatch": "^4.0.4",
+ "tinyglobby": "^0.2.16",
+ "unplugin": "^3.0.0",
+ "unplugin-utils": "^0.3.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": "^3.2.2 || ^4.0.0",
+ "vue": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unplugin-vue-components/node_modules/unplugin": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz",
+ "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "picomatch": "^4.0.3",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/vant": {
+ "version": "4.9.24",
+ "resolved": "https://registry.npmjs.org/vant/-/vant-4.9.24.tgz",
+ "integrity": "sha512-tP1A7Vjzv1/B1ljb95Jhv9Q9w6acaaZDJvy6wcKrwGgY0gQZlg+FXLZH/AIKZBE3xvYGDUsv/M7AuGcr/Pqd6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vant/popperjs": "^1.3.0",
+ "@vant/use": "^1.6.0",
+ "@vue/shared": "^3.5.31"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
"node_modules/vite": {
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz",
@@ -2196,6 +2633,13 @@
"peerDependencies": {
"typescript": ">=5.0.0"
}
+ },
+ "node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+ "dev": true,
+ "license": "MIT"
}
}
}
diff --git a/frontend/package.json b/frontend/package.json
index 5c62d9e..7d52b26 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -19,9 +19,13 @@
},
"devDependencies": {
"@types/node": "^24.10.1",
+ "@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "^6.0.2",
"@vue/tsconfig": "^0.8.1",
"typescript": "~5.9.3",
+ "unplugin-auto-import": "^21.0.0",
+ "unplugin-vue-components": "^32.1.0",
+ "vant": "^4.9.24",
"vite": "^7.2.4",
"vue-tsc": "^3.1.5"
}
diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts
index ac09f8a..df370f7 100644
--- a/frontend/src/router/index.ts
+++ b/frontend/src/router/index.ts
@@ -30,6 +30,12 @@ const router = createRouter({
component: () => import("@/views/mobile/MobileLoginView.vue"),
meta: { layout: "blank" },
},
+ {
+ path: "/m/register",
+ name: "mobile-register",
+ component: () => import("@/views/mobile/MobileRegisterView.vue"),
+ meta: { layout: "blank" },
+ },
{
path: "/m/profile",
name: "mobile-profile",
diff --git a/frontend/src/views/mobile/MobileHomeView.vue b/frontend/src/views/mobile/MobileHomeView.vue
index cb5562b..0aa2b7b 100644
--- a/frontend/src/views/mobile/MobileHomeView.vue
+++ b/frontend/src/views/mobile/MobileHomeView.vue
@@ -8,7 +8,8 @@ const loading = ref(false);
const loadFailed = ref(false);
const listings = ref
([]);
const filterOpen = ref(false);
-const activeSort = ref("默认");
+const activeSort = ref(0);
+const activeFilter = ref([]);
const channels = [
{ label: "全部", icon: "🔥" },
@@ -17,8 +18,18 @@ const channels = [
{ label: "扫码号", icon: "📱" },
{ label: "包夜", icon: "🌙" },
];
-const sortOptions = ["默认", "价格", "比例", "哈夫币", "筛选"];
-const quickFilters = ["三角洲行动", "安卓/扫码", "高段位", "可包夜"];
+const sortOptions = [
+ { text: "默认", value: 0 },
+ { text: "价格", value: 1 },
+ { text: "比例", value: 2 },
+ { text: "哈夫币", value: 3 },
+];
+const quickFilters = [
+ { text: "三角洲行动", value: "a" },
+ { text: "安卓/扫码", value: "b" },
+ { text: "高段位", value: "c" },
+ { text: "可包夜", value: "d" },
+];
const fallbackListings: Listing[] = [
{
@@ -143,10 +154,13 @@ function formatRatio(item: Listing) {
}
return `${Math.round(item.haf_coin_amount / item.price_hourly / 10000)}W/元`;
}
+
+const searchValue = ref("");
+
@@ -159,15 +173,21 @@ function formatRatio(item: Listing) {
-
+
+
-
- 防骗提示
- 平台担保交易,拒绝私下转账/共享验证码,交接全程留痕。
-
+
+
@@ -180,13 +200,19 @@ function formatRatio(item: Listing) {
-
-
-
+
+
+
+
+ {{ item.icon }}
+
+
+ {{ item.label }}
+
+
+
+
{{ item.value }}
@@ -194,31 +220,29 @@ function formatRatio(item: Listing) {
-
-
-
+
+
+
+
+
-
-
-
-
-
- 接口暂不可用,当前展示移动端示例数据。
-
- 正在加载优质账号...
+
+
+ 正在加载优质账号...
+
+
+
-
+
+
+ 首页
+ 租号
+ 发布
+ 订单
+ 我的
+
@@ -282,6 +305,7 @@ function formatRatio(item: Listing) {
padding-bottom: 92px;
}
+/* ========== Hero 区域 ========== */
.mobile-hero {
overflow: hidden;
border-radius: 0 0 28px 28px;
@@ -292,8 +316,7 @@ function formatRatio(item: Listing) {
.mobile-topbar,
.mobile-brand,
.card-title-row,
-.card-footer,
-.mobile-tabbar {
+.card-footer {
display: flex;
align-items: center;
}
@@ -337,41 +360,40 @@ function formatRatio(item: Listing) {
padding: 7px 12px;
}
-.mobile-search {
- display: flex;
- align-items: center;
- gap: 8px;
+/* ========== van-search 覆盖样式 ========== */
+.home-search {
margin-top: 12px;
- border-radius: 999px;
- background: #ffffff;
- padding: 11px 14px;
- box-shadow: 0 10px 24px rgba(0, 54, 135, 0.12);
+ padding: 0;
+ background: transparent;
}
-.mobile-search input {
- min-width: 0;
- flex: 1;
- border: 0;
- outline: 0;
+.home-search :deep(.van-search__content) {
+ border-radius: 999px;
+ background: #ffffff;
+ box-shadow: 0 10px 24px rgba(0, 54, 135, 0.12);
+ padding: 8px 14px;
+}
+
+.home-search :deep(.van-search__content .van-field__control) {
color: #17233d;
font-size: 14px;
}
-.fraud-alert {
+/* ========== van-notice-bar 防骗提示 ========== */
+.fraud-notice {
margin-top: 12px;
border-radius: 12px;
background: #ffe27a;
color: #5c3b00;
- padding: 10px 12px;
font-size: 12px;
line-height: 1.45;
}
-.fraud-alert span {
- margin-right: 6px;
- font-weight: 800;
+.fraud-notice :deep(.van-notice-bar__left-icon) {
+ color: #5c3b00;
}
+/* ========== Banner ========== */
.mobile-banner {
display: flex;
justify-content: space-between;
@@ -422,46 +444,39 @@ function formatRatio(item: Listing) {
transform: rotate(8deg);
}
+/* ========== Content 区 ========== */
.mobile-content {
padding: 14px;
}
-.channel-strip,
-.sort-bar,
-.filter-panel {
- display: flex;
- gap: 8px;
- overflow-x: auto;
- padding-bottom: 2px;
- scrollbar-width: none;
+/* ========== van-grid 频道 ========== */
+.channel-grid {
+ margin-top: 0;
}
-.channel-strip::-webkit-scrollbar,
-.sort-bar::-webkit-scrollbar,
-.filter-panel::-webkit-scrollbar {
- display: none;
-}
-
-.channel-strip button {
- display: grid;
- min-width: 72px;
- gap: 4px;
- flex: 0 0 auto;
- place-items: center;
- border: 0;
- border-radius: 16px;
+.channel-grid :deep(.van-grid-item__content) {
+ padding: 11px 6px;
background: #ffffff;
- color: #24324a;
- padding: 11px 12px;
- font-size: 12px;
- font-weight: 800;
+ border-radius: 16px;
box-shadow: 0 8px 22px rgba(23, 35, 61, 0.06);
}
-.channel-strip span {
- font-size: 18px;
+.channel-grid :deep(.van-grid-item__content--surround::after) {
+ display: none;
}
+.channel-icon {
+ font-size: 22px;
+ line-height: 1;
+}
+
+.channel-label {
+ font-size: 12px;
+ font-weight: 800;
+ color: #24324a;
+}
+
+/* ========== 数据背书面板 ========== */
.trust-panel {
display: grid;
grid-template-columns: repeat(3, 1fr);
@@ -494,46 +509,38 @@ function formatRatio(item: Listing) {
font-size: 11px;
}
-.sort-bar {
- position: sticky;
- top: 0;
- z-index: 10;
+/* ========== van-dropdown-menu 排序筛选 ========== */
+.sort-dropdown {
margin: 12px -14px 0;
+ border-radius: 0;
+}
+
+.sort-dropdown :deep(.van-dropdown-menu__bar) {
background: rgba(238, 245, 255, 0.92);
- padding: 10px 14px 8px;
backdrop-filter: blur(12px);
+ box-shadow: none;
}
-.filter-panel {
- padding: 8px 0 2px;
-}
-
-.sort-bar button,
-.filter-panel button {
- flex: 0 0 auto;
- border: 0;
- border-radius: 999px;
- background: #ffffff;
- color: #4d5b73;
- padding: 9px 12px;
+.sort-dropdown :deep(.van-dropdown-menu__title) {
font-weight: 800;
+ color: #4d5b73;
+ font-size: 14px;
}
-.sort-bar button.active {
- background: #1479ff;
- color: #ffffff;
+.sort-dropdown :deep(.van-dropdown-menu__title--active) {
+ color: #1479ff;
}
-.sort-bar button span {
- margin-left: 2px;
-}
-
-.mobile-tip {
- margin: 12px 2px;
+/* ========== 加载状态 ========== */
+.state-loading {
+ display: flex;
+ justify-content: center;
+ padding: 20px 0;
color: #6b7a90;
font-size: 13px;
}
+/* ========== 卡片列表 ========== */
.mobile-list {
display: grid;
gap: 12px;
@@ -655,45 +662,59 @@ function formatRatio(item: Listing) {
white-space: nowrap;
}
-.mobile-tabbar {
+/* ========== van-tabbar 底部导航 ========== */
+.home-tabbar {
position: fixed;
right: 12px;
bottom: 12px;
left: 12px;
z-index: 20;
- justify-content: space-around;
height: 60px;
border-radius: 22px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 16px 36px rgba(23, 35, 61, 0.16);
backdrop-filter: blur(14px);
+ padding: 0;
}
-.tab-item {
+.home-tabbar :deep(.van-tabbar) {
+ border-radius: 22px;
+ height: 60px;
+}
+
+.home-tabbar :deep(.van-tabbar-item) {
color: #708097;
font-size: 12px;
font-weight: 800;
}
-.tab-item.active {
+.home-tabbar :deep(.van-tabbar-item--active) {
color: #1479ff;
}
-.publish-tab {
- display: grid;
- width: 58px;
- height: 58px;
- place-items: center;
- border: 5px solid #eef5ff;
- border-radius: 50%;
- background: linear-gradient(180deg, #ffb21a, #ff6a00);
- color: #ffffff;
- font-size: 13px;
- font-weight: 900;
- transform: translateY(-18px);
- box-shadow: 0 10px 24px rgba(255, 106, 0, 0.35);
+.home-tabbar :deep(.van-tabbar-item__icon) {
+ font-size: 20px;
}
+/* 发布按钮特殊样式 */
+.tabbar-publish :deep(.van-tabbar-item__icon) {
+ display: grid;
+ width: 48px;
+ height: 48px;
+ place-items: center;
+ border-radius: 50%;
+ background: linear-gradient(180deg, #ffb21a, #ff6a00);
+ color: #ffffff !important;
+ font-size: 22px;
+ transform: translateY(-8px);
+ box-shadow: 0 8px 20px rgba(255, 106, 0, 0.35);
+}
+
+.tabbar-publish :deep(.van-tabbar-item__text) {
+ color: #ff6a00;
+}
+
+/* ========== 响应式适配 ========== */
@media (min-width: 520px) {
.mobile-shell {
max-width: 430px;
@@ -701,7 +722,7 @@ function formatRatio(item: Listing) {
box-shadow: 0 0 0 1px rgba(23, 35, 61, 0.08);
}
- .mobile-tabbar {
+ .home-tabbar {
right: calc((100vw - 430px) / 2 + 12px);
left: calc((100vw - 430px) / 2 + 12px);
}
diff --git a/frontend/src/views/mobile/MobileLoginView.vue b/frontend/src/views/mobile/MobileLoginView.vue
index 0c6f576..c3b967b 100644
--- a/frontend/src/views/mobile/MobileLoginView.vue
+++ b/frontend/src/views/mobile/MobileLoginView.vue
@@ -1,6 +1,7 @@
+
-
- 锤
- 登录大锤商行
- 登录后查看订单、管理发布、提现余额,并享受平台担保交易服务。
- 立即登录
- 担保交易订单留痕安全交接
+
+
+
+ 锤
+
+
+
+ 立即登录
+
+
+ 担保交易订单留痕安全交接
+
+
+
-
- 账户余额(元)¥0.00
-
-
+
+
+
+
+
+ 账户余额(元)
+ ¥0.00
+
+ 查看明细
+
+
+
+
+
+ 提现
+
+
+
+
-
diff --git a/frontend/src/views/mobile/MobileRegisterView.vue b/frontend/src/views/mobile/MobileRegisterView.vue
new file mode 100644
index 0000000..97bba93
--- /dev/null
+++ b/frontend/src/views/mobile/MobileRegisterView.vue
@@ -0,0 +1,447 @@
+
+
+
+
+
+
+
+
锤
+
+
加入大锤商行
+
注册后即可租号、下单、发布账号
+
+
+
+
+
+ 登录
+ 注册
+
+
+ CREATE ACCOUNT
+ 手机号注册
+
+ 先用短信验证码创建账号,后续再补充实名和收款信息。
+
+
+
+
+
+ +86
+
+
+
+
+
+
+
+ {{
+ countDown > 0
+ ? `${countDown}s`
+ : sending
+ ? "发送中"
+ : "发送验证码"
+ }}
+
+
+
+
+
+
+
+
+
+
+ 我已阅读并同意《用户协议》和《隐私政策》
+
+
+
+
+
+ 注册并登录
+
+
+
+
+
+ 已有账号?去登录
+
+
+
+
+
+
+
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 0eb375e..9a245b8 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,19 +1,26 @@
-import { fileURLToPath, URL } from 'node:url'
+import { fileURLToPath, URL } from "node:url";
-import vue from '@vitejs/plugin-vue'
-import { defineConfig } from 'vite'
+import vue from "@vitejs/plugin-vue";
+import { defineConfig } from "vite";
+import Components from "unplugin-vue-components/vite";
+import { VantResolver } from "@vant/auto-import-resolver";
export default defineConfig({
- plugins: [vue()],
+ plugins: [
+ vue(),
+ Components({
+ resolvers: [VantResolver()],
+ }),
+ ],
resolve: {
alias: {
- '@': fileURLToPath(new URL('./src', import.meta.url)),
+ "@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
server: {
port: 5173,
proxy: {
- '/api': 'http://127.0.0.1:8080',
+ "/api": "http://127.0.0.1:8080",
},
},
-})
+});