hookehuyr

feat: 添加axios依赖并初始化数据管理

refactor: 重构页面组件以使用本地数据
style: 调整组件代码结构以提高可读性
docs: 更新组件注释和文档
......@@ -11,6 +11,7 @@
},
"dependencies": {
"@vitejs/plugin-vue-jsx": "4.1.2",
"axios": "^1.8.4",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
......
......@@ -11,6 +11,9 @@ importers:
'@vitejs/plugin-vue-jsx':
specifier: 4.1.2
version: 4.1.2(vite@5.4.18)(vue@3.5.13)
axios:
specifier: ^1.8.4
version: 1.8.4
pinia:
specifier: ^2.1.7
version: 2.3.1(vue@3.5.13)
......@@ -617,6 +620,9 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
autoprefixer@10.4.21:
resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
engines: {node: ^10 || ^12 || >=14}
......@@ -624,6 +630,9 @@ packages:
peerDependencies:
postcss: ^8.1.0
axios@1.8.4:
resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
......@@ -649,6 +658,10 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
......@@ -675,6 +688,10 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
......@@ -709,12 +726,20 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
......@@ -731,6 +756,22 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
es-set-tostringtag@2.1.0:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
......@@ -838,10 +879,23 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
follow-redirects@1.15.9:
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
form-data@4.0.2:
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
engines: {node: '>= 6'}
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
......@@ -857,6 +911,14 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
......@@ -881,10 +943,22 @@ packages:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
has-tostringtag@1.0.2:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
......@@ -994,6 +1068,10 @@ packages:
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
......@@ -1002,6 +1080,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
......@@ -1152,6 +1238,9 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
......@@ -1921,6 +2010,8 @@ snapshots:
argparse@2.0.1: {}
asynckit@0.4.0: {}
autoprefixer@10.4.21(postcss@8.5.3):
dependencies:
browserslist: 4.24.4
......@@ -1931,6 +2022,14 @@ snapshots:
postcss: 8.5.3
postcss-value-parser: 4.2.0
axios@1.8.4:
dependencies:
follow-redirects: 1.15.9
form-data: 4.0.2
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
balanced-match@1.0.2: {}
binary-extensions@2.3.0: {}
......@@ -1957,6 +2056,11 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.4)
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
callsites@3.1.0: {}
camelcase-css@2.0.1: {}
......@@ -1986,6 +2090,10 @@ snapshots:
color-name@1.1.4: {}
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
commander@4.1.1: {}
concat-map@0.0.1: {}
......@@ -2008,10 +2116,18 @@ snapshots:
deep-is@0.1.4: {}
delayed-stream@1.0.0: {}
didyoumean@1.2.2: {}
dlv@1.1.3: {}
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
es-errors: 1.3.0
gopd: 1.2.0
eastasianwidth@0.2.0: {}
electron-to-chromium@1.5.137: {}
......@@ -2022,6 +2138,21 @@ snapshots:
entities@4.5.0: {}
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
es-set-tostringtag@2.1.0:
dependencies:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
hasown: 2.0.2
esbuild@0.21.5:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
......@@ -2186,11 +2317,20 @@ snapshots:
flatted@3.3.3: {}
follow-redirects@1.15.9: {}
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
signal-exit: 4.1.0
form-data@4.0.2:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
mime-types: 2.1.35
fraction.js@4.3.7: {}
fsevents@2.3.3:
......@@ -2200,6 +2340,24 @@ snapshots:
gensync@1.0.0-beta.2: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
function-bind: 1.1.2
get-proto: 1.0.1
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
math-intrinsics: 1.1.0
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
......@@ -2225,8 +2383,16 @@ snapshots:
globals@14.0.0: {}
gopd@1.2.0: {}
has-flag@4.0.0: {}
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
dependencies:
has-symbols: 1.1.0
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
......@@ -2315,6 +2481,8 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
math-intrinsics@1.1.0: {}
merge2@1.4.1: {}
micromatch@4.0.8:
......@@ -2322,6 +2490,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
......@@ -2447,6 +2621,8 @@ snapshots:
prelude-ls@1.2.1: {}
proxy-from-env@1.1.0: {}
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
......
......@@ -13,19 +13,22 @@
* @Description: 文件描述
*/
<template>
<div class="flex flex-col min-h-screen">
<Header />
<main class="flex-grow">
<router-view></router-view>
</main>
<Footer />
</div>
<AppProvider>
<div class="flex flex-col min-h-screen">
<Header />
<main class="flex-grow">
<router-view></router-view>
</main>
<Footer />
</div>
</AppProvider>
</template>
<script setup>
import { onMounted } from 'vue'
import Header from './components/layout/Header.vue'
import Footer from './components/layout/Footer.vue'
import AppProvider from './providers/AppProvider.vue'
// 更新文档标题
onMounted(() => {
......
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="64" fill="#e0e0e0"/>
<circle cx="64" cy="56" r="24" fill="#bdbdbd"/>
<path d="M64 84c-16 0-48 8-48 24v20h96v-20c0-16-32-24-48-24z" fill="#bdbdbd"/>
</svg>
......@@ -42,7 +42,7 @@
<div class="relative">
<button @click="toggleProfileDropdown" class="flex items-center space-x-2">
<div class="h-8 w-8 rounded-full overflow-hidden border-2 border-green-500">
<img :src="currentUser?.avatar || '/assets/images/avatars/default_avatar.png'" alt="User Avatar" class="h-full w-full object-cover" />
<img :src="currentUser?.avatar || '/src/assets/images/avatars/default_avatar.svg'" alt="User Avatar" class="h-full w-full object-cover" />
</div>
<span class="hidden lg:block text-sm font-medium text-gray-700">{{ currentUser?.name || 'User' }}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
......
......@@ -19,7 +19,7 @@
</button>
</template>
<script setup>
<script>
import { computed } from 'vue'
const buttonVariants = {
......@@ -50,53 +50,63 @@ const buttonRounded = {
const buttonBlock = 'w-full flex justify-center'
const props = defineProps({
variant: {
type: String,
default: 'primary',
validator: (value) => Object.keys(buttonVariants).includes(value)
},
size: {
type: String,
default: 'md',
validator: (value) => Object.keys(buttonSizes).includes(value)
},
rounded: {
type: String,
default: 'md',
validator: (value) => Object.keys(buttonRounded).includes(value)
},
block: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
className: {
type: String,
default: ''
},
leftIcon: {
type: [String, Object],
default: null
},
rightIcon: {
type: [String, Object],
default: null
},
type: {
type: String,
default: 'button'
}
})
defineEmits(['click'])
const baseClasses = 'flex items-center justify-center font-medium border shadow-sm transition duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed'
const variantClass = computed(() => buttonVariants[props.variant] || buttonVariants.primary)
const sizeClass = computed(() => buttonSizes[props.size] || buttonSizes.md)
const roundedClass = computed(() => buttonRounded[props.rounded] || buttonRounded.md)
export default {
props: {
variant: {
type: String,
default: 'primary',
validator: (value) => Object.keys(buttonVariants).includes(value)
},
size: {
type: String,
default: 'md',
validator: (value) => Object.keys(buttonSizes).includes(value)
},
rounded: {
type: String,
default: 'md',
validator: (value) => Object.keys(buttonRounded).includes(value)
},
block: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
className: {
type: String,
default: ''
},
leftIcon: {
type: [String, Object],
default: null
},
rightIcon: {
type: [String, Object],
default: null
},
type: {
type: String,
default: 'button'
}
},
emits: ['click'],
setup(props) {
const variantClass = computed(() => buttonVariants[props.variant] || buttonVariants.primary)
const sizeClass = computed(() => buttonSizes[props.size] || buttonSizes.md)
const roundedClass = computed(() => buttonRounded[props.rounded] || buttonRounded.md)
return {
baseClasses,
variantClass,
sizeClass,
roundedClass,
buttonBlock
}
}
}
</script>
......
......@@ -51,7 +51,7 @@
</div>
</template>
<script setup>
<script>
import { computed } from 'vue'
const inputSizes = {
......@@ -60,64 +60,72 @@ const inputSizes = {
lg: 'px-4 py-3 text-lg'
}
const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
export default {
name: 'Input',
props: {
modelValue: {
type: [String, Number],
default: ''
},
label: {
type: String,
default: ''
},
name: {
type: String,
required: true
},
type: {
type: String,
default: 'text'
},
placeholder: {
type: String,
default: ''
},
error: {
type: String,
default: ''
},
size: {
type: String,
default: 'md',
validator: (value) => Object.keys(inputSizes).includes(value)
},
required: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
className: {
type: String,
default: ''
},
helperText: {
type: String,
default: ''
},
leftIcon: {
type: [String, Object],
default: null
},
rightIcon: {
type: [String, Object],
default: null
}
},
label: {
type: String,
default: ''
},
name: {
type: String,
required: true
},
type: {
type: String,
default: 'text'
},
placeholder: {
type: String,
default: ''
},
error: {
type: String,
default: ''
},
size: {
type: String,
default: 'md',
validator: (value) => Object.keys(inputSizes).includes(value)
},
required: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
className: {
type: String,
default: ''
},
helperText: {
type: String,
default: ''
},
leftIcon: {
type: [String, Object],
default: null
},
rightIcon: {
type: [String, Object],
default: null
}
})
emits: ['update:modelValue', 'blur'],
setup(props) {
const inputId = computed(() => `input-${props.name}`)
const sizeClass = computed(() => inputSizes[props.size] || inputSizes.md)
defineEmits(['update:modelValue', 'blur'])
const inputId = computed(() => `input-${props.name}`)
const sizeClass = computed(() => inputSizes[props.size] || inputSizes.md)
return {
inputId,
sizeClass
}
}
}
</script>
......
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
// Set sizes based on the size prop
const sizeClasses = {
sm: 'max-w-md',
md: 'max-w-lg',
lg: 'max-w-2xl',
xl: 'max-w-4xl',
full: 'max-w-full mx-4'
};
const props = defineProps({
isOpen: {
......@@ -33,15 +42,6 @@ const emit = defineEmits(['close']);
const modalRef = ref(null);
const isMounted = ref(false);
// Set sizes based on the size prop
const sizeClasses = {
sm: 'max-w-md',
md: 'max-w-lg',
lg: 'max-w-2xl',
xl: 'max-w-4xl',
full: 'max-w-full mx-4'
};
const modalSize = computed(() => sizeClasses[props.size] || sizeClasses.md);
// Handle ESC key press
......
......@@ -21,6 +21,12 @@
</div>
</template>
<script>
// Define valid values for variant and size props
const validVariants = ['underline', 'pills', 'bordered'];
const validSizes = ['sm', 'md', 'lg'];
</script>
<script setup>
import { ref, computed } from 'vue';
......@@ -39,13 +45,13 @@ const props = defineProps({
},
variant: {
type: String,
default: 'underline', // 'underline', 'pills', 'bordered'
validator: (value) => ['underline', 'pills', 'bordered'].includes(value)
default: 'underline',
validator: (value) => validVariants.includes(value)
},
size: {
type: String,
default: 'md', // 'sm', 'md', 'lg'
validator: (value) => ['sm', 'md', 'lg'].includes(value)
default: 'md',
validator: (value) => validSizes.includes(value)
}
});
......
{
"activities": [
{
"id": "A0001",
"title": "Vue.js 3.0 读书会",
"description": "每周四晚上8点,我们一起学习Vue.js 3.0的新特性和最佳实践。",
"start_time": "2024-04-18 20:00:00",
"end_time": "2024-04-18 22:00:00",
"location": "线上会议室",
"max_participants": 20,
"current_participants": 5,
"organizer_id": "U0001",
"organizer_name": "张三",
"status": "upcoming",
"is_public": true,
"tags": [
"Vue.js",
"前端开发",
"读书会"
],
"requirements": "需要基本的JavaScript和Vue.js基础知识",
"materials": [
"Vue.js 3.0官方文档",
"示例代码仓库"
],
"created_at": "2024-04-10 10:00:00",
"updated_at": "2024-04-10 10:00:00"
}
]
}
{
"messages": [
{
"id": "M0001",
"sender_id": "system",
"recipient_id": "U0001",
"title": "活动提醒",
"content": "您报名的Vue.js 3.0读书会将在明天晚上8点开始,请准时参加。",
"type": "notification",
"priority": "normal",
"read_status": false,
"created_at": "2024-04-17 10:00:00",
"updated_at": "2024-04-17 10:00:00"
}
]
}
{
"registrations": [
{
"id": "R0001",
"activity_id": "A0001",
"user_id": "U0001",
"registration_time": "2024-04-15 14:30:00",
"status": "confirmed",
"custom_fields": [
{
"field_name": "experience_level",
"field_type": "select",
"field_label": "Vue.js经验水平",
"field_options": ["入门", "进阶", "专家"]
}
],
"custom_answers": {
"experience_level": "进阶"
},
"notes": "期待参加读书会!",
"created_at": "2024-04-15 14:30:00",
"updated_at": "2024-04-15 14:30:00"
}
]
}
{
"users": [
{
"id": "U0001",
"name": "张三",
"email": "zhangsan@example.com",
"avatar": "/assets/images/avatars/default.png",
"role": "member",
"created_at": "2024-01-01 00:00:00",
"updated_at": "2024-01-01 00:00:00",
"last_login": "2024-04-17 10:00:00",
"status": "active",
"preferences": {
"notification_email": true,
"notification_web": true
}
}
]
}
......@@ -379,17 +379,22 @@ const submitRegistration = async () => {
onMounted(async () => {
try {
const activityId = route.params.id
const response = await store.fetchActivity(activityId)
activity.value = response
const foundActivity = activities.value.find(a => a.id === activityId)
if (foundActivity) {
activity.value = foundActivity
// Check registration status
const registration = registrations.value.find(r => r.activity_id === activityId)
hasRegistered.value = registration !== null
registrationStatus.value = registration?.status
// Find similar activities
similarActivities.value = activities.value
.filter(a => a.id !== activityId && a.category === foundActivity.category)
.slice(0, 3)
} else {
error.value = '活动不存在'
}
loading.value = false
// Check registration status
const registration = await store.checkRegistration(activityId)
hasRegistered.value = registration !== null
registrationStatus.value = registration?.status
// Fetch similar activities
similarActivities.value = await store.fetchSimilarActivities(activityId)
} catch (err) {
error.value = err.message
loading.value = false
......
......@@ -258,12 +258,16 @@
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAppStore } from '../stores/app'
import activitiesData from '../data/activities.json'
import registrationsData from '../data/registrations.json'
import usersData from '../data/users.json'
import Button from '../components/shared/Button.vue'
const route = useRoute()
const router = useRouter()
const appStore = useAppStore()
const activities = ref(activitiesData.activities)
const registrations = ref(registrationsData.registrations)
const currentUser = ref(usersData.users[0])
const activityId = route.params.activityId
const activity = ref(null)
......@@ -280,22 +284,20 @@ const isSubmitting = ref(false)
// Fetch activity details and user registration
const fetchData = async () => {
try {
if (!appStore.currentUser) {
if (!currentUser.value) {
error.value = '请先登录'
loading.value = false
return
}
if (appStore.activities.length > 0) {
const foundActivity = appStore.getActivityById(activityId)
const foundActivity = activities.value.find(a => a.id === activityId)
if (foundActivity) {
activity.value = foundActivity
if (foundActivity) {
activity.value = foundActivity
// Find user registration for this activity
const registration = appStore.registrations.find(
reg => reg.activity_id === activityId && reg.user_id === appStore.currentUser.id
)
// Find user registration for this activity
const registration = registrations.value.find(
reg => reg.activity_id === activityId && reg.user_id === currentUser.value.id
)
if (registration) {
userRegistration.value = registration
......@@ -314,7 +316,7 @@ const fetchData = async () => {
} else {
error.value = '未找到活动信息'
}
}
loading.value = false
} catch (err) {
console.error('Failed to fetch data:', err)
......
......@@ -86,12 +86,12 @@
</template>
<script setup>
import { ref, computed, watchEffect } from 'vue'
import { useAppStore } from '../stores/app'
import { ref, watchEffect } from 'vue'
import activitiesData from '../data/activities.json'
import ActivityCard from '../components/shared/ActivityCard.vue'
const store = useAppStore()
const { activities, loading } = store
const activities = ref(activitiesData.activities)
const loading = ref(false)
const upcomingActivities = ref([])
const ongoingActivities = ref([])
......
......@@ -169,13 +169,15 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAppStore } from '../stores/app'
import activitiesData from '../data/activities.json'
import registrationsData from '../data/registrations.json'
import Button from '../components/shared/Button.vue'
import Input from '../components/shared/Input.vue'
const route = useRoute()
const router = useRouter()
const store = useAppStore()
const activities = ref(activitiesData.activities)
const registrations = ref(registrationsData.registrations)
const activityId = route.params.activityId
const activity = ref(null)
......@@ -196,10 +198,8 @@ const formErrors = ref({})
// Fetch activity details
onMounted(async () => {
try {
if (store.activities.length > 0) {
const foundActivity = store.getActivityById(activityId)
if (foundActivity) {
const foundActivity = activities.value.find(a => a.id === activityId)
if (foundActivity) {
activity.value = foundActivity
// Pre-fill form with user data if available
......@@ -214,7 +214,7 @@ onMounted(async () => {
} else {
error.value = '未找到活动信息'
}
}
loading.value = false
} catch (err) {
console.error('Failed to fetch activity details:', err)
......
......@@ -17,7 +17,7 @@
<div class="flex flex-col md:flex-row items-center">
<div
class="h-24 w-24 md:h-32 md:w-32 rounded-full overflow-hidden border-4 border-white mb-4 md:mb-0 md:mr-6">
<img :src="currentUser.avatar || '/assets/images/avatars/default_avatar.png'"
<img :src="currentUser.avatar || '/src/assets/images/avatars/default_avatar.svg'"
:alt="currentUser.name" class="h-full w-full object-cover" />
</div>
<div class="text-center md:text-left">
......
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import usersData from '../data/users.json'
import activitiesData from '../data/activities.json'
import registrationsData from '../data/registrations.json'
import messagesData from '../data/messages.json'
export const useAppStore = defineStore('app', () => {
const currentUser = ref(null)
......@@ -10,29 +14,21 @@ export const useAppStore = defineStore('app', () => {
const userMessages = ref([])
// 初始化应用数据
async function fetchInitialData() {
function fetchInitialData() {
try {
loading.value = true
// 获取用户数据
const usersResponse = await fetch('/data/users.json')
const usersData = await usersResponse.json()
currentUser.value = usersData[0]
currentUser.value = usersData.users[0]
// 获取活动数据
const activitiesResponse = await fetch('/data/activities.json')
const activitiesData = await activitiesResponse.json()
activities.value = activitiesData
activities.value = activitiesData.activities
// 获取报名数据
const registrationsResponse = await fetch('/data/registrations.json')
const registrationsData = await registrationsResponse.json()
registrations.value = registrationsData
registrations.value = registrationsData.registrations
// 获取消息数据
const messagesResponse = await fetch('/data/messages.json')
const messagesData = await messagesResponse.json()
userMessages.value = messagesData
userMessages.value = messagesData.messages
loading.value = false
} catch (err) {
......
import axios from 'axios'
// 创建axios实例
const instance = axios.create({
baseURL: '/src/data', // 基础URL,根据实际环境配置
timeout: 10000, // 请求超时时间
headers: {
'Content-Type': 'application/json',
},
})
// 请求拦截器
instance.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
// 例如:添加token
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
return config
},
(error) => {
// 对请求错误做些什么
console.error('Request error:', error)
return Promise.reject(error)
},
)
// 响应拦截器
instance.interceptors.response.use(
(response) => {
// 对响应数据做点什么
return response.data
},
(error) => {
// 对响应错误做点什么
console.error('Response error:', error)
if (error.response) {
switch (error.response.status) {
case 401:
// 未授权处理
break
case 404:
// 资源不存在处理
break
case 500:
// 服务器错误处理
break
default:
break
}
}
return Promise.reject(error)
},
)
export default instance