- 新增用户端答题流程主页面,包含封面、介绍、答题、答题图解四个状态 - 新增用户端组件:封面、介绍页、答题页、答题图解页、题目渲染器及通用布局组件 - 新增用户端类型定义、模拟数据及业务常量 - 新增用户路由及类型声明,包含组别和队伍选择页面 - 新增管理端首页模块:数据卡片、头部横幅、折线图、饼图、创意横幅和项目新闻 - 新增富文本编辑器组件,支持图片和视频上传至OSS - 优化题库分类树,改为扁平化列表结构 - 完善比赛创建功能,增加轮次类型、题目分数类型及关联AP字段 - 修复模板删除函数调用参数错误 - 添加 pinyin-pro 依赖以支持拼音处理功能
117 lines
2.7 KiB
Vue
117 lines
2.7 KiB
Vue
<script setup lang="ts">
|
||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||
import { onBeforeUnmount, shallowRef } from 'vue'
|
||
import { getAliOssTokenAxios } from '@/service/api/upload'
|
||
import { browserPathJoin } from '@/utils/date'
|
||
import { initOSSClient, uploadFileToOSS } from '@/utils/oss'
|
||
import '@wangeditor/editor/dist/css/style.css'
|
||
|
||
interface Props {
|
||
modelValue: string
|
||
placeholder?: string
|
||
mode?: 'default' | 'simple'
|
||
height?: string
|
||
path?: string
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
modelValue: '',
|
||
placeholder: '请输入内容...',
|
||
mode: 'default',
|
||
height: '300px',
|
||
path: 'temp',
|
||
})
|
||
|
||
const emit = defineEmits(['update:modelValue'])
|
||
|
||
// Editor Logic
|
||
const editorRef = shallowRef()
|
||
const toolbarConfig = {
|
||
excludeKeys: [],
|
||
}
|
||
|
||
type InsertFnType = (url: string, alt: string, href: string) => void
|
||
|
||
async function customUpload(file: File, insertFn: InsertFnType) {
|
||
try {
|
||
const { data: tokenData, error: tokenError } = await getAliOssTokenAxios()
|
||
if (tokenError || !tokenData) {
|
||
window.$message?.error('获取上传凭证失败')
|
||
return
|
||
}
|
||
const client = initOSSClient(tokenData?.data || {})
|
||
const path = `${props.path}/${Date.now()}/${file.name}`
|
||
|
||
await uploadFileToOSS(client, file, path)
|
||
|
||
const url = browserPathJoin(import.meta.env.VITE_BASE_OSS_URL, path)
|
||
insertFn(url, file.name, url)
|
||
}
|
||
catch (error) {
|
||
console.error('上传失败', error)
|
||
window.$message?.error('上传失败')
|
||
}
|
||
}
|
||
|
||
const editorConfig = {
|
||
placeholder: props.placeholder,
|
||
MENU_CONF: {
|
||
uploadImage: {
|
||
customUpload,
|
||
},
|
||
uploadVideo: {
|
||
customUpload,
|
||
},
|
||
},
|
||
}
|
||
|
||
// 监听 placeholder 变化
|
||
// watch(() => props.placeholder, (_newVal) => {
|
||
// if (editorRef.value) {
|
||
// // 似乎 wangeditor 不支持动态修改 placeholder,这里作为占位
|
||
// }
|
||
// })
|
||
|
||
function handleCreated(editor: any) {
|
||
editorRef.value = editor
|
||
}
|
||
|
||
function handleChange(editor: any) {
|
||
emit('update:modelValue', editor.getHtml())
|
||
}
|
||
|
||
onBeforeUnmount(() => {
|
||
const editor = editorRef.value
|
||
if (editor == null)
|
||
return
|
||
editor.destroy()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="overflow-hidden border border-gray-200 rounded-lg bg-white">
|
||
<Toolbar
|
||
style="border-bottom: 1px solid #eee"
|
||
:editor="editorRef"
|
||
:default-config="toolbarConfig"
|
||
:mode="mode"
|
||
/>
|
||
<div :style="{ height: props.height }">
|
||
<Editor
|
||
:model-value="modelValue"
|
||
:style="{ height: '100%', overflowY: 'hidden' }"
|
||
:default-config="editorConfig"
|
||
:mode="mode"
|
||
@on-created="handleCreated"
|
||
@on-change="handleChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
:deep(.w-e-text-container) {
|
||
background-color: transparent;
|
||
}
|
||
</style>
|