登陆注册界面优化了

This commit is contained in:
yml
2026-05-22 20:38:52 +08:00
parent 5f1a7f46a8
commit aa9410b990
9 changed files with 1864 additions and 379 deletions
+34
View File
@@ -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']
}
}
+444
View File
@@ -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"
}
}
}
+4
View File
@@ -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"
}
+6
View File
@@ -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",
+162 -141
View File
@@ -8,7 +8,8 @@ const loading = ref(false);
const loadFailed = ref(false);
const listings = ref<Listing[]>([]);
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("");
</script>
<template>
<main class="mobile-shell">
<!-- 顶部品牌区 - 保留手写样式 -->
<section class="mobile-hero">
<div class="mobile-topbar">
<div class="mobile-brand">
@@ -159,15 +173,21 @@ function formatRatio(item: Listing) {
<button class="mobile-service" type="button">客服</button>
</div>
<label class="mobile-search">
<span>🔍</span>
<input type="search" placeholder="搜区服 / 段位 / 哈夫币数量" />
</label>
<!-- 搜索栏 van-search -->
<van-search
v-model="searchValue"
shape="round"
placeholder="搜区服 / 段位 / 哈夫币数量"
class="home-search"
/>
<div class="fraud-alert">
<span>防骗提示</span>
平台担保交易拒绝私下转账/共享验证码交接全程留痕
</div>
<!-- 防骗提示 van-notice-bar -->
<van-notice-bar
left-icon="warning"
scrollable
text="平台担保交易,拒绝私下转账/共享验证码,交接全程留痕。"
class="fraud-notice"
/>
<div class="mobile-banner">
<div>
@@ -180,13 +200,19 @@ function formatRatio(item: Listing) {
</section>
<section class="mobile-content">
<div class="channel-strip" aria-label="快捷频道">
<button v-for="item in channels" :key="item.label" type="button">
<span>{{ item.icon }}</span>
{{ item.label }}
</button>
</div>
<!-- 频道按钮组 van-grid -->
<van-grid :column-num="5" :border="false" class="channel-grid">
<van-grid-item v-for="item in channels" :key="item.label">
<template #icon>
<span class="channel-icon">{{ item.icon }}</span>
</template>
<template #text>
<span class="channel-label">{{ item.label }}</span>
</template>
</van-grid-item>
</van-grid>
<!-- 数据背书面板 - 保留手写样式 -->
<div class="trust-panel" aria-label="交易数据背书">
<div v-for="item in platformStats" :key="item.label">
<strong>{{ item.value }}</strong>
@@ -194,31 +220,29 @@ function formatRatio(item: Listing) {
</div>
</div>
<div class="sort-bar" aria-label="排序方式">
<button
v-for="item in sortOptions"
:key="item"
:class="{ active: activeSort === item }"
type="button"
@click="
item === '筛选' ? (filterOpen = !filterOpen) : (activeSort = item)
"
>
{{ item }}<span v-if="item === '筛选'"></span>
</button>
</div>
<!-- 排序/筛选 van-dropdown-menu -->
<van-dropdown-menu class="sort-dropdown">
<van-dropdown-item v-model="activeSort" :options="sortOptions" />
<van-dropdown-item
v-model="activeFilter"
:options="quickFilters"
title="筛选"
/>
</van-dropdown-menu>
<div v-if="filterOpen" class="filter-panel">
<button v-for="item in quickFilters" :key="item" type="button">
{{ item }}
</button>
</div>
<p v-if="loadFailed" class="mobile-tip">
接口暂不可用当前展示移动端示例数据
</p>
<p v-else-if="loading" class="mobile-tip">正在加载优质账号...</p>
<!-- 加载/错误状态 -->
<van-loading v-if="loading" class="state-loading" size="24px" vertical>
正在加载优质账号...
</van-loading>
<van-notice-bar
v-else-if="loadFailed"
left-icon="info-o"
color="#6b7a90"
background="transparent"
text="接口暂不可用,当前展示移动端示例数据。"
/>
<!-- 列表卡片 - 保留手写 mobile-card 样式 -->
<div class="mobile-list">
<RouterLink
v-for="item in displayListings"
@@ -262,15 +286,14 @@ function formatRatio(item: Listing) {
</div>
</section>
<nav class="mobile-tabbar" aria-label="底部导航">
<RouterLink to="/m" class="tab-item active">首页</RouterLink>
<RouterLink to="/m/listings" class="tab-item">租号</RouterLink>
<RouterLink to="/m/seller/listings/create" class="publish-tab"
>发布</RouterLink
>
<RouterLink to="/m/orders" class="tab-item">订单</RouterLink>
<RouterLink to="/m/profile" class="tab-item">我的</RouterLink>
</nav>
<!-- 底部 TabBar van-tabbar route -->
<van-tabbar route class="home-tabbar">
<van-tabbar-item to="/m" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item to="/m/listings" icon="search">租号</van-tabbar-item>
<van-tabbar-item to="/m/seller/listings/create" icon="add-o" class="tabbar-publish">发布</van-tabbar-item>
<van-tabbar-item to="/m/orders" icon="orders-o">订单</van-tabbar-item>
<van-tabbar-item to="/m/profile" icon="manager-o">我的</van-tabbar-item>
</van-tabbar>
</main>
</template>
@@ -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);
}
+327 -159
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { onUnmounted, reactive, ref } from "vue";
import { RouterLink, useRouter } from "vue-router";
import { showToast, showDialog } from "vant";
import { sendSmsCode } from "@/api/auth";
import { useSessionStore } from "@/stores/session";
@@ -15,27 +16,61 @@ const form = reactive({
code: "",
});
const maskedPhone = computed(() =>
form.phone.length >= 7
? `${form.phone.slice(0, 3)}****${form.phone.slice(-4)}`
: "手机号"
);
const countDown = ref(0);
let timer: ReturnType<typeof setInterval> | null = null;
function startCountDown() {
countDown.value = 60;
timer = setInterval(() => {
countDown.value--;
if (countDown.value <= 0) {
clearInterval(timer!);
timer = null;
}
}, 1000);
}
onUnmounted(() => {
if (timer) {
clearInterval(timer);
timer = null;
}
});
/* --------- 业务方法 --------- */
async function handleSendCode() {
if (!form.phone.trim()) {
showToast("请输入手机号");
return;
}
sending.value = true;
try {
await sendSmsCode(form.phone);
window.alert("验证码已发送,开发环境请查看后端日志");
} catch {
window.alert("验证码发送失败,请稍后重试");
showToast("验证码已发送,开发环境请查看后端日志");
startCountDown();
} catch (error) {
showToast(readError(error, "验证码发送失败,请稍后重试"));
} finally {
sending.value = false;
}
}
function readError(error: unknown, fallback: string) {
if (typeof error === "object" && error && "response" in error) {
const response = (error as { response?: { data?: { message?: string } } })
.response;
return response?.data?.message || fallback;
}
return fallback;
}
async function handleLogin() {
if (!agreed.value) {
window.alert("请先阅读并同意用户协议和隐私政策");
showDialog({
title: "提示",
message: "请先阅读并同意用户协议和隐私政策",
});
return;
}
@@ -44,7 +79,7 @@ async function handleLogin() {
await session.login(form.phone, form.code);
await router.push("/m/profile");
} catch {
window.alert("登录失败,请检查手机号和验证码");
showToast("登录失败,请检查手机号和验证码");
} finally {
loading.value = false;
}
@@ -53,222 +88,355 @@ async function handleLogin() {
<template>
<main class="mobile-login-shell">
<section class="login-brand-card">
<div class="monster-logo"></div>
<p>大锤商行</p>
<small>平台担保 · 安全租号</small>
</section>
<section class="login-panel">
<p class="login-eyebrow">SMS LOGIN</p>
<h1>登录</h1>
<p class="login-subtitle">使用手机号验证码登录当前开发环境验证码会打印在后端日志</p>
<label class="field-card">
<span>手机号</span>
<input v-model="form.phone" maxlength="11" inputmode="tel" placeholder="请输入手机号" />
</label>
<label class="field-card">
<span>验证码</span>
<div class="code-line">
<input v-model="form.code" maxlength="6" inputmode="numeric" placeholder="6 位验证码" />
<button type="button" :disabled="sending" @click="handleSendCode">
{{ sending ? "发送中" : "发送" }}
</button>
<section class="login-card">
<!-- 品牌区域 -->
<div class="login-top">
<div class="monster-logo"></div>
<div>
<p>大锤商行</p>
<small>平台担保 · 安全租号</small>
</div>
</label>
<div class="login-links">
<button type="button">忘记密码</button>
<span>当前输入{{ maskedPhone }}</span>
</div>
<label class="agreement-row">
<input v-model="agreed" type="checkbox" />
<span>我已阅读并同意 <b>用户协议</b> <b>隐私政策</b></span>
</label>
<!-- 登录/注册 Tab 切换 -->
<div class="auth-switch" aria-label="登录注册切换">
<RouterLink to="/m/login" class="active">登录</RouterLink>
<RouterLink to="/m/register">注册</RouterLink>
</div>
<button class="primary-login" type="button" :disabled="loading" @click="handleLogin">
{{ loading ? "登录中..." : "登录" }}
</button>
<p class="login-eyebrow">SMS LOGIN</p>
<h1>手机号登录</h1>
<p class="login-subtitle">验证码登录开发环境验证码查看后端日志</p>
<button class="sms-login" type="button" @click="handleSendCode">
使用手机验证码登录
</button>
<!-- 手机号输入 -->
<van-field
v-model="form.phone"
type="tel"
maxlength="11"
placeholder="请输入手机号"
clearable
class="login-field"
>
<template #left-icon>
<span class="field-prefix">+86</span>
</template>
</van-field>
<!-- 验证码输入 -->
<van-field
v-model="form.code"
type="digit"
maxlength="6"
placeholder="6 位验证码"
class="login-field"
:left-icon="'shield-o'"
>
<template #button>
<van-button
size="small"
type="primary"
plain
round
:disabled="sending || countDown > 0"
:loading="sending"
@click="handleSendCode"
>
{{
countDown > 0
? `${countDown}s`
: sending
? "发送中"
: "发送验证码"
}}
</van-button>
</template>
</van-field>
<!-- 底部链接行 -->
<div class="login-links">
<van-button size="mini" plain type="primary">忘记密码</van-button>
</div>
<!-- 用户协议 -->
<div class="agreement-row">
<van-checkbox v-model="agreed" shape="square" icon-size="16px">
我已阅读并同意<b>用户协议</b><b>隐私政策</b>
</van-checkbox>
</div>
<!-- 登录按钮 -->
<van-button
type="primary"
block
round
class="login-button"
:loading="loading"
loading-text="登录中..."
@click="handleLogin"
>
登录
</van-button>
<!-- 底部注册入口 -->
<RouterLink
to="/m/register"
class="register-entry"
custom
v-slot="{ navigate }"
>
<van-button
plain
type="primary"
block
round
size="small"
class="register-button"
@click="navigate"
>
没有账号立即注册
</van-button>
</RouterLink>
</section>
</main>
</template>
<style scoped>
.mobile-login-shell {
min-height: 100vh;
background: radial-gradient(circle at 50% 0, #f0fffb 0, #f7fbff 34%, #ffffff 78%);
color: #151c28;
padding: 48px 26px 32px;
/* ========== Vant 主题变量覆盖 ========== */
:root {
--van-primary-color: #1777ff;
--van-primary-color-light: #5fd4ff;
--van-button-primary-background: linear-gradient(135deg, #1777ff, #5fd4ff);
--van-button-primary-border-color: transparent;
}
.login-brand-card {
display: grid;
place-items: center;
margin-bottom: 42px;
text-align: center;
/* ========== 页面容器 ========== */
.mobile-login-shell {
min-height: 100dvh;
box-sizing: border-box;
overflow: hidden;
background: radial-gradient(
circle at 50% 0,
#eafff9 0,
#f7fbff 38%,
#ffffff 86%
);
color: #151c28;
padding: 40px 24px;
}
.login-card {
display: flex;
min-height: calc(100dvh - 80px);
flex-direction: column;
justify-content: center;
}
/* ========== 品牌区域 ========== */
.login-top {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 14px;
}
.monster-logo {
display: grid;
width: 72px;
height: 72px;
width: 48px;
height: 48px;
flex: 0 0 auto;
place-items: center;
border-radius: 22px;
border-radius: 16px;
background: linear-gradient(145deg, #1777ff, #5fd4ff);
color: #ffffff;
font-size: 28px;
font-size: 21px;
font-weight: 900;
box-shadow: 0 14px 36px rgba(23, 119, 255, 0.24);
box-shadow: 0 10px 22px rgba(23, 119, 255, 0.2);
}
.login-brand-card p {
margin: 12px 0 2px;
font-size: 24px;
.login-top p {
margin: 0 0 2px;
font-size: 20px;
font-weight: 900;
}
.login-brand-card small,
.login-top small,
.login-subtitle {
color: #667386;
}
.login-panel {
border-radius: 22px;
/* ========== Tab 切换 ========== */
.auth-switch {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-bottom: 16px;
border-radius: 15px;
background: #eef4fb;
padding: 5px;
}
.auth-switch a {
border-radius: 11px;
color: #667386;
padding: 10px 0;
text-align: center;
font-size: 14px;
font-weight: 900;
text-decoration: none;
}
.auth-switch a.active {
background: #ffffff;
color: #111827;
box-shadow: 0 8px 18px rgba(23, 35, 61, 0.08);
}
/* ========== 标题区 ========== */
.login-eyebrow {
margin: 0 0 8px;
margin: 0 0 5px;
color: #02846e;
font-size: 13px;
font-size: 12px;
font-weight: 900;
letter-spacing: 0.04em;
}
.login-panel h1 {
.login-card h1 {
margin: 0;
font-size: 30px;
line-height: 1.2;
font-size: 28px;
line-height: 1.15;
}
.login-subtitle {
margin: 12px 0 28px;
font-size: 14px;
line-height: 1.7;
}
.field-card {
display: block;
margin-top: 14px;
border: 1px solid #e1e6ee;
border-radius: 13px;
background: rgba(255, 255, 255, 0.88);
padding: 14px;
}
.field-card span {
display: block;
margin-bottom: 10px;
color: #5d6674;
margin: 7px 0 14px;
font-size: 13px;
font-weight: 700;
line-height: 1.45;
}
.field-card input {
width: 100%;
box-sizing: border-box;
border: 0;
outline: 0;
background: #eaf2ff;
/* ========== Vant Field 覆盖样式 ========== */
.login-field {
margin-top: 12px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.92);
box-shadow: inset 0 1px 3px rgba(23, 35, 61, 0.04);
overflow: hidden;
}
.login-field :deep(.van-field__control) {
font-size: 15px;
color: #151c28;
padding: 11px 12px;
font-size: 16px;
}
.code-line {
display: grid;
grid-template-columns: 1fr 86px;
gap: 8px;
.login-field :deep(.van-field__label) {
width: auto;
margin-right: 8px;
color: #333b48;
font-weight: 700;
font-size: 13px;
}
.login-field :deep(.van-field__left-icon) {
color: #1777ff;
margin-right: 0;
}
.code-line button,
.sms-login,
.primary-login,
.login-links button {
border: 0;
background: none;
.login-field :deep(.van-field__right-icon) {
color: #999;
}
.field-prefix {
color: #1777ff;
font-weight: 800;
font-size: 14px;
margin-right: 4px;
}
.code-line button {
border-radius: 8px;
background: #ffffff;
color: #3f4652;
box-shadow: inset 0 0 0 1px #d6dce6;
/* ========== 底部链接行 ========== */
.login-links {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 0 12px;
color: #566274;
font-size: 12px;
}
.login-links,
/* ========== 协议行 ========== */
.agreement-row {
display: flex;
align-items: center;
margin-bottom: 16px;
}
.login-links {
justify-content: space-between;
margin: 18px 0 26px;
color: #566274;
font-size: 13px;
}
.login-links button {
color: #08b86f;
}
.agreement-row {
gap: 10px;
.agreement-row :deep(.van-checkbox__label) {
margin-left: 6px;
color: #333b48;
font-size: 12px;
line-height: 1.4;
}
.agreement-row :deep(.van-checkbox__label b) {
color: #1777ff;
font-weight: 600;
}
/* ========== 登录按钮 ========== */
.login-button {
margin-top: 6px;
height: 46px;
font-size: 15px;
font-weight: 800;
background: linear-gradient(135deg, #1777ff, #5fd4ff) !important;
border: none !important;
box-shadow: 0 8px 20px rgba(23, 119, 255, 0.25);
}
/* ========== 注册入口按钮 ========== */
.register-button {
margin-top: 12px;
font-size: 13px;
line-height: 1.5;
font-weight: 800;
}
.agreement-row input {
width: 16px;
height: 16px;
.register-entry {
text-decoration: none;
}
.agreement-row b {
color: #e63145;
font-weight: 500;
}
/* ========== 响应式 ========== */
@media (max-height: 620px) {
.mobile-login-shell {
padding-top: 10px;
padding-bottom: 10px;
}
.primary-login,
.sms-login {
width: 100%;
height: 52px;
margin-top: 22px;
border-radius: 11px;
font-size: 16px;
}
.login-card {
min-height: calc(100dvh - 20px);
}
.primary-login {
background: #050505;
color: #ffffff;
}
.login-top {
margin-bottom: 10px;
}
.primary-login:disabled {
opacity: 0.7;
}
.monster-logo {
width: 42px;
height: 42px;
border-radius: 14px;
}
.sms-login {
background: #ffffff;
color: #151c28;
box-shadow: inset 0 0 0 1px #e1e6ee;
.auth-switch {
margin-bottom: 11px;
}
.login-card h1 {
font-size: 24px;
}
.login-field {
margin-top: 8px;
}
.login-button {
height: 42px;
margin-top: 10px;
}
}
@media (min-width: 520px) {
+426 -72
View File
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { computed } from "vue";
import { RouterLink } from "vue-router";
import { useRouter } from "vue-router";
import { useSessionStore } from "@/stores/session";
const session = useSessionStore();
const router = useRouter();
const isLoggedIn = computed(() => Boolean(session.token));
const displayName = computed(() => session.phone ? `用户${session.phone.slice(-6)}` : `用户${session.userId || 883099}`);
const displayId = computed(() => session.userId || 138865);
@@ -27,104 +28,457 @@ const serviceIcons = ["👥", "🪙", "💸", "💼", "⚔️", "💎", "⭐", "
function logout() {
session.logout();
}
function goToLogin() {
router.push("/m/login");
}
</script>
<template>
<main class="mobile-profile-shell">
<!-- 未登录区 van-empty + van-button -->
<template v-if="!isLoggedIn">
<section class="guest-card">
<div class="guest-logo"></div>
<h1>登录大锤商行</h1>
<p>登录后查看订单管理发布提现余额并享受平台担保交易服务</p>
<RouterLink to="/m/login" class="guest-login">立即登录</RouterLink>
<div class="guest-benefits"><span>担保交易</span><span>订单留痕</span><span>安全交接</span></div>
<section class="guest-section">
<van-empty
image="error"
description="登录后查看订单、管理发布、提现余额"
class="guest-empty"
>
<template #image>
<div class="guest-logo"></div>
</template>
</van-empty>
<van-button
type="primary"
block
round
class="guest-login-btn"
@click="goToLogin"
>
立即登录
</van-button>
<div class="guest-benefits">
<span>担保交易</span><span>订单留痕</span><span>安全交接</span>
</div>
</section>
</template>
<!-- 已登录区 -->
<template v-else>
<!-- 头像区 van-cell + van-image -->
<section class="profile-header">
<div class="avatar">👤</div>
<van-image
round
width="64px"
height="64px"
src=""
class="avatar-image"
>
<template #error>
<div class="avatar-fallback">👤</div>
</template>
</van-image>
<div class="profile-user">
<h1>{{ displayName }}<span></span></h1>
<h1>{{ displayName }}<span class="edit-icon"></span></h1>
<p>ID:{{ displayId }} · {{ maskedPhone }}</p>
</div>
<button class="setting-btn" type="button"></button>
<van-icon name="setting-o" size="24" class="setting-icon" />
</section>
<section class="balance-card">
<div><span>账户余额()</span><strong>¥0.00</strong><button type="button">查看明细</button></div>
<button class="withdraw-btn" type="button">提现</button>
</section>
<!-- 余额卡片 van-cell-group + van-cell -->
<van-cell-group inset class="balance-group">
<van-cell center>
<template #title>
<div class="balance-info">
<span class="balance-label">账户余额()</span>
<strong class="balance-value">¥0.00</strong>
<van-button
size="mini"
plain
hairline
type="warning"
class="detail-btn"
>
查看明细
</van-button>
</div>
</template>
<template #value>
<van-button
type="primary"
size="small"
class="withdraw-btn"
>
提现
</van-button>
</template>
</van-cell>
</van-cell-group>
</template>
<section class="menu-card">
<!-- 订单菜单 van-grid -->
<section class="menu-section">
<h2>我的订单</h2>
<div class="icon-grid four">
<RouterLink v-for="item in sellerOrders" :key="item.label" :to="item.to"><span>{{ item.icon }}</span><em>{{ item.label }}</em></RouterLink>
</div>
<van-grid :column-num="4" :border="false" class="order-grid">
<van-grid-item
v-for="item in sellerOrders"
:key="item.label"
:to="item.to"
>
<template #icon>
<span class="menu-icon">{{ item.icon }}</span>
</template>
<template #text>
<span class="menu-text">{{ item.label }}</span>
</template>
</van-grid-item>
</van-grid>
</section>
<section class="menu-card">
<!-- 我买的 van-grid -->
<section class="menu-section">
<h2>我买的</h2>
<div class="icon-grid four">
<RouterLink v-for="item in buyerOrders" :key="item.label" :to="item.to"><span>{{ item.icon }}</span><em>{{ item.label }}</em></RouterLink>
</div>
<van-grid :column-num="4" :border="false" class="order-grid">
<van-grid-item
v-for="item in buyerOrders"
:key="item.label"
:to="item.to"
>
<template #icon>
<span class="menu-icon">{{ item.icon }}</span>
</template>
<template #text>
<span class="menu-text">{{ item.label }}</span>
</template>
</van-grid-item>
</van-grid>
</section>
<section class="menu-card service-card">
<div class="icon-grid service-grid">
<button v-for="(item, index) in serviceItems" :key="item" type="button"><span>{{ serviceIcons[index] }}</span><em>{{ item }}</em></button>
</div>
</section>
<!-- 服务菜单 van-grid -->
<van-cell-group inset class="service-group">
<van-grid :column-num="4" :border="false" class="service-grid">
<van-grid-item
v-for="(item, index) in serviceItems"
:key="item"
>
<template #icon>
<span class="menu-icon">{{ serviceIcons[index] }}</span>
</template>
<template #text>
<span class="menu-text">{{ item }}</span>
</template>
</van-grid-item>
</van-grid>
</van-cell-group>
<button v-if="isLoggedIn" class="logout-btn" type="button" @click="logout">退出登录</button>
<!-- 退出登录 van-button -->
<van-button
v-if="isLoggedIn"
plain
type="danger"
block
class="logout-btn"
@click="logout"
>
退出登录
</van-button>
<nav class="profile-tabbar" aria-label="底部导航">
<RouterLink to="/m" class="tab-item">首页</RouterLink>
<RouterLink to="/m/listings" class="tab-item">租号</RouterLink>
<RouterLink to="/m/seller/listings/create" class="publish-tab">发布</RouterLink>
<RouterLink to="/m/orders" class="tab-item">订单</RouterLink>
<RouterLink to="/m/profile" class="tab-item active">我的</RouterLink>
</nav>
<!-- 底部 TabBar van-tabbar route -->
<van-tabbar route class="profile-tabbar">
<van-tabbar-item to="/m" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item to="/m/listings" icon="search">租号</van-tabbar-item>
<van-tabbar-item to="/m/seller/listings/create" icon="add-o" class="tabbar-publish">发布</van-tabbar-item>
<van-tabbar-item to="/m/orders" icon="orders-o">订单</van-tabbar-item>
<van-tabbar-item to="/m/profile" icon="manager-o">我的</van-tabbar-item>
</van-tabbar>
</main>
</template>
<style scoped>
.mobile-profile-shell { min-height: 100vh; background: linear-gradient(180deg, #fbfaff 0, #fbfaff 210px, #f7f7f7 210px); color: #1a202c; padding: 28px 18px 96px; }
.profile-header { display: flex; align-items: center; gap: 14px; min-height: 120px; }
.avatar { display: grid; width: 62px; height: 62px; flex: 0 0 auto; place-items: center; border-radius: 50%; background: #fff; font-size: 32px; box-shadow: 0 10px 24px rgba(24,30,45,.08); }
.profile-user { min-width: 0; flex: 1; }
.profile-user h1 { overflow: hidden; margin: 0; font-size: 20px; text-overflow: ellipsis; white-space: nowrap; }
.profile-user h1 span { margin-left: 4px; color: #b7bcc6; font-size: 14px; }
.profile-user p { margin: 7px 0 0; color: #666f7c; font-size: 13px; }
.setting-btn { display: grid; width: 42px; height: 42px; place-items: center; border: 0; border-radius: 50%; background: #fff; font-size: 20px; }
.balance-card, .menu-card, .guest-card { border-radius: 16px; background: #fff; box-shadow: 0 8px 26px rgba(24,30,45,.06); }
.balance-card { display: flex; align-items: center; justify-content: space-between; margin-top: 10px; padding: 24px 28px; }
.balance-card span, .balance-card strong { display: block; }
.balance-card span { color: #8b8f99; font-weight: 700; }
.balance-card strong { margin-top: 14px; font-size: 34px; line-height: 1; }
.balance-card button { border: 0; border-radius: 6px; font-weight: 800; }
.balance-card div button { margin-top: 14px; background: #fff5ee; color: #ff5f00; padding: 6px 8px; box-shadow: inset 0 0 0 1px #ff8a42; }
.withdraw-btn { background: #050505; color: #fff; padding: 12px 18px; }
.menu-card { margin-top: 12px; padding: 16px 14px; }
.menu-card h2 { margin: 0 0 12px; font-size: 18px; }
.icon-grid { display: grid; gap: 14px 8px; }
.icon-grid.four, .service-grid { grid-template-columns: repeat(4, 1fr); }
.icon-grid a, .icon-grid button { display: grid; gap: 7px; place-items: center; border: 0; background: none; color: #5f6671; text-align: center; }
.icon-grid span { font-size: 25px; line-height: 1; }
.icon-grid em { font-size: 13px; font-style: normal; font-weight: 700; }
.service-card { padding-top: 18px; padding-bottom: 18px; }
.guest-card { margin-top: 18px; padding: 34px 26px; text-align: center; }
.guest-logo { display: grid; width: 74px; height: 74px; margin: 0 auto 18px; place-items: center; border-radius: 22px; background: linear-gradient(145deg, #1777ff, #5ed7ff); color: #fff; font-size: 30px; font-weight: 900; }
.guest-card h1 { margin: 0; font-size: 24px; }
.guest-card p { margin: 12px 0 22px; color: #667386; font-size: 14px; line-height: 1.7; }
.guest-login { display: block; border-radius: 13px; background: #050505; color: #fff; padding: 14px; font-weight: 900; }
.guest-benefits { display: flex; justify-content: center; gap: 8px; margin-top: 16px; }
.guest-benefits span { border-radius: 999px; background: #eef6ff; color: #1777ff; padding: 6px 9px; font-size: 12px; font-weight: 800; }
.logout-btn { width: 100%; margin-top: 14px; border: 0; border-radius: 14px; background: #fff; color: #ef4444; padding: 14px; font-weight: 900; box-shadow: 0 8px 26px rgba(24,30,45,.06); }
.profile-tabbar { position: fixed; right: 12px; bottom: 12px; left: 12px; z-index: 20; display: flex; height: 60px; align-items: center; justify-content: space-around; border-radius: 22px; background: rgba(255,255,255,.96); box-shadow: 0 16px 36px rgba(23,35,61,.16); backdrop-filter: blur(14px); }
.tab-item { color: #708097; font-size: 12px; font-weight: 800; }
.tab-item.active { color: #5b6ee1; }
.publish-tab { display: grid; width: 58px; height: 58px; place-items: center; border: 5px solid #f7f7f7; border-radius: 50%; background: #536dc8; color: #fff; font-size: 13px; font-weight: 900; transform: translateY(-18px); box-shadow: 0 10px 24px rgba(83,109,200,.35); }
@media (min-width: 520px) { .mobile-profile-shell { max-width: 430px; margin: 0 auto; box-shadow: 0 0 0 1px rgba(23,35,61,.08); } .profile-tabbar { right: calc((100vw - 430px) / 2 + 12px); left: calc((100vw - 430px) / 2 + 12px); } }
.mobile-profile-shell {
min-height: 100vh;
background: linear-gradient(180deg, #fbfaff 0, #fbfaff 210px, #f7f7f7 210px);
color: #1a202c;
padding: 28px 12px 96px;
}
/* ========== 未登录区 ========== */
.guest-section {
text-align: center;
padding: 20px 14px 0;
}
.guest-empty {
padding: 0;
}
.guest-empty :deep(.van-empty__description) {
color: #667386;
font-size: 14px;
line-height: 1.7;
margin-top: 12px;
}
.guest-logo {
display: grid;
width: 74px;
height: 74px;
margin: 0 auto 0;
place-items: center;
border-radius: 22px;
background: linear-gradient(145deg, #1777ff, #5ed7ff);
color: #fff;
font-size: 30px;
font-weight: 900;
}
.guest-login-btn {
margin-top: 18px;
border-radius: 13px;
background: #050505;
border-color: #050505;
font-size: 16px;
font-weight: 900;
height: 48px;
}
.guest-benefits {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 16px;
}
.guest-benefits span {
border-radius: 999px;
background: #eef6ff;
color: #1777ff;
padding: 6px 9px;
font-size: 12px;
font-weight: 800;
}
/* ========== 已登录头像区 ========== */
.profile-header {
display: flex;
align-items: center;
gap: 14px;
min-height: 120px;
}
.avatar-image {
flex: 0 0 auto;
}
.avatar-fallback {
display: grid;
width: 64px;
height: 64px;
place-items: center;
background: #fff;
border-radius: 50%;
font-size: 32px;
box-shadow: 0 10px 24px rgba(24, 30, 45, 0.08);
}
.profile-user {
min-width: 0;
flex: 1;
}
.profile-user h1 {
overflow: hidden;
margin: 0;
font-size: 20px;
text-overflow: ellipsis;
white-space: nowrap;
}
.edit-icon {
margin-left: 4px;
color: #b7bcc6;
font-size: 14px;
}
.profile-user p {
margin: 7px 0 0;
color: #666f7c;
font-size: 13px;
}
.setting-icon {
color: #8b8f99;
cursor: pointer;
}
/* ========== 余额卡片 ========== */
.balance-group {
margin-top: 10px;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 8px 26px rgba(24, 30, 45, 0.06);
}
.balance-group :deep(.van-cell) {
padding: 24px 20px;
}
.balance-info {
display: flex;
flex-direction: column;
gap: 0;
}
.balance-label {
color: #8b8f99;
font-size: 13px;
font-weight: 700;
}
.balance-value {
margin-top: 10px;
font-size: 34px;
line-height: 1;
color: #1a202c;
}
.detail-btn {
margin-top: 10px;
align-self: flex-start;
border-color: #ff8a42;
color: #ff5f00;
background: #fff5ee;
font-weight: 800;
}
.withdraw-btn {
background: #050505;
border-color: #050505;
border-radius: 6px;
padding: 12px 18px;
font-weight: 800;
height: auto;
}
/* ========== 订单菜单 ========== */
.menu-section {
margin-top: 12px;
border-radius: 16px;
background: #fff;
padding: 16px 14px;
box-shadow: 0 8px 26px rgba(24, 30, 45, 0.06);
}
.menu-section h2 {
margin: 0 0 12px;
font-size: 18px;
}
.order-grid :deep(.van-grid-item__content) {
padding: 8px 4px;
background: transparent;
}
.menu-icon {
font-size: 25px;
line-height: 1;
}
.menu-text {
font-size: 13px;
font-weight: 700;
color: #5f6671;
}
/* ========== 服务菜单 ========== */
.service-group {
margin-top: 12px;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 8px 26px rgba(24, 30, 45, 0.06);
}
.service-group :deep(.van-cell-group) {
margin: 0;
}
.service-grid :deep(.van-grid-item__content) {
padding: 14px 4px;
background: #fff;
}
/* ========== 退出登录 ========== */
.logout-btn {
margin-top: 14px;
border-radius: 14px;
font-weight: 900;
height: 48px;
font-size: 16px;
}
/* ========== van-tabbar 底部导航 ========== */
.profile-tabbar {
position: fixed;
right: 12px;
bottom: 12px;
left: 12px;
z-index: 20;
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;
}
.profile-tabbar :deep(.van-tabbar) {
border-radius: 22px;
height: 60px;
}
.profile-tabbar :deep(.van-tabbar-item) {
color: #708097;
font-size: 12px;
font-weight: 800;
}
.profile-tabbar :deep(.van-tabbar-item--active) {
color: #5b6ee1;
}
.profile-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-profile-shell {
max-width: 430px;
margin: 0 auto;
box-shadow: 0 0 0 1px rgba(23, 35, 61, 0.08);
}
.profile-tabbar {
right: calc((100vw - 430px) / 2 + 12px);
left: calc((100vw - 430px) / 2 + 12px);
}
}
</style>
@@ -0,0 +1,447 @@
<script setup lang="ts">
import { onUnmounted, reactive, ref } from "vue";
import { RouterLink, useRouter } from "vue-router";
import { showToast } from "vant";
import { sendSmsCode } from "@/api/auth";
import { useSessionStore } from "@/stores/session";
const router = useRouter();
const session = useSessionStore();
const loading = ref(false);
const sending = ref(false);
const agreed = ref(false);
const form = reactive({
phone: "",
code: "",
inviteCode: "",
});
/* --------- 验证码倒计时 --------- */
const countDown = ref(0);
let timer: ReturnType<typeof setInterval> | null = null;
function startCountDown() {
countDown.value = 60;
timer = setInterval(() => {
countDown.value--;
if (countDown.value <= 0) {
clearInterval(timer!);
timer = null;
}
}, 1000);
}
onUnmounted(() => {
if (timer) {
clearInterval(timer);
timer = null;
}
});
/* --------- 业务方法 --------- */
async function handleSendCode() {
if (!form.phone.trim()) {
showToast("请输入手机号");
return;
}
sending.value = true;
try {
await sendSmsCode(form.phone);
showToast("验证码已发送,开发环境请查看后端日志");
startCountDown();
} catch (error) {
showToast(readError(error, "验证码发送失败,请稍后重试"));
} finally {
sending.value = false;
}
}
function readError(error: unknown, fallback: string) {
if (typeof error === "object" && error && "response" in error) {
const response = (error as { response?: { data?: { message?: string } } })
.response;
return response?.data?.message || fallback;
}
return fallback;
}
async function handleRegister() {
if (!agreed.value) {
showToast("请先阅读并同意用户协议和隐私政策");
return;
}
loading.value = true;
try {
await session.login(form.phone, form.code);
await router.push("/m/profile");
} catch {
showToast("注册失败,请检查手机号和验证码");
} finally {
loading.value = false;
}
}
</script>
<template>
<main class="mobile-register-shell">
<section class="register-card">
<!-- 品牌区域 -->
<div class="register-top">
<div class="monster-logo"></div>
<div>
<p>加入大锤商行</p>
<small>注册后即可租号下单发布账号</small>
</div>
</div>
<!-- 登录/注册 Tab 切换 -->
<div class="auth-switch" aria-label="登录注册切换">
<RouterLink to="/m/login">登录</RouterLink>
<RouterLink to="/m/register" class="active">注册</RouterLink>
</div>
<p class="register-eyebrow">CREATE ACCOUNT</p>
<h1>手机号注册</h1>
<p class="register-subtitle">
先用短信验证码创建账号后续再补充实名和收款信息
</p>
<!-- 手机号输入 -->
<van-field
v-model="form.phone"
type="tel"
maxlength="11"
inputmode="tel"
placeholder="请输入手机号"
clearable
class="register-field"
>
<template #left-icon>
<span class="field-prefix">+86</span>
</template>
</van-field>
<!-- 验证码输入 -->
<van-field
v-model="form.code"
type="digit"
maxlength="6"
placeholder="6 位验证码"
class="register-field"
:left-icon="'shield-o'"
>
<template #button>
<van-button
size="small"
type="primary"
plain
round
:disabled="sending || countDown > 0"
:loading="sending"
@click="handleSendCode"
>
{{
countDown > 0
? `${countDown}s`
: sending
? "发送中"
: "发送验证码"
}}
</van-button>
</template>
</van-field>
<!-- 邀请码输入 -->
<van-field
v-model="form.inviteCode"
maxlength="16"
placeholder="选填,有邀请码填入"
clearable
class="register-field"
:left-icon="'friends-o'"
/>
<!-- 用户协议 -->
<div class="agreement-row">
<van-checkbox v-model="agreed" shape="square" icon-size="16px">
我已阅读并同意<b>用户协议</b><b>隐私政策</b>
</van-checkbox>
</div>
<!-- 注册按钮 -->
<van-button
type="primary"
block
round
class="register-button"
:loading="loading"
loading-text="注册中..."
@click="handleRegister"
>
注册并登录
</van-button>
<!-- 底部登录入口 -->
<RouterLink
to="/m/login"
class="login-entry"
custom
v-slot="{ navigate }"
>
<van-button
plain
type="primary"
block
round
size="small"
class="login-link-button"
@click="navigate"
>
已有账号去登录
</van-button>
</RouterLink>
</section>
</main>
</template>
<style scoped>
/* ========== Vant 主题变量覆盖 ========== */
:root {
--van-primary-color: #1777ff;
--van-primary-color-light: #5fd4ff;
--van-button-primary-background: linear-gradient(135deg, #1777ff, #5fd4ff);
--van-button-primary-border-color: transparent;
}
/* ========== 页面容器 ========== */
.mobile-register-shell {
min-height: 100dvh;
box-sizing: border-box;
overflow: hidden;
background: radial-gradient(
circle at 50% 0,
#eafff9 0,
#f7fbff 38%,
#ffffff 86%
);
color: #151c28;
padding: 40px 24px;
}
.register-card {
display: flex;
min-height: calc(100dvh - 80px);
flex-direction: column;
justify-content: center;
}
/* ========== 品牌区域 ========== */
.register-top {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 14px;
}
.monster-logo {
display: grid;
width: 48px;
height: 48px;
flex: 0 0 auto;
place-items: center;
border-radius: 16px;
background: linear-gradient(145deg, #1777ff, #5fd4ff);
color: #ffffff;
font-size: 21px;
font-weight: 900;
box-shadow: 0 10px 22px rgba(23, 119, 255, 0.2);
}
.register-top p {
margin: 0 0 2px;
font-size: 20px;
font-weight: 900;
}
.register-top small,
.register-subtitle {
color: #667386;
}
/* ========== Tab 切换 ========== */
.auth-switch {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-bottom: 16px;
border-radius: 15px;
background: #eef4fb;
padding: 5px;
}
.auth-switch a {
border-radius: 11px;
color: #667386;
padding: 10px 0;
text-align: center;
font-size: 14px;
font-weight: 900;
text-decoration: none;
}
.auth-switch a.active {
background: #ffffff;
color: #111827;
box-shadow: 0 8px 18px rgba(23, 35, 61, 0.08);
}
/* ========== 标题区 ========== */
.register-eyebrow {
margin: 0 0 5px;
color: #02846e;
font-size: 12px;
font-weight: 900;
letter-spacing: 0.04em;
}
.register-card h1 {
margin: 0;
font-size: 28px;
line-height: 1.15;
}
.register-subtitle {
margin: 7px 0 14px;
font-size: 13px;
line-height: 1.45;
}
/* ========== Vant Field 覆盖样式 ========== */
.register-field {
margin-top: 12px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.92);
box-shadow: inset 0 1px 3px rgba(23, 35, 61, 0.04);
overflow: hidden;
}
.register-field :deep(.van-field__control) {
font-size: 15px;
color: #151c28;
}
.register-field :deep(.van-field__label) {
width: auto;
margin-right: 8px;
color: #333b48;
font-weight: 700;
font-size: 13px;
}
.register-field :deep(.van-field__left-icon) {
color: #1777ff;
margin-right: 0;
}
.register-field :deep(.van-field__right-icon) {
color: #999;
}
.field-prefix {
color: #1777ff;
font-weight: 800;
font-size: 14px;
margin-right: 4px;
}
/* ========== 协议行 ========== */
.agreement-row {
display: flex;
align-items: center;
margin-top: 16px;
margin-bottom: 16px;
}
.agreement-row :deep(.van-checkbox__label) {
margin-left: 6px;
color: #333b48;
font-size: 12px;
line-height: 1.4;
}
.agreement-row :deep(.van-checkbox__label b) {
color: #1777ff;
font-weight: 600;
}
/* ========== 注册按钮 ========== */
.register-button {
margin-top: 6px;
height: 46px;
font-size: 15px;
font-weight: 800;
background: linear-gradient(135deg, #1777ff, #5fd4ff) !important;
border: none !important;
box-shadow: 0 8px 20px rgba(23, 119, 255, 0.25);
}
/* ========== 登录入口按钮 ========== */
.login-link-button {
margin-top: 12px;
font-size: 13px;
font-weight: 800;
}
.login-entry {
text-decoration: none;
}
/* ========== 响应式 ========== */
@media (max-height: 620px) {
.mobile-register-shell {
padding-top: 10px;
padding-bottom: 10px;
}
.register-card {
min-height: calc(100dvh - 20px);
}
.register-top {
margin-bottom: 10px;
}
.monster-logo {
width: 42px;
height: 42px;
border-radius: 14px;
}
.auth-switch {
margin-bottom: 11px;
}
.register-card h1 {
font-size: 24px;
}
.register-field {
margin-top: 8px;
}
.register-button {
height: 42px;
margin-top: 10px;
}
}
@media (min-width: 520px) {
.mobile-register-shell {
max-width: 430px;
margin: 0 auto;
box-shadow: 0 0 0 1px rgba(23, 35, 61, 0.08);
}
}
</style>
+14 -7
View File
@@ -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",
},
},
})
});