refactor(TiptapEditor): 清理代码注释和格式化代码
This commit is contained in:
parent
3f89777bf8
commit
b18a1b2604
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
// 引入Tiptap编辑器相关依赖
|
||||
|
||||
import { Editor, EditorContent, useEditor } from '@tiptap/vue-3'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Image from '@tiptap/extension-image'
|
||||
@ -10,83 +10,83 @@ import Link from '@tiptap/extension-link'
|
||||
import { Extension, Node } from '@tiptap/core'
|
||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
|
||||
// 引入Vue核心功能
|
||||
|
||||
import { reactive, watch, ref, markRaw, computed, onMounted, onUnmounted, shallowRef } from 'vue'
|
||||
// 引入Naive UI的弹出框组件
|
||||
|
||||
import { NPopover, NIcon } from 'naive-ui'
|
||||
// 引入图标组件
|
||||
|
||||
import {
|
||||
Voice as IconVoice, // 语音图标
|
||||
SourceCode, // 代码图标
|
||||
Local, // 地理位置图标
|
||||
SmilingFace, // 表情图标
|
||||
Pic, // 图片图标
|
||||
FolderUpload, // 文件上传图标
|
||||
Ranking, // 排名图标(用于投票)
|
||||
History, // 历史记录图标
|
||||
Close // 关闭图标
|
||||
Voice as IconVoice,
|
||||
SourceCode,
|
||||
Local,
|
||||
SmilingFace,
|
||||
Pic,
|
||||
FolderUpload,
|
||||
Ranking,
|
||||
History,
|
||||
Close
|
||||
} from '@icon-park/vue-next'
|
||||
|
||||
// 引入状态管理
|
||||
|
||||
import { useDialogueStore, useEditorDraftStore } from '@/store'
|
||||
// 引入获取图片信息的工具函数
|
||||
|
||||
import { getImageInfo } from '@/utils/functions'
|
||||
// 引入编辑器常量定义
|
||||
|
||||
import { EditorConst } from '@/constant/event-bus'
|
||||
// 引入事件调用工具
|
||||
|
||||
import { emitCall } from '@/utils/common'
|
||||
// 引入默认头像常量
|
||||
|
||||
import { defAvatar } from '@/constant/default'
|
||||
// 引入提及建议功能
|
||||
|
||||
import suggestion from './suggestion.js'
|
||||
// 引入编辑器各子组件
|
||||
import MeEditorVote from './MeEditorVote.vue' // 投票组件
|
||||
import MeEditorEmoticon from './MeEditorEmoticon.vue' // 表情组件
|
||||
import MeEditorCode from './MeEditorCode.vue' // 代码编辑组件
|
||||
import MeEditorRecorder from './MeEditorRecorder.vue' // 录音组件
|
||||
// 引入上传API
|
||||
|
||||
import MeEditorVote from './MeEditorVote.vue'
|
||||
import MeEditorEmoticon from './MeEditorEmoticon.vue'
|
||||
import MeEditorCode from './MeEditorCode.vue'
|
||||
import MeEditorRecorder from './MeEditorRecorder.vue'
|
||||
|
||||
import { uploadImg } from '@/api/upload'
|
||||
// 引入事件总线钩子
|
||||
|
||||
import { useEventBus } from '@/hooks'
|
||||
|
||||
// 定义组件的事件
|
||||
|
||||
const emit = defineEmits(['editor-event'])
|
||||
// 获取对话状态管理
|
||||
|
||||
const dialogueStore = useDialogueStore()
|
||||
// 获取编辑器草稿状态管理
|
||||
|
||||
const editorDraftStore = useEditorDraftStore()
|
||||
// 定义组件props
|
||||
|
||||
const props = defineProps({
|
||||
vote: {
|
||||
type: Boolean,
|
||||
default: false // 是否显示投票功能
|
||||
default: false
|
||||
},
|
||||
members: {
|
||||
default: () => [] // 聊天成员列表,用于@功能
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
// 计算当前对话索引名称(标识当前聊天)
|
||||
|
||||
const indexName = computed(() => dialogueStore.index_name)
|
||||
// 控制是否显示编辑器的投票界面
|
||||
|
||||
const isShowEditorVote = ref(false)
|
||||
// 控制是否显示编辑器的代码界面
|
||||
|
||||
const isShowEditorCode = ref(false)
|
||||
// 控制是否显示录音界面
|
||||
|
||||
const isShowEditorRecorder = ref(false)
|
||||
const uploadingImages = ref(new Map())
|
||||
// 图片文件上传DOM引用
|
||||
|
||||
const fileImageRef = ref()
|
||||
// 文件上传DOM引用
|
||||
|
||||
const uploadFileRef = ref()
|
||||
// 表情面板引用
|
||||
|
||||
const emoticonRef = ref()
|
||||
// 表情面板显示状态
|
||||
|
||||
const showEmoticon = ref(false)
|
||||
// 引用消息数据
|
||||
|
||||
const quoteData = ref(null)
|
||||
|
||||
// 自定义Emoji扩展
|
||||
|
||||
const Emoji = Node.create({
|
||||
name: 'emoji',
|
||||
group: 'inline',
|
||||
@ -129,12 +129,12 @@ const Emoji = Node.create({
|
||||
|
||||
|
||||
|
||||
// 创建自定义键盘处理插件,处理Enter键发送消息
|
||||
|
||||
const EnterKeyPlugin = new Plugin({
|
||||
key: new PluginKey('enterKey'),
|
||||
props: {
|
||||
handleKeyDown: (view, event) => {
|
||||
// 如果按下Enter键且没有按下Shift键,则发送消息
|
||||
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
onSendMessage()
|
||||
@ -145,7 +145,7 @@ const EnterKeyPlugin = new Plugin({
|
||||
},
|
||||
})
|
||||
|
||||
// 自定义键盘扩展
|
||||
|
||||
const CustomKeyboard = Extension.create({
|
||||
name: 'customKeyboard',
|
||||
|
||||
@ -156,7 +156,7 @@ const CustomKeyboard = Extension.create({
|
||||
},
|
||||
})
|
||||
|
||||
// 创建编辑器实例
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
@ -284,11 +284,6 @@ const editor = useEditor({
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 上传图片函数
|
||||
* @param file 文件对象
|
||||
* @returns Promise,成功时返回图片URL
|
||||
*/
|
||||
function findImagePos(url) {
|
||||
if (!editor.value) return -1
|
||||
let pos = -1
|
||||
@ -318,51 +313,43 @@ function onUploadImage(file) {
|
||||
image.onload = () => {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
form.append("source", "fonchain-chat"); // 图片来源标识
|
||||
// 添加图片尺寸信息作为URL参数
|
||||
form.append("source", "fonchain-chat");
|
||||
|
||||
form.append("urlParam", `width=${image.width}&height=${image.height}`);
|
||||
|
||||
// 调用上传API
|
||||
|
||||
uploadImg(form).then(({ code, data, message }) => {
|
||||
if (code == 0) {
|
||||
resolve(data.ori_url) // 返回原始图片URL
|
||||
resolve(data.ori_url)
|
||||
} else {
|
||||
resolve('')
|
||||
window['$message'].error(message) // 显示错误信息
|
||||
window['$message'].error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 投票事件处理
|
||||
* @param data 投票数据
|
||||
*/
|
||||
function onVoteEvent(data) {
|
||||
const msg = emitCall('vote_event', data, (ok) => {
|
||||
if (ok) {
|
||||
isShowEditorVote.value = false // 成功后关闭投票界面
|
||||
isShowEditorVote.value = false
|
||||
}
|
||||
})
|
||||
|
||||
emit('editor-event', msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* 表情事件处理
|
||||
* @param data 表情数据
|
||||
*/
|
||||
function onEmoticonEvent(data) {
|
||||
// 关闭表情面板
|
||||
|
||||
showEmoticon.value = false
|
||||
|
||||
if (data.type == 1) {
|
||||
// 插入文本表情
|
||||
|
||||
if (!editor.value) return
|
||||
|
||||
if (data.img) {
|
||||
// 插入图片表情
|
||||
|
||||
editor.value.chain().focus().insertContent({
|
||||
type: 'emoji',
|
||||
attrs: {
|
||||
@ -373,39 +360,31 @@ function onEmoticonEvent(data) {
|
||||
},
|
||||
}).run()
|
||||
} else {
|
||||
// 插入文本表情
|
||||
|
||||
editor.value.chain().focus().insertContent(data.value).run()
|
||||
}
|
||||
} else {
|
||||
// 发送整个表情包
|
||||
|
||||
let fn = emitCall('emoticon_event', data.value, () => {})
|
||||
emit('editor-event', fn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码事件处理
|
||||
* @param data 代码数据
|
||||
*/
|
||||
function onCodeEvent(data) {
|
||||
const msg = emitCall('code_event', data, (ok) => {
|
||||
isShowEditorCode.value = false // 成功后关闭代码界面
|
||||
isShowEditorCode.value = false
|
||||
})
|
||||
|
||||
emit('editor-event', msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传处理
|
||||
* @param e 上传事件对象
|
||||
*/
|
||||
async function onUploadFile(e) {
|
||||
let file = e.target.files[0]
|
||||
|
||||
e.target.value = null // 清空input,允许再次选择相同文件
|
||||
e.target.value = null
|
||||
|
||||
if (file.type.indexOf('image/') === 0) {
|
||||
// 处理图片文件 - 立即显示临时消息,然后上传
|
||||
|
||||
let fn = emitCall('image_event', file, () => {})
|
||||
emit('editor-event', fn)
|
||||
|
||||
@ -413,26 +392,22 @@ async function onUploadFile(e) {
|
||||
}
|
||||
|
||||
if (file.type.indexOf('video/') === 0) {
|
||||
// 处理视频文件
|
||||
|
||||
let fn = emitCall('video_event', file, () => {})
|
||||
emit('editor-event', fn)
|
||||
} else {
|
||||
// 处理其他类型文件
|
||||
|
||||
let fn = emitCall('file_event', file, () => {})
|
||||
emit('editor-event', fn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 录音事件处理
|
||||
* @param file 录音文件
|
||||
*/
|
||||
function onRecorderEvent(file) {
|
||||
emit('editor-event', emitCall('file_event', file))
|
||||
isShowEditorRecorder.value = false // 关闭录音界面
|
||||
isShowEditorRecorder.value = false
|
||||
}
|
||||
|
||||
// 将Tiptap内容转换为消息格式
|
||||
|
||||
function tiptapToMessage() {
|
||||
if (!editor.value) return []
|
||||
|
||||
@ -473,7 +448,7 @@ function tiptapToMessage() {
|
||||
} else if (node.type === 'hardBreak') {
|
||||
currentTextBuffer += '\n'
|
||||
} else if (node.type === 'image') {
|
||||
// 处理段落内的图片
|
||||
|
||||
flushTextBuffer()
|
||||
const data = {
|
||||
...getImageInfo(node.attrs.src),
|
||||
@ -490,7 +465,7 @@ function tiptapToMessage() {
|
||||
if (node.content) {
|
||||
processInlines(node.content)
|
||||
}
|
||||
currentTextBuffer += '\n' // Add newline after each paragraph
|
||||
currentTextBuffer += '\n'
|
||||
} else if (node.type === 'image') {
|
||||
flushTextBuffer()
|
||||
const data = {
|
||||
@ -517,20 +492,20 @@ function tiptapToMessage() {
|
||||
return messages
|
||||
}
|
||||
|
||||
// 将Tiptap内容转换为纯文本
|
||||
|
||||
function tiptapToString() {
|
||||
if (!editor.value) return ''
|
||||
|
||||
return editor.value.getText()
|
||||
}
|
||||
|
||||
// 检查编辑器是否为空
|
||||
|
||||
function isEditorEmpty() {
|
||||
if (!editor.value) return true
|
||||
|
||||
const json = editor.value.getJSON()
|
||||
|
||||
// 检查是否只有一个空段落
|
||||
|
||||
return !json.content || (
|
||||
json.content.length === 1 &&
|
||||
json.content[0].type === 'paragraph' &&
|
||||
@ -538,10 +513,6 @@ function isEditorEmpty() {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息处理
|
||||
* 根据编辑器内容类型发送不同类型的消息
|
||||
*/
|
||||
function onSendMessage() {
|
||||
if (uploadingImages.value.size > 0) {
|
||||
return window['$message'].info('正在上传图片,请稍后再发')
|
||||
@ -563,7 +534,7 @@ function onSendMessage() {
|
||||
return
|
||||
}
|
||||
|
||||
// 添加引用消息参数
|
||||
|
||||
if (quoteData.value) {
|
||||
msg.data.quoteId = quoteData.value.id
|
||||
msg.data.quote = { ...quoteData.value }
|
||||
@ -578,7 +549,7 @@ function onSendMessage() {
|
||||
url: msg.data.url,
|
||||
}
|
||||
|
||||
// 添加引用消息参数
|
||||
|
||||
if (quoteData.value) {
|
||||
data.quoteId = quoteData.value.id
|
||||
data.quote = { ...quoteData.value }
|
||||
@ -588,7 +559,7 @@ function onSendMessage() {
|
||||
}
|
||||
})
|
||||
|
||||
// 如果只有引用消息但没有内容,也发送一条空文本消息带引用
|
||||
|
||||
if (messages.length === 0 && quoteData.value) {
|
||||
const emptyData = {
|
||||
items: [{ type: 1, content: '' }],
|
||||
@ -602,49 +573,41 @@ function onSendMessage() {
|
||||
|
||||
if (canClear) {
|
||||
editor.value?.commands.clearContent(true)
|
||||
// 清空引用数据
|
||||
|
||||
quoteData.value = null
|
||||
// 更新草稿
|
||||
|
||||
onEditorChange()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器内容改变时的处理
|
||||
* 保存草稿并触发输入事件
|
||||
*/
|
||||
function onEditorChange() {
|
||||
if (!editor.value) return
|
||||
|
||||
const text = tiptapToString()
|
||||
|
||||
if (!isEditorEmpty() || quoteData.value) {
|
||||
// 保存草稿到store
|
||||
|
||||
editorDraftStore.items[indexName.value || ''] = JSON.stringify({
|
||||
text: text,
|
||||
content: editor.value.getJSON(),
|
||||
quoteData: quoteData.value
|
||||
})
|
||||
} else {
|
||||
// 编辑器为空时删除对应草稿
|
||||
|
||||
delete editorDraftStore.items[indexName.value || '']
|
||||
}
|
||||
|
||||
// 触发输入事件
|
||||
|
||||
emit('editor-event', emitCall('input_event', text))
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载编辑器草稿内容
|
||||
* 当切换聊天对象时,加载对应的草稿
|
||||
*/
|
||||
function loadEditorDraftText() {
|
||||
if (!editor.value) return
|
||||
|
||||
// 切换会话时清空引用数据,不保存当前引用数据
|
||||
|
||||
quoteData.value = null
|
||||
|
||||
// 从缓存中加载编辑器草稿
|
||||
|
||||
let draft = editorDraftStore.items[indexName.value || '']
|
||||
if (draft) {
|
||||
const parsed = JSON.parse(draft)
|
||||
@ -654,26 +617,22 @@ function loadEditorDraftText() {
|
||||
editor.value.commands.setContent(parsed.text)
|
||||
}
|
||||
|
||||
// 如果草稿中有引用数据,恢复它
|
||||
|
||||
if (parsed.quoteData) {
|
||||
quoteData.value = parsed.quoteData
|
||||
}
|
||||
} else {
|
||||
editor.value.commands.clearContent(true) // 没有草稿则清空编辑器
|
||||
editor.value.commands.clearContent(true)
|
||||
}
|
||||
|
||||
// 设置光标位置到末尾
|
||||
|
||||
editor.value.commands.focus('end')
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理@成员事件
|
||||
* @param data @成员数据
|
||||
*/
|
||||
function onSubscribeMention(data) {
|
||||
if (!editor.value) return
|
||||
|
||||
// 插入@项
|
||||
|
||||
editor.value.chain().focus().insertContent({
|
||||
type: 'mention',
|
||||
attrs: {
|
||||
@ -683,53 +642,42 @@ function onSubscribeMention(data) {
|
||||
}).run()
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理引用事件
|
||||
* @param data 引用数据
|
||||
*/
|
||||
function onSubscribeQuote(data) {
|
||||
if (!editor.value) return
|
||||
|
||||
// 保存引用数据
|
||||
|
||||
quoteData.value = data
|
||||
// 更新草稿
|
||||
|
||||
onEditorChange()
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空引用数据并更新草稿
|
||||
*/
|
||||
function clearQuoteData() {
|
||||
quoteData.value = null
|
||||
// 更新草稿
|
||||
|
||||
onEditorChange()
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理编辑消息事件
|
||||
* @param data 消息数据
|
||||
*/
|
||||
function onSubscribeEdit(data) {
|
||||
if (!editor.value) return
|
||||
|
||||
// 清空当前编辑器内容
|
||||
|
||||
editor.value.commands.clearContent(true)
|
||||
|
||||
// 插入要编辑的文本内容
|
||||
|
||||
editor.value.commands.insertContent(data.content)
|
||||
|
||||
// 设置光标位置到末尾
|
||||
|
||||
editor.value.commands.focus('end')
|
||||
}
|
||||
|
||||
// 底部工具栏配置
|
||||
|
||||
const navs = reactive([
|
||||
{
|
||||
title: '图片',
|
||||
icon: markRaw(Pic),
|
||||
show: true,
|
||||
click: () => {
|
||||
fileImageRef.value.click() // 触发图片上传
|
||||
fileImageRef.value.click()
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -737,38 +685,34 @@ const navs = reactive([
|
||||
icon: markRaw(FolderUpload),
|
||||
show: true,
|
||||
click: () => {
|
||||
uploadFileRef.value.click() // 触发文件上传
|
||||
uploadFileRef.value.click()
|
||||
}
|
||||
},
|
||||
|
||||
])
|
||||
|
||||
// 监听聊天索引变化,切换聊天时加载对应草稿
|
||||
|
||||
watch(indexName, loadEditorDraftText, { immediate: true })
|
||||
|
||||
// 组件挂载时初始化
|
||||
|
||||
onMounted(() => {
|
||||
loadEditorDraftText()
|
||||
})
|
||||
|
||||
// 订阅编辑器相关事件总线事件
|
||||
|
||||
useEventBus([
|
||||
{ name: EditorConst.Mention, event: onSubscribeMention }, // @成员事件
|
||||
{ name: EditorConst.Quote, event: onSubscribeQuote }, // 引用事件
|
||||
{ name: EditorConst.Edit, event: onSubscribeEdit } // 编辑消息事件
|
||||
{ name: EditorConst.Mention, event: onSubscribeMention },
|
||||
{ name: EditorConst.Quote, event: onSubscribeQuote },
|
||||
{ name: EditorConst.Edit, event: onSubscribeEdit }
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 编辑器容器 -->
|
||||
<section class="el-container editor">
|
||||
<section class="el-container is-vertical">
|
||||
|
||||
|
||||
<!-- 工具栏区域 -->
|
||||
<header class="el-header toolbar bdr-t">
|
||||
<div class="tools">
|
||||
<!-- 表情选择器弹出框 -->
|
||||
<n-popover
|
||||
placement="top-start"
|
||||
trigger="click"
|
||||
@ -788,8 +732,6 @@ useEventBus([
|
||||
|
||||
<MeEditorEmoticon @on-select="onEmoticonEvent" />
|
||||
</n-popover>
|
||||
|
||||
<!-- 工具栏其他功能按钮 -->
|
||||
<div
|
||||
class="item pointer"
|
||||
v-for="nav in navs"
|
||||
@ -802,7 +744,7 @@ useEventBus([
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<!-- 引用消息块 -->
|
||||
|
||||
<div v-if="quoteData" class="quote-card-wrapper">
|
||||
<div class="quote-card-content">
|
||||
<div class="quote-card-title">
|
||||
@ -817,20 +759,20 @@ useEventBus([
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 编辑器主体区域 -->
|
||||
|
||||
<main class="el-main height100">
|
||||
<editor-content :editor="editor" class="tiptap-editor" />
|
||||
</main>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- 隐藏的文件上传表单 -->
|
||||
|
||||
<form enctype="multipart/form-data" style="display: none">
|
||||
<input type="file" ref="fileImageRef" accept="image/*" @change="onUploadFile" />
|
||||
<input type="file" ref="uploadFileRef" @change="onUploadFile" />
|
||||
</form>
|
||||
|
||||
<!-- 条件渲染的功能组件 -->
|
||||
|
||||
<MeEditorVote v-if="isShowEditorVote" @close="isShowEditorVote = false" @submit="onVoteEvent" />
|
||||
|
||||
<MeEditorCode
|
||||
@ -847,12 +789,12 @@ useEventBus([
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
/* 编辑器容器样式 */
|
||||
|
||||
.editor {
|
||||
--tip-bg-color: rgb(241 241 241 / 90%); /* 提示背景颜色 */
|
||||
--tip-bg-color: rgb(241 241 241 / 90%);
|
||||
height: 100%;
|
||||
|
||||
/* 引用消息块样式 */
|
||||
|
||||
.quote-card-wrapper {
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
@ -925,7 +867,7 @@ useEventBus([
|
||||
user-select: none;
|
||||
|
||||
.tip-title {
|
||||
display: none; /* 默认隐藏提示文字 */
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0px;
|
||||
@ -943,7 +885,7 @@ useEventBus([
|
||||
|
||||
&:hover {
|
||||
.tip-title {
|
||||
display: block; /* 悬停时显示提示文字 */
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -951,7 +893,6 @@ useEventBus([
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗色模式样式调整 */
|
||||
html[theme-mode='dark'] {
|
||||
.editor {
|
||||
--tip-bg-color: #48484d;
|
||||
@ -982,7 +923,6 @@ html[theme-mode='dark'] {
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
/* 全局编辑器样式 */
|
||||
.tiptap-editor {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
@ -1002,7 +942,7 @@ html[theme-mode='dark'] {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5) url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="background:0 0"><circle cx="50" cy="50" r="32" stroke-width="8" stroke="%23fff" stroke-dasharray="50.26548245743669 50.26548245743669" fill="none" stroke-linecap="round"><animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" keyTimes="0;1" values="0 50 50;360 50 50"/></svg>');
|
||||
background: rgba(0, 0, 0, 0.5) url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="background:0 0"><circle cx="50" cy="50" r="32" stroke-width="8" stroke="%23fff" stroke-dasharray="50.26548245743669 50.26548245743669" fill="none" stroke-linecap="round"><animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" keyTimes="0;1" values="0 50 50;360 50 50"/></svg>');
|
||||
background-size: 30px 30px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
@ -1010,7 +950,6 @@ html[theme-mode='dark'] {
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
|
Loading…
Reference in New Issue
Block a user