feat(admin): 新增用户端答题流程页面与相关功能模块
- 新增用户端答题流程主页面,包含封面、介绍、答题、答题图解四个状态 - 新增用户端组件:封面、介绍页、答题页、答题图解页、题目渲染器及通用布局组件 - 新增用户端类型定义、模拟数据及业务常量 - 新增用户路由及类型声明,包含组别和队伍选择页面 - 新增管理端首页模块:数据卡片、头部横幅、折线图、饼图、创意横幅和项目新闻 - 新增富文本编辑器组件,支持图片和视频上传至OSS - 优化题库分类树,改为扁平化列表结构 - 完善比赛创建功能,增加轮次类型、题目分数类型及关联AP字段 - 修复模板删除函数调用参数错误 - 添加 pinyin-pro 依赖以支持拼音处理功能
This commit is contained in:
116
apps/admin/src/components/common/wang-editor.vue
Normal file
116
apps/admin/src/components/common/wang-editor.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user