林间有风

郭朝夕的日记小站

  • 首页
  • 关于
  • 标签
  • 归档

封装一个仪表盘组件

发表于 2024-07-13

最近因为工作需要,封装了一个仪表盘组件。

阅读全文 »

封装一个折线图组件

发表于 2024-07-13

最近因为工作需要,封装了一个折线图组件。

阅读全文 »

封装一个饼图组件

发表于 2024-07-13

最近因为工作需要,封装了一个饼图组件。

阅读全文 »

封装一个圆柱体图表组件

发表于 2024-07-13

最近因为工作需要,封装了一个圆柱体图表组件。

阅读全文 »

封装一个立体柱状图组件

发表于 2024-07-13

最近因为工作需要,对 echarts 进行了一些封装,正好借此机会熟悉了一下 echarts 的各种配置项。

阅读全文 »

vue3实现权限管理

发表于 2024-05-26

页面权限

思路

  • 用户可以访问的页面权限都放在了用户信息userInfo/permission/menu中
  • 左侧菜单是根据routes自动生成的
  • 为每个权限路由创建一个name属性,每个name对应一个页面权限
  • 在router/index.js定义私有路由表privateRoutes和公开路由表publicRoutes
  • 在store/modules目录下新建一个permission文件,用来创建权限相关的操作
  • 通过filterRoutes这个动作来根据name与页面权限相匹配的方式筛选出权限路由,并返回出去
  • 在src/permission.js中我们通过router.beforeach拦截路由,获取用户信息,获取筛选后的私有路由表,利用router.addRoute来动态添加路由表
  • 退出用户登录的时候记得删除路由表,也就是用removeRoute这个方法
阅读全文 »

实现vue3后台管理系统中的一键换肤功能

发表于 2024-05-25

动态换肤实现原理

今天被问到了一个动态换肤的问题,我只回答了动态修改scss中代表色值的变量就可以实现,回答的有些笼统,过于简单了,所以回来以后查找资料、动手实践,将这一过程记录下来。

想要实现动态换肤的一个前置条件就是:色值不可以写死!

在scss中,我们可以通过$变量名:变量值的方式定义css变量,然后通过该css变量来指定某一块DOM对应的颜色。如果我改变了该css变量的值,那么是不是对应的DOM颜色也会同步发生变化?

当大量的DOM都依赖于这个css变量设置颜色时,我们是不是只需要修改这个css变量,那么所有的DOM颜色也会发生变化,那么动态换肤这个功能是不是就实现了?

这就是动态换肤的实现原理。

动态换肤实现方案分析

明确了上面的实现原理以后我们可以得出一个结论,那就是在实现动态换肤的时候要兼顾两个方面:

  • 动态换肤的关键是修改css变量的值

  • 换肤需要同时兼顾element-plus和非element-plus

那么根据以上关键信息,我们可以得出对应的解决方案:

  1. 创建一个组件ThemeSelect用来处理修改之后的css变量的值

  2. 根据新值修改element-plus的主题颜色

  3. 根据新值修改非element-plus的主题颜色

阅读全文 »

uniapp朝夕社区——全局配置

发表于 2024-04-09

全局配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"globalStyle": {
"navigateBarTextStyle": "black",
"navigateBarTitleText": "朝夕社区",
"navigateBarBackgroundColor": "#ffffff",
"backgroundColor": "#ffffff"
},
"tabBar": {
"color": "#333333",
"selectedColor": "#FC5C82",
"backgroundColor": "#FFFFFF",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/index.png",
"selectedIconPath": "static/tabbar/indexed.png"
},
{
"pagePath": "pages/news/news",
"text": "动态",
"iconPath": "static/tabbar/news.png",
"selectedIconPath": "static/tabbar/newsed.png"
},
{
"pagePath": "pages/msg/msg",
"text": "消息",
"iconPath": "static/tabbar/paper.png",
"selectedIconPath": "static/tabbar/papered.png"
},
{
"pagePath": "pages/my/my",
"text": "我的",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/homeed.png"
}
]
}
}

全局配置源码

uniapp朝夕社区——登录

发表于 2024-04-09

loginFlow

