先看看系统界面,就2个主要界面
第一个是登录界面
第二个是登录后主体界面
前端用TypeScript+Vue3+ElementPlus写,后端用php8+thinkphp8
第一点,功能增强点,原来的维持组件间的登录状态提升为用pinia来改写
main.js文件变动如下
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import { router } from './router'
import App from './App.vue'
const pinia = createPinia()
pinia.use(piniaPersist)
const app = createApp(App)
app.use(pinia)
app.use(ElementPlus)
app.use(router)
app.mount('#app')
新建tokener.js文件
// stores/tokener.js
import { defineStore } from 'pinia'
export const useTokenerStore = defineStore('tokener', {
state: () => {
return { token: 0 }
},
actions: {
setToken(value) {
this.token = value
},
},
persist: {
enabled: true,
strategies: [
{ storage: localStorage, paths: ['token'] },
],
}
})
pinia默认使用sessionStorage来存储,这里使用了localStorage,也可以为不同的变量混用存储方式
然后看看在新写的展示图片列表的地方是怎么使用存储好的token令牌的,这也是第二个新增点,其他地方用法同理
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import axios from 'axios';
import { useTokenerStore } from '../../stores/tokener'
const tokener = useTokenerStore()
onMounted(() => {
console.log(`the component is now mounted.`)
axios.get('http://admin.am8.com/index/getList', {
params: {
token: tokener.token,
}
})
.then(function (response) {
console.log(response);
if (response.data.code === 1) {
tableData.value = response.data.data
} else {
if (response.data.sub_code === 0) {
tokener.$reset()
}
}
})
.catch(function (error) {
console.log(error);
});
})
const tableData = ref([])
</script>
首先需要引入tokener.js文件
import { useTokenerStore } from '../../stores/tokener'
然后是获取存储的对象
const tokener = useTokenerStore()
最后是直接通过对象引用存储好的token值即可
params: {
token: tokener.token,
}
另外还有一个地方重置了token对象,这时候整个对象的值都会恢复成初始值
if (response.data.sub_code === 0) {
tokener.$reset()
}
我们继续看看展示图片列表的视图部分,这个展示图片的模块是新增的
<template>
<el-table :data="tableData" style="width: 100%">
<el-table-column type="index" width="50" />
<el-table-column label="Name" width="180">
<template #default="scope">
<el-image
style="width: 100px; height: 100px"
:src="scope.row.name"
fit="fill"></el-image>
</template>
</el-table-column>
</el-table>
</template>
以表格的方式展示了序列数和图片列表,结合上面逻辑处理部分,就能看到tableData是在哪里来的
if (response.data.code === 1) {
tableData.value = response.data.data
}
后端返回成功时,初始化了tableData的值
后端接口如下
public function getList()
{
$list = Head2::select();
$list2 = [];
foreach ($list as $key => $value) {
$search = '\\';
$replace = '/';
replacedString = str_replace(search, $replace, $value->name);
list\[key]['name'] = Config::get('app.server_url').'storage/'.$replacedString;
}
return mySuccessResponse($list);
}
第三个新增点是将前端代码打包进后端展示
打包就在项目中执行npm run build,生成dist文件夹,复制dist文件夹放到thinkphp8的public文件夹里面,这里我在public文件夹下新增一个admin文件夹,然后再把dist文件夹放到admin文件夹中,想法是项目应该是可以支持多个单文件组件的模式。比如后面计划前台又用另一个单文件组件来写。然后把dist文件夹中的index.html文件剪切到thinkphp8的视图中,想法是前端页面应该可以既用vue3项目写也可以用thinkphp8模板来写,混写。兼容各种技术来实现前端
编写视图接口
use think\facade\View;
class Anonym {
public function index()
{
return View::fetch();
}
...
}
视图结构如下
后端是自动多应用模式,这是一个admin应用的视图结构,那么在浏览器就可以访问http://admin.am8.com/Anonym/index进入到登录页面,admin.am8.com是我配置的映射到admin应用下的域名,大家各自配各自的
然后index.html页面需要稍微调整一下路径
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/admin/dist/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<script type="module" crossorigin src="/admin/dist/assets/index-BBtu1Sxt.js"></script>
<link rel="stylesheet" crossorigin href="/admin/dist/assets/index-DbFK1zW7.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
前面就说了在public文件夹下新建admin文件夹然后再放入dist文件夹,这里的修改就是为了引用对前端的资源文件
第四个改动点是将BaseController里面的登录检查移到应用中间件中实现,原因是因为继承的基础控制器在未登录情况下不能直接返回json对象到前端,需要用die等方法处理成json字符串。如果处理成json字符串,前端又需要解码json字符串成json对象。而且thinkphp8也不推荐使用die等中断程序的方法。
新增admin应用中间件,并且修改代码如下
public function handle($request, \Closure $next)
{
$array = ['anonym/login', 'anonym/index', 'Anonym/index'];
$value = $request->pathinfo();
$sign = true;
foreach ($array as $k => $v) {
if ($v === $value) {
$sign = false;
break;
}
}
if ($sign) {
$exist = AdminToken::where([
'token' => request()->param('token'),
])->find();
if ($exist !== null) {
// 获取当前时间的时间戳
$now = time();
timestamp = intval(exist->update_time);
// 设定1小时的秒数
$oneHourInSeconds = 3600;
if (($now - $timestamp) > $oneHourInSeconds) {
return myFailResponse(0, '登录状态已过期');
}
} else {
return myFailResponse(0, '未登录');
}
}
return next(request);
}
这里没用inArray来实现,因为那个方法判断有时不准确,改成如下写法
$array = ['anonym/login', 'anonym/index', 'Anonym/index'];
$value = $request->pathinfo();
$sign = true;
foreach ($array as $k => $v) {
if ($v === $value) {
$sign = false;
break;
}
}
if ($sign) {
...
}
这样写是为了判断路径,如果路径属于以上几种路径就不用检查登录状态,分别是登录请求、登录界面显示、还有里面主体界面显示。
第五个改动点,将菜单默认的选中项改为动态路由,这是为了页面刷新时能相应地在菜单项选中项也能对应上
<el-menu
router
:default-active="$route.fullPath"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
>
...
</el-menu>
注意看:default-active="$route.fullPath",就是这个改动
第六个改动点,将前端路由由H5模式改为Hash模式
import { createRouter, createWebHashHistory } from 'vue-router'
history: createWebHashHistory(),
这是因为index.html放在thinkphp视图里面,访问时肯定会经过后端路由,为了不影响到前端路由,故前端改用hash模式