fix: 模板项目

This commit is contained in:
Guwan 2025-07-21 00:17:24 +08:00
parent d8c8895346
commit 598cb1afbd
11 changed files with 997 additions and 28 deletions

View File

@ -1,32 +1,6 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>

View File

@ -1,17 +1,42 @@
import Vue from 'vue'
import Vuex from 'vuex'
import user from "@/store/modules/user";
import test from "@/store/modules/test";
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
getters: {
doubleCount: state => state.count * 2
},
mutations: {
increment(state) {
state.count++
},
reset(state) {
state.count = 0
},
setValue(state, value) {
state.count = value
}
},
actions: {
asyncIncrement({commit}) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
modules: {
user,
test
}
})

32
src/store/modules/test.js Normal file
View File

@ -0,0 +1,32 @@
export default {
namespaced: true,
state: {
count: 0
},
getters: {
doubleCount: state => state.count * 2
},
mutations: {
increment(state) {
state.count++
},
reset(state) {
state.count = 0
},
setValue(state, value) {
state.count = value
}
},
actions: {
asyncIncrement({commit}) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
}

14
src/store/modules/user.js Normal file
View File

@ -0,0 +1,14 @@
export default {
namespaced: true,
state: {
userList: []
},
getters: {
userList: state => state.userList
},
mutations: {
},
actions: {},
modules: {}
}

View File

@ -0,0 +1,72 @@
<template>
<div class="child">
<h3>子组件</h3>
<p>从父组件接收的计数: {{ count }}</p>
<!-- 输入框值改变时触发事件 -->
<div class="input-group">
<input
v-model.number="inputValue"
type="number"
placeholder="输入新的值"
/>
<button @click="updateValue">更新值</button>
</div>
</div>
</template>
<script>
export default {
name: 'ChildComponent',
props: {
count: {
type: Number,
required: true
}
},
data() {
return {
inputValue: 0
}
},
methods: {
updateValue() {
//
this.$emit('update', this.inputValue)
}
}
}
</script>
<style scoped>
.child {
border: 1px dashed #3f51b5;
border-radius: 5px;
padding: 15px;
margin-top: 20px;
}
.input-group {
margin-top: 10px;
display: flex;
}
input {
padding: 5px;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 3px;
}
button {
padding: 5px 10px;
background-color: #3f51b5;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,133 @@
<template>
<div class="parent">
<h2>父组件</h2>
<!-- 显示Vuex状态 -->
<p>计数: {{ count }}</p>
<p>双倍计数: {{ doubleCount }}</p>
<!-- 按钮调用Vuex actions和mutations -->
<div class="buttons">
<button @click="increment">增加(Mutation)</button>
<button @click="asyncIncrement">异步增加(Action)</button>
<button @click="reset">重置</button>
</div>
<!-- 子组件传递props并监听事件 -->
<child-component
:count="count"
@update="handleUpdate"
/>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
import ChildComponent from './ChildComponent.vue'
export default {
name: 'ParentComponent',
components: {
ChildComponent
},
computed: {
// storestate
...mapState({
count: state => state.count
}),
// storegetters
...mapGetters([
'doubleCount'
]),
// 访state ()
directCount() {
return this.$store.state.count
},
// 访getters ()
directDoubleCount() {
return this.$store.getters.doubleCount
}
},
//
watch: {
testCount(newValue) {
console.log(`计数更新为: ${newValue}`)
}
},
//
created() {
console.log('组件已创建')
},
mounted() {
console.log('组件已挂载到DOM')
},
methods: {
// storemutations
...mapMutations({
increment: 'increment',
reset: 'reset'
}),
// storeactions
...mapActions([
'asyncIncrement'
]),
// mutation ()
directIncrement() {
this.$store.commit('increment')
},
// mutation ()
directReset() {
this.$store.commit('reset')
},
// action ()
directAsyncIncrement() {
this.$store.dispatch('asyncIncrement')
},
//
handleUpdate(value) {
console.log('子组件更新:', value)
// storemutation
this.$store.commit('setValue', value)
}
}
}
</script>
<style scoped>
.parent {
border: 1px solid #42b983;
border-radius: 5px;
padding: 20px;
margin: 20px;
}
.buttons {
margin: 15px 0;
}
button {
margin-right: 10px;
padding: 5px 10px;
background-color: #42b983;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,398 @@
/* ===== 布局样式 ===== */
/*
* 居中容器
* 用途创建一个水平居中的容器有最大宽度限制
* 适用场景页面主内容区域卡片容器等
*/
.container {
max-width: 1200px; /* 最大宽度限制 */
margin: 0 auto; /* 水平居中 */
padding: 0 20px; /* 两侧内边距,确保小屏幕有边距 */
}
/*
* Flexbox布局 - 水平排列
* 用途创建灵活的水平排列布局
* 适用场景导航菜单工具栏卡片列表等
*/
.flex-row {
display: flex; /* 启用弹性布局 */
flex-direction: row; /* 水平方向排列(默认) */
flex-wrap: wrap; /* 允许换行 */
gap: 20px; /* 项目间距 */
}
/*
* Flexbox布局 - 垂直排列
* 用途创建灵活的垂直排列布局
* 适用场景侧边栏菜单表单布局等
*/
.flex-column {
display: flex;
flex-direction: column; /* 垂直方向排列 */
gap: 15px; /* 项目间距 */
}
/*
* 网格布局 - 自动响应式列
* 用途创建自动调整的多列布局
* 适用场景图片库产品列表卡片网格等
*/
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); /* 自动填充列最小250px */
gap: 20px; /* 网格间距 */
}
/*
* 两列布局 - 侧边栏和主内容
* 用途创建经典的侧边栏+主内容布局
* 适用场景管理界面博客布局等
*/
.two-column-layout {
display: grid;
grid-template-columns: 250px 1fr; /* 固定宽度侧边栏 + 自适应主内容 */
gap: 30px; /* 列间距 */
}
/* ===== 定位样式 ===== */
/*
* 绝对居中定位
* 用途将元素精确居中于父容器
* 适用场景模态框加载指示器弹出提示等
*/
.absolute-center {
position: absolute; /* 绝对定位 */
top: 50%; /* 顶部50% */
left: 50%; /* 左侧50% */
transform: translate(-50%, -50%); /* 向左上偏移自身50%,实现精确居中 */
}
/*
* 固定顶部导航
* 用途创建始终固定在视口顶部的导航栏
* 适用场景网站主导航固定标题栏等
*/
.fixed-top {
position: fixed; /* 固定定位 */
top: 0; /* 顶部对齐 */
left: 0; /* 左侧对齐 */
right: 0; /* 右侧对齐,确保宽度填满 */
z-index: 1000; /* 确保显示在其他内容上方 */
background-color: white; /* 背景色 */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 轻微阴影 */
}
/*
* 粘性定位
* 用途元素在滚动到特定位置时固定
* 适用场景分类标题导航菜单等
*/
.sticky-element {
position: sticky; /* 粘性定位 */
top: 20px; /* 距顶部20px时固定 */
z-index: 100; /* 层叠顺序 */
}
/* ===== 卡片和容器样式 ===== */
/*
* 基础卡片
* 用途创建带阴影和圆角的卡片容器
* 适用场景内容卡片信息展示产品项等
*/
.card {
background-color: white;
border-radius: 8px; /* 圆角 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 阴影效果 */
padding: 20px;
margin-bottom: 20px;
}
/*
* 带边框的容器
* 用途创建带边框的内容容器
* 适用场景分组内容设置区域等
*/
.bordered-container {
border: 1px solid #e0e0e0; /* 浅灰色边框 */
border-radius: 4px; /* 轻微圆角 */
padding: 15px;
margin-bottom: 15px;
}
/*
* 分割线
* 用途创建内容分隔线
* 适用场景分隔不同内容区域
*/
.divider {
height: 1px;
background-color: #e0e0e0; /* 浅灰色 */
margin: 20px 0; /* 上下外边距 */
width: 100%;
}
/* ===== 按钮样式 ===== */
/*
* 主要按钮
* 用途突出显示主要操作按钮
* 适用场景提交按钮确认按钮等
*/
.btn-primary {
background-color: #4caf50; /* 绿色 */
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s; /* 过渡效果 */
}
.btn-primary:hover {
background-color: #45a049; /* 深绿色 */
}
/*
* 次要按钮
* 用途次要操作按钮
* 适用场景取消按钮返回按钮等
*/
.btn-secondary {
background-color: #f5f5f5; /* 浅灰色 */
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-secondary:hover {
background-color: #e0e0e0; /* 深灰色 */
}
/*
* 危险按钮
* 用途表示危险或删除操作
* 适用场景删除按钮危险操作等
*/
.btn-danger {
background-color: #f44336; /* 红色 */
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-danger:hover {
background-color: #d32f2f; /* 深红色 */
}
/* ===== 表单样式 ===== */
/*
* 表单组
* 用途为表单元素创建一致的布局
* 适用场景登录表单注册表单等
*/
.form-group {
margin-bottom: 20px;
}
/*
* 表单标签
* 用途为表单元素创建标签
* 适用场景任何需要标签的表单元素
*/
.form-label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
/*
* 表单输入框
* 用途创建一致样式的输入框
* 适用场景文本输入邮箱输入等
*/
.form-input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
transition: border-color 0.3s;
}
.form-input:focus {
border-color: #4caf50; /* 聚焦时边框变色 */
outline: none; /* 移除默认聚焦轮廓 */
}
/*
* 表单错误状态
* 用途显示输入错误状态
* 适用场景表单验证失败
*/
.form-input.error {
border-color: #f44336; /* 红色边框 */
}
.error-message {
color: #f44336;
font-size: 14px;
margin-top: 5px;
}
/* ===== 响应式工具 ===== */
/*
* 响应式隐藏类
* 用途在不同屏幕尺寸隐藏元素
* 适用场景响应式设计移动优先设计
*/
@media (max-width: 768px) {
.hide-on-mobile {
display: none !important;
}
}
@media (min-width: 769px) {
.hide-on-desktop {
display: none !important;
}
}
/*
* 响应式文本对齐
* 用途在不同屏幕尺寸改变文本对齐方式
* 适用场景响应式排版
*/
@media (max-width: 768px) {
.text-center-mobile {
text-align: center !important;
}
}
/* ===== 辅助工具类 ===== */
/*
* 文本截断 (单行)
* 用途截断过长文本并显示省略号
* 适用场景卡片标题列表项等
*/
.text-truncate {
white-space: nowrap; /* 禁止换行 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis; /* 显示省略号 */
}
/*
* 文本截断 (多行)
* 用途截断多行文本并显示省略号
* 适用场景卡片描述文章摘要等
*/
.text-truncate-multiline {
display: -webkit-box;
-webkit-line-clamp: 3; /* 显示行数 */
-webkit-box-orient: vertical;
overflow: hidden;
}
/*
* 图片响应式
* 用途使图片自适应容器宽度
* 适用场景文章图片产品图片等
*/
.img-responsive {
max-width: 100%; /* 最大宽度为容器宽度 */
height: auto; /* 保持宽高比 */
display: block; /* 块级显示 */
}
/*
* 圆形图片
* 用途创建圆形图片
* 适用场景头像图标等
*/
.img-circle {
border-radius: 50%; /* 圆形 */
object-fit: cover; /* 保持宽高比并填充 */
}
/*
* 阴影效果
* 用途为元素添加阴影
* 适用场景卡片按钮弹窗等
*/
.shadow-sm {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* 小阴影 */
}
.shadow {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* 中等阴影 */
}
.shadow-lg {
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); /* 大阴影 */
}
/*
* 间距工具
* 用途快速添加外边距和内边距
* 适用场景任何需要调整间距的元素
*/
.m-0 {
margin: 0 !important;
}
.mt-1 {
margin-top: 0.25rem !important;
}
.mb-2 {
margin-bottom: 0.5rem !important;
}
.ml-3 {
margin-left: 1rem !important;
}
.mr-4 {
margin-right: 1.5rem !important;
}
.mx-auto {
margin-left: auto !important;
margin-right: auto !important;
}
.p-0 {
padding: 0 !important;
}
.pt-1 {
padding-top: 0.25rem !important;
}
.pb-2 {
padding-bottom: 0.5rem !important;
}
.pl-3 {
padding-left: 1rem !important;
}
.pr-4 {
padding-right: 1.5rem !important;
}

View File

@ -0,0 +1,36 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
getters: {
doubleCount: state => state.count * 2
},
mutations: {
increment(state) {
state.count++
},
reset(state) {
state.count = 0
},
setValue(state, value) {
state.count = value
}
},
actions: {
asyncIncrement({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
})

View File

@ -1,5 +1,128 @@
<!--
<template>
<div class="about">
<h1>This is an about page</h1>
<div class="login-container">
<div class="login-box">
<h2>登录</h2>
&lt;!&ndash; 用户名输入框 &ndash;&gt;
<div class="input-group">
<label for="username">用户名</label>
<input type="text" id="username" placeholder="请输入用户名" required />
</div>
&lt;!&ndash; 密码输入框 &ndash;&gt;
<div class="input-group">
<label for="password">密码</label>
<input type="password" id="password" placeholder="请输入密码" required />
</div>
&lt;!&ndash; 提交按钮 &ndash;&gt;
<button type="submit" class="login-btn">登录</button>
&lt;!&ndash; 忘记密码和注册链接 &ndash;&gt;
<div class="footer">
<a href="#">忘记密码?</a>
<a href="#">没有账户? 注册</a>
</div>
</div>
</div>
</template>
<style scoped>
/* 容器居中并覆盖整个屏幕 */
.login-container {
display: flex; /* 使用flex布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 满屏高度 */
background-color: #f5f5f5; /* 背景颜色 */
}
/* 登录框的样式 */
.login-box {
background: #fff; /* 背景白色 */
padding: 30px; /* 内边距 */
border-radius: 8px; /* 圆角边框 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 阴影效果 */
width: 300px; /* 固定宽度 */
text-align: center; /* 内容居中 */
}
/* 标题样式 */
.login-box h2 {
margin-bottom: 20px; /* 下边距 */
font-family: 'Arial', sans-serif; /* 字体 */
color: #333; /* 字体颜色 */
}
/* 输入框的通用样式 */
.input-group {
margin-bottom: 20px; /* 每个输入框下方的间距 */
text-align: left; /* 标签左对齐 */
}
.input-group label {
font-size: 14px; /* 标签字体大小 */
color: #555; /* 标签字体颜色 */
display: block; /* 标签显示为块级元素 */
margin-bottom: 5px; /* 标签与输入框之间的间距 */
}
.input-group input {
width: 100%; /* 输入框宽度100% */
padding: 10px; /* 内边距 */
border: 1px solid #ccc; /* 边框 */
border-radius: 4px; /* 圆角边框 */
box-sizing: border-box; /* 包含内边距和边框在内的宽度计算 */
font-size: 16px; /* 输入框字体大小 */
outline: none; /* 去除点击时的边框 */
}
.input-group input:focus {
border-color: #66afe9; /* 聚焦时边框颜色 */
box-shadow: 0 0 5px rgba(102, 175, 233, 0.6); /* 聚焦时阴影效果 */
}
/* 登录按钮样式 */
.login-btn {
background-color: #4CAF50; /* 按钮背景色 */
color: white; /* 按钮文字颜色 */
padding: 10px 20px; /* 按钮内边距 */
border: none; /* 去掉默认边框 */
border-radius: 4px; /* 圆角 */
width: 100%; /* 按钮宽度100% */
cursor: pointer; /* 鼠标悬停时显示为指针 */
font-size: 16px; /* 按钮文字大小 */
margin-top: 10px; /* 按钮顶部间距 */
}
.login-btn:hover {
background-color: #45a049; /* 按钮悬停时背景色 */
}
/* 底部的链接样式 */
.footer {
margin-top: 15px; /* 上边距 */
}
.footer a {
color: #007bff; /* 链接颜色 */
text-decoration: none; /* 去掉下划线 */
font-size: 14px; /* 链接文字大小 */
margin: 0 10px; /* 链接间距 */
}
.footer a:hover {
text-decoration: underline; /* 悬停时显示下划线 */
}
</style>
-->
<template>
<Father></Father>
</template>
<script setup lang="ts">
import Father from "@/views/Father.vue";
</script>

View File

@ -0,0 +1,72 @@
<template>
<div class="child">
<h3>子组件</h3>
<p>从父组件接收的计数: {{ count }}</p>
<!-- 输入框值改变时触发事件 -->
<div class="input-group">
<input
v-model.number="inputValue"
type="number"
placeholder="输入新的值"
/>
<button @click="updateValue">更新值</button>
</div>
</div>
</template>
<script>
export default {
name: 'ChildComponent',
props: {
count: {
type: Number,
required: true
}
},
data() {
return {
inputValue: 0
}
},
methods: {
updateValue() {
//
this.$emit('update', this.inputValue)
}
}
}
</script>
<style scoped>
.child {
border: 1px dashed #3f51b5;
border-radius: 5px;
padding: 15px;
margin-top: 20px;
}
.input-group {
margin-top: 10px;
display: flex;
}
input {
padding: 5px;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 3px;
}
button {
padding: 5px 10px;
background-color: #3f51b5;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
</style>

90
src/views/Father.vue Normal file
View File

@ -0,0 +1,90 @@
<template>
<div class="parent">
<h2>父组件</h2>
<!-- 显示Vuex状态 -->
<p>计数: {{ count }}</p>
<p>双倍计数: {{ doubleCount }}</p>
<!-- 按钮调用Vuex actions和mutations -->
<div class="buttons">
<button @click="increment">增加(Mutation)</button>
<button @click="asyncIncrement">异步增加(Action)</button>
<button @click="reset">重置</button>
</div>
<!-- 子组件传递props并监听事件 -->
<child-component
:count="count"
@update="handleUpdate"
/>
</div>
</template>
<script>
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
import ChildComponent from './ChildComponent.vue'
export default {
name: 'Father',
components: {
ChildComponent
},
computed: {
// storestate
...mapState({
count: state => state.count
}),
// storegetters
...mapGetters([
'doubleCount'
])
},
methods: {
// storemutations
...mapMutations({
increment: 'increment',
reset: 'reset'
}),
// storeactions
...mapActions([
'asyncIncrement'
]),
// (payload)
handleUpdate(value) {
console.log('子组件更新:', value)
// storemutation
this.$store.commit('setValue', value)
}
}
}
</script>
<style scoped>
.parent {
border: 1px solid #42b983;
border-radius: 5px;
padding: 20px;
margin: 20px;
}
.buttons {
margin: 15px 0;
}
button {
margin-right: 10px;
padding: 5px 10px;
background-color: #42b983;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
</style>