初始化本地仓库
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ElButton @contextmenu.prevent="showMenu"> 右键触发菜单 </ElButton>
|
||||
|
||||
<!-- 右键菜单组件 -->
|
||||
<ArtMenuRight
|
||||
ref="menuRef"
|
||||
:menu-items="menuItems"
|
||||
:menu-width="180"
|
||||
:submenu-width="140"
|
||||
:border-radius="10"
|
||||
@select="handleSelect"
|
||||
@show="onMenuShow"
|
||||
@hide="onMenuHide"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick } from 'vue'
|
||||
import ArtMenuRight from '@/components/core/others/art-menu-right/index.vue'
|
||||
import type { MenuItemType } from '@/components/core/others/art-menu-right/index.vue'
|
||||
|
||||
defineOptions({ name: 'TemplateContextMenu' })
|
||||
|
||||
const menuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const lastAction = ref('')
|
||||
|
||||
/**
|
||||
* 右键菜单选项配置
|
||||
*/
|
||||
const menuItems = computed((): MenuItemType[] => [
|
||||
{
|
||||
key: 'copy',
|
||||
label: '复制',
|
||||
icon: 'ri:file-copy-line'
|
||||
},
|
||||
{
|
||||
key: 'paste',
|
||||
label: '粘贴',
|
||||
icon: 'ri:capsule-line'
|
||||
},
|
||||
{
|
||||
key: 'cut',
|
||||
label: '剪切',
|
||||
icon: 'ri:clipboard-line',
|
||||
showLine: true
|
||||
},
|
||||
{
|
||||
key: 'export',
|
||||
label: '导出选项',
|
||||
icon: 'ri:export-line',
|
||||
children: [
|
||||
{
|
||||
key: 'exportExcel',
|
||||
label: '导出 Excel',
|
||||
icon: 'ri:file-excel-2-line'
|
||||
},
|
||||
{
|
||||
key: 'exportPdf',
|
||||
label: '导出 PDF',
|
||||
icon: 'ri:file-pdf-2-line'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
label: '编辑选项',
|
||||
icon: 'ri:edit-2-line',
|
||||
children: [
|
||||
{
|
||||
key: 'rename',
|
||||
label: '重命名'
|
||||
},
|
||||
{
|
||||
key: 'duplicate',
|
||||
label: '复制副本'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'share',
|
||||
label: '分享',
|
||||
icon: 'ri:share-forward-line',
|
||||
showLine: true
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: '删除',
|
||||
icon: 'ri:delete-bin-line'
|
||||
},
|
||||
{
|
||||
key: 'disabled',
|
||||
label: '禁用选项',
|
||||
icon: 'ri:close-circle-line',
|
||||
disabled: true
|
||||
}
|
||||
])
|
||||
|
||||
/**
|
||||
* 处理菜单项选择
|
||||
* @param item 选中的菜单项
|
||||
*/
|
||||
const handleSelect = (item: MenuItemType) => {
|
||||
lastAction.value = `${item.label} (${item.key})`
|
||||
ElMessage.success(`执行操作: ${item.label}`)
|
||||
console.log('选择了菜单项:', item)
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示右键菜单
|
||||
* @param e 鼠标事件
|
||||
*/
|
||||
const showMenu = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
nextTick(() => {
|
||||
menuRef.value?.show(e)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单显示回调
|
||||
*/
|
||||
const onMenuShow = () => {
|
||||
console.log('菜单显示')
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单隐藏回调
|
||||
*/
|
||||
const onMenuHide = () => {
|
||||
console.log('菜单隐藏')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div class="page-content mb-5">
|
||||
<div class="mb-15 text-center">
|
||||
<h1 class="my-4 text-2xl font-semibold leading-tight"
|
||||
>基于 VueUse useTransition 的 Count-To 组件</h1
|
||||
>
|
||||
<p class="m-0 text-base leading-relaxed text-g-700"
|
||||
>高性能数字滚动动画组件,支持完整的动画控制和事件监听</p
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 基础用法 -->
|
||||
<div class="mb-15">
|
||||
<h2 class="m-0 mb-6 text-xl font-medium">基础用法</h2>
|
||||
<div class="count">
|
||||
<ArtCountTo :target="1000" :duration="2000" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 带前缀后缀 -->
|
||||
<div class="mb-15">
|
||||
<h2 class="m-0 mb-6 text-xl font-medium">带前缀后缀</h2>
|
||||
<div class="count">
|
||||
<ArtCountTo :target="20000" :duration="2500" prefix="¥" suffix="元" :decimals="2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 小数点和分隔符 -->
|
||||
<div class="mb-15">
|
||||
<h2 class="m-0 mb-6 text-xl font-medium">小数点和分隔符</h2>
|
||||
<div class="count">
|
||||
<ArtCountTo :target="2023.45" :duration="3000" :decimals="2" separator="," />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 动画效果对比 -->
|
||||
<div class="mb-15">
|
||||
<h2 class="m-0 mb-6 text-xl font-medium">动画效果对比</h2>
|
||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-6 mb-8">
|
||||
<div class="text-center" v-for="easing in easingTypes" :key="easing.type">
|
||||
<div class="mb-3 text-sm font-medium text-g-700">{{ easing.name }}</div>
|
||||
<div class="count">
|
||||
<ArtCountTo :target="easingTarget" :duration="3000" :easing="easing.type" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<ElButton @click="triggerEasing">触发所有动画</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<div class="mb-15">
|
||||
<h2 class="m-0 mb-6 text-xl font-medium">控制按钮</h2>
|
||||
<div class="count">
|
||||
<ArtCountTo
|
||||
ref="countToRef"
|
||||
:target="controlTarget"
|
||||
:duration="2000"
|
||||
@started="handleAnimationStarted"
|
||||
@finished="handleAnimationFinished"
|
||||
@paused="handleAnimationPaused"
|
||||
@reset="handleAnimationReset"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 justify-center">
|
||||
<ElButton @click="startCount">开始</ElButton>
|
||||
<ElButton @click="pauseCount">暂停</ElButton>
|
||||
<ElButton @click="resetCount">重置</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ArtCountTo from '@/components/core/text-effect/art-count-to/index.vue'
|
||||
|
||||
defineOptions({ name: 'TemplateCountTo' })
|
||||
|
||||
const controlTarget = ref(0)
|
||||
const countToRef = ref()
|
||||
const easingTarget = ref(0)
|
||||
|
||||
/**
|
||||
* 缓动动画类型配置
|
||||
*/
|
||||
const easingTypes = [
|
||||
{ name: 'Linear', type: 'linear' },
|
||||
{ name: 'Ease Out Cubic', type: 'easeOutCubic' },
|
||||
{ name: 'Ease Out Expo', type: 'easeOutExpo' },
|
||||
{ name: 'Ease Out Sine', type: 'easeOutSine' },
|
||||
{ name: 'Ease In Out', type: 'easeInOutCubic' },
|
||||
{ name: 'Ease In Quad', type: 'easeInQuad' }
|
||||
] as const
|
||||
|
||||
/**
|
||||
* 开始计数动画
|
||||
*/
|
||||
const startCount = () => {
|
||||
const newTarget = 5000
|
||||
controlTarget.value = newTarget
|
||||
countToRef.value?.start(newTarget)
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停计数动画
|
||||
*/
|
||||
const pauseCount = () => {
|
||||
countToRef.value?.pause()
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置计数动画
|
||||
*/
|
||||
const resetCount = () => {
|
||||
countToRef.value?.reset()
|
||||
controlTarget.value = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发缓动效果演示
|
||||
* 在 0 和 1000 之间切换
|
||||
*/
|
||||
const triggerEasing = () => {
|
||||
easingTarget.value = easingTarget.value === 0 ? 1000 : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画开始回调
|
||||
* @param value 目标值
|
||||
*/
|
||||
const handleAnimationStarted = (value: number) => {
|
||||
console.log('动画开始,目标值:', value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画完成回调
|
||||
* @param value 最终值
|
||||
*/
|
||||
const handleAnimationFinished = (value: number) => {
|
||||
console.log('动画完成,最终值:', value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画暂停回调
|
||||
* @param value 当前值
|
||||
*/
|
||||
const handleAnimationPaused = (value: number) => {
|
||||
console.log('动画暂停,当前值:', value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画重置回调
|
||||
*/
|
||||
const handleAnimationReset = () => {
|
||||
console.log('动画已重置')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference '@styles/core/tailwind.css';
|
||||
|
||||
.count {
|
||||
@apply p-5
|
||||
mb-5
|
||||
text-2xl
|
||||
font-semibold
|
||||
text-center
|
||||
bg-g-100
|
||||
rounded-lg
|
||||
tabular-nums
|
||||
border border-g-300/60;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<!-- https://vue-draggable-plus.pages.dev/ -->
|
||||
<template>
|
||||
<div class="page-content mb-5">
|
||||
<ElRow>
|
||||
<ElCard shadow="never" class="w-75 mr-5 mb-7.5">
|
||||
<template #header>
|
||||
<span class="text-base font-bold">基础示例</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<VueDraggable ref="el" v-model="userList">
|
||||
<div
|
||||
class="p-2.5 mb-2.5 cursor-move bg-active-color rounded"
|
||||
v-for="item in userList"
|
||||
:key="item.name"
|
||||
>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</template>
|
||||
</ElCard>
|
||||
|
||||
<ElCard shadow="never" class="w-75 mb-7.5">
|
||||
<template #header>
|
||||
<span class="text-base font-bold">过渡动画</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<VueDraggable v-model="userList" target=".sort-target" :scroll="true">
|
||||
<TransitionGroup type="transition" tag="ul" name="fade" class="sort-target">
|
||||
<li
|
||||
v-for="item in userList"
|
||||
:key="item.name"
|
||||
class="p-2.5 mb-2.5 cursor-move bg-active-color rounded"
|
||||
>
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</TransitionGroup>
|
||||
</VueDraggable>
|
||||
</template>
|
||||
</ElCard>
|
||||
</ElRow>
|
||||
|
||||
<ElCard shadow="never" class="mb-7.5">
|
||||
<template #header>
|
||||
<span class="text-base font-bold">表格拖拽排序</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<VueDraggable target="tbody" v-model="userList" :animation="150">
|
||||
<ArtTable :data="userList">
|
||||
<ElTableColumn label="姓名" prop="name" />
|
||||
<ElTableColumn label="角色" prop="role" />
|
||||
</ArtTable>
|
||||
</VueDraggable>
|
||||
</template>
|
||||
</ElCard>
|
||||
|
||||
<ElCard shadow="never" class="mb-7.5">
|
||||
<template #header>
|
||||
<span class="text-base font-bold">指定元素拖拽排序</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<VueDraggable target="tbody" handle=".handle" v-model="userList" :animation="150">
|
||||
<ArtTable :data="userList">
|
||||
<ElTableColumn label="姓名" prop="name" />
|
||||
<ElTableColumn label="角色" prop="role" />
|
||||
<ElTableColumn label="操作" width="100">
|
||||
<ElButton size="default" class="handle"> 移动 </ElButton>
|
||||
</ElTableColumn>
|
||||
</ArtTable>
|
||||
</VueDraggable>
|
||||
</template>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
|
||||
defineOptions({ name: 'TemplateDrag' })
|
||||
|
||||
/**
|
||||
* 用户列表数据
|
||||
* 用于演示拖拽排序功能
|
||||
*/
|
||||
const userList = ref([
|
||||
{ name: '孙悟空', role: '斗战胜佛' },
|
||||
{ name: '猪八戒', role: '净坛使者' },
|
||||
{ name: '沙僧', role: '金身罗汉' },
|
||||
{ name: '唐僧', role: '旃檀功德佛' }
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fade-move,
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: scaleY(0.01) translate(30px, 0);
|
||||
}
|
||||
|
||||
.fade-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ArtExcelImport @import-success="handleImportSuccess" @import-error="handleImportError">
|
||||
<template #import-text> 上传 Excel </template>
|
||||
</ArtExcelImport>
|
||||
|
||||
<ArtExcelExport
|
||||
style="margin-left: 10px"
|
||||
:data="tableData"
|
||||
filename="用户数据-1"
|
||||
sheetName="用户列表"
|
||||
type="success"
|
||||
:headers="headers"
|
||||
auto-index
|
||||
:columns="columnConfig"
|
||||
@export-success="handleExportSuccess"
|
||||
@export-error="handleExportError"
|
||||
@export-progress="handleProgress"
|
||||
>
|
||||
导出 Excel
|
||||
</ArtExcelExport>
|
||||
|
||||
<ElButton type="danger" @click="handleClear" v-ripple>清除数据</ElButton>
|
||||
|
||||
<ArtTable :data="tableData" style="margin-top: 10px">
|
||||
<ElTableColumn
|
||||
v-for="key in Object.keys(headers)"
|
||||
:key="key"
|
||||
:prop="key"
|
||||
:label="headers[key as keyof typeof headers]"
|
||||
/>
|
||||
</ArtTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'WidgetsExcel' })
|
||||
|
||||
/**
|
||||
* 表格数据类型定义
|
||||
*/
|
||||
interface TableData {
|
||||
name: string
|
||||
age: number
|
||||
city: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格数据
|
||||
*/
|
||||
const tableData = ref<TableData[]>([
|
||||
{ name: '李四', age: 20, city: '上海' },
|
||||
{ name: '张三', age: 25, city: '北京' },
|
||||
{ name: '王五', age: 30, city: '广州' },
|
||||
{ name: '赵六', age: 35, city: '深圳' },
|
||||
{ name: '孙七', age: 28, city: '杭州' },
|
||||
{ name: '周八', age: 32, city: '成都' },
|
||||
{ name: '吴九', age: 27, city: '武汉' },
|
||||
{ name: '郑十', age: 40, city: '南京' },
|
||||
{ name: '刘一', age: 22, city: '重庆' },
|
||||
{ name: '陈二', age: 33, city: '西安' }
|
||||
])
|
||||
|
||||
/**
|
||||
* 表头映射配置
|
||||
* 用于 Excel 导入导出时的字段映射
|
||||
*/
|
||||
const headers = {
|
||||
name: '姓名',
|
||||
age: '年龄',
|
||||
city: '城市'
|
||||
}
|
||||
|
||||
/**
|
||||
* 列配置
|
||||
* 用于 Excel 导出时的列宽和格式化
|
||||
*/
|
||||
const columnConfig = {
|
||||
name: {
|
||||
title: '姓名',
|
||||
width: 20,
|
||||
formatter: (value: unknown) => (value ? String(value) : '未知')
|
||||
},
|
||||
age: {
|
||||
title: '年龄',
|
||||
width: 10,
|
||||
formatter: (value: unknown) => (value ? `${value}岁` : '0岁')
|
||||
},
|
||||
city: {
|
||||
title: '城市',
|
||||
width: 12,
|
||||
formatter: (value: unknown) => (value ? `${value}市` : '未知')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Excel 导入成功
|
||||
* 将导入的数据转换为表格数据格式
|
||||
* @param data 导入的原始数据
|
||||
*/
|
||||
const handleImportSuccess = (data: Array<Record<string, unknown>>) => {
|
||||
const formattedData: TableData[] = data.map((item) => ({
|
||||
name: String(item['姓名'] || ''),
|
||||
age: Number(item['年龄']) || 0,
|
||||
city: String(item['城市'] || '')
|
||||
}))
|
||||
tableData.value = formattedData
|
||||
ElMessage.success(`成功导入 ${formattedData.length} 条数据`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Excel 导入错误
|
||||
* @param error 错误对象
|
||||
*/
|
||||
const handleImportError = (error: Error) => {
|
||||
console.error('导入失败:', error)
|
||||
ElMessage.error(`导入失败: ${error.message}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Excel 导出成功
|
||||
*/
|
||||
const handleExportSuccess = () => {
|
||||
console.log('导出成功')
|
||||
ElMessage.success('Excel 导出成功')
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Excel 导出错误
|
||||
* @param error 错误对象
|
||||
*/
|
||||
const handleExportError = (error: Error) => {
|
||||
ElMessage.error(`导出失败: ${error.message}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理导出进度
|
||||
* @param progress 导出进度百分比
|
||||
*/
|
||||
const handleProgress = (progress: number) => {
|
||||
console.log('导出进度:', progress)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空表格数据
|
||||
*/
|
||||
const handleClear = () => {
|
||||
tableData.value = []
|
||||
ElMessage.info('已清空数据')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="mb-5">
|
||||
<ElSpace wrap>
|
||||
<ElButton :disabled="isLaunching" v-ripple @click="handleSingleLaunch"
|
||||
>✨ 放个小礼花</ElButton
|
||||
>
|
||||
<ElButton :disabled="isLaunching" v-ripple @click="handleImageLaunch(bp)"
|
||||
>🎉 打开幸运红包</ElButton
|
||||
>
|
||||
<ElButton :disabled="isLaunching" v-ripple @click="handleMultipleLaunch('')"
|
||||
>🎆 璀璨烟火秀</ElButton
|
||||
>
|
||||
<ElButton :disabled="isLaunching" v-ripple @click="handleImageLaunch(sd)"
|
||||
>❄️ 飘点小雪花</ElButton
|
||||
>
|
||||
<ElButton :disabled="isLaunching" v-ripple @click="handleMultipleLaunch(sd)"
|
||||
>❄️ 浪漫暴风雪</ElButton
|
||||
>
|
||||
</ElSpace>
|
||||
</div>
|
||||
|
||||
<ElDescriptions
|
||||
title="礼花组件说明"
|
||||
direction="vertical"
|
||||
:column="1"
|
||||
border
|
||||
style="margin-top: 50px"
|
||||
>
|
||||
<ElDescriptionsItem label="显示时机">
|
||||
礼花效果组件已全局注册,触发时机由配置文件控制。默认配置中的日期已过,不会在你使用过程中再次触发,无需担心。
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="礼花样式">
|
||||
默认显示几何图形,可以配置图片,图片需要提前在
|
||||
src/components/core/layouts/art-fireworks-effect/index.vue 文件预先定义
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="配置文件">
|
||||
在 src/config/festival.ts 文件中,可以配置节日和对应的礼花样式
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="快捷键">
|
||||
command + shift + p 或者 ctrl + shift + p
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mittBus } from '@/utils/sys'
|
||||
import bp from '@imgs/ceremony/hb.png'
|
||||
import sd from '@imgs/ceremony/sd.png'
|
||||
|
||||
defineOptions({ name: 'WidgetsFireworks' })
|
||||
|
||||
const timerRef = ref<ReturnType<typeof setInterval> | null>(null)
|
||||
const isLaunching = ref(false)
|
||||
|
||||
/**
|
||||
* 触发连续礼花效果
|
||||
* @param count 发射次数
|
||||
* @param src 图片资源路径
|
||||
*/
|
||||
const triggerFireworks = (count: number, src: string) => {
|
||||
// 清除之前的定时器
|
||||
if (timerRef.value) {
|
||||
clearInterval(timerRef.value)
|
||||
timerRef.value = null
|
||||
}
|
||||
|
||||
isLaunching.value = true
|
||||
|
||||
let fired = 0
|
||||
timerRef.value = setInterval(() => {
|
||||
mittBus.emit('triggerFireworks', src)
|
||||
fired++
|
||||
|
||||
if (fired >= count) {
|
||||
clearInterval(timerRef.value!)
|
||||
timerRef.value = null
|
||||
isLaunching.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射单个礼花
|
||||
*/
|
||||
const handleSingleLaunch = () => {
|
||||
mittBus.emit('triggerFireworks')
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射多个礼花
|
||||
* @param src 图片资源路径
|
||||
*/
|
||||
const handleMultipleLaunch = (src: string) => {
|
||||
triggerFireworks(10, src)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射带图片的礼花
|
||||
* @param src 图片资源路径
|
||||
*/
|
||||
const handleImageLaunch = (src: string) => {
|
||||
mittBus.emit('triggerFireworks', src)
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件卸载时清理定时器
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
if (timerRef.value) {
|
||||
clearInterval(timerRef.value)
|
||||
timerRef.value = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="space-y-5 mb-5">
|
||||
<div class="text-2xl font-medium mt-5 max-sm:text-2xl max-sm:mt-3">图标</div>
|
||||
<div class="text-g-800">
|
||||
v.3.0 版本图标库升级为 iconify,可在
|
||||
<a href="https://icones.js.org/" target="_blank" class="text-theme hover:underline"
|
||||
>Iconify</a
|
||||
>
|
||||
中查找,支持多种图标库,如 Remix Icon, Solar, Tabler Icons 等。
|
||||
</div>
|
||||
<div class="text-g-800">
|
||||
为确保系统图标风格统一,项目全部采用 Remix Icon 图标库,可在
|
||||
<a
|
||||
href="https://icones.js.org/collection/ri"
|
||||
target="_blank"
|
||||
class="text-theme hover:underline"
|
||||
>Iconify</a
|
||||
>
|
||||
或<a href="https://remixicon.com/" target="_blank" class="text-theme hover:underline">
|
||||
Remix Icon 官网 </a
|
||||
>搜索使用。
|
||||
</div>
|
||||
|
||||
<!-- Iconify 图标 -->
|
||||
<div class="art-card-sm p-5">
|
||||
<div class="text-lg font-semibold mb-4">Iconify</div>
|
||||
<div class="flex items-center gap-6">
|
||||
<ArtSvgIcon icon="ri:github-fill" class="text-2xl" />
|
||||
<ArtSvgIcon icon="ri:copilot-line" class="text-2xl text-theme" />
|
||||
<ArtSvgIcon icon="ri:edge-line" class="text-2xl text-secondary" />
|
||||
<ArtSvgIcon icon="ri:planet-line" class="text-2xl text-warning" />
|
||||
<ArtSvgIcon icon="ri:windows-line" class="text-2xl text-info" />
|
||||
<ArtSvgIcon icon="ri:thumb-up-line" class="text-2xl text-danger" />
|
||||
<ArtSvgIcon icon="ri:gift-2-line" class="text-2xl text-success" />
|
||||
<ArtSvgIcon icon="ri:apple-line" class="text-2xl text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Svg Icons -->
|
||||
<div class="art-card-sm p-5">
|
||||
<div class="text-lg font-semibold mb-4">Svg Icons</div>
|
||||
<div class="flex items-center gap-6">
|
||||
<ArtSvgIcon icon="svg-spinners:3-dots-fade" class="text-2xl text-red-400" />
|
||||
<ArtSvgIcon icon="svg-spinners:3-dots-bounce" class="text-2xl text-blue-400" />
|
||||
<ArtSvgIcon icon="svg-spinners:3-dots-move" class="text-2xl text-orange-400" />
|
||||
<ArtSvgIcon icon="svg-spinners:3-dots-rotate" class="text-2xl text-purple-400" />
|
||||
<ArtSvgIcon icon="svg-spinners:blocks-shuffle-2" class="text-2xl text-pink-300" />
|
||||
<ArtSvgIcon icon="svg-spinners:clock" class="text-2xl text-yellow-500" />
|
||||
<ArtSvgIcon icon="svg-spinners:tadpole" class="text-2xl text-orange-500" />
|
||||
<ArtSvgIcon icon="svg-spinners:blocks-wave" class="text-2xl text-blue-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Svg Icons -->
|
||||
<div class="art-card-sm p-5">
|
||||
<div class="text-lg font-semibold mb-4">Material Line Icons</div>
|
||||
<div class="flex items-center gap-6">
|
||||
<ArtSvgIcon icon="line-md:phone-call-twotone-loop" class="text-2xl text-blue-500" />
|
||||
|
||||
<ArtSvgIcon icon="line-md:switch-off" class="text-2xl text-green-500" />
|
||||
<ArtSvgIcon icon="line-md:sun-rising-filled-loop" class="text-2xl text-yellow-400" />
|
||||
<ArtSvgIcon icon="line-md:volume-high-filled" class="text-2xl text-purple-500" />
|
||||
<ArtSvgIcon icon="line-md:github-twotone" class="text-2xl text-gray-700" />
|
||||
<ArtSvgIcon icon="line-md:telegram" class="text-2xl text-sky-500" />
|
||||
<ArtSvgIcon icon="line-md:reddit-loop" class="text-2xl text-orange-400" />
|
||||
<ArtSvgIcon
|
||||
icon="line-md:coffee-half-empty-filled-loop"
|
||||
class="text-2xl text-emerald-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 使用示例 -->
|
||||
<div class="art-card-sm p-5">
|
||||
<div class="text-lg font-semibold mb-4">使用示例</div>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<div class="text-sm text-g-600 mb-2">基础使用</div>
|
||||
<div class="bg-g-200 dark:bg-g-300/30 p-4 rounded font-mono text-sm text-g-800">
|
||||
<ArtSvgIcon icon="ri:home-line" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-g-600 mb-2">自定义大小</div>
|
||||
<div class="bg-g-200 dark:bg-g-300/30 p-4 rounded font-mono text-sm text-g-800">
|
||||
<ArtSvgIcon icon="ri:user-line" class="text-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-g-600 mb-2">自定义颜色</div>
|
||||
<div class="bg-g-200 dark:bg-g-300/30 p-4 rounded font-mono text-sm text-g-800">
|
||||
<ArtSvgIcon icon="ri:heart-fill" class="text-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-g-600 mb-2">组合使用</div>
|
||||
<div class="bg-g-200 dark:bg-g-300/30 p-4 rounded font-mono text-sm text-g-800">
|
||||
<ArtSvgIcon icon="ri:star-fill" class="text-4xl text-yellow-500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ArtSvgIcon from '@/components/core/base/art-svg-icon/index.vue'
|
||||
|
||||
defineOptions({ name: 'IconPage' })
|
||||
</script>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ArtCutterImg
|
||||
v-model:imgUrl="imageUrl"
|
||||
:boxWidth="530"
|
||||
:boxHeight="300"
|
||||
:cutWidth="360"
|
||||
:cutHeight="200"
|
||||
:quality="1"
|
||||
:tool="true"
|
||||
:watermarkText="'My Watermark'"
|
||||
watermarkColor="#ff0000"
|
||||
:showPreview="true"
|
||||
:originalGraph="false"
|
||||
:title="'图片裁剪'"
|
||||
:previewTitle="'预览效果'"
|
||||
@error="handleError"
|
||||
@imageLoadComplete="handleLoadComplete"
|
||||
@imageLoadError="handleLoadError"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import lockImg from '@imgs/lock/bg_dark.webp'
|
||||
|
||||
defineOptions({ name: 'WidgetsImageCrop' })
|
||||
|
||||
/**
|
||||
* 图片 URL
|
||||
*/
|
||||
const imageUrl = ref(lockImg)
|
||||
|
||||
/**
|
||||
* 处理裁剪错误
|
||||
* @param error 错误对象
|
||||
*/
|
||||
const handleError = (error: Error) => {
|
||||
console.error('裁剪错误:', error)
|
||||
ElMessage.error('图片裁剪失败')
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理图片加载完成
|
||||
* @param result 加载结果
|
||||
*/
|
||||
const handleLoadComplete = (result: { url: string; width: number; height: number }) => {
|
||||
console.log('图片加载完成:', result)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理图片加载错误
|
||||
* @param error 错误对象
|
||||
*/
|
||||
const handleLoadError = (error: Error) => {
|
||||
console.error('图片加载失败:', error)
|
||||
ElMessage.error('图片加载失败')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="test-page p-5">
|
||||
<h1 class="text-2xl font-bold mb-5">图片选择器组件测试</h1>
|
||||
|
||||
<!-- 单选测试 -->
|
||||
<div class="mb-8 p-5 bg-white rounded-lg shadow-sm">
|
||||
<h2 class="text-xl font-semibold mb-3 text-g-800">1. 单选模式</h2>
|
||||
<div class="flex items-center gap-5">
|
||||
<ImageSelector
|
||||
v-model="singleImage"
|
||||
:multiple="false"
|
||||
shape="circle"
|
||||
size="medium"
|
||||
@confirm="handleSingleConfirm"
|
||||
/>
|
||||
<div class="text-sm">
|
||||
<div class="font-medium">已选择:</div>
|
||||
<div class="text-g-600 mt-1 break-all">{{ singleImage || '未选择' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 多选测试 -->
|
||||
<div class="mb-8 p-5 bg-white rounded-lg shadow-sm">
|
||||
<h2 class="text-xl font-semibold mb-3 text-g-800">2. 多选模式</h2>
|
||||
<ImageSelector
|
||||
v-model="multipleImages"
|
||||
:multiple="true"
|
||||
shape="square"
|
||||
size="small"
|
||||
@confirm="handleMultipleConfirm"
|
||||
/>
|
||||
<div class="mt-3 text-sm">
|
||||
<div class="font-medium">已选择 ({{ multipleImages.length }} 张):</div>
|
||||
<div class="text-g-600 mt-1 break-all">{{ multipleImages.length > 0 ? multipleImages.join(', ') : '未选择' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 大尺寸单选 -->
|
||||
<div class="mb-8 p-5 bg-white rounded-lg shadow-sm">
|
||||
<h2 class="text-xl font-semibold mb-3 text-g-800">3. 大尺寸单选(带默认值)</h2>
|
||||
<div class="flex items-center gap-5">
|
||||
<ImageSelector
|
||||
v-model="largeImage"
|
||||
:multiple="false"
|
||||
shape="square"
|
||||
size="large"
|
||||
:default-selected="'/upload/default-avatar.webp'"
|
||||
@confirm="handleLargeConfirm"
|
||||
/>
|
||||
<div class="text-sm">
|
||||
<div class="font-medium">已选择:</div>
|
||||
<div class="text-g-600 mt-1 break-all">{{ largeImage || '未选择' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 禁用状态 -->
|
||||
<div class="mb-8 p-5 bg-white rounded-lg shadow-sm">
|
||||
<h2 class="text-xl font-semibold mb-3 text-g-800">4. 禁用状态</h2>
|
||||
<ImageSelector
|
||||
v-model="disabledImage"
|
||||
:multiple="false"
|
||||
shape="circle"
|
||||
size="medium"
|
||||
:disabled="true"
|
||||
/>
|
||||
<div class="mt-2 text-sm text-g-600">
|
||||
禁用状态下的组件不可点击
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 代码示例 -->
|
||||
<div class="mb-8 p-5 bg-white rounded-lg shadow-sm">
|
||||
<h2 class="text-xl font-semibold mb-3 text-g-800">5. 代码示例</h2>
|
||||
<ElTabs>
|
||||
<ElTabPane label="单选">
|
||||
<pre class="bg-g-100 p-3 rounded text-sm overflow-auto">{{ singleCode }}</pre>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="多选">
|
||||
<pre class="bg-g-100 p-3 rounded text-sm overflow-auto">{{ multipleCode }}</pre>
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import ImageSelector from '@/components/form/components/image-selector/index.vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
// 测试数据
|
||||
const singleImage = ref<string>('')
|
||||
const multipleImages = ref<string[]>([])
|
||||
const largeImage = ref<string>('/upload/default-avatar.webp')
|
||||
const disabledImage = ref<string>('/upload/default-avatar.webp')
|
||||
|
||||
// 事件处理
|
||||
const handleSingleConfirm = (image: string) => {
|
||||
ElMessage.success(`单选确认: ${image}`)
|
||||
}
|
||||
|
||||
const handleMultipleConfirm = (images: string[]) => {
|
||||
ElMessage.success(`多选确认: 选择了 ${images.length} 张图片`)
|
||||
}
|
||||
|
||||
const handleLargeConfirm = (image: string) => {
|
||||
ElMessage.success(`大尺寸确认: ${image}`)
|
||||
}
|
||||
|
||||
// 代码示例
|
||||
const singleCode = `<ImageSelector
|
||||
v-model="singleImage"
|
||||
:multiple="false"
|
||||
shape="circle"
|
||||
size="medium"
|
||||
@confirm="handleConfirm"
|
||||
/>`
|
||||
|
||||
const multipleCode = `<ImageSelector
|
||||
v-model="multipleImages"
|
||||
:multiple="true"
|
||||
shape="square"
|
||||
size="small"
|
||||
@confirm="handleConfirm"
|
||||
/>`
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.test-page {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol
|
||||
:span="6"
|
||||
:md="12"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
v-for="preset in qrcodePresets"
|
||||
:key="preset.title"
|
||||
>
|
||||
<ElCard class="mb-5" shadow="never">
|
||||
<template #header>
|
||||
<div>
|
||||
<span class="text-base font-bold">{{ preset.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex-cc p-5 rounded">
|
||||
<QrcodeVue :value="qrValue" v-bind="preset.config" />
|
||||
</div>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import type { Level, RenderAs, ImageSettings } from 'qrcode.vue'
|
||||
|
||||
defineOptions({ name: 'WidgetsQrcode' })
|
||||
|
||||
/**
|
||||
* 二维码内容
|
||||
*/
|
||||
const qrValue = ref('https://www.artd.pro')
|
||||
const isShowLogo = ref(false)
|
||||
|
||||
/**
|
||||
* 预设二维码样式配置
|
||||
*/
|
||||
const qrcodePresets = [
|
||||
{
|
||||
title: '渲染成 svg 标签',
|
||||
config: {
|
||||
size: 160,
|
||||
level: 'H' as Level,
|
||||
renderAs: 'svg' as RenderAs,
|
||||
margin: 0,
|
||||
background: '#ffffff',
|
||||
foreground: '#000000'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '渲染成 canvas 标签',
|
||||
config: {
|
||||
size: 160,
|
||||
level: 'H' as Level,
|
||||
renderAs: 'canvas' as RenderAs,
|
||||
margin: 0,
|
||||
background: '#ffffff',
|
||||
foreground: '#000000'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '自定义颜色',
|
||||
config: {
|
||||
size: 160,
|
||||
level: 'H' as Level,
|
||||
renderAs: 'canvas' as RenderAs,
|
||||
margin: 0,
|
||||
background: '#f0f0f0',
|
||||
foreground: '#4080ff'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '带有Logo',
|
||||
config: {
|
||||
size: 160,
|
||||
level: 'H' as Level,
|
||||
renderAs: 'canvas' as RenderAs,
|
||||
margin: 0,
|
||||
background: '#ffffff',
|
||||
foreground: '#000000',
|
||||
imageSettings: {
|
||||
src: 'https://www.artd.pro/assets/draw1-Ce1WF34i.png',
|
||||
width: 40,
|
||||
height: 40,
|
||||
excavate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 二维码配置
|
||||
*/
|
||||
const qrcodeConfig = reactive({
|
||||
size: 160,
|
||||
level: 'H' as Level,
|
||||
renderAs: 'canvas' as RenderAs,
|
||||
margin: 0,
|
||||
background: '#ffffff',
|
||||
foreground: '#000000',
|
||||
imageSettings: {
|
||||
src: 'https://www.artd.pro/assets/draw1-Ce1WF34i.png',
|
||||
width: 40,
|
||||
height: 40,
|
||||
excavate: true
|
||||
} as ImageSettings
|
||||
})
|
||||
|
||||
/**
|
||||
* 监听是否显示 logo
|
||||
* 根据状态动态设置二维码中心的 logo 图片
|
||||
*/
|
||||
watch(isShowLogo, (val) => {
|
||||
if (!val) {
|
||||
qrcodeConfig.imageSettings = {} as ImageSettings
|
||||
} else {
|
||||
qrcodeConfig.imageSettings = {
|
||||
src: 'https://www.artd.pro/assets/draw1-Ce1WF34i.png',
|
||||
width: 40,
|
||||
height: 40,
|
||||
excavate: true
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="page-content space-y-5">
|
||||
<!-- 基础用法 -->
|
||||
<ArtTextScroll
|
||||
text="Art Design Pro 是一款兼具设计美学与高效开发的后台系统 <a target='_blank' href='https://www.artd.pro/docs/'>点击我 </a>访问官方文档"
|
||||
showClose
|
||||
/>
|
||||
|
||||
<!-- 使用不同的类型 -->
|
||||
<ArtTextScroll type="success" text="这是一条成功类型的滚动公告" />
|
||||
|
||||
<ArtTextScroll type="warning" text="这是一条警告类型的滚动公告" />
|
||||
|
||||
<ArtTextScroll type="danger" text="这是一条危险类型的滚动公告" />
|
||||
|
||||
<ArtTextScroll type="info" text="这是一条信息类型的滚动公告" />
|
||||
|
||||
<!-- 可关闭 -->
|
||||
<ArtTextScroll text="这是一条可关闭的滚动公告" @close="handleClose" />
|
||||
|
||||
<!-- 自定义速度和方向 -->
|
||||
<ArtTextScroll
|
||||
type="warning"
|
||||
text="这是一条速度较慢、向右滚动的公告"
|
||||
:speed="30"
|
||||
direction="right"
|
||||
/>
|
||||
|
||||
<ArtTextScroll
|
||||
text="这是一条文字溢出才会滚动的公告,当文本内容超出容器宽度时才会开始滚动显示,否则保持静止状态"
|
||||
@close="handleClose"
|
||||
:alwaysScroll="false"
|
||||
/>
|
||||
|
||||
<ArtTextScroll type="danger" direction="up" :speed="30" text="这是一条向上滚动的公告" />
|
||||
|
||||
<ArtTextScroll type="info" direction="down" :speed="30" text="这是一条向下滚动的公告" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'WidgetsTextScroll' })
|
||||
|
||||
/**
|
||||
* 处理关闭事件
|
||||
*/
|
||||
const handleClose = () => {
|
||||
console.log('文本滚动组件已关闭')
|
||||
ElMessage.info('已关闭')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="max-w-150">
|
||||
<ArtVideoPlayer
|
||||
playerId="my-video-1"
|
||||
:videoUrl="videoUrl"
|
||||
:posterUrl="posterUrl"
|
||||
:autoplay="false"
|
||||
:volume="1"
|
||||
:playbackRates="[0.5, 1, 1.5, 2]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import lockImg from '@imgs/lock/bg_dark.webp'
|
||||
|
||||
defineOptions({ name: 'WidgetsVideo' })
|
||||
|
||||
/**
|
||||
* 视频源 URL
|
||||
*/
|
||||
const videoUrl = ref(
|
||||
'//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/xgplayer-demo.mp4'
|
||||
)
|
||||
|
||||
/**
|
||||
* 视频封面图片 URL
|
||||
*/
|
||||
const posterUrl = ref(lockImg)
|
||||
</script>
|
||||
@@ -0,0 +1,537 @@
|
||||
<template>
|
||||
<div class="page-content mb-5">
|
||||
<!-- 完整工具栏编辑器 -->
|
||||
<ElCard class="editor-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>🛠️ 完整工具栏编辑器</span>
|
||||
<div class="header-buttons">
|
||||
<ElButton size="small" @click="clearFullEditor">清空</ElButton>
|
||||
<ElButton size="small" @click="getFullEditorContent">获取内容</ElButton>
|
||||
<ElButton size="small" @click="setFullEditorDemo">设置示例</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ArtWangEditor
|
||||
ref="fullEditorRef"
|
||||
v-model="fullEditorHtml"
|
||||
height="400px"
|
||||
placeholder="请输入内容,体验完整的编辑功能..."
|
||||
:exclude-keys="[]"
|
||||
/>
|
||||
</ElCard>
|
||||
|
||||
<!-- 简化工具栏编辑器 -->
|
||||
<ElCard class="editor-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>✨ 简化工具栏编辑器</span>
|
||||
<div class="header-buttons">
|
||||
<ElButton size="small" @click="clearSimpleEditor">清空</ElButton>
|
||||
<ElButton size="small" @click="getSimpleEditorContent">获取内容</ElButton>
|
||||
<ElButton size="small" @click="setSimpleEditorDemo">设置示例</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ArtWangEditor
|
||||
ref="simpleEditorRef"
|
||||
v-model="simpleEditorHtml"
|
||||
height="400px"
|
||||
placeholder="请输入内容,体验简化的编辑功能..."
|
||||
:toolbar-keys="simpleToolbarKeys"
|
||||
/>
|
||||
</ElCard>
|
||||
|
||||
<!-- 内容对比预览 -->
|
||||
<ElCard class="preview-card" shadow="never">
|
||||
<template #header>
|
||||
<span>📖 内容预览对比</span>
|
||||
</template>
|
||||
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<h3>完整编辑器内容</h3>
|
||||
<ElTabs v-model="fullActiveTab">
|
||||
<ElTabPane label="渲染效果" name="preview">
|
||||
<div class="content-preview" v-html="fullEditorHtml"></div>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="HTML源码" name="html">
|
||||
<ElInput
|
||||
v-model="fullEditorHtml"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
placeholder="HTML源码"
|
||||
readonly
|
||||
/>
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</ElCol>
|
||||
|
||||
<ElCol :span="12">
|
||||
<h3>简化编辑器内容</h3>
|
||||
<ElTabs v-model="simpleActiveTab">
|
||||
<ElTabPane label="渲染效果" name="preview">
|
||||
<div class="content-preview" v-html="simpleEditorHtml"></div>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="HTML源码" name="html">
|
||||
<ElInput
|
||||
v-model="simpleEditorHtml"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
placeholder="HTML源码"
|
||||
readonly
|
||||
/>
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElCard>
|
||||
|
||||
<!-- 使用说明 -->
|
||||
<ElCard class="usage-card" shadow="never">
|
||||
<template #header>
|
||||
<span>📚 使用说明</span>
|
||||
</template>
|
||||
|
||||
<ElCollapse v-model="activeCollapse">
|
||||
<ElCollapseItem title="基础用法" name="basic">
|
||||
<pre><code class="language-vue"><template>
|
||||
<ArtWangEditor v-model="content" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const content = ref('<p>初始内容</p>')
|
||||
</script></code></pre>
|
||||
</ElCollapseItem>
|
||||
|
||||
<ElCollapseItem title="完整工具栏配置" name="full">
|
||||
<pre><code class="language-vue"><template>
|
||||
<!-- 显示所有工具,不排除任何功能 -->
|
||||
<ArtWangEditor
|
||||
v-model="content"
|
||||
:exclude-keys="[]"
|
||||
/>
|
||||
</template></code></pre>
|
||||
</ElCollapseItem>
|
||||
|
||||
<ElCollapseItem title="简化工具栏配置" name="simple">
|
||||
<pre><code class="language-vue"><template>
|
||||
<!-- 只显示基础编辑工具 -->
|
||||
<ArtWangEditor
|
||||
v-model="content"
|
||||
:toolbar-keys="[
|
||||
'bold', 'italic', 'underline', '|',
|
||||
'bulletedList', 'numberedList', '|',
|
||||
'insertLink', 'insertImage', '|',
|
||||
'undo', 'redo'
|
||||
]"
|
||||
/>
|
||||
</template></code></pre>
|
||||
</ElCollapseItem>
|
||||
|
||||
<ElCollapseItem title="自定义配置" name="config">
|
||||
<pre><code class="language-vue"><template>
|
||||
<ArtWangEditor
|
||||
v-model="content"
|
||||
height="600px"
|
||||
placeholder="请输入您的内容..."
|
||||
:exclude-keys="['fontFamily', 'fontSize']"
|
||||
:upload-config="{
|
||||
maxFileSize: 5 * 1024 * 1024,
|
||||
maxNumberOfFiles: 5
|
||||
}"
|
||||
/>
|
||||
</template></code></pre>
|
||||
</ElCollapseItem>
|
||||
|
||||
<ElCollapseItem title="组件方法调用" name="methods">
|
||||
<pre><code class="language-vue"><template>
|
||||
<ArtWangEditor ref="editorRef" v-model="content" />
|
||||
<el-button @click="handleClear">清空</el-button>
|
||||
<el-button @click="handleFocus">聚焦</el-button>
|
||||
<el-button @click="handleGetContent">获取内容</el-button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const editorRef = ref()
|
||||
const content = ref('')
|
||||
|
||||
const handleClear = () => {
|
||||
editorRef.value?.clear()
|
||||
}
|
||||
|
||||
const handleFocus = () => {
|
||||
editorRef.value?.focus()
|
||||
}
|
||||
|
||||
const handleGetContent = () => {
|
||||
const html = editorRef.value?.getHtml()
|
||||
console.log('编辑器内容:', html)
|
||||
}
|
||||
</script></code></pre>
|
||||
</ElCollapseItem>
|
||||
|
||||
<ElCollapseItem title="工具栏配置说明" name="toolbar-config">
|
||||
<div class="toolbar-explanation">
|
||||
<h4>完整工具栏 vs 简化工具栏</h4>
|
||||
<ElRow :gutter="16">
|
||||
<ElCol :span="12">
|
||||
<h5>✅ 完整工具栏包含:</h5>
|
||||
<ul>
|
||||
<li>文本格式:加粗、斜体、下划线、字体颜色、背景色</li>
|
||||
<li>段落格式:标题、引用、对齐方式、缩进</li>
|
||||
<li>列表:有序列表、无序列表、待办事项</li>
|
||||
<li>插入:链接、图片、表格、分割线、表情</li>
|
||||
<li>代码:代码块、行内代码</li>
|
||||
<li>操作:撤销、重做、全屏、清除格式</li>
|
||||
</ul>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<h5>⚡ 简化工具栏包含:</h5>
|
||||
<ul>
|
||||
<li>基础格式:加粗、斜体、下划线</li>
|
||||
<li>列表:有序列表、无序列表</li>
|
||||
<li>插入:链接、图片</li>
|
||||
<li>操作:撤销、重做</li>
|
||||
</ul>
|
||||
<p class="note">适用于简单的文本编辑场景,界面更清爽。</p>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
</ElCollapseItem>
|
||||
</ElCollapse>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'WidgetsWangEditor' })
|
||||
|
||||
const fullEditorRef = ref()
|
||||
const simpleEditorRef = ref()
|
||||
const fullActiveTab = ref('preview')
|
||||
const simpleActiveTab = ref('preview')
|
||||
const activeCollapse = ref(['basic'])
|
||||
|
||||
/**
|
||||
* 简化工具栏配置
|
||||
* 只包含基础的编辑功能
|
||||
*/
|
||||
const simpleToolbarKeys = [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'|',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
'|',
|
||||
'insertLink',
|
||||
'insertImage',
|
||||
'|',
|
||||
'undo',
|
||||
'redo'
|
||||
]
|
||||
|
||||
// 完整编辑器内容
|
||||
const fullEditorHtml = ref(`<h1>🎨 完整工具栏编辑器示例</h1>
|
||||
<p>这个编辑器包含所有功能,您可以体验丰富的格式编辑功能。</p>
|
||||
|
||||
<h2>✨ 文本样式</h2>
|
||||
<p><strong>这是加粗的文字</strong></p>
|
||||
<p><em>这是斜体文字</em></p>
|
||||
<p><u>这是下划线文字</u></p>
|
||||
<p><span style="color: rgb(194, 79, 74);">这是彩色文字</span></p>
|
||||
|
||||
<h2>📝 列表和待办</h2>
|
||||
<ul>
|
||||
<li>无序列表项 1</li>
|
||||
<li>无序列表项 2</li>
|
||||
</ul>
|
||||
|
||||
<ol>
|
||||
<li>有序列表项 1</li>
|
||||
<li>有序列表项 2</li>
|
||||
</ol>
|
||||
|
||||
<ul class="w-e-todo">
|
||||
<li class="w-e-todo-item"><input type="checkbox" checked="true" readonly="true" disabled="disabled"><span>已完成的任务</span></li>
|
||||
<li class="w-e-todo-item"><input type="checkbox" readonly="true" disabled="disabled"><span>待完成的任务</span></li>
|
||||
</ul>
|
||||
|
||||
<h2>💬 引用和表格</h2>
|
||||
<blockquote>
|
||||
这是一段引用文字,展示引用格式的效果。
|
||||
</blockquote>
|
||||
|
||||
<table style="border-collapse: collapse; width: 100%;" border="1">
|
||||
<thead>
|
||||
<tr><th>功能</th><th>描述</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>完整工具栏</td><td>包含所有编辑功能</td></tr>
|
||||
<tr><td>自定义配置</td><td>支持灵活的工具栏配置</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>💻 代码块</h2>
|
||||
<pre><code class="language-javascript">// 完整编辑器支持代码高亮
|
||||
function createEditor() {
|
||||
return new WangEditor({
|
||||
container: '#editor',
|
||||
toolbar: 'full' // 完整工具栏
|
||||
});
|
||||
}</code></pre>
|
||||
|
||||
<p>🔗 <a href="https://www.wangeditor.com/" target="_blank">访问官网了解更多</a></p>`)
|
||||
|
||||
// 简化编辑器内容
|
||||
const simpleEditorHtml = ref(`<h1>✨ 简化工具栏编辑器示例</h1>
|
||||
<p>这个编辑器只包含基础的编辑功能,界面更加简洁。</p>
|
||||
|
||||
<h2>基础文本格式</h2>
|
||||
<p><strong>加粗文字</strong></p>
|
||||
<p><em>斜体文字</em></p>
|
||||
<p><u>下划线文字</u></p>
|
||||
|
||||
<h2>列表功能</h2>
|
||||
<ul>
|
||||
<li>无序列表项 1</li>
|
||||
<li>无序列表项 2</li>
|
||||
</ul>
|
||||
|
||||
<ol>
|
||||
<li>有序列表项 1</li>
|
||||
<li>有序列表项 2</li>
|
||||
</ol>
|
||||
|
||||
<h2>链接和图片</h2>
|
||||
<p>支持插入 <a href="https://www.wangeditor.com/" target="_blank">链接</a> 和图片。</p>
|
||||
|
||||
<p>简化版编辑器专注于基础功能,适合简单的内容编辑需求。</p>`)
|
||||
|
||||
/**
|
||||
* 清空完整编辑器内容
|
||||
*/
|
||||
const clearFullEditor = () => {
|
||||
fullEditorRef.value?.clear()
|
||||
ElMessage.success('完整编辑器已清空')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整编辑器内容
|
||||
*/
|
||||
const getFullEditorContent = () => {
|
||||
const content = fullEditorRef.value?.getHtml()
|
||||
console.log('完整编辑器内容:', content)
|
||||
ElMessage.success('完整编辑器内容已输出到控制台')
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置完整编辑器演示内容
|
||||
*/
|
||||
const setFullEditorDemo = () => {
|
||||
const demoContent = `<h2>🎉 完整编辑器演示内容</h2>
|
||||
<p>这是通过方法设置的演示内容,展示完整编辑器的强大功能。</p>
|
||||
<ul>
|
||||
<li>支持丰富的文本格式</li>
|
||||
<li>包含表格、代码块等高级功能</li>
|
||||
<li>提供完整的编辑体验</li>
|
||||
</ul>
|
||||
<table style="border-collapse: collapse; width: 100%;" border="1">
|
||||
<tr><th>特性</th><th>状态</th></tr>
|
||||
<tr><td>完整工具栏</td><td>✅ 已启用</td></tr>
|
||||
<tr><td>高级功能</td><td>✅ 已启用</td></tr>
|
||||
</table>`
|
||||
|
||||
fullEditorRef.value?.setHtml(demoContent)
|
||||
ElMessage.success('已设置完整编辑器演示内容')
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空简化编辑器内容
|
||||
*/
|
||||
const clearSimpleEditor = () => {
|
||||
simpleEditorRef.value?.clear()
|
||||
ElMessage.success('简化编辑器已清空')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取简化编辑器内容
|
||||
*/
|
||||
const getSimpleEditorContent = () => {
|
||||
const content = simpleEditorRef.value?.getHtml()
|
||||
console.log('简化编辑器内容:', content)
|
||||
ElMessage.success('简化编辑器内容已输出到控制台')
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置简化编辑器演示内容
|
||||
*/
|
||||
const setSimpleEditorDemo = () => {
|
||||
const demoContent = `<h2>⚡ 简化编辑器演示内容</h2>
|
||||
<p>这是通过方法设置的演示内容,展示简化编辑器的核心功能。</p>
|
||||
<ul>
|
||||
<li><strong>基础格式</strong>:加粗、斜体、下划线</li>
|
||||
<li><em>列表支持</em>:有序和无序列表</li>
|
||||
<li><u>媒体插入</u>:链接和图片</li>
|
||||
</ul>
|
||||
<ol>
|
||||
<li>界面简洁清爽</li>
|
||||
<li>功能专注实用</li>
|
||||
<li>适合快速编辑</li>
|
||||
</ol>
|
||||
<p>🔗 <a href="https://example.com" target="_blank">这是一个链接示例</a></p>`
|
||||
|
||||
simpleEditorRef.value?.setHtml(demoContent)
|
||||
ElMessage.success('已设置简化编辑器演示内容')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.editor-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.preview-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.preview-card h3 {
|
||||
margin: 0 0 16px;
|
||||
font-size: 16px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.content-preview {
|
||||
min-height: 200px;
|
||||
max-height: 300px;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.content-preview :deep(h1),
|
||||
.content-preview :deep(h2),
|
||||
.content-preview :deep(h3) {
|
||||
margin: 16px 0 8px;
|
||||
}
|
||||
|
||||
.content-preview :deep(p) {
|
||||
margin: 8px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.content-preview :deep(table) {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.content-preview :deep(table th),
|
||||
.content-preview :deep(table td) {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.content-preview :deep(pre) {
|
||||
padding: 12px;
|
||||
margin: 16px 0;
|
||||
overflow-x: auto;
|
||||
background-color: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.content-preview :deep(blockquote) {
|
||||
padding-left: 16px;
|
||||
margin: 16px 0;
|
||||
color: var(--el-text-color-regular);
|
||||
border-left: 4px solid var(--el-color-primary);
|
||||
}
|
||||
|
||||
.usage-card :deep(.el-collapse-item__content) {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.usage-card pre {
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
background-color: var(--el-fill-color-light);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.usage-card pre code {
|
||||
font-family: Consolas, Monaco, 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.toolbar-explanation h4 {
|
||||
margin: 0 0 16px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.toolbar-explanation h5 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.toolbar-explanation ul {
|
||||
padding-left: 20px;
|
||||
margin: 8px 0 16px;
|
||||
}
|
||||
|
||||
.toolbar-explanation ul li {
|
||||
margin: 4px 0;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.toolbar-explanation .note {
|
||||
margin: 8px 0 0;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
.page-content {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: stretch !important;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-card :deep(.el-col) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="page-content mb-5">
|
||||
<!-- 基础文字水印 -->
|
||||
<ElCard class="mb-7.5" shadow="never">
|
||||
<template #header>基础文字水印</template>
|
||||
<ElWatermark content="Art Design Pro" :font="{ color: 'rgba(128, 128, 128, 0.2)' }">
|
||||
<div style="height: 200px"></div>
|
||||
</ElWatermark>
|
||||
</ElCard>
|
||||
|
||||
<!-- 多行文字水印 -->
|
||||
<ElCard class="mb-7.5" shadow="never">
|
||||
<template #header>多行文字水印</template>
|
||||
<ElWatermark
|
||||
:content="['Art Design Pro', '专注用户体验,视觉设计']"
|
||||
:font="{ fontSize: 16, color: 'rgba(128, 128, 128, 0.2)' }"
|
||||
>
|
||||
<div style="height: 200px"></div>
|
||||
</ElWatermark>
|
||||
</ElCard>
|
||||
|
||||
<!-- 图片水印 -->
|
||||
<ElCard class="mb-7.5" shadow="never">
|
||||
<template #header>图片水印</template>
|
||||
<ElWatermark :image="watermarkImage" :opacity="0.2" :width="80" :height="20">
|
||||
<div style="height: 200px"></div>
|
||||
</ElWatermark>
|
||||
</ElCard>
|
||||
|
||||
<!-- 自定义样式水印 -->
|
||||
<ElCard class="mb-7.5" shadow="never">
|
||||
<template #header>自定义样式水印</template>
|
||||
<ElWatermark
|
||||
content="Art Design Pro"
|
||||
:font="{
|
||||
fontSize: 20,
|
||||
fontFamily: 'Arial',
|
||||
color: 'rgba(255, 0, 0, 0.3)'
|
||||
}"
|
||||
:rotate="-22"
|
||||
:gap="[100, 100]"
|
||||
>
|
||||
<div style="height: 200px"></div>
|
||||
</ElWatermark>
|
||||
</ElCard>
|
||||
|
||||
<ElButton
|
||||
:type="settingStore.watermarkVisible ? 'danger' : 'primary'"
|
||||
@click="handleWatermarkVisible"
|
||||
>
|
||||
{{ settingStore.watermarkVisible ? '隐藏全局水印' : '显示全局水印' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
|
||||
defineOptions({ name: 'Watermark' })
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
/**
|
||||
* 水印图片 URL
|
||||
*/
|
||||
const watermarkImage = ref('https://element-plus.org/images/element-plus-logo.svg')
|
||||
|
||||
/**
|
||||
* 切换全局水印显示状态
|
||||
*/
|
||||
const handleWatermarkVisible = () => {
|
||||
useSettingStore().setWatermarkVisible(!settingStore.watermarkVisible)
|
||||
ElMessage.success(settingStore.watermarkVisible ? '已显示全局水印' : '已隐藏全局水印')
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user