AI弹窗
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
<!-- Login.vue -->
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="logo">
|
||||
@@ -11,14 +12,14 @@
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-password icon"></view>
|
||||
<input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-btn">
|
||||
<button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
|
||||
</view>
|
||||
|
||||
|
||||
<view class="xieyi text-center">
|
||||
<text class="text-grey1">柳州市网信办 柳州职业技术学院</text>
|
||||
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -39,22 +40,22 @@
|
||||
showPassword: false
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
login() {
|
||||
let sdata= {
|
||||
let sdata = {
|
||||
username: this.username,
|
||||
password: this.password
|
||||
};
|
||||
doLogin(sdata).then(res=>{
|
||||
doLogin(sdata).then(res => {
|
||||
if (res.code == 200) {
|
||||
uni.setStorageSync('token', res.token);
|
||||
uni.setStorageSync('token', res.token);
|
||||
uni.showToast({
|
||||
title: "登录成功",
|
||||
icon: "success"
|
||||
});
|
||||
getInfo().then(res=>{
|
||||
uni.setStorageSync("roles",res.roles[0])
|
||||
getInfo().then(res => {
|
||||
uni.setStorageSync("roles", res.roles[0])
|
||||
})
|
||||
uni.switchTab({
|
||||
url: "/pages/index/index"
|
||||
@@ -65,19 +66,19 @@
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
icon: "error"
|
||||
|
||||
|
||||
})
|
||||
} else{
|
||||
} else {
|
||||
console.log('11');
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
},
|
||||
|
||||
togglePassword() {
|
||||
this.showPassword = !this.showPassword;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
457
pages/aiChat/ai_index.vue
Normal file
457
pages/aiChat/ai_index.vue
Normal file
@@ -0,0 +1,457 @@
|
||||
<!-- pages/aiChat/ai_index -->
|
||||
<template>
|
||||
<view class="chat-container">
|
||||
<!-- 状态保持:当AI聊天可见时才显示聊天内容 -->
|
||||
|
||||
<!-- 状态栏占位 -->
|
||||
<view class="status-bar-placeholder"></view>
|
||||
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-nav-bar">
|
||||
<!-- 左侧:历史记录图标(触发抽屉) -->
|
||||
<view class="nav-left" @click="toggleHistoryDrawer">
|
||||
<image src="/static/history.svg" mode="aspectFit" class="nav-icon"></image>
|
||||
</view>
|
||||
<!-- 中间标题 -->
|
||||
<view class="nav-title">智水AI辅导员</view>
|
||||
|
||||
<!-- 右侧:新建聊天图标 -->
|
||||
<view class="nav-right" @click="newChat">
|
||||
<image src="/static/newChat.svg" mode="aspectFit" class="nav-icon"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<scroll-view scroll-y class="message-list" :scroll-top="scrollTop" scroll-with-animation>
|
||||
<block v-for="(item, index) in messages" :key="index">
|
||||
<view :class="['message-item', item.sender === 'user' ? 'user-message' : 'ai-message']">
|
||||
<!-- 用户/AI头像 -->
|
||||
<image class="avatar" :src="item.avatar"></image>
|
||||
<view class="message-content">
|
||||
<!-- 文字内容 -->
|
||||
<view v-if="item.content && item.sender === 'ai'" class="markdown-content"
|
||||
v-html="renderMarkdown(item.content)"></view>
|
||||
<text v-else-if="item.content">{{ item.content }}</text>
|
||||
<!-- 图片内容 -->
|
||||
<image v-if="item.image" :src="item.image" class="sent-image"></image>
|
||||
<!-- AI 特有内容 -->
|
||||
<view v-if="item.sender === 'ai'" class="ai-hint">
|
||||
<!-- 引用来源部分 -->
|
||||
<view v-if="item.retrieverResources && item.retrieverResources.length"
|
||||
class="reference-section">
|
||||
<view class="reference-section">
|
||||
<text class="reference-title">引用来源:</text>
|
||||
<!-- 遍历每个引用资源 -->
|
||||
<view v-for="(ref, idx) in item.retrieverResources" :key="idx"
|
||||
class="reference-item-wrapper">
|
||||
<!-- 可点击的文档名 -->
|
||||
<text class="doc-name-link" @click="toggleSingleReference(index, idx)">
|
||||
{{ ref.document_name }}
|
||||
</text>
|
||||
|
||||
<!-- 展开的详情(仅当前项) -->
|
||||
<view v-if="showSingleReference[index] && showSingleReference[index][idx]"
|
||||
class="reference-details-item">
|
||||
<text class="reference-meta">{{ ref.name }}({{ ref.document_name }})</text>
|
||||
<text class="reference-content" v-if="ref.content">{{ ref.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- AI操作区域(点赞/点踩) -->
|
||||
<view class="ai-actions">
|
||||
<text class="ai-text">回答由AI生成</text>
|
||||
<view class="icon-group">
|
||||
<img src="/static/good.svg" class="btn-icon"
|
||||
@click="handleThumbUpClick(item.messageId)" />
|
||||
<img src="/static/tread.svg" class="btn-icon"
|
||||
@click="handleThumbDownClick(item.messageId)" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 输入框和发送按钮 -->
|
||||
<view class="input-container">
|
||||
<!-- 历史记录抽屉组件 -->
|
||||
<HistoryDrawer :visible="showHistoryDrawer" @close="toggleHistoryDrawer" @item-click="onHistoryItemClick" />
|
||||
|
||||
<!-- 消息输入框 -->
|
||||
<input v-model="inputMessage" placeholder="输入消息..." @confirm="sendMessage" confirm-type="send" />
|
||||
<!-- 添加图片按钮 -->
|
||||
<img src="/static/add.svg" class="add-icon" @click="selectImage" />
|
||||
<!-- 发送消息按钮 -->
|
||||
<img src="/static/send.svg" class="send-icon" @click="sendMessage" />
|
||||
</view>
|
||||
|
||||
<!-- 悬浮按钮:固定在右下角 -->
|
||||
<view class="ai-hover" @click="goHome">
|
||||
<view class="ai-hover-content">
|
||||
<text class="ai-hover-text">AI</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* ========== 依赖 ========== */
|
||||
import HistoryDrawer from '@/components/aiChat/HistoryDrawer.vue'; // 历史记录抽屉组件
|
||||
import {
|
||||
createChatStream
|
||||
} from '@/utils/ai_stream.js'; // 流式聊天API
|
||||
import MarkdownIt from 'markdown-it'; // Markdown解析器
|
||||
import DOMPurify from 'dompurify'; // HTML净化器
|
||||
|
||||
/* DOMPurify 白名单加固 */
|
||||
DOMPurify.addHook('afterSanitizeAttributes', node => {
|
||||
if (node.tagName === 'A') node.setAttribute('rel', 'noopener noreferrer');
|
||||
});
|
||||
|
||||
// 初始化Markdown解析器
|
||||
const md = new MarkdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true
|
||||
});
|
||||
|
||||
/* ========== 配置 ========== */
|
||||
const BASE_URL = (function() {
|
||||
// #ifdef H5
|
||||
return 'http://localhost:8080/dev-api/aitutor/aichat'; // H5环境API地址
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
// return 'http://192.168.31.123:8080/aitutor/aichat'; // 真机调试改成你的电脑 IP
|
||||
// #endif
|
||||
})();
|
||||
|
||||
/* ========== 组件 ========== */
|
||||
export default {
|
||||
components: {
|
||||
HistoryDrawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showHistoryDrawer: false, // 是否显示历史记录抽屉
|
||||
inputMessage: '', // 用户输入的消息
|
||||
messages: [], // 聊天消息列表
|
||||
scrollTop: 0, // 滚动位置
|
||||
conversation_id: null, // 当前会话ID
|
||||
showSingleReference: {}, // 控制引用来源的显示状态
|
||||
currentReader: null, // 当前流式读取器
|
||||
/* 新增锁 & 基础信息 */
|
||||
sending: false, // 是否正在发送消息
|
||||
user: uni.getStorageSync('stuNo') || '', // 用户学号
|
||||
userId: uni.getStorageSync('stuId') || '', // 用户ID
|
||||
userName: uni.getStorageSync('stuName') || '' // 用户名
|
||||
};
|
||||
},
|
||||
|
||||
/* ---------- 生命周期 ---------- */
|
||||
onLoad() {
|
||||
// 页面加载时检查登录状态
|
||||
if (!this.user) {
|
||||
this.$toast('请先登录');
|
||||
setTimeout(() => uni.navigateTo({
|
||||
url: '/pages/login/index'
|
||||
}), 1500);
|
||||
return;
|
||||
}
|
||||
this.initChat(); // 初始化聊天
|
||||
},
|
||||
onUnload() {
|
||||
// 页面卸载时取消流式读取
|
||||
this.currentReader?.cancel?.('页面卸载');
|
||||
this.currentReader = null;
|
||||
},
|
||||
async onPullDownRefresh() {
|
||||
// 下拉刷新
|
||||
await this.initChat();
|
||||
uni.stopPullDownRefresh();
|
||||
this.$toast('刷新成功', 'success');
|
||||
},
|
||||
|
||||
/* ---------- 方法 ---------- */
|
||||
methods: {
|
||||
// 返回首页
|
||||
goHome() {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
},
|
||||
|
||||
// 切换历史记录抽屉显示状态
|
||||
toggleHistoryDrawer() {
|
||||
this.showHistoryDrawer = !this.showHistoryDrawer;
|
||||
},
|
||||
|
||||
// 渲染Markdown内容
|
||||
renderMarkdown(text) {
|
||||
return DOMPurify.sanitize(md.render(text || ''));
|
||||
},
|
||||
|
||||
/* 轻量 toast 封装 */
|
||||
$toast(title, icon = 'none') {
|
||||
uni.showToast({
|
||||
title,
|
||||
icon
|
||||
});
|
||||
},
|
||||
|
||||
/* 获取历史会话 or 欢迎语 */
|
||||
async initChat() {
|
||||
if (!this.user) return this.initConversation();
|
||||
try {
|
||||
// 请求获取用户历史消息
|
||||
const res = await uni.request({
|
||||
url: BASE_URL + '/getMessagesToUser',
|
||||
method: 'POST',
|
||||
data: {
|
||||
user: this.user
|
||||
}
|
||||
});
|
||||
const data = res.data || {};
|
||||
if (data.code === 200 && data.data?.conversationId) {
|
||||
// 设置会话ID和消息列表
|
||||
this.conversation_id = data.data.conversationId;
|
||||
uni.setStorageSync('conversation_id', this.conversation_id);
|
||||
this.messages = Array.isArray(data.data.messages) && data.data.messages.length ?
|
||||
data.data.messages :
|
||||
this.welcomeMessage();
|
||||
} else {
|
||||
this.initConversation();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取历史失败', e);
|
||||
this.$toast('连接失败,使用新会话');
|
||||
this.initConversation();
|
||||
}
|
||||
},
|
||||
|
||||
/* 欢迎语兜底 */
|
||||
welcomeMessage() {
|
||||
return [{
|
||||
sender: 'ai',
|
||||
avatar: '/static/AI.png',
|
||||
content: '你好!我是您的 AI 小助手,有什么可以帮您?😊',
|
||||
retrieverResources: [],
|
||||
image: ''
|
||||
}];
|
||||
},
|
||||
|
||||
// 初始化新会话
|
||||
initConversation() {
|
||||
uni.removeStorageSync('chatHistory');
|
||||
this.conversation_id = null;
|
||||
this.messages = this.welcomeMessage();
|
||||
},
|
||||
|
||||
/* 发送消息(带并发锁) */
|
||||
async sendMessage() {
|
||||
const msg = this.inputMessage.trim();
|
||||
if (!msg || this.sending) return;
|
||||
this.sending = true;
|
||||
|
||||
// 添加用户消息到列表
|
||||
this.messages.push({
|
||||
sender: 'user',
|
||||
avatar: '/static/yonghu.png',
|
||||
content: msg,
|
||||
image: '',
|
||||
messageId: Date.now().toString()
|
||||
});
|
||||
this.inputMessage = '';
|
||||
|
||||
// 添加AI占位消息
|
||||
const aiIdx = this.messages.push({
|
||||
sender: 'ai',
|
||||
avatar: '/static/AI.png',
|
||||
content: '',
|
||||
retrieverResources: [],
|
||||
image: '',
|
||||
messageId: null
|
||||
}) - 1;
|
||||
this.$set(this.showSingleReference, aiIdx, {});
|
||||
this.scrollToBottom();
|
||||
|
||||
try {
|
||||
// 创建流式聊天连接
|
||||
const {
|
||||
stream
|
||||
} = createChatStream({
|
||||
conversationId: this.conversation_id,
|
||||
prompt: msg,
|
||||
user: this.user,
|
||||
userId: this.userId,
|
||||
userName: this.userName
|
||||
});
|
||||
const {
|
||||
reader,
|
||||
decoder
|
||||
} = await stream;
|
||||
|
||||
let buffer = '';
|
||||
// 流式读取数据
|
||||
while (true) {
|
||||
const {
|
||||
done,
|
||||
value
|
||||
} = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, {
|
||||
stream: true
|
||||
});
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
// 处理每行数据
|
||||
for (const l of lines) {
|
||||
if (!l.startsWith('data:')) continue;
|
||||
const text = l.slice(5).trim();
|
||||
if (text === '[DONE]') continue;
|
||||
try {
|
||||
const json = JSON.parse(text);
|
||||
// 处理消息内容
|
||||
if (json.event === 'message' && json.answer) {
|
||||
this.$set(this.messages[aiIdx], 'content', this.messages[aiIdx].content + json
|
||||
.answer);
|
||||
this.scrollToBottom();
|
||||
}
|
||||
// 处理消息结束事件
|
||||
if (json.event === 'message_end') {
|
||||
if (json['conversation id']) this.conversation_id = json['conversation id'];
|
||||
if (json.metadata?.retriever_resources) {
|
||||
this.$set(this.messages[aiIdx], 'retrieverResources', json.metadata
|
||||
.retriever_resources);
|
||||
this.$set(this.messages[aiIdx], 'messageId', json.message_id || Date.now()
|
||||
.toString());
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('JSON 解析失败', text, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('流式请求失败', e);
|
||||
this.$set(this.messages[aiIdx], 'content', '抱歉,AI 回复失败,请重试。');
|
||||
} finally {
|
||||
this.sending = false;
|
||||
this.scrollToBottom();
|
||||
uni.setStorageSync('chatHistory', this.messages);
|
||||
}
|
||||
},
|
||||
|
||||
/* 滚动到底部 */
|
||||
scrollToBottom() {
|
||||
this.$nextTick(() => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.message-list')
|
||||
.boundingClientRect(rect => {
|
||||
this.scrollTop = (rect?.height || 0) + 9999;
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
},
|
||||
|
||||
// 切换单个引用来源的显示状态
|
||||
toggleSingleReference(msgIdx, refIdx) {
|
||||
if (!this.showSingleReference[msgIdx]) this.$set(this.showSingleReference, msgIdx, {});
|
||||
const cur = this.showSingleReference[msgIdx][refIdx];
|
||||
this.$set(this.showSingleReference[msgIdx], refIdx, !cur);
|
||||
},
|
||||
|
||||
// 点赞消息处理
|
||||
handleThumbUpClick(id) {
|
||||
if (!id) return;
|
||||
uni.request({
|
||||
url: BASE_URL + '/feedback',
|
||||
method: 'POST',
|
||||
data: {
|
||||
messageId: id,
|
||||
action: 1
|
||||
}
|
||||
}).then(() => this.$toast('点赞成功', 'success'))
|
||||
.catch(() => this.$toast('点赞失败'));
|
||||
},
|
||||
|
||||
// 点踩消息处理
|
||||
handleThumbDownClick(id) {
|
||||
if (!id) return;
|
||||
uni.request({
|
||||
url: BASE_URL + '/feedback',
|
||||
method: 'POST',
|
||||
data: {
|
||||
messageId: id,
|
||||
action: 0
|
||||
}
|
||||
}).then(() => this.$toast('已反馈'))
|
||||
.catch(() => this.$toast('反馈失败'));
|
||||
},
|
||||
|
||||
// 新建聊天会话
|
||||
newChat() {
|
||||
this.messages = [];
|
||||
this.conversation_id = null;
|
||||
uni.removeStorageSync('conversation_id');
|
||||
uni.removeStorageSync('chatHistory');
|
||||
this.initConversation();
|
||||
this.$toast('新聊天已开始');
|
||||
},
|
||||
|
||||
/* 选择图片并上传 */
|
||||
selectImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'], // 压缩
|
||||
sourceType: ['album', 'camera'],
|
||||
success: res => {
|
||||
const temp = res.tempFilePaths[0];
|
||||
// 上传图片
|
||||
uni.uploadFile({
|
||||
url: BASE_URL + '/files/upload',
|
||||
filePath: temp,
|
||||
name: 'file',
|
||||
formData: {
|
||||
user: this.user,
|
||||
userId: this.userId,
|
||||
userName: this.userName
|
||||
},
|
||||
success: ({
|
||||
data
|
||||
}) => {
|
||||
try {
|
||||
const {
|
||||
url
|
||||
} = JSON.parse(data);
|
||||
if (url) {
|
||||
// 添加图片消息到列表
|
||||
this.messages.push({
|
||||
sender: 'user',
|
||||
avatar: '/static/yonghu.png',
|
||||
content: '',
|
||||
image: url,
|
||||
messageId: Date.now().toString()
|
||||
});
|
||||
this.scrollToBottom();
|
||||
}
|
||||
} catch (e) {
|
||||
this.$toast('上传解析失败');
|
||||
}
|
||||
},
|
||||
fail: () => this.$toast('上传失败')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 引入全局样式 */
|
||||
@import '@/static/scss/ai_index.css';
|
||||
</style>
|
@@ -1,3 +1,4 @@
|
||||
<!-- index/index.vue -->
|
||||
<template>
|
||||
<view class="index">
|
||||
<!-- 头部个人信息 -->
|
||||
@@ -32,17 +33,17 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</workbench>
|
||||
|
||||
|
||||
<!-- AI 悬浮按钮 -->
|
||||
<view class="ai-hover" @click="showAI">
|
||||
<view class="ai-hover-content">
|
||||
<view class="ai-hover-text">AI</view>
|
||||
<!-- AI 悬浮按钮 -->
|
||||
<view class="ai-hover" @click="toAI">
|
||||
<view class="ai-hover-content">
|
||||
<text class="ai-hover-text">AI</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view>{{log}}</view> -->
|
||||
<!-- <view>{{log}}</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -169,7 +170,9 @@
|
||||
|
||||
// ]
|
||||
|
||||
log: "log"
|
||||
log: "log",
|
||||
// 新增:AI聊天显示状态,与聊天页同步
|
||||
isAIChatVisible: true
|
||||
}
|
||||
},
|
||||
onLoad(query) {
|
||||
@@ -184,9 +187,27 @@
|
||||
this.getQgzxLogExamineTotal()
|
||||
this.getqgzxMenoyTotal();
|
||||
}
|
||||
|
||||
// 从本地存储获取AI状态
|
||||
const aiStatus = uni.getStorageSync('aiVisibleStatus');
|
||||
if (aiStatus !== null && aiStatus !== undefined) {
|
||||
this.isAIChatVisible = aiStatus;
|
||||
} else {
|
||||
// 默认显示
|
||||
this.isAIChatVisible = true;
|
||||
uni.setStorageSync('aiVisibleStatus', true);
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
// 检查token是否存在
|
||||
if (!getToken()) {
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
});
|
||||
uni.navigateTo({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.getUserInfo();
|
||||
@@ -194,6 +215,9 @@
|
||||
this.getUserRouters();
|
||||
this.getQgzxLogExamineTotal()
|
||||
this.getqgzxMenoyTotal();
|
||||
|
||||
// 页面显示时同步AI状态
|
||||
this.isAIChatVisible = uni.getStorageSync('aiVisibleStatus') || true;
|
||||
},
|
||||
methods: {
|
||||
getUserRouters() {
|
||||
@@ -264,21 +288,13 @@
|
||||
getImgUrl(name) {
|
||||
return require('../../static/images/workbench/' + name + '.png');
|
||||
},
|
||||
async showAI() {
|
||||
let userInfo = {
|
||||
roleGroup: uni.getStorageSync("roles"),
|
||||
nickName: this.nickName,
|
||||
username: this.username,
|
||||
avater: this.avater,
|
||||
user_token: getToken()
|
||||
}
|
||||
//1.获取token
|
||||
userInfo.accessToken = (await this.getAccessToken()).access_token;
|
||||
userInfo.onRefreshToken = async () => (await this.getAccessToken()).accessToken;
|
||||
// console.log("请求AI的信息", userInfo)
|
||||
const sdk = await initCoze(userInfo);
|
||||
sdk.showChatBot();
|
||||
|
||||
toAI() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/aiChat/ai_index'
|
||||
});
|
||||
},
|
||||
|
||||
async getAccessToken() {
|
||||
const res = await getAccessToken(); // 调用请求函数
|
||||
const data = JSON.parse(res.data); // 解析数据
|
||||
@@ -387,7 +403,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ai悬停
|
||||
.ai-hover {
|
||||
position: fixed;
|
||||
@@ -403,5 +418,24 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.ai-hover-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ai-hover-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ai-hover:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user