登录界面支持两种登录方式,一种是手机号+验证码登录;第二种是账号+密码登录。用户可以在这两种登录方式之间进行切换。登录成功以后会获取用户信息存储在 vuex 中并使用 localStorage 做一个持久化存储;同时也会打开 websocket 的链接,并且跳回上一级页面,提示用户登录成功。

checkLogin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<view
class="text-center"
style="padding-top: 130rpx;padding-bottom: 70rpx;font-size: 55rpx;">
{{status ? '手机验证码登录':'账号密码登录'}}
</view>

<view class="px-2">
<template v-if="!status">
<view class="mb-2">
<input
type="text"
v-model="username"
placeholder="昵称/手机号/邮箱"
class="border-bottom p-2" />
</view>
<view class="mb-2 flex align-stretch">
<input
type="text"
v-model="password"
placeholder="请输入密码"
class="border-bottom p-2 flex-1" />
<view
class="border-bottom flex align-center justify-center font text-muted"
style="width: 150rpx;">
忘记密码?</view
>
</view>
</template>

<template v-else>
<view class="mb-2 flex align-stretch">
<view class="border-bottom flex align-center justify-center font px-2"
>+86</view
>
<input
type="text"
v-model="phone"
placeholder="手机号"
class="border-bottom p-2 flex-1" />
</view>
<view class="mb-2 flex align-stretch">
<input
type="text"
v-model="code"
placeholder="请输入验证码"
class="border-bottom p-2 flex-1" />
<view
class="border-bottom flex align-center justify-center font-sm text-white"
:class="codeTime > 0 ? 'bg-main-disabled' : 'bg-main'"
style="width: 180rpx;"
@click="getCode">
{{codeTime > 0 ? codeTime + ' s' : '获取验证码'}}
</view>
</view>
</template>
</view>

<script>
// 切换登录方式
changeStatus() {
// 初始化表单
this.initForm()
this.status = !this.status
},
// 初始化表单
initForm() {
this.username = ''
this.password = ''
this.phone = ''
this.code = ''
},
</script>

disabledButton

在用户信息没有填写完整之前,我们应该将登录按钮设置为禁用状态。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
<view class="py-2 px-3">
<button
class="text-white"
style="border-radius: 50rpx;border: 0;"
type="primary"
:disabled="disabled"
:class="disabled ? 'bg-main-disabled':'bg-main'"
@click="submit"
:loading="loading">
{{loading ? '登录中...' : '登录'}}
</button>
</view>
1
2
3
4
5
6
7
8
computed: {
disabled() {
if ((this.username === '' || this.password === '') && (this.phone === '' || this.code === '')) {
return true
}
return false
}
}

getPhoneCode

获取验证码登录是很常见的一种方式,一般都是通过获取验证码的接口发短信到用户的手机上,用户填入对应的验证码,进行比对以后就可以正常登录。

验证手机号格式
用户如果采用手机号+验证码的方式登录,那么我们应该校验手机号格式。如下代码所示:

1
2
3
4
5
6
7
8
<!-- 省去其他代码... -->
<view
class="border-bottom flex align-center justify-center font-sm text-white"
:class="codeTime > 0 ? 'bg-main-disabled' : 'bg-main'"
style="width: 180rpx;"
@click="getCode">
{{codeTime > 0 ? codeTime + ' s' : '获取验证码'}}
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 表单验证
validate() {
//手机号正则
var mPattern = /^1[34578]\d{9}$/;
if (!mPattern.test(this.phone)) {
uni.showToast({
title: '手机号格式不正确',
icon: 'none'
});
return false
}
// ...更多验证
return true
}
getCode() {
// 防止重复获取
if (this.codeTime > 0) {
return;
}
// 验证手机号
if (!this.validate()) return;
// 请求数据
this.$H.post('/user/sendcode', {
phone: this.phone
}, {
native: true
}).then(res => {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
// 倒计时
this.codeTime = 60
let timer = setInterval(() => {
if (this.codeTime >= 1) {
this.codeTime--
} else {
this.codeTime = 0
clearInterval(timer)
}
}, 1000)
})
}

