1 Vue与uni-app
Uni-app是基于Vue的,但以不完成等同于Vue,Vue的前端实现只能使用浏览器进行渲染显示,而uni-app的前端实现,可以在小程序、App、浏览器都能够进行渲染显示。
- Vue与Razor
1、如果没能指定需要前后端分离实现,本从建议首先考虑Razor,因为Vue的变化太过频繁,会由于搭建环境的迭代变化从而导致,前端实现不能被正常运行。
2、从前端实现的角度来说Razor,不如Vue, Vue有丰富的组件,可供渲染显示使用;Vue彻底脱离了后端代码,只关注前端实现,从专业性和团队开发来说依然是Vue胜出。
3、Razor实质上是一种特殊的“*.cs”文件,即它是后端实现的扩展和延伸,与后端实现有着紧密的耦合关系,不能脱离后端而单独存在。Vue只关注与后端数据的绑定和交互操作,不关心后端的逻辑实现,可以完全脱离后端而单独存在,从耦合的角度来说依然是Vue胜出。
3 VSCode与HBuilderX
VSCode与HbuilderX是两个IDE开发环景两者都可以对Vue和uni-app前端进行开发实现,当前来说使用VSCode开发uni-app前端配置比较麻烦,由于前后端分离的主要需求是小程序和App开发,且HbuilderX比VSCode更加方便和容易上手,所建议使用Hbuilder进行Vue和uni-app前端开发,但不能放弃VSCode因为二者没有分出胜负。当前前端项目的开发将从使用VSCode转到HbuilderX中,因以后的工作重点是uni-app,而Vue则是学习使用uni-app的台阶。
4 重构博客文章
4.1 定义src\api\api.js
import { createApp } from 'vue'//在vue-cli4(4.5.0版本的脚手架)后取代了:import Vue from 'vue';
import axios from 'axios';
//后端的根路由,该根路由来源于VisaulStuido调试,非IIS部署。
let base = 'https://localhost:7037';
// 如果是IIS部署,用这个,因为 IIS 只能是 CORS 跨域,不能代理
// let base = process.env.NODE_ENV=="production"? 'http://localhost:8081':'';
/****************************定义“axios”的“http response”拦截操作************************************/
axios.interceptors.response.use(
response => {
return response;
},
error => {
let errInfo = { success: false, message: "错误" }
// 超时请求处理
var originalRequest = error.config;
if(error.code == 'ECONNABORTED' && error.message.indexOf('timeout')!=-1 && !originalRequest._retry){
errInfo.message = "请求超时!";
originalRequest._retry = true
}
else if (error.response) {
if (error.response.status == 401) {
var curTime = new Date()
var refreshtime = new Date(Date.parse(window.localStorage.refreshtime))
// 在用户操作的活跃期内
if (window.localStorage.refreshtime && (curTime <= refreshtime)) {
return refreshToken({token: window.localStorage.Token}).then((res) => {
if (res.success) {
createApp.prototype.$message({
message: '成功获取“TokenJwt”字符串,数据载入中...',
type: 'success'
});
//store.commit("saveToken", res.response.token);
//var curTime = new Date();
//var expiredate = new Date(curTime.setSeconds(curTime.getSeconds() + res.response.expires_in));
// store.commit("saveTokenExpire", expiredate);
error.config.__isRetryRequest = true;
error.config.headers.Authorization = 'Bearer ' + res.response.token;
return axios(error.config);
} else {
// 刷新token失败 清除token信息并跳转到登录页面
//ToLogin()
}
});
} else {
// 返回 401,并且不知用户操作活跃期内 清除token信息并跳转到登录页面
//ToLogin()
}
}
// 403 无权限
else if (error.response.status == 403) {
errInfo.message = "失败!该操作无权限";
}
// 429 ip限流
else if (error.response.status == 429) {
errInfo.message = "刷新次数过多,请稍事休息重试!";
}else if (error.response.status == 404) {
// 404 不存在
errInfo.message = "失败!访问接口不存在";
}else if (error.response.status == 500) {
// 500 服务器异常
errInfo.message = "失败!服务器异常";
}else if (error.response.status == 405) {
// 405 请求http方法错误
errInfo.message = "失败!请求http方法错误";
}else if (error.response.status == 415) {
// 415 参数没有指定Body还是Query
errInfo.message = "失败!参数没有指定Body还是Query";
}else {
//其他错误参数
errInfo.message = '失败!请求错误' + error.response.status;
}
}else{
errInfo.message = "失败!服务器断开";
}
createApp.prototype.$message({
message: errInfo.message,
type: 'error'
});
return errInfo; // 返回接口返回的错误信息
}
);
/****************************API集中管理--博客文章模块************************************/
// 获取1分页内的所有的博客文章。
export const getBlogListPage = params => {
return axios.get(`${base}/api/Blog`, {params: params});
};
/****************************API集中管理--登录模块************************************/
// 获取1个新的TokenJwt字符串,代替旧的TokenJwt字符串。
export const refreshToken = params => {
return axios.get(`${base}/api/login/RefreshToken`, {params: params}).then(res => res.data);
};
// 登录后获取TokeneJwt3字符串。
export const requestLogin = params => {
return axios.get(`${base}/api/login/jwttoken3.0`, {params: params}).then(res => res.data);
};
4.2 重构BlogsView.vue
<template>
<!--列表-->
<el-table :data="blogList" v-loading="listLoading" style="width: 100%" ref="table" class="custom-tbl">
<el-table-column type="selection" width="50"> </el-table-column>
<el-table-column prop="id" label="ID" width="100" sortable>
</el-table-column>
<el-table-column prop="btitle" label="标题" width="" sortable>
</el-table-column>
<el-table-column prop="content" label="内容" width="400" sortable>
<template #default="scope">
<span v-html="scope.row.content"></span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" :formatter="formatCreateTime" width="250" sortable>
</el-table-column>>
</el-table>
</template>
<script>
import { getBlogListPage } from "../../api/api.js";
export default ({
data() {
return {
//数组局部变量,用于存储从后端获取的“博客文章”实体的所有实例与当前页面进行绑定,以在页面进行渲染显示时,同时渲染显示出这些实例数据。
blogList: [],
listLoading: false,
//获取指定参数下的“博客文章”实体的所有实例。
filters: {
LinkUrl: "",
},
page: 1,
};
},
methods: {
//获取“博客文章”实体的所有实例。
async getBlogList() {
this.listLoading = true;
let para = {
page: this.page,
key: this.filters.name,
};
let res = await getBlogListPage(para);
//console.log(res);
this.blogList = res.data.response.data;
this.listLoading = false;
},
//格式化日期显示格式。
formatCreateTime: function(row, column) {
let data = row[column.property];
if (data == null) {
return null;
}
let dt = new Date(data);
return dt.getFullYear() + '-' + (dt.getMonth() + 1) + '-' + dt.getDate();
},
},
mounted() {
//在页面渲染显示完成前,把数据加载到页面中,以便在页面渲染显示后把数据渲染显示出来;“mounted”方法执行结束即为页面渲染显示后。
this.getBlogList();
}
});
</script>
<style scoped>
.custom-tbl /deep/ .has-gutter .el-checkbox {
display: none;
}
</style>
5 定义登录页面
5.1 使用选项式Api(Options Api)定义登录页面
5.1.1 重构src\router\index.js
const routes = [{
path: '/',
name: 'QQ欢迎页',
component: () => import('../views/WelcomeView.vue'),
meta: {
title: 'QQ欢迎页',
}
},
{
path: '/Login',
name: '登录',
component: () => import('../views/LoginView.vue'),
meta: {
title: '登录',
}
},
{
path: '/LoginComposition',
name: '登录(组合API)',
component: () => import('../views/LoginCompositionView.vue'),
meta: {
title: '登录(组合API)',
}
},
{
path: '/Blog/Blogs',
name: '博客管理',
component: () => import('../views/Blog/BlogsView.vue'),
meta: {
title: '博客管理',
}
},
{
path: '/home',
name: 'home',
component: () => import('../views/HomeView.vue')
},
]
5.1.2 复制
1、复制源程序“src\assets”目录中的所有图片到当前程序的同一目录。
2、复制源程序“public”目录中的所有图片到当前程序的同一目录。
5.1.3 定义src\views\ LoginView.vue
<template>
<div class="wrapper">
<!-- 背景动画 -->
<ul class="bg-bubbles">
<li v-for="n in 10" :key="n + 'n'"></li>
<ol v-for="m in 5" :key="m + 'm'"></ol>
</ul>
<!-- 背景 -->
<div class="bg bg-blur" style="display: none;"></div>
<div style="height: 10%;"></div>
<!-- 登录表单 -->
<el-form :model="formLogin" :rules="ruleLogin" ref="refRuleLogin" label-position="left" label-width="0px"
class="demo-ruleForm login-container">
<h3 class="title">系统登录</h3>
<el-form-item prop="account">
<el-input type="text" v-model="formLogin.account" auto-complete="off" placeholder="账号"></el-input>
</el-form-item>
<el-form-item prop="checkPass">
<el-input v-model="formLogin.checkPass" auto-complete="off" show-password placeholder="密码"></el-input>
</el-form-item>
<el-checkbox v-model="checked" checked class="remember">记住密码</el-checkbox>
<el-form-item style="width:100%;">
<el-button type="primary" style="width:100%;" @click="submitLogin" :loading="logining">
{{ loginStr }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {
requestLogin
} from "../api/api.js";
export default {
data() {
return {
formLogin: {
account: 'test',
checkPass: 'test'
},
ruleLogin: {
account: [{
required: true,
message: '请输入账号',
trigger: 'blur'
}, ],
checkPass: [{
required: true,
message: '请输入密码',
trigger: 'blur'
}, ],
},
checked: true,
loginStr: '登录',
logining: false,
};
},
methods: {
async submitLogin() {
this.$refs.refRuleLogin.validate(async (valid) => {
if (valid) {
this.logining = true;
this.loginStr = "登录中...";
let loginParams = {
name: this.formLogin.account,
pass: this.formLogin.checkPass
};
let res = await requestLogin(loginParams);
//console.log(res);
if (res.status == 200) {
this.$message.success("成功登录");
//通过定时器,3秒钟后跳转。
await setInterval(
async () => {
await this.$router.replace(this.$route.query.redirect ? this
.$route.query.redirect : "/");
}, 3000);
} else {
this.$message.error(res.msg);
this.logining = false;
this.loginStr = "重新登录";
}
} else {
console.log('输入不能通过验证 !');
return false;
}
});
},
},
mounted() {
//this.submitLogin();
},
}
</script>
<style lang="scss">
.bg {
margin: 0px;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: url(../assets/loginbck.png) no-repeat top left;
background-repeat: no-repeat;
background-size: cover;
width: 100%;
height: 100%;
}
.login-container {
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-border-radius: 5px;
background-clip: padding-box;
margin: auto;
width: 350px;
padding: 35px 35px 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
z-index: 9999;
position: relative;
}
.login-container .title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
.login-container .remember {
margin: 0px 0px 25px 0px;
}
.wrapper {
background: #50a3a2;
background: -webkit-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%);
background: linear-gradient(to bottom right, #127c7b 0, #50a3a2);
opacity: 0.8;
position: absolute;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.wrapper.form-success .containerLogin h1 {
-webkit-transform: translateY(85px);
-ms-transform: translateY(85px);
transform: translateY(85px);
}
.containerLogin {
max-width: 600px;
margin: 0 auto;
padding: 80px 0;
height: 400px;
text-align: center;
}
.containerLogin h1 {
font-size: 40px;
-webkit-transition-duration: 1s;
transition-duration: 1s;
-webkit-transition-timing-function: ease-in-put;
transition-timing-function: ease-in-put;
font-weight: 200;
}
.bg-bubbles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.bg-bubbles li,
.bg-bubbles ol {
position: absolute;
list-style: none;
display: block;
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.15);
bottom: -160px;
-webkit-animation: square 25s infinite;
animation: square 25s infinite;
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
}
ol {
padding: 0 !important;
}
.bg-bubbles ol:nth-child(11) {
left: 10%;
top: 10%;
width: 20px;
height: 20px;
}
.bg-bubbles ol:nth-child(12) {
left: 20%;
top: 40%;
width: 60px;
height: 60px;
}
.bg-bubbles ol:nth-child(13) {
left: 65%;
top: 30%;
width: 100px;
height: 60px;
}
.bg-bubbles ol:nth-child(14) {
left: 70%;
top: 30%;
width: 100px;
height: 150px;
}
.bg-bubbles ol:nth-child(15) {
left: 50%;
top: 70%;
width: 40px;
height: 60px;
}
.bg-bubbles li:nth-child(1) {
left: 10%;
}
.bg-bubbles li:nth-child(2) {
left: 20%;
width: 80px;
height: 80px;
-webkit-animation-delay: 2s;
animation-delay: 2s;
-webkit-animation-duration: 17s;
animation-duration: 17s;
}
.bg-bubbles li:nth-child(3) {
left: 25%;
-webkit-animation-delay: 4s;
animation-delay: 4s;
}
.bg-bubbles li:nth-child(4) {
left: 40%;
width: 60px;
height: 60px;
-webkit-animation-duration: 22s;
animation-duration: 22s;
background-color: rgba(255, 255, 255, 0.25);
}
.bg-bubbles li:nth-child(5) {
left: 70%;
}
.bg-bubbles li:nth-child(6) {
left: 80%;
width: 120px;
height: 120px;
-webkit-animation-delay: 3s;
animation-delay: 3s;
background-color: rgba(255, 255, 255, 0.2);
}
.bg-bubbles li:nth-child(7) {
left: 32%;
width: 160px;
height: 160px;
-webkit-animation-delay: 7s;
animation-delay: 7s;
}
.bg-bubbles li:nth-child(8) {
left: 55%;
width: 20px;
height: 20px;
-webkit-animation-delay: 15s;
animation-delay: 15s;
-webkit-animation-duration: 40s;
animation-duration: 40s;
}
.bg-bubbles li:nth-child(9) {
left: 25%;
width: 10px;
height: 10px;
-webkit-animation-delay: 2s;
animation-delay: 2s;
-webkit-animation-duration: 40s;
animation-duration: 40s;
background-color: rgba(255, 255, 255, 0.3);
}
.bg-bubbles li:nth-child(10) {
left: 90%;
width: 160px;
height: 160px;
-webkit-animation-delay: 11s;
animation-delay: 11s;
}
@-webkit-keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-700px) rotate(600deg);
transform: translateY(-700px) rotate(600deg);
}
}
@keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-700px) rotate(600deg);
transform: translateY(-700px) rotate(600deg);
}
}
</style>
5.2 使用组合式api (Composition Api)定义登录页面
5.2.1 定义src\views\LoginCompositionView.vue
<template>
<div class="wrapper">
<!-- 背景动画 -->
<ul class="bg-bubbles">
<li v-for="n in 10" :key="n + 'n'"></li>
<ol v-for="m in 5" :key="m + 'm'"></ol>
</ul>
<!-- 背景 -->
<div class="bg bg-blur" style="display: none;"></div>
<div style="height: 10%;"></div>
<!-- 登录表单 -->
<el-form :model="formLogin" :rules="ruleLogin" ref="refRuleLogin" label-position="left" label-width="0px"
class="demo-ruleForm login-container">
<h3 class="title">系统登录</h3>
<el-form-item prop="account">
<el-input type="text" v-model="formLogin.account" auto-complete="off" placeholder="账号"></el-input>
</el-form-item>
<el-form-item prop="checkPass">
<el-input v-model="formLogin.checkPass" auto-complete="off" show-password placeholder="密码"></el-input>
</el-form-item>
<el-checkbox v-model="checked" checked class="remember">记住密码</el-checkbox>
<el-form-item style="width:100%;">
<el-button type="primary" style="width:100%;" @click="submitLogin(refRuleLogin)" :loading="logining">
{{ loginStr }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import {
ref,
reactive,
getCurrentInstance
} from "vue";
import {
requestLogin
} from "../api/api.js";
const formLogin = reactive({
account: 'test',
checkPass: 'test',
});
const ruleLogin = reactive({
account: [{
required: true,
message: '请输入账号',
trigger: 'blur'
}, ],
checkPass: [{
required: true,
message: '请输入密码',
trigger: 'blur'
}, ],
});
const checked = ref(true);
const loginStr = ref('登录');
const logining = ref(false);
//实例化表单“ref”属性所对应的值。
const refRuleLogin = ref('');
const {
$router,
$route,
$message
} = getCurrentInstance().root.ctx.$root;
//表单单击提交事件。
const submitLogin = (formEl) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (valid) {
logining.value = true;
loginStr.value = "登录中...";
let loginParams = {
name: formLogin.account,
pass: formLogin.checkPass
};
let res = await requestLogin(loginParams);
//console.log(res);
if (res.status == 200) {
$message.success("成功登录");
//通过定时器,3秒钟后跳转。
await setInterval(
async () => {
$router.replace($route.query.redirect ? $route.query.redirect : "/");
}, 3000);
}
else {
$message.error(res.msg);
logining.value = false;
loginStr.value = "重新登录";
}
} else {
console.log('输入不能通过验证 !');
return false;
}
});
}
</script>
<style lang="scss">
.bg {
margin: 0px;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: url(../assets/loginbck.png) no-repeat top left;
background-repeat: no-repeat;
background-size: cover;
width: 100%;
height: 100%;
}
.login-container {
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-border-radius: 5px;
background-clip: padding-box;
margin: auto;
width: 350px;
padding: 35px 35px 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
z-index: 9999;
position: relative;
}
.login-container .title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
.login-container .remember {
margin: 0px 0px 25px 0px;
}
.wrapper {
background: #50a3a2;
background: -webkit-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%);
background: linear-gradient(to bottom right, #127c7b 0, #50a3a2);
opacity: 0.8;
position: absolute;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.wrapper.form-success .containerLogin h1 {
-webkit-transform: translateY(85px);
-ms-transform: translateY(85px);
transform: translateY(85px);
}
.containerLogin {
max-width: 600px;
margin: 0 auto;
padding: 80px 0;
height: 400px;
text-align: center;
}
.containerLogin h1 {
font-size: 40px;
-webkit-transition-duration: 1s;
transition-duration: 1s;
-webkit-transition-timing-function: ease-in-put;
transition-timing-function: ease-in-put;
font-weight: 200;
}
.bg-bubbles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.bg-bubbles li,
.bg-bubbles ol {
position: absolute;
list-style: none;
display: block;
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.15);
bottom: -160px;
-webkit-animation: square 25s infinite;
animation: square 25s infinite;
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
}
ol {
padding: 0 !important;
}
.bg-bubbles ol:nth-child(11) {
left: 10%;
top: 10%;
width: 20px;
height: 20px;
}
.bg-bubbles ol:nth-child(12) {
left: 20%;
top: 40%;
width: 60px;
height: 60px;
}
.bg-bubbles ol:nth-child(13) {
left: 65%;
top: 30%;
width: 100px;
height: 60px;
}
.bg-bubbles ol:nth-child(14) {
left: 70%;
top: 30%;
width: 100px;
height: 150px;
}
.bg-bubbles ol:nth-child(15) {
left: 50%;
top: 70%;
width: 40px;
height: 60px;
}
.bg-bubbles li:nth-child(1) {
left: 10%;
}
.bg-bubbles li:nth-child(2) {
left: 20%;
width: 80px;
height: 80px;
-webkit-animation-delay: 2s;
animation-delay: 2s;
-webkit-animation-duration: 17s;
animation-duration: 17s;
}
.bg-bubbles li:nth-child(3) {
left: 25%;
-webkit-animation-delay: 4s;
animation-delay: 4s;
}
.bg-bubbles li:nth-child(4) {
left: 40%;
width: 60px;
height: 60px;
-webkit-animation-duration: 22s;
animation-duration: 22s;
background-color: rgba(255, 255, 255, 0.25);
}
.bg-bubbles li:nth-child(5) {
left: 70%;
}
.bg-bubbles li:nth-child(6) {
left: 80%;
width: 120px;
height: 120px;
-webkit-animation-delay: 3s;
animation-delay: 3s;
background-color: rgba(255, 255, 255, 0.2);
}
.bg-bubbles li:nth-child(7) {
left: 32%;
width: 160px;
height: 160px;
-webkit-animation-delay: 7s;
animation-delay: 7s;
}
.bg-bubbles li:nth-child(8) {
left: 55%;
width: 20px;
height: 20px;
-webkit-animation-delay: 15s;
animation-delay: 15s;
-webkit-animation-duration: 40s;
animation-duration: 40s;
}
.bg-bubbles li:nth-child(9) {
left: 25%;
width: 10px;
height: 10px;
-webkit-animation-delay: 2s;
animation-delay: 2s;
-webkit-animation-duration: 40s;
animation-duration: 40s;
background-color: rgba(255, 255, 255, 0.3);
}
.bg-bubbles li:nth-child(10) {
left: 90%;
width: 160px;
height: 160px;
-webkit-animation-delay: 11s;
animation-delay: 11s;
}
@-webkit-keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-700px) rotate(600deg);
transform: translateY(-700px) rotate(600deg);
}
}
@keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-700px) rotate(600deg);
transform: translateY(-700px) rotate(600deg);
}
}
</style>
对以上功能更为具体实现和注释见:221215_002_admin(前端登录页面的定义实现)。