提交 7918d64a 编写于 作者: R root

Auto Commit

上级 aa50c4cb
# VueJS-with-Vite # 智慧养老平台(Vue3 + Vite)
Vue.js 是基于 JavaScript 构建用户界面的库。该模板使用 Vite 来提供应用程序服务。 ## 项目简介
本项目是基于 Vue3 + Vite + Element Plus 的"智慧养老"主题平台,涵盖登录注册、新闻资讯、商品展示、社区互动、服务预约等多模块,界面美观、交互丰富,适合课程设计、项目考核和实际展示。
## 推荐的IDE设置 ## 主要功能
- **首页**:轮播图、健康打卡、欢迎文案
- **商品页**:商品卡片、父子组件、插槽、智能设备推荐
- **新闻页**:新闻列表、详情页、配图/视频、评论区、相关文章推荐
- **社区页**:社区活动、家属留言
- **登录/注册**:正则验证、记住我、验证码、第三方登录、自适应美化
- **服务预约**:表单填写、预约历史
- **紧急呼叫**:右下角悬浮按钮
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). ## 技术栈
- Vue3 + Vite
- vue-router
- Element Plus
- 本地存储(localStorage)数据持久化
- 响应式布局与页面美化
## 自定义配置 ## 目录结构
```
请参阅 [[Vite配置参考](https://vitejs.dev/config/). ├── public/assets # 图片、视频等素材
├── src/
## 项目设置 │ ├── components/ # 公共组件
│ ├── views/ # 页面视图
```sh │ ├── router/ # 路由配置
npm install │ └── main.js # 入口文件
├── package.json
├── README.md
└── ...
``` ```
### 在开发环境中启动和热更新 ## 运行方法
1. 安装依赖
```bash
npm install
# 或 yarn install
```
2. 启动开发环境
```bash
npm run dev
# 或 yarn dev
```
3. 打开浏览器访问 [http://localhost:5173](http://localhost:5173)
```sh ## 主要特色与考核点
npm run dev - 结构规范,代码分层清晰
``` - 路由、父子组件、插槽、正则验证等 Vue3 核心考点
- 页面美观,主色调统一,动画丰富,响应式适配
- 本地存储实现健康打卡、评论、预约、留言等数据持久化
- 健壮性处理,异常友好提示
- 素材管理与路径规范
### 编译用于生产环境 ## 扩展建议
- 可接入后端API实现数据同步
- 增加更多智能设备、健康服务、社区互动功能
- 优化移动端体验
```sh ## 截图与演示
npm run build (请自行补充项目截图、演示视频等)
```
---
如有问题欢迎交流!
\ No newline at end of file
项目名称:智慧养老网站
运行方式:
1. 安装依赖:npm install
2. 启动项目:npm run dev
3. 打包项目:npm run build
评分点说明:
- 父子组件与插槽:Products.vue 和 ProductCard.vue
- 路由跳转:VueRouter 配置,所有页面可跳转
- 正则验证:Login.vue/Register.vue
- 页面布局与样式:App.vue 使用 element-plus
- 至少5个功能模块:登录、注册、简介、商品展示、新闻、社区
- 图片素材:放在 public/assets 目录
\ No newline at end of file
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title> <title>智慧养老平台</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
{ {
"name": "vuejs-with-vite", "name": "smart-elderly-care",
"version": "0.0.1", "version": "1.0.0",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview --port 4173" "serve": "vite preview"
}, },
"dependencies": { "dependencies": {
"guess": "^1.0.2", "element-plus": "^2.3.0",
"vue": "^3.2.37" "vue": "^3.4.0",
"vue-router": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^3.0.1", "@vitejs/plugin-vue": "^5.2.4",
"vite": "^5.0.1" "vite": "^5.0.0"
} }
} }
# 保持public目录存在
\ No newline at end of file
# 保持assets目录存在
\ No newline at end of file
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>
<template> <template>
<header> <el-container>
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" /> <el-header>
<el-menu :default-active="$route.path" mode="horizontal" router>
<div class="wrapper"> <el-menu-item index="/">首页</el-menu-item>
<HelloWorld msg="You did it!" /> <el-menu-item index="/about">简介</el-menu-item>
<el-menu-item index="/products">商品展示</el-menu-item>
<el-menu-item index="/news">新闻列表</el-menu-item>
<el-menu-item index="/community">社区</el-menu-item>
<el-menu-item index="/login">登录</el-menu-item>
<el-menu-item index="/register">注册</el-menu-item>
</el-menu>
</el-header>
<el-main>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</el-main>
<el-button class="emergency-btn" type="danger" circle size="large" @click="emergencyDialog=true">
<el-icon><i class="el-icon-phone"></i></el-icon>
</el-button>
<el-dialog v-model="emergencyDialog" title="紧急呼叫" width="320px">
<div style="text-align:center;">
<el-icon style="font-size:40px;color:#f56c6c;"><i class="el-icon-phone"></i></el-icon>
<p style="margin:16px 0 8px 0;font-size:18px;">紧急联系电话:<b>120</b></p>
<p style="color:#888;">如遇突发状况,请立即拨打急救电话并告知您的位置。</p>
</div> </div>
</header> <template #footer>
<el-button @click="emergencyDialog=false">关闭</el-button>
<main> </template>
<TheWelcome /> </el-dialog>
</main> </el-container>
</template> </template>
<style scoped> <script setup>
header { import { ref } from 'vue'
line-height: 1.5; const emergencyDialog = ref(false)
} </script>
.logo { <style>
display: block; :root {
margin: 0 auto 2rem; --el-color-primary: #67C23A;
} }
body {
@media (min-width: 1024px) { background: #f6f8fa;
header { font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
display: flex; color: #222;
place-items: center; }
padding-right: calc(var(--section-gap) / 2); h1, h2, h3 {
} font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
font-weight: bold;
.logo { color: #333;
margin: 0 2rem 0 0; }
} .el-button {
font-size: 1.1rem;
header .wrapper { letter-spacing: 1px;
border-radius: 8px;
transition: background 0.3s, box-shadow 0.3s;
}
.el-button:hover {
background: #85ce61;
box-shadow: 0 4px 16px #b0c4de80;
}
.product-card, .el-card, .news-card, .community-card, .detail-card {
transition: box-shadow 0.3s, transform 0.3s;
}
.product-card:hover, .el-card:hover, .news-card:hover, .community-card:hover, .detail-card:hover {
box-shadow: 0 12px 36px #b0c4de80;
transform: translateY(-8px) scale(1.04);
}
img, .carousel-img, .community-img, .home-img {
transition: box-shadow 0.3s, transform 0.3s;
}
img:hover, .carousel-img:hover, .community-img:hover, .home-img:hover {
box-shadow: 0 8px 32px #b0c4de80;
transform: scale(1.03);
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.emergency-btn {
position: fixed;
right: 32px;
bottom: 32px;
z-index: 9999;
box-shadow: 0 4px 16px #f56c6c40;
font-size: 28px;
display: flex; display: flex;
place-items: flex-start; align-items: center;
flex-wrap: wrap; justify-content: center;
}
} }
</style> </style>
\ No newline at end of file
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
position: relative;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition: color 0.5s, background-color 0.5s;
line-height: 1.6;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
\ No newline at end of file
@import "./base.css";
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}
<script setup>
defineProps({
msg: {
type: String,
required: true
}
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
You’ve successfully created a project with
<a target="_blank" href="https://vitejs.dev/">Vite</a> +
<a target="_blank" href="https://vuejs.org/">Vue 3</a>.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>
<template>
<el-col :xs="24" :sm="12" :md="6">
<el-card class="product-card" shadow="hover">
<img :src="product.img" class="product-img" />
<h3>{{ product.name }}</h3>
<p class="desc">{{ product.desc }}</p>
<p class="price">{{ product.price }}</p>
<slot name="footer"></slot>
</el-card>
</el-col>
</template>
<script setup>
defineProps(['product'])
</script>
<style scoped>
.product-card {
border-radius: 18px;
transition: box-shadow 0.3s, transform 0.3s;
margin-bottom: 32px;
text-align: center;
box-shadow: 0 2px 12px #e0e0e0;
}
.product-card:hover {
box-shadow: 0 8px 32px #b0c4de80;
transform: translateY(-6px) scale(1.03);
}
.product-img {
width: 100%;
height: 150px;
object-fit: cover;
border-radius: 12px;
margin-bottom: 16px;
}
.price {
color: #e67e22;
font-weight: bold;
font-size: 20px;
}
.desc {
color: #888;
min-height: 32px;
}
</style>
\ No newline at end of file
<script setup>
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vue’s
<a target="_blank" href="https://vuejs.org/">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vitejs.dev/guide/features.html" target="_blank">Vite</a>. The recommended IDE
setup is <a href="https://code.visualstudio.com/" target="_blank">VSCode</a> +
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>. If you need to test
your components and web pages, check out
<a href="https://www.cypress.io/" target="_blank">Cypress</a> and
<a href="https://on.cypress.io/component" target="_blank"
>Cypress Component Testing</a
>.
<br />
More instructions are available in <code>README.md</code>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a target="_blank" href="https://pinia.vuejs.org/">Pinia</a>,
<a target="_blank" href="https://router.vuejs.org/">Vue Router</a>,
<a target="_blank" href="https://test-utils.vuejs.org/">Vue Test Utils</a>, and
<a target="_blank" href="https://github.com/vuejs/devtools">Vue Dev Tools</a>. If you need more
resources, we suggest paying
<a target="_blank" href="https://github.com/vuejs/awesome-vue">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a target="_blank" href="https://chat.vuejs.org">Vue Land</a>, our official Discord server, or
<a target="_blank" href="https://stackoverflow.com/questions/tagged/vue.js">StackOverflow</a>.
You should also subscribe to
<a target="_blank" href="https://news.vuejs.org">our mailing list</a> and follow the official
<a target="_blank" href="https://twitter.com/vuejs">@vuejs</a>
twitter account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a target="_blank" href="https://vuejs.org/sponsor/">becoming a sponsor</a>.
</WelcomeItem>
</template>
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import './assets/main.css' const app = createApp(App)
app.use(router)
createApp(App).mount('#app') app.use(ElementPlus)
app.mount('#app')
\ No newline at end of file
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
import Products from '../views/Products.vue'
import News from '../views/News.vue'
import Community from '../views/Community.vue'
import Login from '../views/Login.vue'
import Register from '../views/Register.vue'
import ProductDetail from '../views/ProductDetail.vue'
import NewsDetail from '../views/NewsDetail.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/products', component: Products },
{ path: '/products/:id', component: ProductDetail },
{ path: '/news', component: News },
{ path: '/news/:id', component: NewsDetail },
{ path: '/community', component: Community },
{ path: '/login', component: Login },
{ path: '/register', component: Register },
]
const router = createRouter({
history: createWebHistory(),
routes,
})
// 登录拦截
router.beforeEach((to, from, next) => {
const isLogin = localStorage.getItem('isLogin') === 'true'
if (!isLogin && to.path !== '/login' && to.path !== '/register') {
next('/login')
} else {
next()
}
})
export default router
\ No newline at end of file
<template>
<el-card class="reserve-card">
<h2>服务预约</h2>
<el-form :model="form" label-width="80px" style="max-width:400px;margin:0 auto;">
<el-form-item label="服务类型">
<el-select v-model="form.type" placeholder="请选择服务类型">
<el-option label="体检" value="体检" />
<el-option label="家政" value="家政" />
<el-option label="陪护" value="陪护" />
<el-option label="康复" value="康复" />
</el-select>
</el-form-item>
<el-form-item label="预约时间">
<el-date-picker v-model="form.date" type="datetime" placeholder="选择日期时间" style="width:100%" />
</el-form-item>
<el-form-item label="联系方式">
<el-input v-model="form.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitReserve">提交预约</el-button>
</el-form-item>
</el-form>
<el-divider />
<h3>预约历史</h3>
<el-table :data="history" size="small" v-if="history.length">
<el-table-column prop="type" label="服务类型" width="100" />
<el-table-column prop="date" label="预约时间" width="180" />
<el-table-column prop="phone" label="联系方式" width="120" />
</el-table>
<div v-else style="color:#888;text-align:center;">暂无预约记录</div>
</el-card>
</template>
<script setup>
import { ref } from 'vue'
const form = ref({ type: '', date: '', phone: '' })
const history = ref(JSON.parse(localStorage.getItem('reserveHistory') || '[]'))
function submitReserve() {
if (!form.value.type || !form.value.date || !form.value.phone) {
window.$message?.warning('请填写完整信息')
return
}
history.value.unshift({ ...form.value })
history.value = history.value.slice(0, 10)
localStorage.setItem('reserveHistory', JSON.stringify(history.value))
form.value = { type: '', date: '', phone: '' }
window.$message?.success('预约成功!')
}
</script>
<style scoped>
.reserve-card {
max-width: 600px;
margin: 40px auto;
border-radius: 20px;
box-shadow: 0 8px 32px #b0c4de80;
padding: 32px 24px;
}
</style>
\ No newline at end of file
<template>
<el-card class="community-card">
<h2 class="community-title">智慧养老社区</h2>
<img src="/assets/community-event.jpg" alt="社区活动" class="community-img" />
<p>欢迎大家在此交流智慧养老经验与心得!</p>
<el-divider />
<h3>家属留言</h3>
<el-form :model="msgForm" label-width="60px" style="max-width:400px;margin:0 auto;">
<el-form-item label="姓名">
<el-input v-model="msgForm.name" placeholder="您的姓名" />
</el-form-item>
<el-form-item label="留言">
<el-input v-model="msgForm.content" placeholder="写下您的关怀或祝福" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitMsg">留言</el-button>
</el-form-item>
</el-form>
<el-card class="msg-history-card" v-if="msgHistory.length">
<h4>留言墙</h4>
<ul class="msg-list">
<li v-for="(item, idx) in msgHistory" :key="idx">
<b>{{ item.name }}</b>{{ item.content }}
</li>
</ul>
</el-card>
</el-card>
</template>
<script setup>
import { ref } from 'vue'
const msgForm = ref({ name: '', content: '' })
const msgHistory = ref(JSON.parse(localStorage.getItem('msgHistory') || '[]'))
function submitMsg() {
if (!msgForm.value.name || !msgForm.value.content) {
window.$message?.warning('请填写完整留言')
return
}
msgHistory.value.unshift({ ...msgForm.value })
msgHistory.value = msgHistory.value.slice(0, 10)
localStorage.setItem('msgHistory', JSON.stringify(msgHistory.value))
msgForm.value = { name: '', content: '' }
window.$message?.success('留言成功!')
}
</script>
<style scoped>
.community-card {
max-width: 600px;
margin: 40px auto;
border-radius: 20px;
box-shadow: 0 8px 32px #b0c4de80;
padding: 32px 24px;
}
.community-title {
color: #409EFF;
margin-bottom: 16px;
font-size: 2rem;
font-weight: bold;
}
.community-img {
width: 100%;
border-radius: 16px;
margin: 16px 0;
box-shadow: 0 2px 8px #e0e0e0;
}
.msg-history-card {
margin: 24px auto 0 auto;
border-radius: 12px;
box-shadow: 0 2px 8px #e0e0e0;
padding: 16px 20px;
max-width: 400px;
}
.msg-list {
margin: 0;
padding: 0 0 0 16px;
color: #555;
font-size: 15px;
}
.msg-list li {
margin-bottom: 8px;
}
</style>
\ No newline at end of file
<template>
<div class="home-bg">
<el-card class="home-card">
<el-carousel height="220px" indicator-position="outside" class="home-carousel">
<el-carousel-item v-for="img in carouselImages" :key="img">
<img :src="img" class="carousel-img" />
</el-carousel-item>
</el-carousel>
<div class="home-content">
<img src="/assets/elderly-home.png" alt="智慧养老" class="home-img" />
<h2>欢迎来到智慧养老平台</h2>
<p>为长者提供智能、便捷、健康的生活服务。</p>
<el-button type="primary" size="large" @click="$router.push('/about')">了解更多</el-button>
<el-divider />
<el-button type="success" size="large" @click="openHealthDialog">健康打卡</el-button>
</div>
</el-card>
<el-dialog v-model="healthDialog" title="今日健康打卡" width="350px">
<el-form :model="healthForm">
<el-form-item label="步数">
<el-input v-model="healthForm.steps" placeholder="请输入今日步数" />
</el-form-item>
<el-form-item label="心情">
<el-select v-model="healthForm.mood" placeholder="请选择心情">
<el-option label="愉快" value="愉快" />
<el-option label="一般" value="一般" />
<el-option label="低落" value="低落" />
</el-select>
</el-form-item>
<el-form-item label="血压">
<el-input v-model="healthForm.bp" placeholder="如120/80" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="healthDialog=false">取消</el-button>
<el-button type="primary" @click="submitHealth">打卡</el-button>
</template>
</el-dialog>
<el-card class="health-history-card" v-if="healthHistory.length">
<h3>本周健康打卡历史</h3>
<el-table :data="healthHistory" size="small" style="margin-top:10px">
<el-table-column prop="date" label="日期" width="100" />
<el-table-column prop="steps" label="步数" width="80" />
<el-table-column prop="mood" label="心情" width="80" />
<el-table-column prop="bp" label="血压" width="100" />
</el-table>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue'
const carouselImages = [
'/assets/elderly-home.png',
'/assets/community-event.jpg',
'/assets/band1.jpg',
'/assets/band2.jpg'
]
const healthDialog = ref(false)
const healthForm = ref({ steps: '', mood: '', bp: '' })
const healthHistory = ref(JSON.parse(localStorage.getItem('healthHistory') || '[]'))
function openHealthDialog() {
healthDialog.value = true
}
function submitHealth() {
const today = new Date().toLocaleDateString()
healthHistory.value.unshift({
date: today,
steps: healthForm.value.steps,
mood: healthForm.value.mood,
bp: healthForm.value.bp
})
// 只保留最近7天
healthHistory.value = healthHistory.value.slice(0, 7)
localStorage.setItem('healthHistory', JSON.stringify(healthHistory.value))
healthDialog.value = false
healthForm.value = { steps: '', mood: '', bp: '' }
}
</script>
<style scoped>
.home-bg {
min-height: 100vh;
background: linear-gradient(135deg, #e0eafc 0%, #cfdef3 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.home-card {
max-width: 600px;
margin: 40px auto 0 auto;
text-align: center;
background: #fff;
border-radius: 24px;
box-shadow: 0 8px 32px #b0c4de80;
padding: 40px 32px 24px 32px;
}
.home-carousel {
margin-bottom: 16px;
border-radius: 16px;
overflow: hidden;
}
.carousel-img {
width: 100%;
height: 220px;
object-fit: cover;
border-radius: 16px;
}
.home-img {
width: 140px;
margin-bottom: 24px;
border-radius: 20px;
box-shadow: 0 2px 12px #e0e0e0;
}
.home-content h2 {
margin: 20px 0 12px 0;
color: #409EFF;
font-size: 2.2rem;
font-weight: bold;
}
.home-content p {
color: #666;
font-size: 1.1rem;
margin-bottom: 24px;
}
.health-history-card {
max-width: 600px;
margin: 24px auto;
border-radius: 16px;
box-shadow: 0 4px 24px #e0e0e0;
padding: 16px 24px;
}
</style>
\ No newline at end of file
<template>
<div class="form-bg">
<div class="form-center">
<el-card class="form-card">
<div class="form-logo">
<img src="/assets/elderly-home.png" alt="logo" />
<h2>智慧养老平台</h2>
</div>
<el-form :model="form" :rules="rules" ref="loginForm" label-width="80px">
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" />
</el-form-item>
<el-form-item label="验证码" prop="captcha">
<el-row :gutter="8">
<el-col :span="16">
<el-input v-model="form.captcha" maxlength="4" placeholder="请输入验证码" />
</el-col>
<el-col :span="8">
<div class="captcha-box" @click="refreshCaptcha">{{ captcha }}</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.remember">记住我</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit" size="large" style="width:100%">登录</el-button>
</el-form-item>
</el-form>
<div class="third-login">
<span>第三方登录:</span>
<el-button type="info" icon="el-icon-user" circle size="small" title="微信" />
<el-button type="primary" icon="el-icon-user" circle size="small" title="QQ" style="background:#55acee;border:none;" />
</div>
<div class="form-switch">
还没有账号?<el-link type="primary" @click="$router.push('/register')">去注册</el-link>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const form = ref({ phone: '', password: '', captcha: '', remember: false })
const rules = {
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少6位', trigger: 'blur' }
],
captcha: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ validator: (rule, value) => value === captcha.value, message: '验证码错误', trigger: 'blur' }
]
}
const loginForm = ref(null)
const captcha = ref(randomCaptcha())
function randomCaptcha() {
return Math.random().toString(36).slice(2, 6).toUpperCase()
}
function refreshCaptcha() {
captcha.value = randomCaptcha()
}
const onSubmit = () => {
loginForm.value.validate(valid => {
if (valid) {
if (form.value.remember) {
localStorage.setItem('rememberPhone', form.value.phone)
} else {
localStorage.removeItem('rememberPhone')
}
localStorage.setItem('isLogin', 'true')
alert('登录成功')
router.push('/')
}
})
}
// 自动填充记住的手机号
if (localStorage.getItem('rememberPhone')) {
form.value.phone = localStorage.getItem('rememberPhone')
form.value.remember = true
}
</script>
<style scoped>
.form-bg {
min-height: 100vh;
background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
display: flex;
align-items: center;
justify-content: center;
}
.form-center {
width: 100vw;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.form-card {
border-radius: 20px;
box-shadow: 0 8px 32px #fda08580;
padding: 32px 24px 16px 24px;
max-width: 400px;
width: 100%;
}
.form-logo {
text-align: center;
margin-bottom: 18px;
}
.form-logo img {
width: 56px;
height: 56px;
border-radius: 16px;
box-shadow: 0 2px 8px #e0e0e0;
margin-bottom: 8px;
}
.form-logo h2 {
font-size: 1.5rem;
color: #409EFF;
margin: 0;
font-weight: bold;
}
.captcha-box {
background: #f6f8fa;
border: 1px solid #dcdfe6;
border-radius: 6px;
text-align: center;
font-size: 18px;
font-weight: bold;
letter-spacing: 2px;
cursor: pointer;
user-select: none;
height: 38px;
line-height: 38px;
}
.third-login {
text-align: center;
margin: 16px 0 0 0;
color: #888;
font-size: 15px;
}
.form-switch {
text-align: center;
margin-top: 12px;
color: #888;
font-size: 15px;
}
</style>
\ No newline at end of file
此差异已折叠。
<script setup>
import { useRoute, useRouter } from 'vue-router'
import { ref, watchEffect } from 'vue'
const route = useRoute()
const router = useRouter()
const newsList = JSON.parse(localStorage.getItem('newsWithComments') || '[]')
const news = ref(null)
watchEffect(() => {
const idx = Number(route.params.id)
if (isNaN(idx) || idx < 0 || idx >= newsList.length) {
news.value = null
} else {
news.value = newsList[idx]
}
})
</script>
<template>
<el-card v-if="news">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
<img :src="news.author?.avatar" style="width:36px;height:36px;border-radius:50%;" v-if="news.author" />
<div>
<b>{{ news.author?.name || '佚名' }}</b>
<div style="color:#888;font-size:13px;">{{ news.date }}</div>
</div>
</div>
<h2>{{ news.title }}</h2>
<img v-if="news.img" :src="news.img" style="width:100%;max-height:220px;object-fit:cover;border-radius:12px;margin-bottom:16px;" />
<div v-html="news.content"></div>
<video v-if="news.video" :src="news.video" controls style="width:100%;border-radius:12px;margin:12px 0;"></video>
<el-divider />
<h3>评论区</h3>
<el-timeline v-if="news.comments && news.comments.length" style="max-height:220px;overflow:auto;">
<el-timeline-item v-for="(c, idx) in news.comments" :key="idx" :timestamp="c.time" color="#67C23A">
<div style="display:flex;align-items:flex-start;gap:8px;">
<img :src="c.avatar" style="width:28px;height:28px;border-radius:50%;object-fit:cover;" />
<div style="flex:1;">
<b>{{ c.user }}</b>
<span v-if="c.emoji" style="margin-left:4px;">{{ c.emoji }}</span>
<span v-if="c.replyTo" style="color:#409EFF;margin-left:8px;">@{{ c.replyTo }}</span>
{{ c.text }}
</div>
</div>
</el-timeline-item>
</el-timeline>
<el-empty v-else description="暂无评论" />
<el-divider />
<h3>相关文章推荐</h3>
<el-row :gutter="16">
<el-col :span="12" v-for="(item, i) in newsList.filter((n, i2) => i2 !== Number(route.params.id)).slice(0, 2)" :key="i">
<el-card @click="$router.push(`/news/${newsList.indexOf(item)}`)" style="cursor:pointer;">
<img :src="item.img" style="width:100%;height:80px;object-fit:cover;border-radius:8px;" />
<div style="margin:8px 0 0 0;"><b>{{ item.title }}</b></div>
<div style="color:#888;font-size:13px;">{{ item.date }}</div>
</el-card>
</el-col>
</el-row>
<el-button @click="$router.back()" style="margin-top:16px;">返回</el-button>
</el-card>
<el-empty v-else description="未找到该新闻" />
</template>
\ No newline at end of file
<template>
<el-card class="detail-card">
<el-carousel height="240px" indicator-position="outside">
<el-carousel-item v-for="img in images" :key="img">
<img :src="img" class="carousel-img" />
</el-carousel-item>
</el-carousel>
<!-- 嵌入B站视频 -->
<div style="margin: 20px 0; border-radius: 12px; overflow: hidden;">
<iframe
src="https://player.bilibili.com/player.html?bvid=BV1xu411272w&autoplay=0"
scrolling="no"
border="0"
frameborder="no"
framespacing="0"
allowfullscreen="true"
style="width:100%;height:400px;"
></iframe>
</div>
<h2>{{ product.name }}</h2>
<p>{{ product.desc }}</p>
<p>{{ product.price }}</p>
</el-card>
</template>
<style scoped>
.detail-card {
border-radius: 20px;
box-shadow: 0 8px 32px #b0c4de80;
max-width: 800px;
margin: 40px auto;
padding: 32px 24px;
}
.carousel-img {
width: 100%;
height: 220px;
object-fit: cover;
border-radius: 12px;
box-shadow: 0 2px 8px #e0e0e0;
}
</style>
<script setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const id = route.params.id
const product = ref({ name: '智能手环', desc: '健康监测', price: 299 })
const images = ['/assets/band1.jpg', '/assets/band2.jpg']
</script>
\ No newline at end of file
<template>
<div>
<el-row :gutter="20">
<ProductCard
v-for="item in products"
:key="item.id"
:product="item"
@click="goDetail(item.id)"
>
<template #footer>
<el-button type="primary" @click.stop="goDetail(item.id)">查看详情</el-button>
</template>
</ProductCard>
</el-row>
<el-card class="recommend-card">
<h3>智能设备推荐</h3>
<el-row :gutter="16">
<el-col :xs="24" :sm="12" :md="8" v-for="item in recommends" :key="item.id">
<el-card class="recommend-item" shadow="hover">
<img :src="item.img" style="width:100%;height:100px;object-fit:cover;border-radius:8px;">
<div style="margin:10px 0 0 0;">
<b>{{ item.name }}</b>
<p style="color:#888;font-size:13px;">{{ item.desc }}</p>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup>
import ProductCard from '../components/ProductCard.vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const products = [
{ id: 1, name: '智能手环', desc: '健康监测', price: 299, img: '/assets/band.jpg' },
{ id: 2, name: '智能床垫', desc: '睡眠监测', price: 999, img: '/assets/mat.jpg' },
{ id: 3, name: '健康手杖', desc: '定位报警', price: 199, img: '/assets/stick.jpg' },
{ id: 4, name: '智能药盒', desc: '定时提醒', price: 149, img: '/assets/box.jpg' },
]
const goDetail = id => router.push(`/products/${id}`)
const recommends = [
{ id: 101, name: '智能血压计', desc: '实时监测血压,异常提醒', img: '/assets/band1.jpg' },
{ id: 102, name: '跌倒报警器', desc: '自动检测跌倒并报警', img: '/assets/band2.jpg' },
{ id: 103, name: '智能体温计', desc: '精准测温,数据同步', img: '/assets/box.jpg' }
]
</script>
<style scoped>
.recommend-card {
margin: 32px auto;
max-width: 900px;
border-radius: 16px;
box-shadow: 0 4px 24px #e0e0e0;
padding: 24px 16px;
}
.recommend-item {
margin-bottom: 16px;
border-radius: 12px;
text-align: center;
}
</style>
\ No newline at end of file
<template>
<div class="form-bg">
<div class="form-center">
<el-card class="form-card">
<div class="form-logo">
<img src="/assets/elderly-home.png" alt="logo" />
<h2>智慧养老平台</h2>
</div>
<el-form :model="form" :rules="rules" ref="registerForm" label-width="80px">
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" />
</el-form-item>
<el-form-item label="确认密码" prop="confirm">
<el-input v-model="form.confirm" type="password" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit" size="large" style="width:100%">注册</el-button>
</el-form-item>
</el-form>
<div class="form-switch">
已有账号?<el-link type="primary" @click="$router.push('/login')">去登录</el-link>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const form = ref({ phone: '', password: '', confirm: '' })
const rules = {
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少6位', trigger: 'blur' }
],
confirm: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{ validator: (rule, value) => value === form.value.password, message: '两次密码不一致', trigger: 'blur' }
]
}
const registerForm = ref(null)
const onSubmit = () => {
registerForm.value.validate(valid => {
if (valid) {
localStorage.setItem('isLogin', 'true')
alert('注册成功')
router.push('/')
}
})
}
</script>
<style scoped>
.form-bg {
min-height: 100vh;
background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
display: flex;
align-items: center;
justify-content: center;
}
.form-center {
width: 100vw;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.form-card {
border-radius: 20px;
box-shadow: 0 8px 32px #fda08580;
padding: 32px 24px 16px 24px;
max-width: 400px;
width: 100%;
}
.form-logo {
text-align: center;
margin-bottom: 18px;
}
.form-logo img {
width: 56px;
height: 56px;
border-radius: 16px;
box-shadow: 0 2px 8px #e0e0e0;
margin-bottom: 8px;
}
.form-logo h2 {
font-size: 1.5rem;
color: #409EFF;
margin: 0;
font-weight: bold;
}
.form-switch {
text-align: center;
margin-top: 12px;
color: #888;
font-size: 15px;
}
</style>
\ No newline at end of file
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()],
server: { server: {
host: true, port: 5173
allowedHosts: true }
},
plugins: [vue()]
}) })
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册