mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-28 10:03:54 +08:00
update:聊天记录展现
This commit is contained in:
@@ -22,4 +22,7 @@ public class AgentChatHistoryDTO {
|
|||||||
|
|
||||||
@Schema(description = "音频ID")
|
@Schema(description = "音频ID")
|
||||||
private String audioId;
|
private String audioId;
|
||||||
|
|
||||||
|
@Schema(description = "MAC地址")
|
||||||
|
private String macAddress;
|
||||||
}
|
}
|
||||||
@@ -97,4 +97,50 @@ export default {
|
|||||||
});
|
});
|
||||||
}).send();
|
}).send();
|
||||||
},
|
},
|
||||||
|
// 获取智能体会话列表
|
||||||
|
getAgentSessions(agentId, params, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/agent/${agentId}/sessions`)
|
||||||
|
.method('GET')
|
||||||
|
.data(params)
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime();
|
||||||
|
callback(res);
|
||||||
|
})
|
||||||
|
.fail(() => {
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getAgentSessions(agentId, params, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
|
// 获取智能体聊天记录
|
||||||
|
getAgentChatHistory(agentId, sessionId, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/agent/${agentId}/chat-history/${sessionId}`)
|
||||||
|
.method('GET')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime();
|
||||||
|
callback(res);
|
||||||
|
})
|
||||||
|
.fail(() => {
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getAgentChatHistory(agentId, sessionId, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAgentSessions(agentId, params) {
|
||||||
|
return request({
|
||||||
|
url: `/agent/${agentId}/sessions`,
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAgentChatHistory(agentId, sessionId) {
|
||||||
|
return request({
|
||||||
|
url: `/agent/${agentId}/chat-history/${sessionId}`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,366 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog :title="'与' + agentName + '的聊天记录' + (currentMacAddress ? '[' + currentMacAddress + ']' : '')"
|
||||||
|
:visible.sync="dialogVisible" width="80%" :before-close="handleClose" custom-class="chat-history-dialog">
|
||||||
|
<div class="chat-container">
|
||||||
|
<div class="session-list" @scroll="handleScroll">
|
||||||
|
<div v-for="session in sessions" :key="session.sessionId" class="session-item"
|
||||||
|
:class="{ active: currentSessionId === session.sessionId }" @click="selectSession(session)">
|
||||||
|
<img src="@/assets/xiaozhi-logo.png" class="avatar" />
|
||||||
|
<div class="session-info">
|
||||||
|
<div class="session-time">{{ formatTime(session.createdAt) }}</div>
|
||||||
|
<div class="message-count">{{ session.chatCount }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="loading" class="loading">加载中...</div>
|
||||||
|
<div v-if="!hasMore" class="no-more">没有更多记录了</div>
|
||||||
|
</div>
|
||||||
|
<div class="chat-content">
|
||||||
|
<div v-if="currentSessionId" class="messages">
|
||||||
|
<div v-for="(message, index) in messagesWithTime" :key="message.id">
|
||||||
|
<div v-if="message.type === 'time'" class="time-divider">
|
||||||
|
{{ message.content }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="message-item" :class="{ 'user-message': message.chatType === 1 }">
|
||||||
|
<img :src="message.chatType === 1 ? require('@/assets/user-avatar.png') : require('@/assets/xiaozhi-logo.png')"
|
||||||
|
class="avatar" />
|
||||||
|
<div class="message-content">{{ message.content }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="no-session-selected">
|
||||||
|
请选择会话查看聊天记录
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Api from '@/apis/api';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ChatHistoryDialog',
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
agentId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
agentName: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dialogVisible: false,
|
||||||
|
sessions: [],
|
||||||
|
messages: [],
|
||||||
|
currentSessionId: '',
|
||||||
|
currentMacAddress: '',
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
loading: false,
|
||||||
|
hasMore: true,
|
||||||
|
scrollTimer: null,
|
||||||
|
isFirstLoad: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(val) {
|
||||||
|
this.dialogVisible = val;
|
||||||
|
if (val) {
|
||||||
|
this.resetData();
|
||||||
|
this.loadSessions();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dialogVisible(val) {
|
||||||
|
if (!val) {
|
||||||
|
this.$emit('update:visible', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
messagesWithTime() {
|
||||||
|
if (!this.messages || this.messages.length === 0) return [];
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
const TIME_INTERVAL = 60 * 1000; // 1分钟的时间间隔(毫秒)
|
||||||
|
|
||||||
|
// 添加第一条消息的时间标记
|
||||||
|
if (this.messages[0]) {
|
||||||
|
result.push({
|
||||||
|
type: 'time',
|
||||||
|
content: this.formatTime(this.messages[0].createdAt),
|
||||||
|
id: `time-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理消息列表
|
||||||
|
for (let i = 0; i < this.messages.length; i++) {
|
||||||
|
const currentMessage = this.messages[i];
|
||||||
|
result.push(currentMessage);
|
||||||
|
|
||||||
|
// 检查是否需要添加时间标记
|
||||||
|
if (i < this.messages.length - 1) {
|
||||||
|
const currentTime = new Date(currentMessage.createdAt).getTime();
|
||||||
|
const nextTime = new Date(this.messages[i + 1].createdAt).getTime();
|
||||||
|
|
||||||
|
if (nextTime - currentTime > TIME_INTERVAL) {
|
||||||
|
result.push({
|
||||||
|
type: 'time',
|
||||||
|
content: this.formatTime(this.messages[i + 1].createdAt),
|
||||||
|
id: `time-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetData() {
|
||||||
|
this.sessions = [];
|
||||||
|
this.messages = [];
|
||||||
|
this.currentSessionId = '';
|
||||||
|
this.currentMacAddress = '';
|
||||||
|
this.page = 1;
|
||||||
|
this.loading = false;
|
||||||
|
this.hasMore = true;
|
||||||
|
this.isFirstLoad = true;
|
||||||
|
},
|
||||||
|
handleClose() {
|
||||||
|
this.dialogVisible = false;
|
||||||
|
},
|
||||||
|
loadSessions() {
|
||||||
|
if (this.loading || (!this.isFirstLoad && !this.hasMore)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
const params = {
|
||||||
|
page: this.page,
|
||||||
|
limit: this.limit
|
||||||
|
};
|
||||||
|
|
||||||
|
Api.agent.getAgentSessions(this.agentId, params, (res) => {
|
||||||
|
if (res.data && res.data.data && Array.isArray(res.data.data.list)) {
|
||||||
|
const list = res.data.data.list;
|
||||||
|
this.hasMore = list.length === this.limit;
|
||||||
|
|
||||||
|
this.sessions = [...this.sessions, ...list];
|
||||||
|
this.page++;
|
||||||
|
|
||||||
|
if (this.sessions.length > 0 && !this.currentSessionId) {
|
||||||
|
this.selectSession(this.sessions[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.loading = false;
|
||||||
|
this.isFirstLoad = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectSession(session) {
|
||||||
|
this.currentSessionId = session.sessionId;
|
||||||
|
Api.agent.getAgentChatHistory(this.agentId, session.sessionId, (res) => {
|
||||||
|
if (res.data && res.data.data) {
|
||||||
|
this.messages = res.data.data;
|
||||||
|
if (this.messages.length > 0 && this.messages[0].macAddress) {
|
||||||
|
this.currentMacAddress = this.messages[0].macAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleScroll(e) {
|
||||||
|
if (this.scrollTimer) {
|
||||||
|
clearTimeout(this.scrollTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollTimer = setTimeout(() => {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||||
|
// 当滚动到底部时加载更多
|
||||||
|
if (scrollHeight - scrollTop <= clientHeight + 50) {
|
||||||
|
this.loadSessions();
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
},
|
||||||
|
formatTime(timestamp) {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const now = new Date();
|
||||||
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||||
|
const yesterday = new Date(today);
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
|
||||||
|
const hours = date.getHours().toString().padStart(2, '0');
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
if (date >= today) {
|
||||||
|
return `今天 ${hours}:${minutes}`;
|
||||||
|
} else if (date >= yesterday) {
|
||||||
|
return `昨天 ${hours}:${minutes}`;
|
||||||
|
} else {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
const day = date.getDate().toString().padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chat-container {
|
||||||
|
display: flex;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-list {
|
||||||
|
width: 250px;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-item:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-item.active {
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-time {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #272727;
|
||||||
|
float: left;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
/* 为消息数量留出空间 */
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-count {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1890ff;
|
||||||
|
border-radius: 20px;
|
||||||
|
float: left;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item.user-message {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
max-width: 60%;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
margin: 0 10px;
|
||||||
|
text-align: left;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message .message-content {
|
||||||
|
background-color: #1890ff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.no-more {
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 10px 30px 10px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-session-selected {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-divider {
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-divider::before,
|
||||||
|
.time-divider::after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 30%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #eee;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.chat-history-dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 700px;
|
||||||
|
margin: 0 !important;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
height: 80vh;
|
||||||
|
max-width: 85vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-history-dialog .el-dialog__body {
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
height: calc(80vh - 54px);
|
||||||
|
/* 减去标题栏的高度 */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -26,6 +26,9 @@
|
|||||||
<div class="settings-btn" @click="handleDeviceManage">
|
<div class="settings-btn" @click="handleDeviceManage">
|
||||||
设备管理({{ device.deviceCount }})
|
设备管理({{ device.deviceCount }})
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-btn" @click="handleChatHistory">
|
||||||
|
聊天记录
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="version-info">
|
<div class="version-info">
|
||||||
<div>最近对话:{{ device.lastConnectedAt }}</div>
|
<div>最近对话:{{ device.lastConnectedAt }}</div>
|
||||||
@@ -51,6 +54,9 @@ export default {
|
|||||||
},
|
},
|
||||||
handleDeviceManage() {
|
handleDeviceManage() {
|
||||||
this.$router.push({ path: '/device-management', query: { agentId: this.device.agentId } });
|
this.$router.push({ path: '/device-management', query: { agentId: this.device.agentId } });
|
||||||
|
},
|
||||||
|
handleChatHistory() {
|
||||||
|
this.$emit('chat-history', { agentId: this.device.agentId, agentName: this.device.agentName })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="device-list-container">
|
<div class="device-list-container">
|
||||||
<template v-if="isLoading">
|
<template v-if="isLoading">
|
||||||
<div
|
<div v-for="i in skeletonCount" :key="'skeleton-' + i" class="skeleton-item">
|
||||||
v-for="i in skeletonCount"
|
|
||||||
:key="'skeleton-'+i"
|
|
||||||
class="skeleton-item"
|
|
||||||
>
|
|
||||||
<div class="skeleton-image"></div>
|
<div class="skeleton-image"></div>
|
||||||
<div class="skeleton-content">
|
<div class="skeleton-content">
|
||||||
<div class="skeleton-line"></div>
|
<div class="skeleton-line"></div>
|
||||||
@@ -46,14 +42,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<DeviceItem
|
<DeviceItem v-for="(item, index) in devices" :key="index" :device="item" @configure="goToRoleConfig"
|
||||||
v-for="(item, index) in devices"
|
@deviceManage="handleDeviceManage" @delete="handleDeleteAgent" @chat-history="handleShowChatHistory" />
|
||||||
:key="index"
|
|
||||||
:device="item"
|
|
||||||
@configure="goToRoleConfig"
|
|
||||||
@deviceManage="handleDeviceManage"
|
|
||||||
@delete="handleDeleteAgent"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,6 +52,7 @@
|
|||||||
<el-footer>
|
<el-footer>
|
||||||
<version-footer />
|
<version-footer />
|
||||||
</el-footer>
|
</el-footer>
|
||||||
|
<chat-history-dialog :visible.sync="showChatHistory" :agent-id="currentAgentId" :agent-name="currentAgentName" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
@@ -69,13 +60,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import Api from '@/apis/api';
|
import Api from '@/apis/api';
|
||||||
import AddWisdomBodyDialog from '@/components/AddWisdomBodyDialog.vue';
|
import AddWisdomBodyDialog from '@/components/AddWisdomBodyDialog.vue';
|
||||||
|
import ChatHistoryDialog from '@/components/ChatHistoryDialog.vue';
|
||||||
import DeviceItem from '@/components/DeviceItem.vue';
|
import DeviceItem from '@/components/DeviceItem.vue';
|
||||||
import HeaderBar from '@/components/HeaderBar.vue';
|
import HeaderBar from '@/components/HeaderBar.vue';
|
||||||
import VersionFooter from '@/components/VersionFooter.vue';
|
import VersionFooter from '@/components/VersionFooter.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HomePage',
|
name: 'HomePage',
|
||||||
components: { DeviceItem, AddWisdomBodyDialog, HeaderBar, VersionFooter },
|
components: { DeviceItem, AddWisdomBodyDialog, HeaderBar, VersionFooter, ChatHistoryDialog },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
addDeviceDialogVisible: false,
|
addDeviceDialogVisible: false,
|
||||||
@@ -85,6 +77,9 @@ export default {
|
|||||||
searchRegex: null,
|
searchRegex: null,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
skeletonCount: localStorage.getItem('skeletonCount') || 8,
|
skeletonCount: localStorage.getItem('skeletonCount') || 8,
|
||||||
|
showChatHistory: false,
|
||||||
|
currentAgentId: '',
|
||||||
|
currentAgentName: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -177,6 +172,11 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).catch(() => { });
|
}).catch(() => { });
|
||||||
|
},
|
||||||
|
handleShowChatHistory({ agentId, agentName }) {
|
||||||
|
this.currentAgentId = agentId;
|
||||||
|
this.currentAgentName = agentName;
|
||||||
|
this.showChatHistory = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,7 +302,9 @@ export default {
|
|||||||
|
|
||||||
/* 骨架屏动画 */
|
/* 骨架屏动画 */
|
||||||
@keyframes shimmer {
|
@keyframes shimmer {
|
||||||
100% { transform: translateX(100%); }
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-item {
|
.skeleton-item {
|
||||||
@@ -353,12 +355,10 @@ export default {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(
|
background: linear-gradient(90deg,
|
||||||
90deg,
|
rgba(255, 255, 255, 0),
|
||||||
rgba(255,255,255,0),
|
rgba(255, 255, 255, 0.3),
|
||||||
rgba(255,255,255,0.3),
|
rgba(255, 255, 255, 0));
|
||||||
rgba(255,255,255,0)
|
|
||||||
);
|
|
||||||
animation: shimmer 1.5s infinite;
|
animation: shimmer 1.5s infinite;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user