Initial commit: One Pipe System
完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
124
src/views/widgets/context-menu/index.vue
Normal file
124
src/views/widgets/context-menu/index.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ElButton @contextmenu.prevent="showMenu"> 右键触发菜单 </ElButton>
|
||||
|
||||
<!-- 右键菜单组件 -->
|
||||
<ArtMenuRight
|
||||
ref="menuRef"
|
||||
:menu-items="menuItems"
|
||||
:menu-width="160"
|
||||
:submenu-width="200"
|
||||
:border-radius="10"
|
||||
@select="handleSelect"
|
||||
@show="onMenuShow"
|
||||
@hide="onMenuHide"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const menuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const lastAction = ref('')
|
||||
|
||||
// 右键菜单选项
|
||||
const menuItems = computed((): MenuItemType[] => [
|
||||
{
|
||||
key: 'copy',
|
||||
label: '复制',
|
||||
icon: ''
|
||||
},
|
||||
{
|
||||
key: 'paste',
|
||||
label: '粘贴',
|
||||
icon: ''
|
||||
},
|
||||
{
|
||||
key: 'cut',
|
||||
label: '剪切',
|
||||
icon: '',
|
||||
showLine: true
|
||||
},
|
||||
{
|
||||
key: 'export',
|
||||
label: '导出选项',
|
||||
icon: '',
|
||||
children: [
|
||||
{
|
||||
key: 'exportExcel',
|
||||
label: '导出 Excel',
|
||||
icon: ''
|
||||
},
|
||||
{
|
||||
key: 'exportPdf',
|
||||
label: '导出 PDF',
|
||||
icon: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
label: '编辑选项',
|
||||
icon: '',
|
||||
children: [
|
||||
{
|
||||
key: 'rename',
|
||||
label: '重命名',
|
||||
icon: ''
|
||||
},
|
||||
{
|
||||
key: 'duplicate',
|
||||
label: '复制副本',
|
||||
icon: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'share',
|
||||
label: '分享',
|
||||
icon: '',
|
||||
showLine: true
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: '删除',
|
||||
icon: ''
|
||||
},
|
||||
{
|
||||
key: 'disabled',
|
||||
label: '禁用选项',
|
||||
icon: '',
|
||||
disabled: true
|
||||
}
|
||||
])
|
||||
|
||||
const handleSelect = (item: MenuItemType) => {
|
||||
lastAction.value = `${item.label} (${item.key})`
|
||||
ElMessage.success(`执行操作: ${item.label}`)
|
||||
console.log('选择了菜单项:', item)
|
||||
}
|
||||
|
||||
const showMenu = (e: MouseEvent) => {
|
||||
console.log('触发右键菜单', e)
|
||||
// 确保阻止默认行为
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
// 延迟一帧执行,确保事件处理完成
|
||||
nextTick(() => {
|
||||
menuRef.value?.show(e)
|
||||
})
|
||||
}
|
||||
|
||||
const onMenuShow = () => {
|
||||
console.log('菜单显示')
|
||||
}
|
||||
|
||||
const onMenuHide = () => {
|
||||
console.log('菜单隐藏')
|
||||
}
|
||||
</script>
|
||||
103
src/views/widgets/count-to/index.vue
Normal file
103
src/views/widgets/count-to/index.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<!-- 文档:https://github.com/PanJiaChen/vue-countTo -->
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<!-- 基础用法 -->
|
||||
<h2>基础用法</h2>
|
||||
<CountTo :endVal="count1" :duration="1000"></CountTo>
|
||||
|
||||
<!-- 带前缀后缀 -->
|
||||
<h2>带前缀后缀</h2>
|
||||
<CountTo prefix="¥" suffix="元" :startVal="0" :endVal="count2" :duration="2000"></CountTo>
|
||||
|
||||
<!-- 小数点和分隔符 -->
|
||||
<h2>小数点和分隔符</h2>
|
||||
<CountTo
|
||||
:startVal="0"
|
||||
:endVal="count3"
|
||||
:decimals="2"
|
||||
decimal="."
|
||||
separator=","
|
||||
:duration="2500"
|
||||
></CountTo>
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<h2>控制按钮</h2>
|
||||
<CountTo
|
||||
ref="countTo"
|
||||
:startVal="0"
|
||||
:endVal="count4"
|
||||
:duration="3000"
|
||||
:autoplay="false"
|
||||
></CountTo>
|
||||
|
||||
<div class="mt-4">
|
||||
<ElButtonGroup>
|
||||
<ElButton @click="start" v-ripple>开始</ElButton>
|
||||
<ElButton @click="pause" v-ripple>暂停</ElButton>
|
||||
<ElButton @click="reset" v-ripple>重置</ElButton>
|
||||
</ElButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { CountTo } from 'vue3-count-to'
|
||||
|
||||
const count1 = ref(1000)
|
||||
const count2 = ref(19999.99)
|
||||
const count3 = ref(2023.45)
|
||||
const count4 = ref(5000)
|
||||
|
||||
const countTo = ref()
|
||||
const isCounting = ref(false)
|
||||
|
||||
// 控制方法
|
||||
const start = () => {
|
||||
if (isCounting.value) return
|
||||
|
||||
try {
|
||||
countTo.value?.reset()
|
||||
countTo.value?.start()
|
||||
isCounting.value = true
|
||||
} catch (error) {
|
||||
console.error('启动计数器失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const pause = () => {
|
||||
if (!isCounting.value) return
|
||||
|
||||
try {
|
||||
countTo.value?.pause()
|
||||
isCounting.value = false
|
||||
} catch (error) {
|
||||
console.error('暂停计数器失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
try {
|
||||
countTo.value?.reset()
|
||||
isCounting.value = false
|
||||
} catch (error) {
|
||||
console.error('重置计数器失败:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 20px 0;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
125
src/views/widgets/drag/index.vue
Normal file
125
src/views/widgets/drag/index.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<!-- https://vue-draggable-plus.pages.dev/ -->
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ElRow>
|
||||
<ElCard shadow="never" style="width: 300px; margin-right: 20px">
|
||||
<template #header>
|
||||
<span class="card-header">基础示例</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<VueDraggable ref="el" v-model="userList">
|
||||
<div class="demo1-item" v-for="item in userList" :key="item.name">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</template>
|
||||
</ElCard>
|
||||
|
||||
<ElCard shadow="never" style="width: 300px">
|
||||
<template #header>
|
||||
<span class="card-header">过渡动画</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="demo1-item">
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</TransitionGroup>
|
||||
</VueDraggable>
|
||||
</template>
|
||||
</ElCard>
|
||||
</ElRow>
|
||||
|
||||
<ElCard shadow="never">
|
||||
<template #header>
|
||||
<span class="card-header">表格拖拽排序</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<VueDraggable target="tbody" v-model="userList" :animation="150">
|
||||
<ArtTable :data="userList" :pagination="false">
|
||||
<ElTableColumn label="姓名" prop="name" />
|
||||
<ElTableColumn label="角色" prop="role" />
|
||||
</ArtTable>
|
||||
</VueDraggable>
|
||||
</template>
|
||||
</ElCard>
|
||||
|
||||
<ElCard shadow="never">
|
||||
<template #header>
|
||||
<span class="card-header">指定元素拖拽排序</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<VueDraggable target="tbody" handle=".handle" v-model="userList" :animation="150">
|
||||
<ArtTable :data="userList" :pagination="false">
|
||||
<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'
|
||||
|
||||
const userList = ref([
|
||||
{
|
||||
name: '孙悟空',
|
||||
role: '斗战胜佛'
|
||||
},
|
||||
{
|
||||
name: '猪八戒',
|
||||
role: '净坛使者'
|
||||
},
|
||||
{
|
||||
name: '沙僧',
|
||||
role: '金身罗汉'
|
||||
},
|
||||
{
|
||||
name: '唐僧',
|
||||
role: '旃檀功德佛'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
.demo1-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
cursor: move;
|
||||
background-color: rgba(var(--art-gray-200-rgb), 0.8);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
margin-bottom: 30px;
|
||||
|
||||
.card-header {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
89
src/views/widgets/excel/index.vue
Normal file
89
src/views/widgets/excel/index.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<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="用户数据"
|
||||
sheetName="用户列表"
|
||||
type="success"
|
||||
:headers="headers"
|
||||
@export-success="handleExportSuccess"
|
||||
@export-error="handleExportError"
|
||||
>
|
||||
导出 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">
|
||||
interface TableData {
|
||||
name: string
|
||||
age: number
|
||||
city: string
|
||||
}
|
||||
|
||||
const handleImportSuccess = (data: any[]) => {
|
||||
// 将导入的数据转换为正确的格式
|
||||
const formattedData = data.map((item) => ({
|
||||
name: item['姓名'],
|
||||
age: Number(item['年龄']),
|
||||
city: item['城市']
|
||||
}))
|
||||
tableData.value = formattedData
|
||||
|
||||
// tableData.value = data
|
||||
}
|
||||
|
||||
const handleImportError = (error: Error) => {
|
||||
// 处理导入错误
|
||||
console.error('导入失败:', error)
|
||||
}
|
||||
|
||||
// 使用类型化的ref
|
||||
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: '西安' }
|
||||
])
|
||||
|
||||
// 自定义表头映射
|
||||
const headers = {
|
||||
name: '姓名',
|
||||
age: '年龄',
|
||||
city: '城市'
|
||||
}
|
||||
|
||||
const handleExportSuccess = () => {
|
||||
ElMessage.success('导出成功')
|
||||
}
|
||||
|
||||
const handleExportError = (error: Error) => {
|
||||
ElMessage.error(`导出失败: ${error.message}`)
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
tableData.value = []
|
||||
}
|
||||
</script>
|
||||
102
src/views/widgets/fireworks/index.vue
Normal file
102
src/views/widgets/fireworks/index.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="action-buttons">
|
||||
<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
|
||||
>
|
||||
</div>
|
||||
|
||||
<ElDescriptions
|
||||
title="礼花组件说明"
|
||||
direction="vertical"
|
||||
:column="1"
|
||||
border
|
||||
style="margin-top: 50px"
|
||||
>
|
||||
<ElDescriptionsItem label="显示时机">
|
||||
礼花效果组件全局注册了,在节假日的时候,会自动显示,你可以通过配置文件来控制显示时机
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="礼花样式">
|
||||
默认显示几何图形,可以配置图片,图片需要提前在 components/Ceremony/Fireworks 文件预先定义
|
||||
</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'
|
||||
|
||||
const timerRef = ref<ReturnType<typeof setInterval> | null>(null)
|
||||
const isLaunching = ref(false)
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const handleMultipleLaunch = (src: string) => {
|
||||
triggerFireworks(10, src)
|
||||
}
|
||||
|
||||
const handleImageLaunch = (src: string) => {
|
||||
mittBus.emit('triggerFireworks', src)
|
||||
}
|
||||
|
||||
// 组件卸载时清理定时器
|
||||
onUnmounted(() => {
|
||||
if (timerRef.value) {
|
||||
clearInterval(timerRef.value)
|
||||
timerRef.value = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action-buttons {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
175
src/views/widgets/icon-list/index.vue
Normal file
175
src/views/widgets/icon-list/index.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="form">
|
||||
<ElSelect v-model="iconType" placeholder="Select" style="width: 240px">
|
||||
<ElOption
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
<div class="colors-icon">
|
||||
<ElCheckbox v-model="isColorsIcon" label="彩色图标" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<ul class="icon-list">
|
||||
<li v-for="icon in systemIconClasses" :key="icon.className" @click="copyIcon(icon)">
|
||||
<i
|
||||
class="iconfont-sys"
|
||||
v-if="iconType === 'unicode'"
|
||||
v-html="icon.unicode"
|
||||
:style="getIconStyle()"
|
||||
></i>
|
||||
<i :class="`iconfont-sys ${icon.className}`" v-else :style="getIconStyle()"></i>
|
||||
<span>{{ iconType === 'unicode' ? icon.unicode : icon.className }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { extractIconClasses, IconfontType } from '@/utils/constants'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const iconType = ref('unicode')
|
||||
const options = [
|
||||
{
|
||||
value: 'unicode',
|
||||
label: 'Unicode'
|
||||
},
|
||||
{
|
||||
value: 'fontClass',
|
||||
label: 'Font class'
|
||||
}
|
||||
]
|
||||
const systemIconClasses = ref<IconfontType[]>([])
|
||||
|
||||
const isColorsIcon = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
systemIconClasses.value = extractIconClasses()
|
||||
})
|
||||
|
||||
const copyIcon = (text: IconfontType) => {
|
||||
if (!text) return
|
||||
|
||||
let copyipt = document.createElement('input')
|
||||
copyipt.setAttribute(
|
||||
'value',
|
||||
(iconType.value === 'unicode' ? text.unicode : text.className) || ''
|
||||
)
|
||||
document.body.appendChild(copyipt)
|
||||
copyipt.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(copyipt)
|
||||
|
||||
ElMessage.success(`已复制`)
|
||||
}
|
||||
|
||||
const getRandomColor = () => {
|
||||
const colors = ['#2d8cf0', '#19be6b', '#ff9900', '#f24965', '#9463f7']
|
||||
return colors[Math.floor(Math.random() * colors.length)]
|
||||
}
|
||||
|
||||
const getIconStyle = () => {
|
||||
return isColorsIcon.value ? { color: getRandomColor() } : { color: 'var(--art-text-gray-700)' }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
$border-color: #eee;
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.colors-icon {
|
||||
box-sizing: border-box;
|
||||
height: var(--el-component-custom-height);
|
||||
padding: 0 30px;
|
||||
margin-left: 10px;
|
||||
border: 1px solid var(--art-border-dashed-color);
|
||||
border-radius: calc(var(--custom-radius) / 3 + 2px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-top: 20px;
|
||||
|
||||
.icon-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
width: calc(100% + 16px);
|
||||
|
||||
li {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
aspect-ratio: 1 / 1;
|
||||
padding: 0 8px;
|
||||
margin: 0 16px 16px 0;
|
||||
overflow: hidden;
|
||||
color: rgba(#fff, 0.8);
|
||||
text-align: center;
|
||||
border: 1px solid rgb(var(--art-gray-300-rgb), 0.8);
|
||||
border-radius: 12px !important;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: var(--art-gray-100);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 26px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--art-text-gray-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $device-notebook) {
|
||||
.page-content {
|
||||
.list {
|
||||
.icon-list {
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $device-ipad-vertical) {
|
||||
.page-content {
|
||||
.list {
|
||||
.icon-list {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $device-phone) {
|
||||
.page-content {
|
||||
.list {
|
||||
.icon-list {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
56
src/views/widgets/icon-selector/index.vue
Normal file
56
src/views/widgets/icon-selector/index.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="select">
|
||||
<div class="item">
|
||||
<h3>Unicode</h3>
|
||||
<ArtIconSelector
|
||||
:iconType="IconTypeEnum.UNICODE"
|
||||
@getIcon="getIcon"
|
||||
defaultIcon=""
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h3>ClassName</h3>
|
||||
<ArtIconSelector
|
||||
:iconType="IconTypeEnum.CLASS_NAME"
|
||||
@getIcon="getIcon"
|
||||
width="260px"
|
||||
defaultIcon="iconsys-baitianmoshi3"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h3>禁用</h3>
|
||||
<ArtIconSelector
|
||||
:iconType="IconTypeEnum.CLASS_NAME"
|
||||
@getIcon="getIcon"
|
||||
width="260px"
|
||||
defaultIcon="iconsys-baitianmoshi3"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { IconTypeEnum } from '@/enums/appEnum'
|
||||
|
||||
// 获取选择的图标
|
||||
const getIcon = (icon: string) => {
|
||||
console.log(icon)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.select {
|
||||
.item {
|
||||
margin-bottom: 30px;
|
||||
|
||||
h3 {
|
||||
padding-bottom: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
39
src/views/widgets/image-crop/index.vue
Normal file
39
src/views/widgets/image-crop/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ArtCutterImg
|
||||
style="margin-top: 20px"
|
||||
v-model:imgUrl="imageUrl"
|
||||
:boxWidth="540"
|
||||
:boxHeight="300"
|
||||
:cutWidth="360"
|
||||
:cutHeight="200"
|
||||
:quality="1"
|
||||
:tool="true"
|
||||
:watermarkText="'My Watermark'"
|
||||
watermarkColor="#ff0000"
|
||||
:showPreview="true"
|
||||
:originalGraph="false"
|
||||
:previewTitle="'预览效果'"
|
||||
@error="handleError"
|
||||
@imageLoadComplete="handleLoadComplete"
|
||||
@imageLoadError="handleLoadError"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import lockImg from '@imgs/lock/lock_screen_1.webp'
|
||||
const imageUrl = ref(lockImg)
|
||||
|
||||
const handleError = (error: any) => {
|
||||
console.error('裁剪错误:', error)
|
||||
}
|
||||
|
||||
const handleLoadComplete = (result: any) => {
|
||||
console.log('图片加载完成:', result)
|
||||
}
|
||||
|
||||
const handleLoadError = (error: any) => {
|
||||
console.error('图片加载失败:', error)
|
||||
}
|
||||
</script>
|
||||
136
src/views/widgets/qrcode/index.vue
Normal file
136
src/views/widgets/qrcode/index.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="6" v-for="preset in qrcodePresets" :key="preset.title">
|
||||
<ElCard class="qrcode-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ preset.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="qrcode-preview">
|
||||
<QrcodeVue :value="qrValue" v-bind="preset.config" />
|
||||
</div>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import type { Level, RenderAs, ImageSettings } from 'qrcode.vue'
|
||||
|
||||
// 二维码内容
|
||||
const qrValue = ref('https://www.lingchen.kim')
|
||||
const isShowLogo = ref(false)
|
||||
|
||||
// 预设二维码样式配置
|
||||
const qrcodePresets = [
|
||||
{
|
||||
title: '渲染成 img 标签',
|
||||
config: {
|
||||
size: 160,
|
||||
level: 'H' as Level,
|
||||
renderAs: 'canvas' 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.lingchen.kim/art-design-pro/assets/avatar-DJIoI-3F.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.lingchen.kim/art-design-pro/assets/avatar-DJIoI-3F.png',
|
||||
width: 40,
|
||||
height: 40,
|
||||
excavate: true
|
||||
} as ImageSettings
|
||||
})
|
||||
|
||||
// 监听是否显示 logo
|
||||
watch(isShowLogo, (val) => {
|
||||
if (!val) {
|
||||
qrcodeConfig.imageSettings = {} as ImageSettings
|
||||
} else {
|
||||
qrcodeConfig.imageSettings = {
|
||||
src: 'https://www.lingchen.kim/art-design-pro/assets/avatar-DJIoI-3F.png',
|
||||
width: 40,
|
||||
height: 40,
|
||||
excavate: true
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 20px;
|
||||
|
||||
.qrcode-card {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.card-header {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.qrcode-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
28
src/views/widgets/text-scroll/index.vue
Normal file
28
src/views/widgets/text-scroll/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<!-- 基础用法 -->
|
||||
<ArtTextScroll
|
||||
text="Art Design Pro 是一款专注于用户体验和视觉设计的后台管理系统模版 <a target='_blank' href='https://www.lingchen.kim/art-design-pro/docs/'>点击我 </a>访问官方文档"
|
||||
/>
|
||||
|
||||
<!-- 使用不同的类型 -->
|
||||
<ArtTextScroll type="success" text="这是一条成功类型的滚动公告" />
|
||||
|
||||
<ArtTextScroll type="warning" text="这是一条警告类型的滚动公告" />
|
||||
|
||||
<ArtTextScroll type="danger" text="这是一条危险类型的滚动公告" />
|
||||
|
||||
<ArtTextScroll type="info" text="这是一条信息类型的滚动公告" />
|
||||
|
||||
<!-- 自定义速度和方向 -->
|
||||
<ArtTextScroll text="这是一条速度较慢、向右滚动的公告" :speed="30" direction="right" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
:deep(.text-scroll-container) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
31
src/views/widgets/video/index.vue
Normal file
31
src/views/widgets/video/index.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="video-container">
|
||||
<ArtVideoPlayer
|
||||
playerId="my-video-1"
|
||||
:videoUrl="videoUrl"
|
||||
:posterUrl="posterUrl"
|
||||
:autoplay="false"
|
||||
:volume="0.5"
|
||||
:playbackRates="[0.5, 1, 1.5, 2]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import lockImg from '@imgs/lock/lock_screen_1.webp'
|
||||
|
||||
// 视频源和封面图片URL
|
||||
const videoUrl = ref(
|
||||
'//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/xgplayer-demo.mp4'
|
||||
)
|
||||
const posterUrl = ref(lockImg)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.video-container {
|
||||
max-width: 600px;
|
||||
}
|
||||
</style>
|
||||
67
src/views/widgets/wang-editor/index.vue
Normal file
67
src/views/widgets/wang-editor/index.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ArtWangEditor v-model="editorHtml" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const editorHtml = ref(`<h1>欢迎使用富文本编辑器</h1>
|
||||
<p>这是一个段落示例,下面展示了一些常用的富文本格式:</p>
|
||||
|
||||
<h2>文本样式</h2>
|
||||
<p><strong>这是加粗的文字</strong></p>
|
||||
<p><em>这是斜体文字</em></p>
|
||||
|
||||
<h2>列表示例</h2>
|
||||
<ul>
|
||||
<li>无序列表项 1</li>
|
||||
<li>无序列表项 2</li>
|
||||
<li>无序列表项 3</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<ol>
|
||||
<li>有序列表项 1</li>
|
||||
<li>有序列表项 2</li>
|
||||
<li>有序列表项 3</li>
|
||||
</ol>
|
||||
|
||||
<h2>引用示例</h2>
|
||||
<blockquote style="border-left: 4px solid #ccc; margin-left: 0; padding-left: 1em;">
|
||||
这是一段引用文字,可以用来突出显示重要内容。
|
||||
</blockquote>
|
||||
|
||||
<h2>表格示例</h2>
|
||||
<table border="1" cellpadding="5">
|
||||
<tr>
|
||||
<th>表头 1</th>
|
||||
<th>表头 2</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>单元格 1</td>
|
||||
<td>单元格 2</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>代码示例</h2>
|
||||
<pre><code class="language-javascript">// JavaScript 代码示例
|
||||
function greeting(name) {
|
||||
return \`Hello, \${name}!\`;
|
||||
}
|
||||
|
||||
console.log(greeting('世界'));</code></pre>
|
||||
|
||||
<pre><code class="language-python"># Python 代码示例
|
||||
def fibonacci(n):
|
||||
if n <= 1:
|
||||
return n
|
||||
return fibonacci(n-1) + fibonacci(n-2)
|
||||
|
||||
print(fibonacci(10))</code></pre>
|
||||
|
||||
<h2>链接示例</h2>
|
||||
<p><a href="https://www.lingchen.kim/art-design-pro/docs/">这是一个超链接</a></p>
|
||||
`)
|
||||
</script>
|
||||
77
src/views/widgets/watermark/index.vue
Normal file
77
src/views/widgets/watermark/index.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<!-- 基础文字水印 -->
|
||||
<ElCard class="card" 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="card" 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="card" shadow="never">
|
||||
<template #header>图片水印</template>
|
||||
<ElWatermark :image="watermarkImage" :opacity="0.2" :width="80" :height="20">
|
||||
<div style="height: 200px"></div>
|
||||
</ElWatermark>
|
||||
</ElCard>
|
||||
|
||||
<!-- 自定义样式水印 -->
|
||||
<ElCard class="card" 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'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
// 这里替换成你的实际logo图片地址
|
||||
const watermarkImage = ref('https://element-plus.org/images/element-plus-logo.svg')
|
||||
|
||||
const handleWatermarkVisible = () => {
|
||||
useSettingStore().setWatermarkVisible(!settingStore.watermarkVisible)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 20px;
|
||||
|
||||
.el-card {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user