如上代码所示,在验证手机号以后,访问接口拿到验证码,同时倒计时 60 秒开始计时。

loginAction

当用户填入表单信息以后,点击登录按钮就会触发登录操作。在这里要注意的是要先判断一下是用的哪种登录方式,从而改变对应的后台 url 和表单信息。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
<view class="py-2 px-3">
<button
class="text-white"
style="border-radius: 50rpx;border: 0;"
type="primary"
:disabled="disabled"
:class="disabled ? 'bg-main-disabled':'bg-main'"
@click="submit"
:loading="loading">
{{loading ? '登录中...' : '登录'}}
</button>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
submit() {
// 后端请求地址
let url = ''
// 表单信息
let data = ''
if (this.status) {
// 手机验证码登录
if (!this.validate()) return;
url = '/user/phonelogin'
data = {
phone: this.phone,
code: this.code
}
} else {
// 账号密码登录
url = '/user/login'
data = {
username: this.username,
password: this.password
}
}
}

setStorage

登录成功以后,会获取用户的信息,并且放在前端的vuex状态中,同时呢使用了uni.setStorageSync将信息做一个持久化存储。如代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// pages/login/login.vue
// 触发名为login的mutation,来修改vuex的state,并做持久化存储
this.$store.commit('login', res)

// store/index.js
// 登录
login(state,user){
// 修改登录状态
state.loginStatus = true
// 保存用户信息
state.user = user
// 保存当前用户的token
state.token = state.user.token
// 持久化存储
uni.setStorageSync('user', JSON.stringify(user));
}

openSocket

用户在登录成功以后,除了要将用户信息保存在前端供其他页面使用以外,还要开启socket,连接上 socket 以后就等于说是上线了,可以跟其他用户进行实时通讯,比如私信。这里用到了uni.connectSocket如代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// pages/login/login.vue 触发名为openSocket的action
this.$store.dispatch('openSocket')

// store/index.js
// 打开socket
openSocket({ state,dispatch }){
// 防止重复连接
if(state.IsOpen) return
// 连接
state.SocketTask = uni.connectSocket({
url: $C.websocketUrl,
complete: ()=> {}
});
if (!state.SocketTask) return;
// 监听开启
state.SocketTask.onOpen(()=>{
// 将连接状态设为已连接
console.log('将连接状态设为已连接');
state.IsOpen = true
})
// 监听关闭
state.SocketTask.onClose(()=>{
console.log('连接已关闭');
state.IsOpen = false;
state.SocketTask = false;
state.IsOnline = false
// 清空会话列表
// 更新未读数提示
})
// 监听错误
state.SocketTask.onError(()=>{
console.log('连接错误');
state.IsOpen = false;
state.SocketTask = false;
state.IsOnline = false
})
// 监听接收信息
state.SocketTask.onMessage((e)=>{
console.log('接收消息',e);
// 字符串转json
let res = JSON.parse(e.data);
// 绑定返回结果
if (res.type === "bind"){
// 用户绑定
return dispatch('userBind',res.data)
}
// 处理接收信息
if (res.type !== 'text') return;
/*
{
to_id:1, // 接收人
from_id:12, // 发送人id
from_username:"某某", // 发送人昵称
from_userpic:"http://pic136.nipic.com/file/20170725/10673188_152559977000_2.jpg",
type:"text", // 发送类型
data:"你好啊", // 发送内容
time:151235451 // 接收到的时间
}
*/
// 处理接收消息
dispatch('handleChatMessage',res)
})
},

loginSuccess

用户登录成功以后要给用户一个成功的弹窗提示,并且跳回到之前的页面中去。这里跳转用到了uni.navigateBack和uni.showToast这两个 API。如下代码所示:

1
2
3
4
5
6
7
8
// 提示和跳转
uni.navigateBack({
delta: 1
})
uni.showToast({
title: '登录成功',
icon: 'none'
})

uniApi

uni.navigateBack

uni.navigateBack()链接地址

关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 注意:调用 navigateTo 跳转时,调用该方法的页面会被加入堆栈,而 redirectTo 方法则不会。见下方示例代码

// 此处是A页面
uni.navigateTo({
url: 'B?id=1'
})

