前端课设Web2
App.vue
一、模板结构 (Template)
-
布局结构
- 使用 Element UI 的栅格系统
<el-row>
和<el-col>
实现 24 分栏布局。 :gutter="20"
表示列之间的水平间距为 20px。- 左侧导航栏占 4 列 (
span="4"
),右侧内容区域占 20 列 (span="20"
)。
- 使用 Element UI 的栅格系统
-
左侧导航菜单
<el-menu>
组件实现导航菜单:router
属性开启路由模式,点击菜单项会自动跳转到对应路由。default-active="$route.path"
根据当前路由路径高亮对应菜单项。- 样式属性设置背景色、文字颜色和高亮颜色。
v-for
遍历navList
生成菜单项,每个菜单项绑定路由路径 (:index="item.name"
),显示图标和文字。
-
右侧内容区域
<router-view>
是 Vue Router 的出口,根据当前路由动态渲染对应组件。.menu-right
类通过margin-left
微调内容区域位置。
二、脚本逻辑 (Script)
数据对象
navList
定义了导航菜单项的路由路径和显示名称,例如:// 点击跳转到该路由,显示“计算机器” { name: '/components/Compurte', navItem: '计算机器' }
方法
handleOpen
和handleClose
是 Element 菜单组件的事件回调,目前仅打印日志,可扩展功能(如菜单展开/折叠动画)。
三、样式设计 (Style)
- 全局样式
#app
设置字体、颜色和顶部边距。
- 内容区域调整
.menu-right
添加左外边距,避免内容与左侧导航栏重叠。
四、实现思路
五、代码
<template><div id="app"><!-- 将布局元素包裹在 el-row 中(ElementUI 规范) --><el-row :gutter="20"><!-- 左侧导航栏 --><el-col :span="4"><el-menu:default-active="$route.path"routermode="horizontal"class="el-menu-vertical-demo"@open="handleOpen"@close="handleClose"background-color="#545c64"text-color="#fff"active-text-color="#ffd04b"><el-menu-item v-for="(item, i) in navList" :key="i" :index="item.name"><template slot="title"><i class="el-icon-s-platform" /><span>{{ item.navItem }}</span></template></el-menu-item></el-menu></el-col><!-- 主内容区域 --><el-col :span="20"><router-view class="menu-right" /></el-col></el-row></div>
</template><!-- <script>export default {components:{Test,},name: 'App'
}
</script> --><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
.menu-right{margin-left:200px;}
</style><script>export default {data() { return { navList:[ {name:'/components/Compurte',navItem:'计算机器'},{name:'/components/Province',navItem:'城市选择'},{name:'/components/RegisterForm',navItem:'百度注册'},{name:'/components/Shoping',navItem:'购物界面'},{name:'/components/ClockComponent',navItem:'时间界面'},{name:'/components/StuScore',navItem:'学生成绩'},{name:'/components/Speakin',navItem:'留言界面'},{name:'/components/StuCont',navItem:'学生管理'},{name:'/components/WorkMone',navItem:'员工工资'},{name:'/components/ZutheCe',navItem:'用户注册'}, ] } },methods: {handleOpen(key, keyPath) {console.log(key, keyPath);},handleClose(key, keyPath) {console.log(key, keyPath);}}}
</script><!-- <style>.menu-right{margin-left:200px;}</style> -->
index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Compurte from '@/components/Compurte'
import Province from '@/components/Province'
import RegisterForm from '@/components/RegisterForm'
import RegisterSuccess from '@/components/RegisterSuccess'
import Shoping from '@/components/Shoping'
import ClockComponent from '@/components/ClockComponent'
import StuScore from '@/components/StuScore'
import Speakin from '@/components/Speakin'
import WorkMone from '@/components/WorkMone'
import StuCont from '@/components/StuCont'
import ZuTheCe from '@/components/ZutheCe'
Vue.use(Router)export default new Router({routes: [{path: '/',name: 'HelloWorld',component: HelloWorld},{path:'/components/Compurte',name:'Compurte',component: Compurte},{path:'/components/Province',name:'Province',component: Province},{path: '/components/RegisterForm',name: 'Register',component: RegisterForm},{path: '/RegisterSuccess',name: 'RegisterSuccess',component: RegisterSuccess},{path:'/components/Shoping',name: 'Shoping',component: Shoping},{path:'/components/ClockComponent',name: 'ClockComponent',component: ClockComponent},{path:'/components/StuScore',name: 'StuScore',component: StuScore},{path:'/components/Speakin',name: 'Speakin',component: Speakin},{path:'/components/WorkMone',neam: 'WorkMone',component: WorkMone},{path:'/components/StuCont',neam: 'StuCont',component: StuCont},{path: '/components/ZutheCe',name: 'ZutheCe',component: ZuTheCe},{path: '*',redirect: '/components/HelloWorld' // 默认重定向到注册页面}]
})
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'import axios from 'axios'Vue.use(ElementUI)
Vue.config.productionTip = false/* eslint-disable no-new */
new Vue({el: '#app',router,components: { App },template: '<App/>'
})
组件—components
Compurte.vue
一、模板结构 (Template)
-
计算器界面:
- 输入框:
<input type="text" v-model="displayValue">
绑定到displayValue
,用于显示输入和结果。 - 按钮布局:数字、运算符和功能按钮(AC、=)按计算器常见布局排列。
- 事件绑定:按钮通过
@click
调用方法:- 数字/运算符:
appendToInput
- 清空:
clear
- 计算:
calculate
- 数字/运算符:
- 输入框:
-
代码展示区块:
- 通过
v-if="showCode"
动态显示/隐藏代码。 toggleCodeDisplay
方法切换显示状态。<pre>
标签展示格式化后的codeSnippet
字符串。
- 通过
二、脚本逻辑 (Script)
-
数据对象:
currentInput
: 存储当前输入的表达式(如"1+2"
)。displayValue
: 显示在输入框中的内容。showCode
: 控制代码区块的可见性。codeSnippet
: 字符串形式的方法代码(用于演示)。
-
核心方法:
-
appendToInput(value)
:- 禁止以运算符开头(如直接输入
+
)。 - 禁止连续输入运算符(如
++
)。 - 更新
currentInput
和displayValue
。
- 禁止以运算符开头(如直接输入
-
clear()
:清空输入和显示内容。 -
calculate()
:- 替换运算符(如
%
转为/100
)。 - 使用
eval
计算结果,处理异常(如除以零)。 - 显示结果或错误信息(1秒后自动清空)。
- 替换运算符(如
-
toggleCodeDisplay()
:切换代码区块显示状态。
-
三、样式设计 (Style)
-
计算器样式:
- 黑色背景、圆角边框、白色按钮。
- 按钮悬停动画:背景色和文字颜色渐变切换。
- 特殊按钮(AC、0)加宽处理。
-
代码展示区样式:
- 灰色背景、居中对齐。
- 切换按钮样式简洁,悬停效果平滑。
四、关键逻辑分析
-
输入验证:
- 运算符不能作为第一个字符。
- 禁止连续输入运算符,确保表达式合法。
-
计算逻辑:
-
eval
的使用:虽然方便但存在安全风险,此处因场景简单而接受。 - 百分比处理:
%
替换为/100
,实现50% → 0.5
。
-
-
错误处理:
- 异常捕获后显示“错误”,1秒后自动清空。
setTimeout(this.clear, 1000)
确保this
指向正确。
五、代码
<template><div><div id="calculator"><input type="text" id="txt" v-model="displayValue" readonly><br><input type="button" id="AC" value="AC" @click="clear"><input type="button" @click="appendToInput('/')" value="/"><input type="button" @click="appendToInput('%')" value="%"><br><input type="button" id="Seven" @click="appendToInput('7')" value="7"><input type="button" id="Eight" @click="appendToInput('8')" value="8"><input type="button" id="Nine" @click="appendToInput('9')" value="9"><input type="button" @click="appendToInput('+')" value="+"><br><input type="button" id="Four" @click="appendToInput('4')" value="4"><input type="button" id="Five" @click="appendToInput('5')" value="5"><input type="button" id="Six" @click="appendToInput('6')" value="6"><input type="button" @click="appendToInput('-')" value="-"><br><input type="button" id="One" @click="appendToInput('1')" value="1"><input type="button" id="Two" @click="appendToInput('2')" value="2"><input type="button" id="Three" @click="appendToInput('3')" value="3"><input type="button" @click="appendToInput('*')" value="*"><br><input type="button" id="Zero" @click="appendToInput('0')" value="0"><input type="button" id="Dot" @click="appendToInput('.')" value="."><input type="button" @click="calculate" value="="></div><div id="code-div"><!-- 调试用代码展示区块 --><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }}</button></div></div></template><script>
export default {name: 'Calculator',data() {return {currentInput: '',displayValue: '',showCode: false,codeSnippet:`appendToInput(value) {// 如果是运算符且当前输入为空,则不处理if (['+', '-', '*', '/', '%'].includes(value) && this.currentInput === '') {return;}// 如果当前输入已经是运算符,不允许连续输入运算符lastChar = this.currentInput.slice(-1);if (['+', '-', '*', '/', '%'].includes(lastChar) && ['+', '-', '*', '/', '%'].includes(value)) {return;}// 添加新输入this.currentInput += value;this.displayValue = this.currentInput;},clear() {this.currentInput = '';this.displayValue = '';},calculate() {try {// 替换显示的运算符为JavaScript可识别的运算符let expression = this.currentInput.replace(/×/g, '*').replace(/÷/g, '/').replace(/%/g, '/100');// 使用eval计算表达式result = eval(expression);// 检查结果是否为有限数字if (!isFinite(result)) {throw new Error('无效的计算');}// 显示结果this.displayValue += '=';this.displayValue += result;} catch (error) {this.displayValue = '错误';setTimeout(this.clear, 1000);延时函数}}`}},methods: {appendToInput(value) {// 如果是运算符且当前输入为空,则不处理if (['+', '-', '*', '/', '%'].includes(value) && this.currentInput === '') {return;}// 如果当前输入已经是运算符,不允许连续输入运算符const lastChar = this.currentInput.slice(-1);if (['+', '-', '*', '/', '%'].includes(lastChar) && ['+', '-', '*', '/', '%'].includes(value)) {return;}// 添加新输入this.currentInput += value;this.displayValue = this.currentInput;},clear() {this.currentInput = '';this.displayValue = '';},calculate() {try {// 替换显示的运算符为JavaScript可识别的运算符let expression = this.currentInput.replace(/×/g, '*').replace(/÷/g, '/').replace(/%/g, '/100');// 使用eval计算表达式let result = eval(expression);// 检查结果是否为有限数字if (!isFinite(result)) {throw new Error('无效的计算');}// 显示结果this.displayValue += '=';this.displayValue += result;} catch (error) {this.displayValue = '错误';setTimeout(this.clear, 1000);}},toggleCodeDisplay() {this.showCode = !this.showCode;}}
}
</script><style scoped>
#calculator {margin: 0 auto;border: #000 2px solid;border-radius: 15px 15px 15px 15px;width: 300px;height: 460px;background: #000000;
}#hiddentxt {height: 100px;
}#txt {width: 88%;height: 80px;border-radius: 12px 12px 12px 12px;font-size: 24px;font-weight: bold;background: #000;color: white;border: #fff 5px solid;margin-top: 15px;margin-left:12px;font-family: "Comic Sans MS", "Leelawadee UI";
}input[type=button] {width: 56px;height: 56px;margin-top: 10px;margin-left: 13px;border-radius: 10px;font-size: 20px;font-weight: bold;background: #fff;font-family:"Comic Sans MS";
}#Zero, #AC {width: 129px;
}input[type=button]:hover {color: #fff;background: #000;animation-name: mybutton;animation-duration: 0.8s;animation-timing-function: ease-in-out;
}@keyframes mybutton {0% {background: #fff;color: #000;}100% {background:#000;color: #fff;}
}button {padding: 10px 24px;margin: 0 10px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}#code-div {margin: 0 auto;width:600px;
}.code-section {margin-top: 20px;padding: 15px;background: #f5f5f5;border-radius: 4px;
}.code-toggle {display: block;margin: 20px auto 0;background: #eef;
}
</style>
Provinece.vue
一、模板部分解析
<template><div><!-- 级联选择器容器 --><div class="cascader-container"><form><div class="select-group">省份:<!-- 省份选择框 --><select v-model="selectedProvince" @change="loadCity" required><option value="">--请选择--</option><!-- 遍历省份数据生成选项 --><option v-for="province in provinces" :key="province.name" :value="province">{{ province.name }}</option></select>城市:<!-- 城市选择框 --><select v-model="selectedCity"><option value="">--请选择--</option><!-- 遍历城市数据生成选项 --><option v-for="city in cities" :key="city" :value="city">{{ city }}</option></select></div></form></div><!-- 代码展示区域 --><div id="code-div"><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre> <!-- 显示代码片段 --></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }} <!-- 动态按钮文本 --></button></div></div>
</template>
二、脚本部分解析
<script>
export default {name: 'ProvinceCityCascader',data() {return {selectedProvince: '', // 当前选中的省份对象selectedCity: '', // 当前选中的城市cities: [], // 当前省份对应的城市列表provinces: [ // 硬编码的省份数据{"name":"广东省","info":['广州市','深圳市','佛山市', '珠海市']},{"name":"四川省","info":['成都市','德阳市','绵阳市','广元市']},{"name":"云南省","info":['昆明市','大理市','丽江市']},{"name":"贵州省","info":['贵阳市','遵义市']}],showCode: false, // 控制代码显示状态codeSnippet:`loadCity() { // 要显示的代码片段// ...方法实现}`}},methods: {loadCity() {this.selectedCity = ''; // 重置城市选择if (!this.selectedProvince) {this.cities = []; // 清空城市列表return;}this.cities = this.selectedProvince.info; // 获取对应城市},toggleCodeDisplay() {this.showCode = !this.showCode; // 切换代码显示状态}}
}
</script>
三、样式部分解析
<style scoped>
/* 容器居中、选择框样式 */
.cascader-container { text-align: center; margin: 20px auto; }
select { width: 120px; padding: 8px; border-radius: 4px; }/* 代码区块样式 */
.code-section { background: #f5f5f5; padding: 15px; }
.code-toggle { background: #eef; margin: 20px auto; }
</style>
四、实验实现思路分析
-
数据驱动设计
- 使用
provinces
数组存储结构化数据,每个省份对象包含name
和城市列表info
- 通过
v-model
实现双向数据绑定:
- 使用
selectedProvince // 绑定省份选择框
selectedCity // 绑定城市选择框
级联更新机制
- 省份选择框的
@change
事件触发loadCity
方法 - 核心逻辑代码:
loadCity() {this.selectedCity = ''; // 清空已选城市this.cities = this.selectedProvince?.info || [];
}
响应式更新流程
数据流向图示
五、代码
<template><div><div class="cascader-container"><form><div class="select-group">省份:<select v-model="selectedProvince" @change="loadCity" required><option value="">--请选择--</option><option v-for="province in provinces" :key="province.name" :value="province">{{ province.name }}</option></select>城市:<select v-model="selectedCity"><option value="">--请选择--</option><option v-for="city in cities" :key="city" :value="city">{{ city }}</option></select></div></form></div><div id="code-div"><!-- 调试用代码展示区块 --><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }}</button></div></div>
</template><script>
export default {name: 'ProvinceCityCascader',data() {return {selectedProvince: '',selectedCity: '',cities: [],// JSON对象数据provinces: [{"name":"广东省","info":['广州市','深圳市','佛山市', '珠海市']},{"name":"四川省","info":['成都市','德阳市','绵阳市','广元市']},{"name":"云南省","info":['昆明市','大理市','丽江市']},{"name":"贵州省","info":['贵阳市','遵义市']}],showCode: false,codeSnippet:`loadCity() {// 重置选中的城市this.selectedCity = '';if (!this.selectedProvince) {this.cities = [];return;}// 从JSON对象中获取对应省份的城市列表this.cities = this.selectedProvince.info;}`}},methods: {loadCity() {// 重置选中的城市this.selectedCity = '';if (!this.selectedProvince) {this.cities = [];return;}// 从JSON对象中获取对应省份的城市列表this.cities = this.selectedProvince.info;},toggleCodeDisplay() {this.showCode = !this.showCode;}}
}
</script><style scoped>
.cascader-container {text-align: center;margin: 20px auto;
}.select-group {display: inline-block;
}select {width: 120px;padding: 8px;margin: 5px 10px;border: 1px solid #ddd;border-radius: 4px;font-size: 14px;
}select:focus {outline: none;border-color: #409eff;
}select:disabled {background-color: #f5f5f5;cursor: not-allowed;
}button {padding: 10px 24px;margin: 0 10px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}#code-div {margin: 0 auto;width:600px;
}.code-section {margin-top: 20px;padding: 15px;background: #f5f5f5;border-radius: 4px;
}.code-toggle {display: block;margin: 20px auto 0;background: #eef;
}
</style>
RegisterForm.vue / RegisterSucess.vue
一、 <template>
部分
二、 <script>
部分
三、 <style>
部分
四、实验实现的主要思路
-
双向绑定与响应式:
- 使用
v-model
实现表单输入与data
的自动同步。 - 错误信息(如
usernameError
)动态更新,实时反馈验证结果。
- 使用
-
前端验证策略:
- 即时验证:通过
@blur
事件在输入框失去焦点时触发验证。 - 提交时二次验证:在
submitForm
中调用所有验证方法,确保数据最终正确性。 - 密码一致性检查:在
validatePassword
中同时验证密码和确认密码。
- 即时验证:通过
-
动态数据生成:
- 年、月、日选项通过
Array.from
动态生成,避免硬编码。
- 年、月、日选项通过
-
路由与状态传递:
- 使用
vue-router
跳转到成功页面,通过query
参数传递用户名。
- 使用
-
调试辅助功能:
- 通过
toggleCodeDisplay
切换显示表单提交和验证的代码片段,便于调试或教学。
- 通过
五、代码
RegisterForm.vue
<template><div><div class="center"><img src="../assets/header1.png" /><form @submit.prevent="submitForm"><table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td class="left">用户名:</td><td><input v-model="form.username" type="text" class="inputs" @blur="validateUsername"/><div id="userId" class="red">{{ usernameError }}</div></td></tr><tr><td class="left">密码:</td><td><input v-model="form.password" type="password" class="inputs" @blur="validatePassword"/><div id="pwdId" class="red">{{ passwordError }}</div></td></tr><tr><td class="left">确认密码:</td><td><input v-model="form.repassword" type="password" class="inputs" @blur="validatePassword"/><div id="repwdId" class="red">{{ repasswordError }}</div></td></tr><tr><td class="left">性别:</td><td><div style="float:left;"><input v-model="form.sex" name="sex" type="radio" value="男" checked/>男 <input v-model="form.sex" name="sex" type="radio" value="女"/>女</div><div id="sexId" class="red">{{ sexError }}</div></td></tr><tr><td class="left">电子邮件地址</td><td><input v-model="form.email" type="text" class="inputs" @blur="validateEmail"/><div id="emailId" class="red">{{ emailError }}</div></td></tr><tr><td class="left">出生日期:</td><td><select v-model="form.year" id="year"><option v-for="year in years" :value="year" :key="year">{{ year }}</option></select>年<select v-model="form.month" id="month"><option v-for="month in months" :value="month" :key="month">{{ month }}</option></select>月<select v-model="form.day" id="day"><option v-for="day in days" :value="day" :key="day">{{ day }}</option></select>日</td></tr><tr><td> </td><td><input name="sub" type="submit" value="注册"/> <input name="cancel" type="reset" value="清除" @click="resetForm"/></td></tr></table></form></div><div id="code-div"><!-- 调试用代码展示区块 --><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }}</button></div></div>
</template><script>
export default {name: 'RegisterForm',data() {return {form: {username: '',password: '',repassword: '',sex: '男',email: '',year: new Date().getFullYear(),month: 1,day: 1},usernameError: '',passwordError: '',repasswordError: '',emailError: '',sexError: '',years: Array.from({length: 110}, (_, i) => 1900 + i),months: Array.from({length: 12}, (_, i) => 1 + i),days: Array.from({length: 31}, (_, i) => 1 + i),showCode: false,codeSnippet:` submitForm() {this.clearErrors();const isUsernameValid = this.validateUsername();const isPasswordValid = this.validatePassword();const isEmailValid = this.validateEmail();if (isUsernameValid && isPasswordValid && isEmailValid) {// 表单验证通过,跳转到成功页面this.$router.push({path: '/RegisterSuccess',query: {username: this.form.username}});}},validateUsername() {const value = this.form.username.trim();if (value === '') {this.usernameError = '用户名不能为空';return false;}if (value.length < 4 || value.length > 12) {this.usernameError = '用户名长度应为4-12个字符';return false;}if (!/^[a-zA-Z0-9_]+$/.test(value)) {this.usernameError = '用户名只能包含字母、数字和下划线';return false;}this.usernameError = '';return true;},validatePassword() {const value = this.form.password.trim();if (value === '') {this.passwordError = '密码不能为空';return false;}if (value.length < 6 || value.length > 12) {this.passwordError = '密码长度应为6-12个字符';return false;}// 验证两次密码是否一致const repwdValue = this.form.repassword.trim();if (repwdValue === '') {this.repasswordError = '请确认密码';return false;}if (value !== repwdValue) {this.repasswordError = '两次输入的密码不一致';return false;}this.passwordError = '';this.repasswordError = '';return true;},validateEmail() {const value = this.form.email.trim();if (value === '') {this.emailError = '邮箱不能为空';return false;}if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) {this.emailError = '请输入有效的邮箱地址';return false;}this.emailError = '';return true;},clearErrors() {this.usernameError = '';this.passwordError = '';this.repasswordError = '';this.emailError = '';this.sexError = '';},resetForm() {this.form = {username: '',password: '',repassword: '',sex: '男',email: '',year: new Date().getFullYear(),month: 1,day: 1};this.clearErrors();}`}},methods: {submitForm() {this.clearErrors();const isUsernameValid = this.validateUsername();const isPasswordValid = this.validatePassword();const isEmailValid = this.validateEmail();if (isUsernameValid && isPasswordValid && isEmailValid) {// 表单验证通过,跳转到成功页面this.$router.push({path: '/RegisterSuccess',query: {username: this.form.username}});}},validateUsername() {const value = this.form.username.trim();if (value === '') {this.usernameError = '用户名不能为空';return false;}if (value.length < 4 || value.length > 12) {this.usernameError = '用户名长度应为4-12个字符';return false;}if (!/^[a-zA-Z0-9_]+$/.test(value)) {this.usernameError = '用户名只能包含字母、数字和下划线';return false;}this.usernameError = '';return true;},validatePassword() {const value = this.form.password.trim();if (value === '') {this.passwordError = '密码不能为空';return false;}if (value.length < 6 || value.length > 12) {this.passwordError = '密码长度应为6-12个字符';return false;}// 验证两次密码是否一致const repwdValue = this.form.repassword.trim();if (repwdValue === '') {this.repasswordError = '请确认密码';return false;}if (value !== repwdValue) {this.repasswordError = '两次输入的密码不一致';return false;}this.passwordError = '';this.repasswordError = '';return true;},validateEmail() {const value = this.form.email.trim();if (value === '') {this.emailError = '邮箱不能为空';return false;}if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) {this.emailError = '请输入有效的邮箱地址';return false;}this.emailError = '';return true;},clearErrors() {this.usernameError = '';this.passwordError = '';this.repasswordError = '';this.emailError = '';this.sexError = '';},resetForm() {this.form = {username: '',password: '',repassword: '',sex: '男',email: '',year: new Date().getFullYear(),month: 1,day: 1};this.clearErrors();},toggleCodeDisplay() {this.showCode = !this.showCode;}}
}
</script><style scoped>
/* 保留原有的样式 */
.center {float: none;width: 503px;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;
}.inputs {border: 1px solid #333;width: 120px;float: left;
}.left {width: 120px;text-align: right;padding-right: 5px;
}.red {color: #ff0000;padding-left: 10px;font-size: 12px;
}button {padding: 10px 24px;margin: 0 10px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}#code-div {margin: 0 auto;width:600px;
}.code-section {margin-top: 20px;padding: 15px;background: #f5f5f5;border-radius: 4px;
}.code-toggle {display: block;margin: 20px auto 0;background: #eef;
}
</style>
RegisterSucess.vue
<template><div class="success-page"><img src="../assets/top.png" /><br /><h1>欢迎您{{ username }}注册成为百度用户!</h1></div>
</template><script>
export default {name: 'RegisterSuccess',data() {return {username: ''}},created() {// 从路由参数中获取用户名this.username = this.$route.query.username || '';}
}
</script><style scoped>
.success-page {margin: 0;text-align: center;font-size: 24px;font-weight: bold;
}
</style>
shopping.vue
一、模板部分(Template)
二、脚本部分(Script)
三、样式部分(Style)
四、实验思路分析
五、代码
<template><div><div class="content"><div class="logo"><img :src="require('../assets/shoplogo.png')" alt="当当购物车"><span @click="closePlan">关闭</span></div><div class="cartList"><ul v-for="(item, index) in cartItems" :key="index"><li><p @click="moveToCollection(index)">移入收藏</p><p @click="deleteItem(index)">删除</p></li><li>¥{{ item.price.toFixed(2) }}</li><li><input type="button" name="minus" value="-" @click="decreaseQuantity(index)"><input type="text" name="amount" v-model.number="item.quantity" @change="updatePrice(index)"><input type="button" name="plus" value="+" @click="increaseQuantity(index)"></li><li>¥<input type="text" name="price" :value="(item.price * item.quantity).toFixed(2)" readonly></li></ul><ol><li>¥{{ totalPrice.toFixed(2) }}</li><li><span @click="checkout">结 算</span></li></ol></div></div><div id="code-div"><!-- 调试用代码展示区块 --><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }}</button></div></div>
</template><script>
export default {name: 'DangdangCart',data() {return {cartItems: [{ price: 159.00, quantity: 1 },{ price: 132.00, quantity: 1 }],showCode: false,codeSnippet:`closePlan() {// 关闭购物车逻辑console.log('关闭购物车')this.$emit('close-cart')},moveToCollection(index) {// 移入收藏夹逻辑const item = this.cartItems[index]console.log('将商品移入收藏夹:', item)this.$emit('add-to-favorites', item)},deleteItem(index) {// 删除商品逻辑this.cartItems.splice(index, 1)},increaseQuantity(index) {this.cartItems[index].quantity++this.updatePrice(index)},decreaseQuantity(index) {if (this.cartItems[index].quantity > 1) {this.cartItems[index].quantity--this.updatePrice(index)}},updatePrice(index) {// 确保数量是数字且大于0if (isNaN(this.cartItems[index].quantity) || this.cartItems[index].quantity < 1) {this.cartItems[index].quantity = 1}},checkout() {// 结算逻辑console.log('结算总价:', this.totalPrice)this.$emit('checkout', {items: this.cartItems,total: this.totalPrice})}`}},computed: {totalPrice() {return this.cartItems.reduce((total, item) => {return total + (item.price * item.quantity)}, 0)}},methods: {closePlan() {// 关闭购物车逻辑if (window.confirm('确定要关闭购物车吗?')) {console.log('关闭购物车')this.$emit('close-cart')}},moveToCollection(index) {// 移入收藏夹逻辑const item = this.cartItems[index]console.log('将商品移入收藏夹:', item)this.$emit('add-to-favorites', item)},deleteItem(index) {// 删除商品逻辑// 添加确认对话框if (window.confirm('确定要删除该商品吗?')) {// 安全删除逻辑if (index > -1 && index < this.cartItems.length) {this.cartItems.splice(index, 1)}}},increaseQuantity(index) {this.cartItems[index].quantity++this.updatePrice(index)},decreaseQuantity(index) {if (this.cartItems[index].quantity > 1) {this.cartItems[index].quantity--this.updatePrice(index)}},updatePrice(index) {// 确保数量是数字且大于0if (isNaN(this.cartItems[index].quantity) || this.cartItems[index].quantity < 1) {this.cartItems[index].quantity = 1}},checkout() {// 结算逻辑const itemsInfo = this.cartItems.map((item, idx) => `商品${idx + 1}: ¥${item.price.toFixed(2)} × ${item.quantity}件`).join('\n')window.alert(`购买商品明细:\n${itemsInfo}\n\n总计:¥${this.totalPrice.toFixed(2)}`)this.$emit('checkout', {items: this.cartItems,total: this.totalPrice})},toggleCodeDisplay() {this.showCode = !this.showCode;}}
}
</script><style scoped>
/* 保留原有的CSS样式 */
body, ul, li, div, p, h1, h2, ol {margin: 0;padding: 0;
}
ul, li, ol {list-style: none;
}
.content {width: 910px;margin: 0 auto;font-family: "微软雅黑";
}
.logo {margin: 10px 0;
}
.logo span {display: inline-block;width: 60px;height: 30px;line-height: 30px;font-size: 14px;background: #ff0000;color: #ffffff;text-align: center;border-radius: 10px;margin-top: 5px;margin-right: 10px;cursor: pointer;font-weight: bold;
}
.cartList {background: url("../assets/shoppingBg.png") no-repeat;background-size: 100%;height: 414px;overflow: hidden;
}
.cartList ul {float: right;width: 550px;
}
.cartList ul:nth-of-type(1) {margin-top: 96px;
}
.cartList ul:nth-of-type(2) {margin-top: 70px;
}
.cartList ul li {font-family: "微软雅黑";font-size: 12px;color: #666666;text-align: center;line-height: 25px;float: right;
}
.cartList ul li input[name="price"] {border: none;background: transparent;width: 45px;text-align: center;
}
.cartList ul li input[name="amount"] {width: 45px;text-align: center;border: 1px solid #999999;border-left: none;border-right: none;height: 21px;
}
.cartList ul li input[name="minus"],
.cartList ul li input[name="plus"] {height: 25px;border: 1px #999999 solid;width: 25px;text-align: center;cursor: pointer;
}
.cartList ul li:nth-of-type(1) {width: 130px;
}
.cartList ul li:nth-of-type(2) {width: 100px;
}
.cartList ul li:nth-of-type(3) {width: 130px;
}
.cartList ul li p {cursor: pointer;
}
.cartList ol {float: right;clear: both;margin-top: 60px;
}
.cartList ol li {float: left;
}
.cartList ol li:nth-of-type(1) {color: #ff0000;width: 100px;font-weight: 900;
}
.cartList ol li span {display: inline-block;width: 80px;height: 35px;line-height: 35px;font-size: 14px;font-family: "微软雅黑";background: #ff0000;color: #ffffff;text-align: center;margin-top: 5px;margin-right: 15px;cursor: pointer;font-weight: bold;
}button {padding: 10px 24px;margin: 0 10px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}#code-div {margin: 0 auto;width:600px;
}.code-section {margin-top: 20px;padding: 15px;background: #f5f5f5;border-radius: 4px;
}.code-toggle {display: block;margin: 20px auto 0;background: #eef;
}
</style>
ClockComponent.vue
一、模板部分(Template)
二、脚本部分(Script)
三、样式部分(Style)
四、实验思路分析
五、代码
<template><div><div class="clock-container"><div class="content"><div id="title">{{ title }}</div><div id="date">{{ dateString }}</div><div id="myclock">{{ currentTime }}</div><div id="weekday">{{ weekday }}</div></div></div><div id="code-div"><!-- 调试用代码展示区块 --><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }}</button></div></div>
</template><script>
export default {data() {return {title: '当前时间',currentTime: '',weekday: '',dateString: '',showCode: false,codeSnippet:`updateTime() {const date = new Date()this.currentTime = {this.pad(date.getHours())}:{this.pad(date.getMinutes())}:{this.pad(date.getSeconds())}this.weekday = this.getChineseWeekday(date.getDay())this.dateString = {date.getFullYear()}年{this.pad(date.getMonth()+1)}月{this.pad(date.getDate())}日},pad(n) {return n < 10 ? '0' + n : n},getChineseWeekday(day) {const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']return weekdays[day]}`}},mounted() {this.updateTime()this.timer = setInterval(this.updateTime, 1000)},beforeDestroy() {clearInterval(this.timer)},methods: {updateTime() {const date = new Date()this.currentTime = `${this.pad(date.getHours())}:${this.pad(date.getMinutes())}:${this.pad(date.getSeconds())}`this.weekday = this.getChineseWeekday(date.getDay())this.dateString = `${date.getFullYear()}年${this.pad(date.getMonth()+1)}月${this.pad(date.getDate())}日`},pad(n) {return n < 10 ? '0' + n : n},getChineseWeekday(day) {const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']return weekdays[day]},toggleCodeDisplay() {this.showCode = !this.showCode;}}
}
</script><style scoped>
.clock-container {background: #000;min-height: 100vh;display: flex;align-items: center;
}.content {width: 400px;margin: 0 auto;color: #fff;text-align: center;padding: 20px 0;
}#title {font-size: 25px;margin-bottom: 10px;
}#myclock {margin-top: 20px;font-size: 60px;font-weight: 900;
}#weekday {font-size: 30px;margin: 15px 0;
}#date {font-size: 28px;margin-top: 10px;color: #ccc;
}button {padding: 10px 24px;margin: 0 10px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}#code-div {margin: 0 auto;width:600px;
}.code-section {margin-top: 20px;padding: 15px;background: #f5f5f5;border-radius: 4px;
}.code-toggle {display: block;margin: 20px auto 0;background: #eef;
}</style>
StuScore.vue
一、模板部分(Template)
-
学生信息展示
- 显示学生姓名、性别和年龄,数据来自组件的
data
属性。 - 使用双大括号
{{ }}
进行数据绑定。
- 显示学生姓名、性别和年龄,数据来自组件的
-
成绩表格
- 表头包括学期、数学、物理、化学、英语、计算机和总分。
- 使用
v-for
循环遍历grades
数组,动态生成每个学期的成绩行。 - 各科分数从
grade.scores
对象中获取,总分通过calculateTotal
方法计算。
-
代码展示交互
- 通过按钮切换显示/隐藏代码片段(
showCode
状态控制)。 - 使用
<pre>
标签保留代码格式,展示codeSnippet
中的方法代码。
- 通过按钮切换显示/隐藏代码片段(
二、脚本部分(Script)
-
数据对象(data)
name
,sex
,age
: 学生基本信息。grades
: 包含多个学期成绩的数组,每个学期有各科分数。showCode
: 控制代码块显示状态的布尔值。codeSnippet
: 存储计算总分方法的代码字符串。
-
方法(methods)
calculateTotal(scores)
:
使用Object.values
将分数对象转为数组,通过reduce
累加求和。toggleCodeDisplay()
: 切换showCode
状态,实现代码块的显示/隐藏。
三、样式部分(Style)
-
布局样式
- 成绩表居中显示(
margin: 30px auto
),宽度固定为 560px。 - 使用浮动(
float: left
)实现表头和内容的横向排列。
- 成绩表居中显示(
-
视觉设计
- 表头背景色为浅灰色(
#f6f6f6
)。 - 边框和间距提升可读性(
border: 1px solid #ddd
)。 - 按钮具有悬停动效(
transition: all 0.3s
)和圆角边框(border-radius: 4px
)。
- 表头背景色为浅灰色(
-
代码块样式
- 浅灰色背景(
#f5f5f5
)突出显示代码区域。 - 通过
margin: 0 auto
实现居中布局。
- 浅灰色背景(
四、实验思路分析
五、代码
speakin.vue
一、模板部分(Template)
-
留言表单
- 使用
<form>
包裹,提交时调用add
方法(.prevent
修饰符阻止默认提交行为) - 三个输入项通过
v-model
实现双向绑定:- 姓名绑定
username
- 标题绑定
title
- 内容绑定
content
- 姓名绑定
- 包含背景图片的表格布局
- 使用
-
留言展示区
- 使用
v-for
循环渲染messages
数组 - 显示每条留言的标题、用户名、内容和时间戳
- 使用
-
调试代码面板
- 通过
v-if="showCode"
控制代码段的显示/隐藏 - 使用
<pre>
标签保持代码格式 - 切换按钮显示当前状态(显示代码/隐藏代码)
- 通过
二、脚本部分(Script)
三、样式部分(Style)
- 背景图片系统:使用多张背景图(a_03.jpg, a_05.jpg等)构建视觉层级
- 响应式布局:
- 主容器宽度754px居中
- 代码面板宽度600px居中
- 留言内容区左侧200px留白
- 交互效果:
- 按钮悬停动画(0.3s过渡)
- 代码面板灰色背景+圆角设计
四、实验思路分析
五、代码
<template><div><div id="app"><div id="ddiv"><form @submit.prevent="add"><table width="761" border="0" align="center"><tr><td align="center" bgcolor="#F9F8EF"><table width="749" border="0" style="border-collapse: collapse"><tr><td height="57" background="../assets/images/a_03.jpg"> </td></tr><tr><td height="36" background="../assets/images/a_05.jpg"> 姓名:<input v-model="username" maxlength="64"></td></tr><tr><td height="36" background="../assets/images/a_05.jpg"> 标题:<input v-model="title" maxlength="64" size="30"></td></tr><tr><td background="../assets/images/a_05.jpg"> 内容:<textarea v-model="content" cols="60" rows="8" style="background:url(../assets/images/mrbccd.gif)"></textarea><table width="734" align="center"><tr><td height="40" align="center"><input type="submit" value="填写留言"></td></tr></table></td></tr></table></td></tr></table></form><div class="dhead"></div><div class="dcon"><div v-for="(msg, index) in messages" :key="index">标题:{{ msg.title }}<br>用户名:{{ msg.username }}<br>留言内容:{{ msg.content }}<br>时间:{{ msg.time }}</div></div></div></div><div id="code-div"><!-- 调试用代码展示区块 --><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }}</button></div></div>
</template><script>
export default {data() {return {username: '',title: '',content: '',messages: [],showCode: false,codeSnippet:`add() {if (!this.username || !this.title || !this.content) {alert('请填写所有字段')return}this.messages.unshift({username: this.username,title: this.title,content: this.content,time: new Date().toLocaleString()})this.username = ''this.title = ''this.content = ''}`}},methods: {add() {if (!this.username || !this.title || !this.content) {alert('请填写所有字段')return}this.messages.unshift({username: this.username,title: this.title,content: this.content,time: new Date().toLocaleString()})this.username = ''this.title = ''this.content = ''},toggleCodeDisplay() {this.showCode = !this.showCode;}}
}
</script><style>
.dhead {margin: 0 auto;width: 754px;height: 57px;background: url("../assets/images/b_03.jpg") no-repeat;
}
#ddiv {margin: 0 auto;width: 754px;background: url("../assets/images/a_05.jpg") repeat-y;
}
.dcon {width: 754px;padding-top: 20px;padding-left: 200px;
}
.dfoot {margin: 0 auto;width: 754px;height: 35px;background: url("../assets/images/a_07.jpg") no-repeat;
}button {padding: 10px 24px;margin: 0 10px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}#code-div {margin: 0 auto;width:600px;
}.code-section {margin-top: 20px;padding: 15px;background: #f5f5f5;border-radius: 4px;
}.code-toggle {display: block;margin: 20px auto 0;background: #eef;
}
</style>
StuCont.vue
一、模板部分(Template)
-
新增学生表单
- 包含姓名(必填)、年龄(数字校验)、性别(下拉选择)
- 使用
v-model.trim
和v-model.number
自动处理输入 - 点击「添加学生」调用
addStudent
方法
-
数据操作区
- 显示平均年龄按钮触发
showAverage
- 升降序切换按钮通过
toggleSort
实现 - 搜索框通过
v-model
绑定searchQuery
- 显示平均年龄按钮触发
-
学生列表表格
- 动态表头支持点击排序(
sortBy
方法) - 使用
filteredStudents
计算属性渲染数据 - 每行附带编辑和删除操作按钮
- 动态表头支持点击排序(
-
编辑模态框
- 通过
showEditModal
控制显示 - 编辑时深拷贝学生数据防止直接修改原数据
- 通过
-
代码展示区块
- 通过
showCode
控制调试代码的显隐
- 通过
二、脚本部分(Script)
三、样式部分(Style)
- 响应式布局使用
flex
和max-width
- 表格斑马纹效果:
tr:nth-child(even)
- 模态框居中定位:
position: fixed + flex
布局 - 操作按钮使用颜色编码:
- 绿色:添加/保存
- 蓝色:通用操作
- 黄色:编辑
- 红色:删除
四、实验思路分析
主流程架构
添加学生流程
删除学生流程
编辑学生流程
排序控制流程
搜索过滤流程
数据流动全景
五、代码
<template><div><div class="student-manager"><!-- 新增学生表单 --><div class="form-section"><h3>学生信息管理</h3><div class="form-row"><div class="form-group"><label>姓名:</label><input type="text" v-model.trim="newStudent.name" required></div><div class="form-group"><label>年龄:</label><input type="number" v-model.number="newStudent.age" min="1" required></div><div class="form-group"><label>性别:</label><select v-model="newStudent.gender"><option value="男">男</option><option value="女">女</option></select></div><button @click="addStudent" class="btn-add">添加学生</button></div></div><!-- 数据操作区 --><div class="data-section"><div class="controls"><button @click="showAverage" class="btn-action">显示平均年龄</button><button @click="toggleSort" class="btn-action">{{ sortOrder === 'asc' ? '降序排列' : '升序排列' }}</button><input type="text" v-model="searchQuery" placeholder="搜索姓名" class="search-input"></div><!-- 学生列表 --><table><thead><tr><th v-for="(col, index) in columns" :key="index" @click="sortBy(col.key)">{{ col.title }}<span v-if="sortKey === col.key">{{ sortOrder === 'asc' ? '↑' : '↓' }}</span></th><th>操作</th></tr></thead><tbody><tr v-for="(student, index) in filteredStudents" :key="student.id"><td>{{ student.name }}</td><td>{{ student.age }}</td><td>{{ student.gender }}</td><td><button @click="editStudent(index)" class="btn-edit">编辑</button><button @click="deleteStudent(index)" class="btn-delete">删除</button></td></tr></tbody></table></div><!-- 编辑模态框 --><div v-if="showEditModal" class="modal"><div class="modal-content"><h3>编辑学生信息</h3><div class="form-group"><label>姓名:</label><input type="text" v-model="editingStudent.name"></div><div class="form-group"><label>年龄:</label><input type="number" v-model.number="editingStudent.age"></div><div class="button-group"><button @click="saveEdit" class="btn-save">保存</button><button @click="cancelEdit" class="btn-cancel">取消</button></div></div></div></div><div id="code-div"><!-- 调试用代码展示区块 --><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }}</button></div></div>
</template><script>
export default {data() {return {students: [{ id: 1, name: '张三', age: 20, gender: '男' },{ id: 2, name: '李四', age: 22, gender: '男' },{ id: 3, name: '王五', age: 21, gender: '女' }],newStudent: {name: '',age: null,gender: '男'},searchQuery: '',sortKey: 'age',sortOrder: 'asc',showEditModal: false,editingStudent: {},editingIndex: -1,columns: [{ title: '姓名', key: 'name' },{ title: '年龄', key: 'age' },{ title: '性别', key: 'gender' }],showCode: false,codeSnippet: `addStudent() {if (!this.validateStudent()) returnthis.students.push({id: Date.now(),...this.newStudent})this.resetForm()},deleteStudent(index) {if (confirm('确定删除该学生吗?')) {this.students.splice(index, 1)}},editStudent(index) {this.editingStudent = { ...this.students[index] }this.editingIndex = indexthis.showEditModal = true},saveEdit() {this.students.splice(this.editingIndex, 1, this.editingStudent)this.cancelEdit()},cancelEdit() {this.showEditModal = falsethis.editingStudent = {}this.editingIndex = -1},showAverage() {const average = this.students.reduce((sum, stu) => sum + stu.age, 0) / this.students.lengthalert(学生平均年龄:{average.toFixed(2)})},toggleSort() {this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc'},sortBy(key) {if (this.sortKey === key) {this.toggleSort()} else {this.sortKey = keythis.sortOrder = 'asc'}},validateStudent() {if (!this.newStudent.name.trim()) {alert('请输入学生姓名')return false}if (!this.newStudent.age || this.newStudent.age < 1) {alert('请输入有效年龄')return false}return true},resetForm() {this.newStudent = { name: '', age: null, gender: '男' }}`}},computed: {sortedStudents() {return [...this.students].sort((a, b) => {const modifier = this.sortOrder === 'asc' ? 1 : -1return a[this.sortKey] > b[this.sortKey] ? 1 * modifier : -1 * modifier})},filteredStudents() {const query = this.searchQuery.toLowerCase()return this.sortedStudents.filter(student => student.name.toLowerCase().includes(query))}},methods: {addStudent() {if (!this.validateStudent()) returnthis.students.push({id: Date.now(),...this.newStudent})this.resetForm()},deleteStudent(index) {if (confirm('确定删除该学生吗?')) {this.students.splice(index, 1)}},editStudent(index) {this.editingStudent = { ...this.students[index] }this.editingIndex = indexthis.showEditModal = true},saveEdit() {this.students.splice(this.editingIndex, 1, this.editingStudent)this.cancelEdit()},cancelEdit() {this.showEditModal = falsethis.editingStudent = {}this.editingIndex = -1},showAverage() {const average = this.students.reduce((sum, stu) => sum + stu.age, 0) / this.students.lengthalert(`学生平均年龄:${average.toFixed(2)}`)},toggleSort() {this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc'},sortBy(key) {if (this.sortKey === key) {this.toggleSort()} else {this.sortKey = keythis.sortOrder = 'asc'}},validateStudent() {if (!this.newStudent.name.trim()) {alert('请输入学生姓名')return false}if (!this.newStudent.age || this.newStudent.age < 1) {alert('请输入有效年龄')return false}return true},resetForm() {this.newStudent = { name: '', age: null, gender: '男' }},toggleCodeDisplay() {this.showCode = !this.showCode;}}
}
</script><style scoped>
.student-manager {max-width: 800px;margin: 20px auto;padding: 20px;font-family: '微软雅黑';
}.form-section {background: #f5f5f5;padding: 20px;border-radius: 8px;margin-bottom: 20px;
}.form-row {display: flex;gap: 15px;flex-wrap: wrap;
}.form-group {flex: 1;min-width: 200px;
}.form-group label {display: block;margin-bottom: 5px;font-weight: bold;
}input, select {width: 100%;padding: 8px;border: 1px solid #ddd;border-radius: 4px;
}.controls {display: flex;gap: 10px;margin-bottom: 15px;flex-wrap: wrap;
}.btn-add, .btn-action {padding: 8px 15px;border: none;border-radius: 4px;cursor: pointer;transition: background 0.3s;
}.btn-add {background: #28a745;color: white;align-self: flex-end;
}.btn-action {background: #007bff;color: white;
}.search-input {flex: 1;max-width: 200px;padding: 8px;
}table {width: 100%;border-collapse: collapse;margin-top: 15px;
}th, td {border: 1px solid #ddd;padding: 12px;text-align: left;
}th {background: #007bff;color: white;cursor: pointer;
}tr:nth-child(even) {background: #f9f9f9;
}.btn-edit, .btn-delete {padding: 5px 10px;margin: 0 3px;border: none;border-radius: 3px;cursor: pointer;
}.btn-edit {background: #ffc107;color: black;
}.btn-delete {background: #dc3545;color: white;
}.modal {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: rgba(0,0,0,0.5);display: flex;justify-content: center;align-items: center;
}.modal-content {background: white;padding: 20px;border-radius: 8px;width: 90%;max-width: 400px;
}.button-group {margin-top: 15px;text-align: right;
}.btn-save {background: #28a745;color: white;
}.btn-cancel {background: #6c757d;color: white;margin-left: 10px;
}button {padding: 10px 24px;margin: 0 10px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}#code-div {margin: 0 auto;width:600px;
}.code-section {margin-top: 20px;padding: 15px;background: #f5f5f5;border-radius: 4px;
}.code-toggle {display: block;margin: 20px auto 0;background: #eef;
}
</style>
WorkMone.vue
一、模板结构(Template)
-
工资表格部分
- 表头(
.header
)显示"姓名、月度收入、专项扣除、个税、工资"五列 - 使用
v-for
循环渲染员工数据(staff数组) - 每行显示:
- 员工姓名
- 原始收入(income)
- 固定专项扣除(insurance)
- 计算后的个税(calculateTax)
- 税后工资(calculateSalary)
- 表头(
-
代码查看部分
- 通过按钮切换显示/隐藏代码片段(
toggleCodeDisplay
方法) - 使用
v-if
条件渲染代码区块 - 显示预格式化的代码内容(
codeSnippet
字符串)
- 通过按钮切换显示/隐藏代码片段(
二、脚本部分(Script)
三、样式部分(Style)
-
表格布局
- 使用flex布局实现表头和表格行
- 固定列宽100px
- 边框和背景色区分表头与内容
-
代码显示区
- 灰色背景突出显示代码
- 按钮居中显示,带淡蓝色背景
- 600px固定宽度容器
-
响应式交互
- 按钮点击有过渡动画(transition)
- 代码区块带圆角边框
四、实验思路分析
五、代码
<template><div><div class="salary-container"><div class="header"><div class="header-item">姓名</div><div class="header-item">月度收入</div><div class="header-item">专项扣除</div><div class="header-item">个税</div><div class="header-item">工资</div></div><div class="row"v-for="employee in staff":key="employee.name"><div class="cell">{{ employee.name }}</div><div class="cell">{{ employee.income }}</div><div class="cell">{{ insurance }}</div><div class="cell">{{ calculateTax(employee) }}</div><div class="cell">{{ calculateSalary(employee) }}</div></div></div><div id="code-div"><!-- 调试用代码展示区块 --><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }}</button></div></div></template><script>export default {data() {return {insurance: 1000,threshold: 5000,tax: 0.03,staff: [{ name: '张无忌', income: 6600 },{ name: '令狐冲', income: 8000 },{ name: '韦小宝', income: 7000 }],showCode: false,codeSnippet:`calculateTax(employee) {const taxable = employee.income - this.threshold - this.insurancereturn taxable > 0 ? Math.round(taxable * this.tax) : 0},calculateSalary(employee) {return employee.income - this.calculateTax(employee)}`}},methods: {calculateTax(employee) {const taxable = employee.income - this.threshold - this.insurancereturn taxable > 0 ? Math.round(taxable * this.tax) : 0},calculateSalary(employee) {return employee.income - this.calculateTax(employee)},toggleCodeDisplay() {this.showCode = !this.showCode;}}}</script><style scoped>.salary-container {text-align: center;font-family: 微软雅黑;font-size: 14px;width: 500px;}.header {display: flex;background: #f6f6f6;font-size: 18px;border: 1px solid #ddd;}.row {display: flex;border: 1px solid #ddd;border-top: none;}.header-item, .cell {width: 100px;text-align: center;line-height: 36px;height: 36px;flex-shrink: 0;}button {padding: 10px 24px;margin: 0 10px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}#code-div {margin: 0 auto;width:600px;
}.code-section {margin-top: 20px;padding: 15px;background: #f5f5f5;border-radius: 4px;
}.code-toggle {display: block;margin: 20px auto 0;background: #eef;
}</style>
ZutheCe.vue
一、模板部分(Template)
-
登录表单:
- 包含用户名和密码输入框,使用
v-model.trim
绑定数据并自动去除首尾空格 - 表单提交触发
login
方法,.prevent
修饰符阻止默认提交行为
- 包含用户名和密码输入框,使用
-
状态控制:
- 登录按钮通过
:disabled="loading"
在请求期间禁用 - 按钮文字动态显示"登录中..."或"登录"
- 登录按钮通过
-
调试功能:
- 可切换显示的代码区块(v-if="showCode")
- 使用
<pre>
标签展示代码片段(codeSnippet) - 通过toggleCodeDisplay方法切换显示状态
二、脚本部分(Script)
三、样式部分(Style)
- 响应式布局:容器使用
max-width: 300px
和margin: 50px auto
居中 - 视觉设计:
- 阴影效果(box-shadow)
- 输入框过渡动画(transition)
- 按钮悬停效果
- 代码展示区:
- 灰色背景(background: #f5f5f5)
- 固定宽度500px(应考虑响应式适配)
四、实验思路分析
五、代码
<template><div><div class="login-container"><div class="title">用户登录</div><form @submit.prevent="login" class="login-form"><div class="form-item"><label class="form-label">用户名:</label><div class="input-wrapper"><inputtype="text"v-model.trim="username"placeholder="请输入用户名"></div></div><div class="form-item"><label class="form-label">密码:</label><div class="input-wrapper"><inputtype="password"v-model.trim="password"placeholder="请输入密码"></div></div><div class="action-group"><button type="submit" class="submit-btn" :disabled="loading">{{ loading ? '登录中...' : '登录' }}</button><button type="button" class="reset-btn" @click="handleReset">重置</button></div></form></div><div id="code-div"><!-- 调试用代码展示区块 --><div class="code-section" v-if="showCode"><pre>{{ codeSnippet }}</pre></div><button @click="toggleCodeDisplay" class="code-toggle">{{ showCode ? '隐藏代码' : '显示代码' }}</button></div></div>
</template><script>
import axios from 'axios';export default {name: 'LoginPage',data() {return {username: '',password: '',loading: false,showCode: false,codeSnippet: `async login() {this.loading = true;try {// 基本输入验证if (!this.username.trim()) {alert("请输入用户名");return;}const response = await axios.get('http://localhost:8080/api/test');const users = response.data;// 用户存在性验证const userExists = users.some(user => user.name === this.username);if (userExists) {alert("登录成功");// 典型登录成功处理:// 1. 存储用户状态// 2. 跳转到仪表盘// this.$router.push('/dashboard');} else {alert("用户名不存在");}} catch (error) {console.error("登录失败:", error);alert("登录失败,请稍后重试");} finally {this.loading = false;}},handleReset() {this.username = '';this.password = '';},`};},methods: {async login() {this.loading = true;try {// 基本输入验证if (!this.username.trim()) {alert("请输入用户名");return;}const response = await axios.get('http://localhost:8080/api/test');const users = response.data;// 用户存在性验证const userExists = users.some(user => user.name === this.username);if (userExists) {alert("登录成功");// 典型登录成功处理:// 1. 存储用户状态// 2. 跳转到仪表盘// this.$router.push('/dashboard');} else {alert("用户名不存在");}} catch (error) {console.error("登录失败:", error);alert("登录失败,请稍后重试");} finally {this.loading = false;}},handleReset() {this.username = '';this.password = '';},toggleCodeDisplay() {this.showCode = !this.showCode;}}
};
</script><style scoped>
/* 保持原有样式不变 */
.login-container {max-width: 300px;margin: 50px auto;padding: 30px;background: #fff;border-radius: 8px;box-shadow: 0 2px 12px rgba(0,0,0,0.1);
}.title {font-size: 24px;text-align: center;margin-bottom: 30px;color: #333;
}.form-item {margin-bottom: 24px;
}.form-label {display: block;margin-bottom: 8px;color: #666;
}.input-wrapper {position: relative;
}input {width: 95%;height: 40px;padding: 8px 15px;border: 1px solid #ddd;border-radius: 4px;transition: border-color 0.3s;
}.action-group {margin-top: 30px;text-align: center;
}button {padding: 10px 24px;margin: 0 10px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}.code-section {margin-top: 20px;width: 500px;padding: 15px;background: #f5f5f5;border-radius: 4px;
}.code-toggle {display: block;margin: 20px auto 0;background: #eef;
}
</style>