页面优化
This commit is contained in:
BIN
src/assets/qr-code.png
Normal file
BIN
src/assets/qr-code.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
BIN
src/assets/v1.mp4
Normal file
BIN
src/assets/v1.mp4
Normal file
Binary file not shown.
@@ -1 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#fb7185"/>
|
||||
<stop offset="100%" stop-color="#e11d48"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="50" cy="50" r="50" fill="url(#gradient)" filter="url(#shadow)"/>
|
||||
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="white" font-size="24" font-weight="bold">A</text>
|
||||
<filter id="shadow">
|
||||
<feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="rgba(0, 0, 0, 0.5)"/>
|
||||
</filter>
|
||||
<style>
|
||||
svg circle {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
svg circle:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 805 B |
@@ -1,7 +1,8 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import AOS from 'aos'
|
||||
import 'aos/dist/aos.css'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -11,86 +12,255 @@ const formState = ref({
|
||||
remember: true
|
||||
})
|
||||
|
||||
const show = () => visible.value = true
|
||||
const userStore = useUserStore() // Initialize the store outside the handleSubmit function
|
||||
|
||||
onMounted(() => {
|
||||
AOS.init({
|
||||
duration: 800,
|
||||
offset: 100,
|
||||
easing: 'ease-out-cubic',
|
||||
once: false
|
||||
})
|
||||
})
|
||||
|
||||
const show = () => {
|
||||
visible.value = true
|
||||
// 在下一个渲染周期触发AOS动画
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('[data-aos]').forEach(el => {
|
||||
el.classList.add('aos-animate')
|
||||
})
|
||||
}, 10)
|
||||
}
|
||||
|
||||
const close = () => visible.value = false
|
||||
|
||||
const handleSubmit = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await useUserStore().login(formState.value)
|
||||
await userStore.login(formState.value)
|
||||
close()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
defineExpose({
|
||||
show,
|
||||
close
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
title="欢迎登录"
|
||||
:footer="null"
|
||||
:width="400"
|
||||
:maskClosable="false"
|
||||
@cancel="close"
|
||||
:wrapClassName="'auth-modal'"
|
||||
<!-- 模态框背景遮罩 -->
|
||||
<div
|
||||
v-if="visible"
|
||||
class="fixed inset-0 z-50 overflow-y-auto"
|
||||
aria-labelledby="modal-title"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<a-form
|
||||
layout="vertical"
|
||||
:model="formState"
|
||||
@finish="handleSubmit"
|
||||
class="px-8 pb-6"
|
||||
>
|
||||
<a-form-item name="username">
|
||||
<a-input
|
||||
v-model:value="formState.username"
|
||||
placeholder="用户名"
|
||||
size="large"
|
||||
autocomplete="username"
|
||||
class="rounded-lg hover:border-primary focus:border-primary"
|
||||
>
|
||||
<template #prefix><UserOutlined class="text-gray-400" /></template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<!-- 毛玻璃背景遮罩 (80% 模糊) -->
|
||||
<div
|
||||
class="fixed inset-0 transition-opacity backdrop-blur-xl bg-gray-500/80 dark:bg-gray-900/80"
|
||||
aria-hidden="true"
|
||||
@click="close"
|
||||
></div>
|
||||
|
||||
<a-form-item name="password">
|
||||
<a-input-password
|
||||
v-model:value="formState.password"
|
||||
placeholder="密码"
|
||||
size="large"
|
||||
autocomplete="current-password"
|
||||
class="rounded-lg hover:border-primary"
|
||||
>
|
||||
<template #prefix><LockOutlined class="text-gray-400" /></template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<!-- 使模态框居中 -->
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
:loading="loading"
|
||||
block
|
||||
size="large"
|
||||
class="rounded-lg transition-all hover:shadow-md"
|
||||
>
|
||||
立即登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<!-- 模态框内容 -->
|
||||
<div
|
||||
data-aos="zoom-in"
|
||||
data-aos-duration="600"
|
||||
class="inline-block align-bottom bg-white/90 dark:bg-gray-800/90 backdrop-blur-md rounded-2xl text-left overflow-hidden shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-md sm:w-full"
|
||||
>
|
||||
<div class="absolute top-0 right-0 pt-4 pr-4">
|
||||
<button
|
||||
type="button"
|
||||
class="bg-transparent rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 focus:outline-none cursor-pointer"
|
||||
@click="close"
|
||||
>
|
||||
<span class="sr-only">关闭</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="px-8 pt-12 pb-8">
|
||||
<div class="text-center mb-8" data-aos="fade-down" data-aos-delay="100">
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white" id="modal-title">
|
||||
欢迎登录
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleSubmit" class="space-y-6">
|
||||
<!-- 用户名 -->
|
||||
<div data-aos="fade-up" data-aos-delay="200">
|
||||
<label for="username" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
用户名
|
||||
</label>
|
||||
<div class="relative rounded-lg shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="username"
|
||||
v-model="formState.username"
|
||||
type="text"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
required
|
||||
class="block w-full pl-10 pr-3 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-rose-500 focus:border-rose-500 bg-white/50 dark:bg-gray-700/50 backdrop-blur-sm text-gray-900 dark:text-white text-sm"
|
||||
placeholder="请输入用户名"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 密码 -->
|
||||
<div data-aos="fade-up" data-aos-delay="300">
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
密码
|
||||
</label>
|
||||
<div class="relative rounded-lg shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="password"
|
||||
v-model="formState.password"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
class="block w-full pl-10 pr-3 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-rose-500 focus:border-rose-500 bg-white/50 dark:bg-gray-700/50 backdrop-blur-sm text-gray-900 dark:text-white text-sm"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记住我 -->
|
||||
<div class="flex items-center justify-between" data-aos="fade-up" data-aos-delay="400">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="remember-me"
|
||||
v-model="formState.remember"
|
||||
name="remember-me"
|
||||
type="checkbox"
|
||||
class="h-4 w-4 text-rose-600 focus:ring-rose-500 border-gray-300 dark:border-gray-600 rounded cursor-pointer"
|
||||
/>
|
||||
<label for="remember-me" class="ml-2 block text-sm text-gray-700 dark:text-gray-300 cursor-pointer">
|
||||
记住我
|
||||
</label>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<a href="#" class="font-medium text-rose-600 hover:text-rose-500 dark:text-rose-400 dark:hover:text-rose-300 cursor-pointer">
|
||||
忘记密码?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<div data-aos="fade-up" data-aos-delay="500">
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 cursor-pointer transition-all duration-300 hover:shadow-lg"
|
||||
:disabled="loading"
|
||||
>
|
||||
<svg v-if="loading" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
{{ loading ? '登录中...' : '立即登录' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 分割线和其他登录方式 -->
|
||||
<div class="mt-6" data-aos="fade-up" data-aos-delay="600">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300 dark:border-gray-600"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-sm">
|
||||
<span class="px-2 bg-white/90 dark:bg-gray-800/90 text-gray-500 dark:text-gray-400">
|
||||
其他登录方式
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<a href="#" class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm bg-white/70 dark:bg-gray-700/70 backdrop-blur-sm text-sm font-medium text-gray-500 dark:text-gray-300 hover:bg-white dark:hover:bg-gray-600 cursor-pointer transition-all duration-300">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="#" class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm bg-white/70 dark:bg-gray-700/70 backdrop-blur-sm text-sm font-medium text-gray-500 dark:text-gray-300 hover:bg-white dark:hover:bg-gray-600 cursor-pointer transition-all duration-300">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="#" class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm bg-white/70 dark:bg-gray-700/70 backdrop-blur-sm text-sm font-medium text-gray-500 dark:text-gray-300 hover:bg-white dark:hover:bg-gray-600 cursor-pointer transition-all duration-300">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 注册链接 -->
|
||||
<div class="text-sm text-center mt-6" data-aos="fade-up" data-aos-delay="700">
|
||||
<span class="text-gray-600 dark:text-gray-400">还没有账号? </span>
|
||||
<a href="#" class="font-medium text-rose-600 hover:text-rose-500 dark:text-rose-400 dark:hover:text-rose-300 cursor-pointer">
|
||||
立即注册
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.ant-modal-body) {
|
||||
padding: 40px;
|
||||
/* 自定义样式 */
|
||||
button, a, [role="button"], .cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
padding: 12px 16px;
|
||||
|
||||
/* 毛玻璃效果增强 */
|
||||
.backdrop-blur-xl {
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
</style>
|
||||
|
||||
.backdrop-blur-md {
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.backdrop-blur-sm {
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
/* 确保动画平滑 */
|
||||
* {
|
||||
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,65 +1,188 @@
|
||||
<script setup>
|
||||
import { ProLayout } from '@ant-design-vue/pro-layout'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import AOS from 'aos'
|
||||
import 'aos/dist/aos.css'
|
||||
|
||||
const router = useRouter()
|
||||
const selectedKeys = computed(() => [router.currentRoute.value.name])
|
||||
const route = useRoute()
|
||||
const selectedKeys = computed(() => route.name)
|
||||
const collapsed = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
AOS.init({
|
||||
duration: 1200,
|
||||
offset: 120,
|
||||
easing: 'ease-out-cubic',
|
||||
once: false
|
||||
})
|
||||
})
|
||||
|
||||
const menuData = [
|
||||
{
|
||||
path: '/admin/dashboard',
|
||||
name: 'Dashboard',
|
||||
meta: { icon: 'PieChartOutlined', title: '数据看板' }
|
||||
icon: 'pie-chart',
|
||||
title: '数据看板'
|
||||
},
|
||||
{
|
||||
path: '/admin/projects',
|
||||
name: 'ProjectManage',
|
||||
meta: { icon: 'AppstoreOutlined', title: '项目管理' }
|
||||
}
|
||||
icon: 'layout-grid',
|
||||
title: '项目管理'
|
||||
},
|
||||
// {
|
||||
// path: '/admin/users',
|
||||
// name: 'UserManage',
|
||||
// icon: 'users',
|
||||
// title: '用户管理'
|
||||
// },
|
||||
// {
|
||||
// path: '/admin/settings',
|
||||
// name: 'Settings',
|
||||
// icon: 'settings',
|
||||
// title: '系统设置'
|
||||
// }
|
||||
]
|
||||
|
||||
const navigateTo = (path) => {
|
||||
router.push(path)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProLayout
|
||||
:menu-data="menuData"
|
||||
:selected-keys="selectedKeys"
|
||||
layout="mix"
|
||||
:content-width="1200"
|
||||
:header-height="64"
|
||||
fix-siderbar
|
||||
:primary-color="'#1890ff'"
|
||||
>
|
||||
<template #logo>
|
||||
<div class="flex items-center text-lg font-bold">
|
||||
<img src="@/assets/logo-admin.png" class="h-8 mr-2" />
|
||||
项目管理系统
|
||||
<div class="admin-layout bg-gray-50 dark:bg-gray-900 min-h-screen flex">
|
||||
<!-- 侧边栏 -->
|
||||
<aside
|
||||
:class="`${collapsed ? 'w-20' : 'w-60'} bg-white dark:bg-gray-800 shadow-lg transition-all duration-300 ease-in-out fixed h-screen z-20`"
|
||||
>
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center px-4 py-5 border-b border-gray-100 dark:border-gray-700">
|
||||
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-rose-400 to-rose-600 flex items-center justify-center shadow-md">
|
||||
<span class="text-white text-sm font-bold">A</span>
|
||||
</div>
|
||||
<span v-if="!collapsed" class="text-lg font-bold ml-3 text-gray-900 dark:text-white transition-opacity duration-300" data-aos="fade-right">管理系统</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #rightContent>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-space class="cursor-pointer px-4">
|
||||
<a-avatar :size="32" src="https://via.placeholder.com/32" />
|
||||
<span class="hidden md:inline">管理员</span>
|
||||
</a-space>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="logout">退出登录</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<!-- 菜单 -->
|
||||
<nav class="mt-6 px-3">
|
||||
<div v-for="item in menuData" :key="item.name" class="mb-2">
|
||||
<a
|
||||
:href="item.path"
|
||||
@click.prevent="navigateTo(item.path)"
|
||||
:class="`flex items-center px-4 py-3 rounded-xl transition-colors duration-200 cursor-pointer ${
|
||||
selectedKeys === item.name
|
||||
? 'bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-400'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700/50 hover:text-gray-900 dark:hover:text-white'
|
||||
}`"
|
||||
>
|
||||
<!-- 图标 -->
|
||||
<div class="w-6 h-6 flex items-center justify-center">
|
||||
<svg v-if="item.icon === 'pie-chart'" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z" />
|
||||
</svg>
|
||||
<svg v-else-if="item.icon === 'layout-grid'" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
|
||||
</svg>
|
||||
<svg v-else-if="item.icon === 'users'" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<svg v-else-if="item.icon === 'settings'" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<router-view />
|
||||
</ProLayout>
|
||||
<!-- 标题 -->
|
||||
<span v-if="!collapsed" class="ml-3 text-sm font-medium transition-opacity duration-300">{{ item.title }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div :class="`${collapsed ? 'ml-20' : 'ml-60'} flex-1 transition-all duration-300 ease-in-out`">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="bg-white dark:bg-gray-800 shadow-sm h-16 fixed top-0 right-0 left-0 z-10" :class="collapsed ? 'pl-20' : 'pl-60'">
|
||||
<div class="flex items-center justify-between h-full px-6">
|
||||
<!-- 折叠按钮 -->
|
||||
<button
|
||||
class="w-10 h-10 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 transition-colors cursor-pointer"
|
||||
@click="collapsed = !collapsed"
|
||||
data-aos="fade-right"
|
||||
>
|
||||
<svg v-if="collapsed" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- 用户头像下拉菜单 -->
|
||||
<div class="relative group">
|
||||
<div class="flex items-center space-x-2 cursor-pointer">
|
||||
<div class="relative">
|
||||
<div class="absolute -inset-0.5 rounded-full bg-gradient-to-br from-rose-400 to-rose-600 opacity-75 blur-sm group-hover:opacity-100 transition-opacity"></div>
|
||||
<img
|
||||
src="https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280"
|
||||
alt="User Avatar"
|
||||
class="h-10 w-10 rounded-full object-cover border-2 border-white dark:border-gray-800 relative z-10"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-gray-700 dark:text-gray-300 font-medium">管理员</span>
|
||||
</div>
|
||||
|
||||
<!-- 下拉菜单 -->
|
||||
<div class="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-xl shadow-xl opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300 transform origin-top-right scale-95 group-hover:scale-100 z-50">
|
||||
<div class="py-2">
|
||||
<a
|
||||
href="/admin/profile"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer"
|
||||
>
|
||||
个人资料
|
||||
</a>
|
||||
<a
|
||||
href="/"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer"
|
||||
>
|
||||
返回前台
|
||||
</a>
|
||||
<hr class="my-1 border-gray-200 dark:border-gray-700" />
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer"
|
||||
>
|
||||
退出登录
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<!-- <main class="pt-20 px-6 pb-6">-->
|
||||
<!-- <div data-aos="fade-up">-->
|
||||
<!-- <router-view />-->
|
||||
<!-- </div>-->
|
||||
<!-- </main>-->
|
||||
<main class="pt-20 px-6 pb-6">
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ant-pro-sider-menu) {
|
||||
@apply shadow-lg;
|
||||
<style scoped>
|
||||
/* 自定义样式 */
|
||||
.admin-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
:deep(.ant-pro-page-container) {
|
||||
@apply bg-gray-50;
|
||||
|
||||
/* 确保所有按钮和可点击元素在鼠标悬停时显示为小手指针 */
|
||||
button, a, [role="button"], .cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup>
|
||||
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { UserOutlined, MenuOutlined } from '@ant-design/icons-vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import AuthModal from '@/components/common/AuthModal.vue'
|
||||
import AOS from 'aos'
|
||||
import 'aos/dist/aos.css'
|
||||
|
||||
// 合并后的状态管理
|
||||
const router = useRouter()
|
||||
@@ -15,10 +16,10 @@ const isScrolled = ref(false)
|
||||
const mobileMenuVisible = ref(false)
|
||||
|
||||
const menuItems = [
|
||||
{ path: '/', title: '首页', icon: 'HomeOutlined' },
|
||||
{ path: '/projects', title: '项目列表', icon: 'InfoCircleOutlined' },
|
||||
{ path: '/about', title: '关于我们', icon: 'InfoCircleOutlined' },
|
||||
{ path: '/services', title: '服务项目', icon: 'AppstoreOutlined' },
|
||||
{ path: '/', title: '首页', icon: 'home' },
|
||||
{ path: '/projects', title: '项目列表', icon: 'grid' },
|
||||
{ path: '/services', title: '服务项目', icon: 'briefcase' },
|
||||
{ path: '/about', title: '关于我们', icon: 'info' },
|
||||
]
|
||||
|
||||
// 滚动监听逻辑
|
||||
@@ -26,136 +27,274 @@ onMounted(() => {
|
||||
window.addEventListener('scroll', () => {
|
||||
isScrolled.value = window.scrollY > 0
|
||||
})
|
||||
|
||||
AOS.init({
|
||||
duration: 1200,
|
||||
offset: 120,
|
||||
easing: 'ease-out-cubic',
|
||||
once: false
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-layout class="min-h-screen body-box">
|
||||
<div class="min-h-screen bg-gradient-to-b from-gray-50 to-white dark:from-gray-900 dark:to-gray-800">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="sticky top-0 z-50 bg-gradient-to-r from-blue-600 to-purple-700 transition-shadow duration-300" :class="{ 'shadow-lg': isScrolled }">
|
||||
<div class="flex items-center justify-between px-6 py-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<img src="@/assets/nl-logo.png" alt="Logo" class="h-10 w-10 transition-transform hover:scale-110">
|
||||
<span class="text-2xl font-extrabold tracking-wide bg-gradient-to-r from-white to-blue-100 bg-clip-text text-transparent">AppStop</span>
|
||||
</div>
|
||||
<header
|
||||
class="sticky top-0 z-50 transition-all duration-300 backdrop-blur-md"
|
||||
:class="{
|
||||
'bg-white/80 dark:bg-gray-900/80 shadow-lg': isScrolled,
|
||||
'bg-transparent': !isScrolled
|
||||
}"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16 md:h-20">
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-rose-400 to-rose-600 flex items-center justify-center shadow-lg transform transition-transform hover:scale-110">
|
||||
<span class="text-white text-xl font-bold">A</span>
|
||||
</div>
|
||||
<span class="text-2xl font-extrabold tracking-wide text-gray-900 dark:text-white">AppStop</span>
|
||||
</div>
|
||||
|
||||
<nav class="hidden md:flex space-x-4">
|
||||
<a
|
||||
href="javascript:;"
|
||||
v-for="item in menuItems"
|
||||
:key="item.path"
|
||||
type="text"
|
||||
:class="`${route.path === item.path? 'router-link-active': 'router-link'} flex items-center p-3 rounded-lg hover:bg-opacity-10 hover:bg-white transition-colors text-white text-lg`"
|
||||
@click="router.push(item.path)"
|
||||
>
|
||||
<!-- <component :is="item.icon" class="mr-3" />-->
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</nav>
|
||||
<!-- 桌面端导航菜单 -->
|
||||
<nav class="hidden md:flex items-center space-x-1">
|
||||
<a
|
||||
v-for="item in menuItems"
|
||||
:key="item.path"
|
||||
class="px-4 py-2 rounded-xl text-gray-700 dark:text-gray-300 hover:text-rose-600 dark:hover:text-rose-400 transition-colors duration-200 font-medium"
|
||||
:class="route.path === item.path ? 'bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-400' : ''"
|
||||
@click="router.push(item.path)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<AuthModal ref="authModal" />
|
||||
<a-button
|
||||
v-if="!userStore.isAuthenticated"
|
||||
type="primary"
|
||||
class="text-sm"
|
||||
@click="authModal?.show()"
|
||||
>
|
||||
登录/注册
|
||||
</a-button>
|
||||
<div v-else>
|
||||
<a-dropdown>
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
<img src="https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280" alt="User Avatar" class="h-8 w-8 rounded-full cursor-pointer">
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item>
|
||||
<a href="/personal">个人中心</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="userStore.user?.role === 'admin'">
|
||||
<a href="/admin">后台管理</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<!-- 用户操作区 -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<button
|
||||
v-if="!userStore.isAuthenticated"
|
||||
class="px-5 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-full shadow-md hover:shadow-lg transition-all duration-300 text-sm font-medium"
|
||||
@click="authModal?.show()"
|
||||
>
|
||||
登录/注册
|
||||
</button>
|
||||
|
||||
<!-- 用户头像下拉菜单 -->
|
||||
<div v-else class="relative group">
|
||||
<div class="relative">
|
||||
<div class="absolute -inset-0.5 rounded-full bg-gradient-to-br from-rose-400 to-rose-600 opacity-75 blur-sm group-hover:opacity-100 transition-opacity"></div>
|
||||
<img
|
||||
src="https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280"
|
||||
alt="User Avatar"
|
||||
class="h-10 w-10 rounded-full object-cover border-2 border-white dark:border-gray-800 cursor-pointer relative z-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 下拉菜单 -->
|
||||
<div class="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-xl shadow-xl opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300 transform origin-top-right scale-95 group-hover:scale-100 z-50">
|
||||
<div class="py-2">
|
||||
<a
|
||||
href="/personal"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400"
|
||||
>
|
||||
个人中心
|
||||
</a>
|
||||
<a
|
||||
v-if="userStore.user?.role === 'admin'"
|
||||
href="/admin"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400"
|
||||
>
|
||||
后台管理
|
||||
</a>
|
||||
<hr class="my-1 border-gray-200 dark:border-gray-700" />
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400"
|
||||
>
|
||||
退出登录
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 移动端菜单按钮 -->
|
||||
<button
|
||||
class="md:hidden w-10 h-10 flex items-center justify-center rounded-full bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300"
|
||||
@click="mobileMenuVisible = true"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 移动端菜单 -->
|
||||
<a-drawer
|
||||
v-model:visible="mobileMenuVisible"
|
||||
placement="left"
|
||||
:width="280"
|
||||
class="md:hidden"
|
||||
>
|
||||
<div class="p-4 space-y-4">
|
||||
<a-button
|
||||
v-for="item in menuItems"
|
||||
:key="item.path"
|
||||
type="link"
|
||||
:class="`${route.path === item.path? 'router-link-active': 'router-link'} flex items-center p-3 rounded-lg hover:bg-opacity-10 hover:bg-white transition-colors text-white text-lg`"
|
||||
@click="router.push(item.path)"
|
||||
>
|
||||
<component :is="item.icon" class="mr-4" />
|
||||
{{ item.title }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
<!-- 移动端侧边菜单 -->
|
||||
<div
|
||||
class="fixed inset-0 bg-black bg-opacity-50 z-50 transition-opacity md:hidden"
|
||||
:class="mobileMenuVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'"
|
||||
@click="mobileMenuVisible = false"
|
||||
></div>
|
||||
|
||||
<!-- 移动端菜单开关 -->
|
||||
<div class="fixed bottom-6 right-6 z-50 md:hidden">
|
||||
<a-button
|
||||
shape="circle"
|
||||
type="primary"
|
||||
size="large"
|
||||
class="!w-14 !h-14 shadow-xl hover:scale-110 transition-transform"
|
||||
@click="mobileMenuVisible = true"
|
||||
>
|
||||
<template #icon><MenuOutlined class="text-2xl" /></template>
|
||||
</a-button>
|
||||
<div
|
||||
class="fixed top-0 right-0 bottom-0 w-64 bg-white dark:bg-gray-800 z-50 transform transition-transform duration-300 ease-in-out shadow-2xl md:hidden"
|
||||
:class="mobileMenuVisible ? 'translate-x-0' : 'translate-x-full'"
|
||||
>
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-rose-400 to-rose-600 flex items-center justify-center">
|
||||
<span class="text-white text-sm font-bold">A</span>
|
||||
</div>
|
||||
<span class="text-lg font-bold text-gray-900 dark:text-white">AppStop</span>
|
||||
</div>
|
||||
<button
|
||||
class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
@click="mobileMenuVisible = false"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="space-y-3">
|
||||
<a
|
||||
v-for="item in menuItems"
|
||||
:key="item.path"
|
||||
class="block px-4 py-3 rounded-xl text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400 transition-colors duration-200 font-medium"
|
||||
:class="route.path === item.path ? 'bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-400' : ''"
|
||||
@click="router.push(item.path); mobileMenuVisible = false"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
v-if="!userStore.isAuthenticated"
|
||||
class="w-full px-4 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-full shadow-md hover:shadow-lg transition-all duration-300 text-sm font-medium"
|
||||
@click="authModal?.show(); mobileMenuVisible = false"
|
||||
>
|
||||
登录/注册
|
||||
</button>
|
||||
<div v-else class="space-y-3">
|
||||
<a
|
||||
href="/personal"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400 rounded-xl"
|
||||
@click="mobileMenuVisible = false"
|
||||
>
|
||||
个人中心
|
||||
</a>
|
||||
<a
|
||||
v-if="userStore.user?.role === 'admin'"
|
||||
href="/admin"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400 rounded-xl"
|
||||
@click="mobileMenuVisible = false"
|
||||
>
|
||||
后台管理
|
||||
</a>
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="userStore.logout(); mobileMenuVisible = false"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400 rounded-xl"
|
||||
>
|
||||
退出登录
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-layout-content class="body-box">
|
||||
<!-- 主内容区 -->
|
||||
<main>
|
||||
<!-- 登录/注册按钮 -->
|
||||
<AuthModal ref="authModal" />
|
||||
<router-view />
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</main>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 py-12">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
<div class="col-span-1 md:col-span-2">
|
||||
<div class="flex items-center space-x-3 mb-4">
|
||||
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-rose-400 to-rose-600 flex items-center justify-center">
|
||||
<span class="text-white text-xl font-bold">A</span>
|
||||
</div>
|
||||
<span class="text-2xl font-bold text-gray-900 dark:text-white">AppStop</span>
|
||||
</div>
|
||||
<p class="text-gray-600 dark:text-gray-400 max-w-md">
|
||||
我们致力于为企业提供创新的数字化解决方案,帮助客户在数字时代实现业务增长
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">快速链接</h3>
|
||||
<ul class="space-y-2">
|
||||
<li v-for="item in menuItems" :key="item.path">
|
||||
<a
|
||||
:href="item.path"
|
||||
class="text-gray-600 dark:text-gray-400 hover:text-rose-600 dark:hover:text-rose-400 transition-colors"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">联系我们</h3>
|
||||
<ul class="space-y-2 text-gray-600 dark:text-gray-400">
|
||||
<li class="flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
contact@example.com
|
||||
</li>
|
||||
<li class="flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-rose-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||||
</svg>
|
||||
400-123-4567
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-8 border-t border-gray-200 dark:border-gray-700 text-center text-gray-600 dark:text-gray-400">
|
||||
<p>© {{ new Date().getFullYear() }} AppStop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.body-box {
|
||||
background-color: #131a2a !important;
|
||||
min-height: 100vh;
|
||||
}
|
||||
:deep(.ant-menu-horizontal) {
|
||||
border-bottom: none !important;
|
||||
|
||||
/* 添加轻奢风格的渐变背景和阴影效果 */
|
||||
.bg-gradient-luxury {
|
||||
background: linear-gradient(135deg, #f6f8fb 0%, #f0f4f8 100%);
|
||||
}
|
||||
|
||||
:deep(.ant-drawer-body) {
|
||||
background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
|
||||
}
|
||||
|
||||
header {
|
||||
backdrop-filter: blur(10px);
|
||||
background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
|
||||
// 激活状态样式增强
|
||||
.router-link {
|
||||
color: #fff !important;
|
||||
}
|
||||
// 激活状态样式增强
|
||||
.router-link-active {
|
||||
color: #fff !important;
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
/* 暗色模式下的轻奢风格 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.bg-gradient-luxury {
|
||||
background: linear-gradient(135deg, #1a1d23 0%, #111827 100%);
|
||||
}
|
||||
}
|
||||
/* 确保所有按钮和可点击元素在鼠标悬停时显示为小手指针 */
|
||||
button, a, [role="button"], .cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,58 +1,312 @@
|
||||
<template>
|
||||
<a-layout>
|
||||
<a-page-header title="数据看板" class="p-4 bg-white"/>
|
||||
|
||||
<a-layout-content class="p-4">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<!-- 统计卡片 -->
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<div class="flex items-center">
|
||||
<div class="text-2xl font-bold mr-4">12</div>
|
||||
<div class="text-gray-500">进行中项目</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<div class="flex items-center">
|
||||
<div class="text-2xl font-bold mr-4">86%</div>
|
||||
<div class="text-gray-500">完成率</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<a-card class="mt-4">
|
||||
<div id="chart-container" style="height: 400px">
|
||||
<!-- 图表占位 -->
|
||||
<div class="flex items-center justify-center h-full text-gray-400">
|
||||
图表加载中...
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { Layout, PageHeader, Card, Row, Col } from 'ant-design-vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import AOS from 'aos'
|
||||
import 'aos/dist/aos.css'
|
||||
|
||||
const [ALayout, ALayoutContent, APageHeader, ACard, ARow, ACol] = [
|
||||
Layout,
|
||||
Layout.Content,
|
||||
PageHeader,
|
||||
Card,
|
||||
Row,
|
||||
Col
|
||||
onMounted(() => {
|
||||
AOS.init({
|
||||
duration: 1200,
|
||||
offset: 120,
|
||||
easing: 'ease-out-cubic',
|
||||
once: false
|
||||
})
|
||||
|
||||
// 这里可以添加图表初始化代码
|
||||
// 例如使用 ECharts 或其他图表库
|
||||
})
|
||||
|
||||
// 统计数据
|
||||
const stats = [
|
||||
{
|
||||
title: '项目总数',
|
||||
value: 24,
|
||||
change: '+12%',
|
||||
trend: 'up',
|
||||
icon: 'folder',
|
||||
color: 'from-rose-400 to-rose-600'
|
||||
},
|
||||
{
|
||||
title: '活跃用户',
|
||||
value: 1842,
|
||||
change: '+22%',
|
||||
trend: 'up',
|
||||
icon: 'users',
|
||||
color: 'from-blue-400 to-blue-600'
|
||||
},
|
||||
{
|
||||
title: '本月收入',
|
||||
value: '¥ 128,400',
|
||||
change: '+18%',
|
||||
trend: 'up',
|
||||
icon: 'dollar',
|
||||
color: 'from-emerald-400 to-emerald-600'
|
||||
},
|
||||
{
|
||||
title: '完成率',
|
||||
value: '94%',
|
||||
change: '+2%',
|
||||
trend: 'up',
|
||||
icon: 'check-circle',
|
||||
color: 'from-amber-400 to-amber-600'
|
||||
}
|
||||
]
|
||||
|
||||
// 最近项目
|
||||
const recentProjects = [
|
||||
{ id: 1, title: '电商平台开发', status: '进行中', progress: 75, date: '2024-03-20' },
|
||||
{ id: 2, title: 'CRM系统定制', status: '已完成', progress: 100, date: '2024-03-15' },
|
||||
{ id: 3, title: '数据分析平台', status: '进行中', progress: 45, date: '2024-03-10' },
|
||||
{ id: 4, title: '企业官网重构', status: '已完成', progress: 100, date: '2024-03-05' },
|
||||
{ id: 5, title: '移动端APP开发', status: '进行中', progress: 30, date: '2024-03-01' }
|
||||
]
|
||||
|
||||
// 最近活动
|
||||
const recentActivities = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'project',
|
||||
action: '创建了新项目',
|
||||
target: '电商平台开发',
|
||||
user: '管理员',
|
||||
time: '2小时前',
|
||||
icon: 'plus-circle',
|
||||
color: 'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'user',
|
||||
action: '新用户注册',
|
||||
target: '张三',
|
||||
user: '系统',
|
||||
time: '4小时前',
|
||||
icon: 'user-plus',
|
||||
color: 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'project',
|
||||
action: '更新了项目',
|
||||
target: 'CRM系统定制',
|
||||
user: '管理员',
|
||||
time: '昨天',
|
||||
icon: 'edit',
|
||||
color: 'bg-amber-100 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'payment',
|
||||
action: '收到新付款',
|
||||
target: '¥ 19,999',
|
||||
user: '李四',
|
||||
time: '2天前',
|
||||
icon: 'credit-card',
|
||||
color: 'bg-rose-100 text-rose-600 dark:bg-rose-900/30 dark:text-rose-400'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面标题 -->
|
||||
<div class="mb-8" data-aos="fade-up">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">数据看板</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400 mt-1">欢迎回来,这是您的系统概览</p>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8" data-aos="fade-up">
|
||||
<div
|
||||
v-for="(stat, index) in stats"
|
||||
:key="index"
|
||||
class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6 transition-transform hover:-translate-y-1 hover:shadow-xl"
|
||||
data-aos="fade-up"
|
||||
:data-aos-delay="index * 100"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-1">{{ stat.title }}</p>
|
||||
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ stat.value }}</p>
|
||||
</div>
|
||||
<div :class="`w-12 h-12 rounded-xl bg-gradient-to-br ${stat.color} flex items-center justify-center text-white shadow-lg`">
|
||||
<svg v-if="stat.icon === 'folder'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||
</svg>
|
||||
<svg v-else-if="stat.icon === 'users'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<svg v-else-if="stat.icon === 'dollar'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<svg v-else-if="stat.icon === 'check-circle'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" viewBox="0 0 24 24" stroke="currentColor" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center">
|
||||
<span
|
||||
:class="`text-sm font-medium ${stat.trend === 'up' ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`"
|
||||
>
|
||||
{{ stat.change }}
|
||||
</span>
|
||||
<svg
|
||||
v-if="stat.trend === 'up'"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 text-green-600 dark:text-green-400 ml-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
|
||||
</svg>
|
||||
<svg
|
||||
v-else
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 text-red-600 dark:text-red-400 ml-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 17h8m0 0v-8m0 8l-8-8-4 4-6-6" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400 ml-2">较上月</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表和项目列表 -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
|
||||
<!-- 图表区域 -->
|
||||
<div class="lg:col-span-2" data-aos="fade-up">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-lg font-bold text-gray-900 dark:text-white">收入趋势</h2>
|
||||
<div class="flex space-x-2">
|
||||
<button class="px-3 py-1 text-sm bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-400 rounded-lg">月度</button>
|
||||
<button class="px-3 py-1 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg">季度</button>
|
||||
<button class="px-3 py-1 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg">年度</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-80 flex items-center justify-center bg-gray-50 dark:bg-gray-700/50 rounded-xl">
|
||||
<!-- 图表占位 -->
|
||||
<div class="text-gray-400 dark:text-gray-500 flex flex-col items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
<p>图表加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 最近活动 -->
|
||||
<div data-aos="fade-up">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6 mt-5" data-aos="fade-up">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-lg font-bold text-gray-900 dark:text-white">最近活动</h2>
|
||||
<button class="text-rose-600 dark:text-rose-400 hover:text-rose-700 dark:hover:text-rose-300 text-sm font-medium">
|
||||
查看全部
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div
|
||||
v-for="activity in recentActivities"
|
||||
:key="activity.id"
|
||||
class="flex"
|
||||
>
|
||||
<div :class="`w-10 h-10 rounded-full ${activity.color} flex items-center justify-center mr-4 flex-shrink-0`">
|
||||
<svg v-if="activity.icon === 'plus-circle'" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<svg v-else-if="activity.icon === 'user-plus'" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z" />
|
||||
</svg>
|
||||
<svg v-else-if="activity.icon === 'edit'" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
<svg v-else-if="activity.icon === 'credit-card'" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center mb-1">
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ activity.user }}</span>
|
||||
<span class="mx-2 text-gray-500 dark:text-gray-400">{{ activity.action }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ activity.target }}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ activity.time }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 最近项目 -->
|
||||
<div data-aos="fade-up">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-lg font-bold text-gray-900 dark:text-white">最近项目</h2>
|
||||
<a href="/admin/projects" class="text-rose-600 dark:text-rose-400 hover:text-rose-700 dark:hover:text-rose-300 text-sm font-medium">
|
||||
查看全部
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="project in recentProjects"
|
||||
:key="project.id"
|
||||
class="p-4 border border-gray-100 dark:border-gray-700 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
|
||||
>
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<h3 class="font-medium text-gray-900 dark:text-white">{{ project.title }}</h3>
|
||||
<span
|
||||
class="px-2 py-1 text-xs font-medium rounded-full"
|
||||
:class="project.status === '进行中' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300' : 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'"
|
||||
>
|
||||
{{ project.status }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400 mb-1">
|
||||
<span>进度</span>
|
||||
<span>{{ project.progress }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
class="h-2 rounded-full bg-gradient-to-r from-rose-400 to-rose-600"
|
||||
:style="`width: ${project.progress}%`"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ project.date }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ant-page-header) {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
/* 添加轻奢风格的渐变背景和阴影效果 */
|
||||
.bg-gradient-luxury {
|
||||
background: linear-gradient(135deg, #f6f8fb 0%, #f0f4f8 100%);
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 暗色模式下的轻奢风格 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.bg-gradient-luxury {
|
||||
background: linear-gradient(135deg, #1a1d23 0%, #111827 100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const formState = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
remember: true
|
||||
})
|
||||
|
||||
const rules = {
|
||||
username: [{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: 'blur'
|
||||
}],
|
||||
password: [{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: 'blur'
|
||||
}]
|
||||
}
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
await userStore.login({
|
||||
username: formState.username,
|
||||
password: formState.password
|
||||
})
|
||||
message.success('登录成功')
|
||||
router.push('/admin/dashboard')
|
||||
} catch (e) {
|
||||
message.error('登录失败:' + e.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-100">
|
||||
<a-card class="w-full max-w-md shadow-lg" :bordered="false">
|
||||
<h2 class="text-2xl font-bold mb-6 text-center">后台管理系统</h2>
|
||||
<a-form
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
@finish="handleLogin"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item name="username">
|
||||
<a-input
|
||||
v-model:value="formState.username"
|
||||
placeholder="用户名"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix><UserOutlined /></template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="password">
|
||||
<a-input-password
|
||||
v-model:value="formState.password"
|
||||
placeholder="密码"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix><LockOutlined /></template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
size="large"
|
||||
class="w-full"
|
||||
>
|
||||
立即登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
@apply rounded-lg px-3 py-2;
|
||||
}
|
||||
:deep(.ant-btn-primary) {
|
||||
@apply rounded-lg;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,16 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||
import { Table, Modal } from 'ant-design-vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import AOS from 'aos'
|
||||
import 'aos/dist/aos.css'
|
||||
|
||||
onMounted(() => {
|
||||
AOS.init({
|
||||
duration: 1200,
|
||||
offset: 120,
|
||||
easing: 'ease-out-cubic',
|
||||
once: false
|
||||
})
|
||||
})
|
||||
|
||||
const dataSource = ref([
|
||||
{
|
||||
@@ -10,107 +19,533 @@ const dataSource = ref([
|
||||
category: 'Web应用',
|
||||
tags: ['Vue3', 'Node.js'],
|
||||
author: 'Admin',
|
||||
price: '29,999',
|
||||
status: '已发布',
|
||||
createdAt: '2024-03-20'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'CRM系统',
|
||||
category: '企业系统',
|
||||
tags: ['React', 'Express'],
|
||||
author: 'Admin',
|
||||
price: '19,999',
|
||||
status: '已发布',
|
||||
createdAt: '2024-03-15'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '数据分析平台',
|
||||
category: 'Web应用',
|
||||
tags: ['Vue3', 'Python'],
|
||||
author: 'Admin',
|
||||
price: '24,999',
|
||||
status: '草稿',
|
||||
createdAt: '2024-03-10'
|
||||
}
|
||||
])
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
key: 'tags',
|
||||
customRender: ({ text }) => text.join(', ')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 120,
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
const categoryOptions = [
|
||||
{ value: 'Web应用', label: 'Web应用' },
|
||||
{ value: '移动端', label: '移动端' },
|
||||
{ value: '小程序', label: '小程序' },
|
||||
{ value: '企业系统', label: '企业系统' }
|
||||
]
|
||||
|
||||
const visible = ref(false)
|
||||
const currentItem = ref(null)
|
||||
const statusOptions = [
|
||||
{ value: '草稿', label: '草稿' },
|
||||
{ value: '已发布', label: '已发布' },
|
||||
{ value: '已下架', label: '已下架' }
|
||||
]
|
||||
|
||||
const showModal = (item = null) => {
|
||||
currentItem.value = item || { tags: [] }
|
||||
visible.value = true
|
||||
// 分页
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const totalItems = ref(dataSource.value.length)
|
||||
|
||||
const paginatedData = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
return dataSource.value.slice(start, end)
|
||||
})
|
||||
|
||||
const changePage = (page) => {
|
||||
currentPage.value = page
|
||||
}
|
||||
|
||||
const handleDelete = (id) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个项目吗?',
|
||||
onOk: () => {
|
||||
dataSource.value = dataSource.value.filter(item => item.id !== id)
|
||||
// 模态框
|
||||
const showModal = ref(false)
|
||||
const currentItem = ref(null)
|
||||
const formLoading = ref(false)
|
||||
|
||||
const openModal = (item = null) => {
|
||||
currentItem.value = item ? { ...item } : {
|
||||
title: '',
|
||||
category: 'Web应用',
|
||||
tags: [],
|
||||
price: '',
|
||||
status: '草稿',
|
||||
description: ''
|
||||
}
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
showModal.value = false
|
||||
}
|
||||
|
||||
// 确认删除模态框
|
||||
const showDeleteConfirm = ref(false)
|
||||
const itemToDelete = ref(null)
|
||||
|
||||
const openDeleteConfirm = (id) => {
|
||||
itemToDelete.value = id
|
||||
showDeleteConfirm.value = true
|
||||
}
|
||||
|
||||
const closeDeleteConfirm = () => {
|
||||
showDeleteConfirm.value = false
|
||||
itemToDelete.value = null
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
if (itemToDelete.value) {
|
||||
dataSource.value = dataSource.value.filter(item => item.id !== itemToDelete.value)
|
||||
totalItems.value = dataSource.value.length
|
||||
closeDeleteConfirm()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
formLoading.value = true
|
||||
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
if (currentItem.value.id) {
|
||||
// 更新现有项目
|
||||
const index = dataSource.value.findIndex(item => item.id === currentItem.value.id)
|
||||
if (index !== -1) {
|
||||
dataSource.value[index] = { ...currentItem.value }
|
||||
}
|
||||
} else {
|
||||
// 添加新项目
|
||||
const newId = Math.max(...dataSource.value.map(item => item.id), 0) + 1
|
||||
dataSource.value.push({
|
||||
...currentItem.value,
|
||||
id: newId,
|
||||
author: 'Admin',
|
||||
createdAt: new Date().toISOString().split('T')[0]
|
||||
})
|
||||
totalItems.value = dataSource.value.length
|
||||
}
|
||||
})
|
||||
|
||||
formLoading.value = false
|
||||
closeModal()
|
||||
}, 800)
|
||||
}
|
||||
|
||||
// 标签管理
|
||||
const tagInputValue = ref('')
|
||||
const handleAddTag = () => {
|
||||
if (tagInputValue.value && !currentItem.value.tags.includes(tagInputValue.value)) {
|
||||
currentItem.value.tags.push(tagInputValue.value)
|
||||
tagInputValue.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const removeTag = (index) => {
|
||||
currentItem.value.tags.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<div class="mb-4 flex justify-between items-center">
|
||||
<h2 class="text-xl font-semibold">项目管理</h2>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-3xl shadow-xl p-6" data-aos="fade-up">
|
||||
<!-- 页面标题和新建按钮 -->
|
||||
<div class="mb-6 flex justify-between items-center">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">项目管理</h2>
|
||||
<button
|
||||
class="px-5 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-xl shadow-md hover:shadow-lg transition-all duration-300 flex items-center cursor-pointer"
|
||||
@click="openModal()"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
新建项目
|
||||
</a-button>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:scroll="{ x: 800 }"
|
||||
bordered
|
||||
>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-tooltip title="编辑">
|
||||
<a-button type="link" @click="showModal(record)">
|
||||
<EditOutlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="删除">
|
||||
<a-button type="link" danger @click="handleDelete(record.id)">
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 表格 -->
|
||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-700">
|
||||
<div class="overflow-x-auto" style="height: 75vh">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<!-- 表头 -->
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||
项目名称
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||
分类
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||
标签
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||
价格
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||
状态
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||
创建时间
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||
操作
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
:title="currentItem.id ? '编辑项目' : '新建项目'"
|
||||
width="800px"
|
||||
>
|
||||
<!-- 项目表单内容 -->
|
||||
<template #footer>
|
||||
<a-button @click="visible = false">取消</a-button>
|
||||
<a-button type="primary">确认</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
<!-- 表格内容 -->
|
||||
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr v-for="item in paginatedData" :key="item.id" class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ item.title }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ item.category }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span
|
||||
v-for="(tag, index) in item.tags"
|
||||
:key="index"
|
||||
class="px-2 py-1 bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-300 text-xs font-medium rounded-full"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
||||
¥ {{ item.price }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span
|
||||
:class="`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
item.status === '已发布'
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'
|
||||
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
|
||||
}`"
|
||||
>
|
||||
{{ item.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ item.createdAt }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button
|
||||
class="p-1.5 text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors cursor-pointer"
|
||||
title="查看"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="p-1.5 text-amber-600 hover:text-amber-800 dark:text-amber-400 dark:hover:text-amber-300 hover:bg-amber-50 dark:hover:bg-amber-900/20 rounded-lg transition-colors cursor-pointer"
|
||||
title="编辑"
|
||||
@click="openModal(item)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="p-1.5 text-rose-600 hover:text-rose-800 dark:text-rose-400 dark:hover:text-rose-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 rounded-lg transition-colors cursor-pointer"
|
||||
title="删除"
|
||||
@click="openDeleteConfirm(item.id)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="bg-white dark:bg-gray-800 px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<div class="text-sm text-gray-700 dark:text-gray-300">
|
||||
共 {{ totalItems }} 条记录
|
||||
</div>
|
||||
<div class="flex space-x-1">
|
||||
<button
|
||||
class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
|
||||
:disabled="currentPage === 1"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': currentPage === 1 }"
|
||||
@click="currentPage > 1 && changePage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
<button
|
||||
v-for="page in Math.ceil(totalItems / pageSize)"
|
||||
:key="page"
|
||||
class="px-3 py-1 border rounded-md text-sm font-medium cursor-pointer"
|
||||
:class="page === currentPage
|
||||
? 'bg-rose-600 text-white border-rose-600 dark:bg-rose-600 dark:border-rose-600'
|
||||
: 'border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'"
|
||||
@click="changePage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
|
||||
:disabled="currentPage === Math.ceil(totalItems / pageSize)"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': currentPage === Math.ceil(totalItems / pageSize) }"
|
||||
@click="currentPage < Math.ceil(totalItems / pageSize) && changePage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- 项目表单模态框 -->
|
||||
<div v-if="showModal" class="fixed inset-0 z-50 " aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<!-- 背景遮罩 -->
|
||||
<div
|
||||
class="fixed inset-0 transition-opacity backdrop-blur-xl bg-gray-500/80 dark:bg-gray-900/80"
|
||||
aria-hidden="true"
|
||||
@click="closeModal"
|
||||
></div>
|
||||
|
||||
<!-- 使模态框居中 -->
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
|
||||
<!-- 模态框内容 -->
|
||||
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full relative z-50">
|
||||
<div class="bg-white dark:bg-gray-800 px-6 pt-5 pb-4 sm:p-6">
|
||||
<div class="flex justify-between items-center mb-5">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-white" id="modal-title">
|
||||
{{ currentItem?.id ? '编辑项目' : '新建项目' }}
|
||||
</h3>
|
||||
<button
|
||||
class="text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400 cursor-pointer"
|
||||
@click="closeModal"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleSubmit" class="space-y-6">
|
||||
<!-- 项目名称 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目名称</label>
|
||||
<input
|
||||
v-model="currentItem.title"
|
||||
type="text"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="请输入项目名称"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 分类和状态 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目分类</label>
|
||||
<select
|
||||
v-model="currentItem.category"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white cursor-pointer"
|
||||
>
|
||||
<option v-for="option in categoryOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目状态</label>
|
||||
<select
|
||||
v-model="currentItem.status"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white cursor-pointer"
|
||||
>
|
||||
<option v-for="option in statusOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 价格 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目价格</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 pl-3 flex items-center text-gray-500 dark:text-gray-400">¥</span>
|
||||
<input
|
||||
v-model="currentItem.price"
|
||||
type="text"
|
||||
class="w-full pl-8 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="请输入项目价格"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 标签 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目标签</label>
|
||||
<div class="flex flex-wrap gap-2 mb-2">
|
||||
<span
|
||||
v-for="(tag, index) in currentItem.tags"
|
||||
:key="index"
|
||||
class="px-3 py-1.5 bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-300 text-sm font-medium rounded-lg flex items-center"
|
||||
>
|
||||
{{ tag }}
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1.5 text-rose-500 hover:text-rose-700 dark:text-rose-400 dark:hover:text-rose-200 cursor-pointer"
|
||||
@click="removeTag(index)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input
|
||||
v-model="tagInputValue"
|
||||
type="text"
|
||||
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-l-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="输入标签"
|
||||
@keyup.enter="handleAddTag"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-r-xl transition-colors cursor-pointer"
|
||||
@click="handleAddTag"
|
||||
>
|
||||
添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目描述 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目描述</label>
|
||||
<textarea
|
||||
v-model="currentItem.description"
|
||||
rows="4"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="请输入项目描述"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 表单按钮 -->
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
class="px-5 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer"
|
||||
@click="closeModal"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-5 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-xl shadow-md hover:shadow-lg transition-all duration-300 flex items-center cursor-pointer"
|
||||
:disabled="formLoading"
|
||||
>
|
||||
<span v-if="formLoading" class="mr-2">
|
||||
<svg class="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
{{ currentItem?.id ? '保存修改' : '创建项目' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 删除确认模态框 -->
|
||||
<div v-if="showDeleteConfirm" class="fixed inset-0 z-50" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<!-- 背景遮罩 -->
|
||||
<div
|
||||
class="fixed inset-0 transition-opacity backdrop-blur-xl bg-gray-500/80 dark:bg-gray-900/80"
|
||||
aria-hidden="true"
|
||||
@click="closeDeleteConfirm"
|
||||
></div>
|
||||
|
||||
<!-- 使模态框居中 -->
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
|
||||
<!-- 模态框内容 -->
|
||||
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full relative z-50">
|
||||
<div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-900/30 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white" id="modal-title">
|
||||
确认删除
|
||||
</h3>
|
||||
<div class="mt-2">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
确定要删除这个项目吗?此操作无法撤销。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-gray-700 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<a-button
|
||||
type="button"
|
||||
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm cursor-pointer"
|
||||
@click="handleDelete"
|
||||
>
|
||||
确认
|
||||
</a-button>
|
||||
<a-button
|
||||
type="button"
|
||||
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-800 text-base font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm cursor-pointer"
|
||||
@click="closeDeleteConfirm"
|
||||
>
|
||||
取消
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
@apply bg-gray-50;
|
||||
/* 自定义样式 */
|
||||
|
||||
/* 毛玻璃效果增强 */
|
||||
.backdrop-blur-xl {
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
:deep(.ant-table-tbody > tr:hover td) {
|
||||
@apply bg-gray-100;
|
||||
|
||||
.backdrop-blur-md {
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
</style>
|
||||
|
||||
.backdrop-blur-sm {
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -40,14 +40,14 @@ const project = {
|
||||
'会员管理和积分系统',
|
||||
'数据分析和报表功能'
|
||||
],
|
||||
technicalStack: ['前端:Vue3 + TypeScript', '后端:NestJS + MySQL', '部署:Docker + Kubernetes'],
|
||||
technicalStack: ['前端:Vue3 + TypeScript', '后端:PHP【Laravel 12.x】 + MySQL', '部署:Docker + Kubernetes'],
|
||||
screenshots: [
|
||||
'https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280',
|
||||
'https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280',
|
||||
'https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280'
|
||||
],
|
||||
video: 'https://www.youtube.com/embed/dQw4w9WgXcQ',
|
||||
wechatQR: 'https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280'
|
||||
video: 'http://d-jy.nailaoyun.cn//storage/video//20250327/2cc1abf9bd69410ce7c69660daf54260.mp4',
|
||||
wechatQR: '/assets/qr-code.png'
|
||||
}
|
||||
|
||||
const relatedProjects = [
|
||||
@@ -289,11 +289,16 @@ const swiperOptions = {
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<p class="text-center text-gray-500 dark:text-gray-400 mb-4">扫码添加客服</p>
|
||||
<p class="text-center text-gray-500 dark:text-gray-400 mb-4">扫码添加微信</p>
|
||||
<div class="flex justify-center">
|
||||
<!-- <img-->
|
||||
<!-- :src="project.wechatQR"-->
|
||||
<!-- class="w-32 h-32 object-cover rounded-lg border border-gray-200 dark:border-gray-700"-->
|
||||
<!-- alt="微信二维码"-->
|
||||
<!-- />-->
|
||||
<img
|
||||
:src="project.wechatQR"
|
||||
class="w-32 h-32 object-cover rounded-lg border border-gray-200 dark:border-gray-700"
|
||||
src="@/assets/qr-code.png"
|
||||
class="w-48 h-68 object-cover rounded-lg border border-gray-200 dark:border-gray-700"
|
||||
alt="微信二维码"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user