// 此处是B页面
uni.navigateTo({
url: 'C?id=1'
})

// 在C页面内 navigateBack,将返回A页面
uni.navigateBack({
delta: 2
})

uni.showToast

uni.showToast()链接地址

显示消息提示框。

示例

1
2
3
4
uni.showToast({
title: '标题',
duration: 2000
})

uni.setStorageSync

uni.setStorageSync链接地址

将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个同步接口。

示例代码

1
2
3
4
5
try {
uni.setStorageSync('storage_key', 'hello');
} catch (e) {
// error
}

uni.connectSocket

uni.connectSocket链接地址

创建一个 WebSocket 连接。

示例代码

1
2
3
4
5
6
7
8
9
uni.connectSocket({
url: 'wss://www.example.com/socket',
header: {
'content-type': 'application/json'
},
protocols: ['protocol1'],
method: 'GET'
});

返回值

如果希望返回一个socketTask对象,需要至少传入 success / fail / complete 参数中的一个。例如:

1
2
3
4
5
var socketTask = uni.connectSocket({
url: 'wss://www.example.com/socket', //仅为示例,并非真实接口地址。
complete: ()=> {}
});

api doc

登录页面用到的几个接口,以下是文档说明。

sendCode

简介:发送验证码的接口。

  • url: http://localhost:8081/api/user/sendcode
  • method: ‘POST’

请求参数

1
2
3
{ 
"phone": "18888888888"
}

phoneLogin

简介:使用验证码登录的接口。

  • url: http://localhost:8081/api/user/phonelogin
  • method: ‘POST’

请求参数

1
2
3
4
{
"code":"8888",
"phone": "18830004000"
}

userLogin

简介:使用账户密码登录的接口。

  • url: http://localhost:8081/api/user/login
  • method: ‘POST’

请求参数

1
2
3
4
{
"username": "admin",
"password": "123456"
}

登录页源码

tab页聊天室

发表于 2024-04-08

实现多Tab页聊天

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>  
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简单聊天室</title>
<style>
#chat-log {
height: 200px;
overflow-y: scroll;
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="chat-log"></div>
<input type="text" id="message-input" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>

<script src="chatroom.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 监听 localStorage 变化
window.addEventListener('storage', function (event) {
if (event.key === 'newMessage') {
const newMessage = event.newValue
displayMessage(newMessage)
}
})

// 显示消息到聊天日志中
function displayMessage(message) {
const chatLog = document.getElementById('chat-log')
const p = document.createElement('p')
p.textContent = message
chatLog.appendChild(p)
chatLog.scrollTop = chatLog.scrollHeight // 滚动到最新消息
}

// 发送消息到 localStorage
function sendMessage() {
const messageInput = document.getElementById('message-input')
const message = messageInput.value.trim()

if (message) {
localStorage.setItem('newMessage', message) // 存储消息到 localStorage
messageInput.value = '' // 清空输入框
messageInput.focus() // 重新聚焦到输入框
displayMessage(`我: ${message}`) // 在当前标签页也显示消息
}
}

// 初始化时检查是否有未处理的消息
;(function checkInitialMessages() {
const initialMessage = localStorage.getItem('newMessage')
if (initialMessage) {
displayMessage(initialMessage) // 显示初始消息
localStorage.removeItem('newMessage') // 清除 localStorage 中的消息,避免重复显示
}
})()

在这个例子中,我们监听storage事件来捕获其他标签页中localStorage的变化。当用户在某个标签页中输入消息并点击“发送”按钮时,消息会被存储到localStorage中,并且触发storage事件。所有监听这个事件的标签页都会接收到这个事件,并从localStorage中读取新的消息,然后显示在聊天日志中。

请注意,由于localStorage的更新是异步的,并且不同的浏览器可能有不同的行为,因此在实际应用中可能需要添加一些额外的逻辑来处理可能出现的竞态条件或数据不一致问题。此外,这个例子没有处理用户身份验证和消息历史记录管理,这些都是在实际聊天室应用中需要考虑的重要方面。

上一页123…7下一页

62 日志
19 标签
© 2025 郭朝夕
冀ICP备18008237号