创建uniapp基础形态

This commit is contained in:
2026-07-15 17:39:10 +08:00
parent 743c5cb4eb
commit 11e0763766
22 changed files with 5311 additions and 43 deletions
+19 -6
View File
@@ -1,18 +1,31 @@
<script>
export default {
onLaunch: function() {
onLaunch() {
console.log('App Launch')
},
onShow: function() {
onShow() {
console.log('App Show')
},
onHide: function() {
onHide() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
@import './src/uni.scss';
<style lang="scss">
@import 'uview-plus/index.scss';
@import './src/styles/theme.scss';
@import './src/styles/fontawesome.scss';
page {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background-color: #f8fafc;
color: #1e293b;
box-sizing: border-box;
}
view, text {
box-sizing: border-box;
}
</style>
+179
View File
@@ -0,0 +1,179 @@
<template>
<view class="app-tabbar">
<view class="tabbar-pill">
<view
v-for="tab in tabs"
:key="tab.name"
class="tab-item"
:class="{ active: current === tab.name }"
@tap="onChange(tab.name)"
>
<FaIcon
:name="tab.icon"
:color="current === tab.name ? '#3c9cff' : '#9acafc'"
:size="20"
/>
<text class="tab-text">{{ tab.text }}</text>
</view>
</view>
<view class="safe-area" />
</view>
</template>
<script setup>
const props = defineProps({
current: {
type: String,
default: 'dashboard'
}
})
const tabs = [
{ name: 'dashboard', text: '仪表盘', icon: 'table-cells' },
{ name: 'features', text: '功能', icon: 'grip' },
{ name: 'profile', text: '我的', icon: 'user' }
]
const routes = {
dashboard: '/pages/dashboard/dashboard',
features: '/pages/features/features',
profile: '/pages/profile/profile'
}
function onChange(name) {
if (name === props.current) return
uni.redirectTo({ url: routes[name] })
}
</script>
<style scoped>
.app-tabbar {
flex-shrink: 0;
padding: 16rpx 32rpx 0;
background: #ffffff;
}
.tabbar-pill {
display: flex;
align-items: center;
justify-content: space-around;
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 999rpx;
padding: 16rpx 12rpx;
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
padding: 8rpx 0;
}
.tab-text {
font-size: 22rpx;
color: #9acafc;
}
.tab-item.active .tab-text {
color: #3c9cff;
font-weight: 500;
}
.safe-area {
height: constant(safe-area-inset-bottom);
height: env(safe-area-inset-bottom);
}
</style>
+61
View File
@@ -0,0 +1,61 @@
<template>
<text class="fa-icon" :class="fontClass" :style="iconStyle">{{ iconChar }}</text>
</template>
<script setup>
import { computed } from 'vue'
import { FA_ICONS } from '@/utils/fa-icons.js'
const props = defineProps({
name: {
type: String,
required: true
},
type: {
type: String,
default: 'solid'
},
color: {
type: String,
default: 'inherit'
},
size: {
type: [Number, String],
default: 16
}
})
const iconChar = computed(() => FA_ICONS[props.name] || '')
const fontClass = computed(() =>
props.type === 'brands' ? 'fa-font-brands' : 'fa-font-solid'
)
const iconStyle = computed(() => ({
color: props.color,
fontSize: typeof props.size === 'number' ? `${props.size}px` : props.size,
lineHeight: 1
}))
</script>
<style scoped>
.fa-icon {
display: inline-block;
font-style: normal;
vertical-align: middle;
}
</style>
<style>
.fa-font-solid {
font-family: 'Font Awesome 7 Free';
font-weight: 900;
font-style: normal;
}
.fa-font-brands {
font-family: 'Font Awesome 7 Brands';
font-weight: 400;
font-style: normal;
}
</style>
+2
View File
@@ -1,5 +1,6 @@
import App from './App'
import uviewPlus from 'uview-plus'
import FaIcon from '@/components/FaIcon.vue'
// #ifndef VUE3
import Vue from 'vue'
@@ -19,6 +20,7 @@ import {
export function createApp() {
const app = createSSRApp(App)
app.use(uviewPlus)
app.component('FaIcon', FaIcon)
return {
app
}
+413 -1
View File
@@ -5,15 +5,26 @@
"packages": {
"": {
"dependencies": {
"@fortawesome/fontawesome-free": "^7.3.0",
"clipboard": "^2.0.11",
"dayjs": "^1.11.21",
"uview-plus": "^3.8.55"
},
"devDependencies": {
"rollup-plugin-visualizer": "^7.0.1",
"sass": "^1.63.2",
"sass-loader": "^10.4.1"
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "7.3.0",
"resolved": "https://registry.npmmirror.com/@fortawesome/fontawesome-free/-/fontawesome-free-7.3.0.tgz",
"integrity": "sha512-Yw4Qa43P6e4Xwh0TwEUkHQNRJInWaqIBo73VJmns3j2AIlZPAvUcR4yGIxCPqPRCgyJ5KIVIalF/I1GxhZ/Kgw==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": {
"node": ">=6"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -387,6 +398,32 @@
"ajv": "^6.9.1"
}
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
@@ -494,6 +531,22 @@
"license": "MIT",
"peer": true
},
"node_modules/bundle-name": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz",
"integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"run-applescript": "^7.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001805",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz",
@@ -563,6 +616,21 @@
"tiny-emitter": "^2.0.0"
}
},
"node_modules/cliui": {
"version": "9.0.1",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-9.0.1.tgz",
"integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
@@ -577,6 +645,49 @@
"integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==",
"license": "MIT"
},
"node_modules/default-browser": {
"version": "5.5.0",
"resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.5.0.tgz",
"integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"bundle-name": "^4.1.0",
"default-browser-id": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/default-browser-id": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-5.0.1.tgz",
"integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/define-lazy-prop": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
"integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz",
@@ -591,6 +702,13 @@
"license": "ISC",
"peer": true
},
"node_modules/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz",
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
"dev": true,
"license": "MIT"
},
"node_modules/emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz",
@@ -630,7 +748,6 @@
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6"
}
@@ -757,6 +874,29 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-east-asian-width": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -818,6 +958,22 @@
"node": ">=8"
}
},
"node_modules/is-docker": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz",
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
"dev": true,
"license": "MIT",
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -841,6 +997,38 @@
"node": ">=0.10.0"
}
},
"node_modules/is-in-ssh": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
"integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-inside-container": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz",
"integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-docker": "^3.0.0"
},
"bin": {
"is-inside-container": "cli.js"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
@@ -851,6 +1039,22 @@
"node": ">=0.12.0"
}
},
"node_modules/is-wsl": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.1.tgz",
"integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-inside-container": "^1.0.0"
},
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/jest-worker": {
"version": "27.5.1",
"resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-27.5.1.tgz",
@@ -1097,6 +1301,27 @@
"node": ">=0.10.0"
}
},
"node_modules/open": {
"version": "11.0.0",
"resolved": "https://registry.npmmirror.com/open/-/open-11.0.0.tgz",
"integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
"dev": true,
"license": "MIT",
"dependencies": {
"default-browser": "^5.4.0",
"define-lazy-prop": "^3.0.0",
"is-in-ssh": "^1.0.0",
"is-inside-container": "^1.0.0",
"powershell-utils": "^0.1.0",
"wsl-utils": "^0.3.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@@ -1118,6 +1343,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/powershell-utils": {
"version": "0.1.0",
"resolved": "https://registry.npmmirror.com/powershell-utils/-/powershell-utils-0.1.0.tgz",
"integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
@@ -1152,6 +1390,73 @@
"node": ">=0.10.0"
}
},
"node_modules/rollup-plugin-visualizer": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-7.0.1.tgz",
"integrity": "sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==",
"dev": true,
"license": "MIT",
"dependencies": {
"open": "^11.0.0",
"picomatch": "^4.0.2",
"source-map": "^0.7.4",
"yargs": "^18.0.0"
},
"bin": {
"rollup-plugin-visualizer": "dist/bin/cli.js"
},
"engines": {
"node": ">=22"
},
"peerDependencies": {
"rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc",
"rollup": "2.x || 3.x || 4.x"
},
"peerDependenciesMeta": {
"rolldown": {
"optional": true
},
"rollup": {
"optional": true
}
}
},
"node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz",
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/rollup-plugin-visualizer/node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz",
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">= 12"
}
},
"node_modules/run-applescript": {
"version": "7.1.0",
"resolved": "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz",
"integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/sass": {
"version": "1.63.2",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.63.2.tgz",
@@ -1279,6 +1584,40 @@
"source-map": "^0.6.0"
}
},
"node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz",
@@ -1542,6 +1881,79 @@
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/wrap-ansi": {
"version": "9.0.2",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wsl-utils": {
"version": "0.3.1",
"resolved": "https://registry.npmmirror.com/wsl-utils/-/wsl-utils-0.3.1.tgz",
"integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-wsl": "^3.1.0",
"powershell-utils": "^0.1.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "18.0.0",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-18.0.0.tgz",
"integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^9.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"string-width": "^7.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^22.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
}
},
"node_modules/yargs-parser": {
"version": "22.0.0",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-22.0.0.tgz",
"integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
}
}
}
}
+2
View File
@@ -1,10 +1,12 @@
{
"dependencies": {
"@fortawesome/fontawesome-free": "^7.3.0",
"clipboard": "^2.0.11",
"dayjs": "^1.11.21",
"uview-plus": "^3.8.55"
},
"devDependencies": {
"rollup-plugin-visualizer": "^7.0.1",
"sass": "^1.63.2",
"sass-loader": "^10.4.1"
}
+30 -8
View File
@@ -1,26 +1,48 @@
{
"easycom": {
"autoscan": true,
// 注意一定要放在custom里,否则无效,https://ask.dcloud.net.cn/question/131175
"custom": {
"^FaIcon$": "@/components/FaIcon.vue",
"^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue"
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue"
}
},
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
"pages": [
{
"path": "pages/index/index",
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "uni-app"
"navigationStyle": "custom",
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/dashboard/dashboard",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "仪表盘"
}
},
{
"path": "pages/features/features",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "功能"
}
},
{
"path": "pages/profile/profile",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "我的"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
"navigationBarTitleText": "云泽",
"navigationBarBackgroundColor": "#F8FAFC",
"backgroundColor": "#F8FAFC"
},
"uniIdRouter": {}
}
+342
View File
@@ -0,0 +1,342 @@
<template>
<view class="page">
<view class="header">
<view class="header-row">
<view class="user-info">
<text class="greeting">{{ greeting }}</text>
<text class="username">{{ user?.nickname || '云泽用户' }}</text>
</view>
<view class="avatar">
<text class="avatar-text">{{ avatarText }}</text>
</view>
</view>
<view class="search-bar">
<FaIcon name="magnifying-glass" color="#9acafc" :size="18" />
<text class="search-text">搜索功能数据...</text>
</view>
</view>
<view class="main-scroll">
<view class="stats-grid">
<view class="stat-card" v-for="item in stats" :key="item.label">
<text class="stat-value">{{ item.value }}</text>
<text class="stat-label">{{ item.label }}</text>
<text class="stat-trend" :class="item.up ? 'up' : 'down'">
{{ item.up ? '↑' : '↓' }} {{ item.trend }}
</text>
</view>
</view>
<view class="section">
<text class="section-title">快捷入口</text>
<view class="quick-grid">
<view
class="quick-item"
v-for="item in quickActions"
:key="item.name"
@tap="onQuickTap(item)"
>
<view class="quick-icon">
<FaIcon :name="item.icon" color="#3c9cff" :size="22" />
</view>
<text class="quick-name">{{ item.name }}</text>
</view>
</view>
</view>
<view class="section">
<view class="section-header">
<text class="section-title">最近动态</text>
<text class="section-more">查看全部</text>
</view>
<view class="activity-card">
<view class="activity-item" v-for="(item, index) in activities" :key="index">
<view class="activity-dot" />
<view class="activity-body">
<text class="activity-title">{{ item.title }}</text>
<text class="activity-time">{{ item.time }}</text>
</view>
</view>
</view>
</view>
<view class="bottom-space" />
</view>
<AppTabbar current="dashboard" />
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getUser, isLoggedIn } from '@/utils/auth.js'
import AppTabbar from '@/components/AppTabbar.vue'
const user = ref(null)
const greeting = computed(() => {
const hour = new Date().getHours()
if (hour < 12) return '早上好'
if (hour < 18) return '下午好'
return '晚上好'
})
const avatarText = computed(() => {
const name = user.value?.nickname || '云'
return name.charAt(0).toUpperCase()
})
const stats = ref([
{ label: '今日访问', value: '2,847', trend: '12%', up: true },
{ label: '活跃用户', value: '1,256', trend: '8%', up: true },
{ label: '转化率', value: '68.5%', trend: '3%', up: false },
{ label: '总收入', value: '¥8.2k', trend: '15%', up: true }
])
const quickActions = ref([
{ name: '数据分析', icon: 'chart-line' },
{ name: '消息中心', icon: 'bell' },
{ name: '订单管理', icon: 'clipboard-list' },
{ name: '设置', icon: 'gear' }
])
const activities = ref([
{ title: '新用户注册 +128', time: '5 分钟前' },
{ title: '系统更新完成 v2.1', time: '1 小时前' },
{ title: '订单 #8821 已发货', time: '2 小时前' },
{ title: '数据备份成功', time: '昨天 23:00' }
])
onMounted(() => {
if (!isLoggedIn()) {
uni.reLaunch({ url: '/pages/login/login' })
return
}
user.value = getUser()
})
function onQuickTap(item) {
uni.showToast({ title: item.name, icon: 'none' })
}
</script>
<style lang="scss" scoped>
.page {
height: 100vh;
background: #ffffff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.header {
flex-shrink: 0;
padding: calc(var(--status-bar-height, 44px) + 24rpx) 40rpx 32rpx;
background: #ffffff;
}
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
}
.greeting {
display: block;
font-size: 26rpx;
color: $u-primary-disabled;
}
.username {
display: block;
font-size: 40rpx;
font-weight: 600;
color: $u-primary;
margin-top: 6rpx;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-text {
font-size: 32rpx;
font-weight: 600;
color: $u-primary;
}
.search-bar {
display: flex;
align-items: center;
gap: 16rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 22rpx 28rpx;
}
.search-text {
font-size: 26rpx;
color: $u-primary-disabled;
}
.main-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 8rpx 40rpx 0;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
margin-bottom: 48rpx;
}
.stat-card {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 28rpx;
}
.stat-value {
display: block;
font-size: 36rpx;
font-weight: 600;
color: $u-primary;
}
.stat-label {
display: block;
font-size: 24rpx;
color: $u-primary-disabled;
margin-top: 6rpx;
}
.stat-trend {
display: block;
margin-top: 12rpx;
font-size: 22rpx;
&.up {
color: $u-primary;
}
&.down {
color: $u-primary-disabled;
}
}
.section {
margin-bottom: 48rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: $u-primary;
}
.section-more {
font-size: 24rpx;
color: $u-primary-disabled;
}
.quick-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx;
}
.quick-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
}
.quick-icon {
width: 96rpx;
height: 96rpx;
border-radius: 24rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
&:active {
border-color: $u-primary-disabled;
}
}
.quick-name {
font-size: 22rpx;
color: $u-primary-disabled;
}
.activity-card {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
overflow: hidden;
}
.activity-item {
display: flex;
align-items: center;
padding: 28rpx;
gap: 20rpx;
& + & {
border-top: 2rpx solid #f0f6fb;
}
}
.activity-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: $u-primary;
flex-shrink: 0;
}
.activity-body {
flex: 1;
}
.activity-title {
display: block;
font-size: 28rpx;
color: $u-primary;
}
.activity-time {
display: block;
font-size: 22rpx;
color: $u-primary-disabled;
margin-top: 6rpx;
}
.bottom-space {
height: 40rpx;
}
</style>
+451
View File
@@ -0,0 +1,451 @@
<template>
<view class="page">
<view class="page-header">
<text class="page-title">功能中心</text>
<text class="page-desc">探索更多精彩功能</text>
</view>
<view class="main-scroll">
<view class="feature-banner" @tap="onBannerTap">
<view class="banner-content">
<text class="banner-title">新功能上线</text>
<text class="banner-desc">AI 智能助手效率翻倍</text>
</view>
<view class="banner-btn">立即体验</view>
</view>
<view class="category" v-for="cat in categories" :key="cat.title">
<text class="category-title">{{ cat.title }}</text>
<view class="feature-grid">
<view
class="feature-card"
v-for="item in cat.items"
:key="item.name"
@tap="onFeatureTap(item)"
>
<view class="feature-icon">
<FaIcon :name="item.icon" color="#3c9cff" :size="22" />
</view>
<text class="feature-name">{{ item.name }}</text>
<text class="feature-desc">{{ item.desc }}</text>
</view>
</view>
</view>
<view class="bottom-space" />
</view>
<AppTabbar current="features" />
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { isLoggedIn } from '@/utils/auth.js'
import AppTabbar from '@/components/AppTabbar.vue'
const categories = ref([
{
title: '常用工具',
items: [
{ name: '扫一扫', desc: '快速识别', icon: 'qrcode' },
{ name: '收付款', desc: '便捷支付', icon: 'coins' },
{ name: '卡包', desc: '优惠券', icon: 'ticket' },
{ name: '出行', desc: '交通服务', icon: 'car' }
]
},
{
title: '生活服务',
items: [
{ name: '外卖', desc: '美食到家', icon: 'bag-shopping' },
{ name: '电影', desc: '在线购票', icon: 'film' },
{ name: '酒店', desc: '预订住宿', icon: 'hotel' },
{ name: '更多', desc: '发现更多', icon: 'ellipsis' }
]
},
{
title: '办公效率',
items: [
{ name: '文档', desc: '在线协作', icon: 'file-lines' },
{ name: '日历', desc: '日程管理', icon: 'calendar-days' },
{ name: '云盘', desc: '文件存储', icon: 'folder' },
{ name: '会议', desc: '视频会议', icon: 'video' }
]
}
])
onMounted(() => {
if (!isLoggedIn()) {
uni.reLaunch({ url: '/pages/login/login' })
}
})
function onBannerTap() {
uni.showToast({ title: 'AI 智能助手', icon: 'none' })
}
function onFeatureTap(item) {
uni.showToast({ title: item.name, icon: 'none' })
}
</script>
<style lang="scss" scoped>
.page {
height: 100vh;
background: #ffffff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.page-header {
flex-shrink: 0;
padding: calc(var(--status-bar-height, 44px) + 24rpx) 40rpx 24rpx;
}
.page-title {
display: block;
font-size: 40rpx;
font-weight: 600;
color: $u-primary;
}
.page-desc {
display: block;
font-size: 26rpx;
color: $u-primary-disabled;
margin-top: 6rpx;
}
.main-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 0 40rpx;
}
.feature-banner {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 32rpx 28rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 48rpx;
&:active {
border-color: $u-primary-disabled;
}
}
.banner-title {
display: block;
font-size: 30rpx;
font-weight: 600;
color: $u-primary;
}
.banner-desc {
display: block;
font-size: 24rpx;
color: $u-primary-disabled;
margin-top: 8rpx;
}
.banner-btn {
padding: 14rpx 28rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
font-size: 24rpx;
color: $u-primary;
font-weight: 500;
}
.category {
margin-bottom: 48rpx;
}
.category-title {
display: block;
font-size: 30rpx;
font-weight: 600;
color: $u-primary;
margin-bottom: 24rpx;
}
.feature-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.feature-card {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 28rpx;
&:active {
border-color: $u-primary-disabled;
}
}
.feature-icon {
width: 80rpx;
height: 80rpx;
border-radius: 20rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.feature-name {
display: block;
font-size: 28rpx;
font-weight: 600;
color: $u-primary;
}
.feature-desc {
display: block;
font-size: 22rpx;
color: $u-primary-disabled;
margin-top: 4rpx;
}
.bottom-space {
height: 40rpx;
}
</style>
+446
View File
@@ -0,0 +1,446 @@
<template>
<view class="login-page">
<view class="page-inner">
<!-- 品牌区 -->
<view class="header">
<text class="title">欢迎回来</text>
<text class="subtitle">登录云泽开启你的旅程</text>
</view>
<!-- 登录方式切换 -->
<view class="tabs">
<view
class="tab"
:class="{ active: loginType === 'phone' }"
@tap="loginType = 'phone'"
>手机登录</view>
<view
class="tab"
:class="{ active: loginType === 'account' }"
@tap="loginType = 'account'"
>账号登录</view>
</view>
<!-- 表单 -->
<view class="form">
<template v-if="loginType === 'phone'">
<view class="field">
<u-input
v-model="phone"
placeholder="请输入手机号"
type="number"
maxlength="11"
border="none"
color="#3c9cff"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
>
<template #prefix>
<text class="prefix">+86</text>
</template>
</u-input>
</view>
<view class="field field-row">
<u-input
v-model="code"
placeholder="请输入验证码"
type="number"
maxlength="6"
border="none"
color="#3c9cff"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
<text
class="code-link"
:class="{ disabled: countdown > 0 }"
@tap="sendCode"
>{{ countdown > 0 ? `${countdown}s 后重发` : '获取验证码' }}</text>
</view>
</template>
<template v-else>
<view class="field">
<u-input
v-model="username"
placeholder="请输入账号"
border="none"
color="#3c9cff"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
</view>
<view class="field">
<u-input
v-model="password"
placeholder="请输入密码"
type="password"
border="none"
color="#3c9cff"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
</view>
</template>
<view class="btn-primary" @tap="handleLogin"> </view>
<view class="btn-ghost" @tap="handleTestLogin">测试登录</view>
</view>
<!-- 协议 -->
<view class="agreement" @tap="agreed = !agreed">
<view class="agree-dot" :class="{ on: agreed }">
<text v-if="agreed" class="agree-check"></text>
</view>
<text class="agree-text">
登录即表示同意
<text class="agree-link">用户协议</text>
<text class="agree-link">隐私政策</text>
</text>
</view>
<!-- 第三方 -->
<view class="oauth">
<text class="oauth-label">其他方式</text>
<view class="oauth-icons">
<view class="oauth-btn" @tap="socialLogin('wechat')">
<FaIcon name="weixin" type="brands" color="#3c9cff" :size="22" />
</view>
<view class="oauth-btn" @tap="socialLogin('qq')">
<FaIcon name="qq" type="brands" color="#3c9cff" :size="22" />
</view>
<view class="oauth-btn" @tap="socialLogin('alipay')">
<FaIcon name="alipay" type="brands" color="#3c9cff" :size="22" />
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { loginSuccess, isLoggedIn } from '@/utils/auth.js'
const loginType = ref('phone')
const phone = ref('')
const code = ref('')
const username = ref('')
const password = ref('')
const agreed = ref(true)
const countdown = ref(0)
let timer = null
const inputStyle = {
backgroundColor: 'transparent',
padding: '0 8rpx',
height: '96rpx',
fontSize: '28rpx'
}
const placeholderStyle = 'color: #9acafc; font-size: 28rpx'
onMounted(() => {
if (isLoggedIn()) {
uni.reLaunch({ url: '/pages/dashboard/dashboard' })
}
})
function sendCode() {
if (countdown.value > 0) return
if (!/^1\d{10}$/.test(phone.value)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
countdown.value = 60
timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) clearInterval(timer)
}, 1000)
uni.showToast({ title: '验证码已发送', icon: 'success' })
}
function navigateAfterLogin() {
uni.showToast({ title: '登录成功', icon: 'success' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/dashboard/dashboard' })
}, 500)
}
function handleTestLogin() {
loginSuccess({ nickname: '测试用户', phone: '13800000000' })
navigateAfterLogin()
}
function handleLogin() {
if (!agreed.value) {
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
return
}
if (loginType.value === 'phone') {
if (!/^1\d{10}$/.test(phone.value)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
if (!code.value || code.value.length < 4) {
uni.showToast({ title: '请输入验证码', icon: 'none' })
return
}
loginSuccess({ phone: phone.value, nickname: '用户' + phone.value.slice(-4) })
} else {
if (!username.value.trim()) {
uni.showToast({ title: '请输入账号', icon: 'none' })
return
}
if (!password.value.trim()) {
uni.showToast({ title: '请输入密码', icon: 'none' })
return
}
loginSuccess({ nickname: username.value })
}
navigateAfterLogin()
}
function socialLogin(type) {
if (!agreed.value) {
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
return
}
const names = { wechat: '微信', qq: 'QQ', alipay: '支付宝' }
loginSuccess({ nickname: names[type] + '用户' })
uni.showToast({ title: `${names[type]}登录成功`, icon: 'success' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/dashboard/dashboard' })
}, 500)
}
</script>
<style lang="scss" scoped>
.login-page {
min-height: 100vh;
background: #ffffff;
}
.page-inner {
padding: 0 56rpx;
padding-top: calc(var(--status-bar-height, 44px) + 80rpx);
}
/* ---- 品牌 ---- */
.header {
margin-bottom: 72rpx;
}
.logo {
width: 88rpx;
height: 88rpx;
border-radius: 22rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 36rpx;
}
.logo-char {
font-size: 44rpx;
font-weight: 600;
color: $u-primary;
}
.title {
display: block;
font-size: 52rpx;
font-weight: 600;
color: $u-primary;
letter-spacing: 2rpx;
}
.subtitle {
display: block;
font-size: 26rpx;
color: $u-primary-disabled;
margin-top: 12rpx;
}
/* ---- Tab ---- */
.tabs {
display: flex;
gap: 48rpx;
margin-bottom: 48rpx;
}
.tab {
font-size: 30rpx;
color: $u-primary-disabled;
padding-bottom: 12rpx;
position: relative;
&.active {
color: $u-primary;
font-weight: 600;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 4rpx;
background: $u-primary;
border-radius: 2rpx;
}
}
}
/* ---- 表单 ---- */
.form {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.field {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
padding: 0 28rpx;
overflow: hidden;
}
.field-row {
display: flex;
align-items: center;
}
.prefix {
font-size: 28rpx;
color: $u-primary;
padding-right: 20rpx;
margin-right: 20rpx;
border-right: 1rpx solid $u-primary-disabled;
}
.code-link {
flex-shrink: 0;
font-size: 26rpx;
color: $u-primary;
padding-left: 16rpx;
white-space: nowrap;
&.disabled {
color: $u-primary-disabled;
}
}
.btn-primary {
height: 96rpx;
background: $u-primary;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 600;
color: #fff;
letter-spacing: 6rpx;
margin-top: 16rpx;
&:active {
background: $u-primary-dark;
}
}
.btn-ghost {
height: 96rpx;
background: #ffffff;
border: 2rpx solid $u-primary;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: $u-primary;
&:active {
background: #f8fbff;
}
}
/* ---- 协议 ---- */
.agreement {
display: flex;
align-items: flex-start;
gap: 12rpx;
margin-top: 36rpx;
}
.agree-dot {
width: 30rpx;
height: 30rpx;
border-radius: 50%;
border: 2rpx solid $u-primary-disabled;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 2rpx;
&.on {
background: $u-primary;
border-color: $u-primary;
}
}
.agree-check {
font-size: 18rpx;
color: #fff;
}
.agree-text {
font-size: 22rpx;
color: $u-primary-disabled;
line-height: 1.7;
}
.agree-link {
color: $u-primary;
}
/* ---- 第三方 ---- */
.oauth {
margin-top: 80rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 28rpx;
padding-bottom: 60rpx;
}
.oauth-label {
font-size: 24rpx;
color: $u-primary-disabled;
}
.oauth-icons {
display: flex;
gap: 40rpx;
}
.oauth-btn {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
&:active {
border-color: $u-primary-disabled;
}
}
</style>
+649
View File
@@ -0,0 +1,649 @@
<template>
<view class="page">
<view class="profile-header">
<view class="profile-info">
<view class="profile-avatar">
<text class="avatar-text">{{ avatarText }}</text>
</view>
<view class="profile-detail">
<text class="profile-name">{{ user?.nickname || '云泽用户' }}</text>
<text class="profile-id">ID: {{ userId }}</text>
</view>
<view class="profile-edit" @tap="onEdit">
<FaIcon name="pen-to-square" color="#3c9cff" :size="18" />
</view>
</view>
<view class="profile-stats">
<view class="profile-stat" v-for="item in profileStats" :key="item.label">
<text class="stat-num">{{ item.value }}</text>
<text class="stat-label">{{ item.label }}</text>
</view>
</view>
</view>
<view class="main-scroll">
<view class="menu-group" v-for="(group, gIndex) in menuGroups" :key="gIndex">
<view
class="menu-item"
v-for="item in group"
:key="item.title"
@tap="onMenuTap(item)"
>
<view class="menu-left">
<view class="menu-icon">
<FaIcon :name="item.icon" color="#3c9cff" :size="18" />
</view>
<text class="menu-title">{{ item.title }}</text>
</view>
<view class="menu-right">
<text v-if="item.badge" class="menu-badge">{{ item.badge }}</text>
<FaIcon name="chevron-right" color="#9acafc" :size="14" />
</view>
</view>
</view>
<view class="logout-btn" @tap="handleLogout">
<text class="logout-text">退出登录</text>
</view>
<view class="bottom-space" />
</view>
<AppTabbar current="profile" />
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getUser, logout, isLoggedIn } from '@/utils/auth.js'
import AppTabbar from '@/components/AppTabbar.vue'
const user = ref(null)
const avatarText = computed(() => {
const name = user.value?.nickname || '云'
return name.charAt(0).toUpperCase()
})
const userId = computed(() => {
return user.value?.phone
? user.value.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
: '10086'
})
const profileStats = ref([
{ label: '关注', value: '128' },
{ label: '粉丝', value: '256' },
{ label: '获赞', value: '1.2k' }
])
const menuGroups = ref([
[
{ title: '我的订单', icon: 'clipboard-list' },
{ title: '我的收藏', icon: 'star' },
{ title: '浏览历史', icon: 'clock-rotate-left' }
],
[
{ title: '消息通知', icon: 'bell', badge: '3' },
{ title: '账号安全', icon: 'lock' },
{ title: '隐私设置', icon: 'eye' }
],
[
{ title: '帮助中心', icon: 'circle-question' },
{ title: '关于我们', icon: 'circle-info' },
{ title: '设置', icon: 'gear' }
]
])
onMounted(() => {
if (!isLoggedIn()) {
uni.reLaunch({ url: '/pages/login/login' })
return
}
user.value = getUser()
})
function onEdit() {
uni.showToast({ title: '编辑资料', icon: 'none' })
}
function onMenuTap(item) {
uni.showToast({ title: item.title, icon: 'none' })
}
function handleLogout() {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success(res) {
if (res.confirm) {
logout()
uni.reLaunch({ url: '/pages/login/login' })
}
}
})
}
</script>
<style lang="scss" scoped>
.page {
height: 100vh;
background: #ffffff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.profile-header {
flex-shrink: 0;
padding: calc(var(--status-bar-height, 44px) + 24rpx) 40rpx 32rpx;
background: #ffffff;
}
.profile-info {
display: flex;
align-items: center;
gap: 24rpx;
}
.profile-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.avatar-text {
font-size: 40rpx;
font-weight: 600;
color: $u-primary;
}
.profile-detail {
flex: 1;
}
.profile-name {
display: block;
font-size: 36rpx;
font-weight: 600;
color: $u-primary;
}
.profile-id {
display: block;
font-size: 24rpx;
color: $u-primary-disabled;
margin-top: 6rpx;
}
.profile-edit {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
&:active {
border-color: $u-primary-disabled;
}
}
.profile-stats {
display: flex;
justify-content: space-around;
margin-top: 32rpx;
padding: 28rpx 0;
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
}
.profile-stat {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-num {
font-size: 32rpx;
font-weight: 600;
color: $u-primary;
}
.stat-label {
font-size: 22rpx;
color: $u-primary-disabled;
margin-top: 4rpx;
}
.main-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 8rpx 40rpx 0;
}
.menu-group {
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
margin-bottom: 24rpx;
overflow: hidden;
}
.menu-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 28rpx;
border-bottom: 2rpx solid #f0f6fb;
&:last-child {
border-bottom: none;
}
&:active {
background: #f8fafc;
}
}
.menu-left {
display: flex;
align-items: center;
gap: 20rpx;
}
.menu-icon {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
display: flex;
align-items: center;
justify-content: center;
}
.menu-title {
font-size: 28rpx;
color: $u-primary;
}
.menu-right {
display: flex;
align-items: center;
gap: 12rpx;
}
.menu-badge {
font-size: 20rpx;
color: #ffffff;
background: $u-primary;
padding: 2rpx 12rpx;
border-radius: 999rpx;
min-width: 32rpx;
text-align: center;
}
.logout-btn {
margin-top: 8rpx;
height: 96rpx;
background: #ffffff;
border: 2rpx solid #e8f0f8;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
border-color: $u-primary-disabled;
background: #f8fafc;
}
}
.logout-text {
font-size: 30rpx;
color: $u-primary-disabled;
font-weight: 500;
}
.bottom-space {
height: 40rpx;
}
</style>
+24
View File
@@ -0,0 +1,24 @@
/**
* 从 Font Awesome SCSS 变量生成图标 Unicode 映射表。
* 升级 @fortawesome/fontawesome-free 后执行: node scripts/gen-fa-icons.js
*/
const fs = require('fs')
const path = require('path')
const scssPath = path.join(
__dirname,
'../node_modules/@fortawesome/fontawesome-free/scss/_variables.scss'
)
const outputPath = path.join(__dirname, '../utils/fa-icons.js')
const source = fs.readFileSync(scssPath, 'utf8')
const icons = {}
for (const match of source.matchAll(/\$var-([a-z0-9-]+):\s*\\([a-f0-9]+);/gi)) {
const [, name, hex] = match
icons[name] = String.fromCharCode(parseInt(hex, 16))
}
const content = `export const FA_ICONS = ${JSON.stringify(icons, null, 2)}\n`
fs.writeFileSync(outputPath, content, 'utf8')
console.log(`Generated ${Object.keys(icons).length} icons -> utils/fa-icons.js`)
+15
View File
@@ -0,0 +1,15 @@
@font-face {
font-family: 'Font Awesome 7 Free';
font-style: normal;
font-weight: 900;
font-display: block;
src: url('/static/webfonts/fa-solid-900.woff2') format('woff2');
}
@font-face {
font-family: 'Font Awesome 7 Brands';
font-style: normal;
font-weight: 400;
font-display: block;
src: url('/static/webfonts/fa-brands-400.woff2') format('woff2');
}
+19
View File
@@ -0,0 +1,19 @@
// 简约潮流主题变量
$color-primary: #6366f1;
$color-primary-light: #818cf8;
$color-primary-dark: #4f46e5;
$color-accent: #ec4899;
$color-bg: #f8fafc;
$color-card: #ffffff;
$color-text: #1e293b;
$color-text-secondary: #64748b;
$color-text-muted: #94a3b8;
$color-border: #e2e8f0;
$gradient-primary: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
$gradient-soft: linear-gradient(160deg, #eef2ff 0%, #faf5ff 50%, #fdf2f8 100%);
$shadow-sm: 0 2rpx 12rpx rgba(99, 102, 241, 0.08);
$shadow-md: 0 8rpx 32rpx rgba(99, 102, 241, 0.12);
$radius-sm: 16rpx;
$radius-md: 24rpx;
$radius-lg: 32rpx;
$radius-full: 999rpx;
+2 -1
View File
@@ -1 +1,2 @@
@import 'uview-plus/theme.scss';
// 已迁移至项目根目录 uni.scss,此文件保留兼容引用
@import '../uni.scss';
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -3,7 +3,7 @@
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
"@/*": ["./*"]
},
"lib": ["esnext", "dom"],
"types": [
+3
View File
@@ -74,3 +74,6 @@ $uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;
/* uview-plus 主题变量与 mixin,需放在根目录 uni.scss 以便全局注入 */
@import 'uview-plus/theme.scss';
+37
View File
@@ -0,0 +1,37 @@
const TOKEN_KEY = 'app_token'
const USER_KEY = 'app_user'
export function setToken(token) {
uni.setStorageSync(TOKEN_KEY, token)
}
export function getToken() {
return uni.getStorageSync(TOKEN_KEY) || ''
}
export function setUser(user) {
uni.setStorageSync(USER_KEY, user)
}
export function getUser() {
return uni.getStorageSync(USER_KEY) || null
}
export function isLoggedIn() {
return !!getToken()
}
export function logout() {
uni.removeStorageSync(TOKEN_KEY)
uni.removeStorageSync(USER_KEY)
}
export function loginSuccess(user = {}) {
setToken('demo_token_' + Date.now())
setUser({
nickname: user.nickname || '云泽用户',
avatar: user.avatar || '',
phone: user.phone || '',
...user
})
}
File diff suppressed because it is too large Load Diff
+14 -26
View File
@@ -1,28 +1,16 @@
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import { visualizer } from "rollup-plugin-visualizer";
import { uviewPlus } from "uview-plus/vite-plugin";
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
uni(),
uviewPlus({ appFontLocal: true }),
visualizer()
],
css: {
preprocessorOptions: {
scss: {
// 取消sass废弃API的报警
silenceDeprecations: ['legacy-js-api', 'color-functions', 'import'],
},
},
},
server: {
port: 5100,
fs: {
// Allow serving files from one level up to the project root
allow: ['..']
export default defineConfig({
plugins: [
uni()
],
css: {
preprocessorOptions: {
scss: {
// 关闭废弃 API 警告
silenceDeprecations: ['legacy-js-api', 'color-functions', 'import'],
}
}
}
},
});
})