feat: 优化文本组件
parent
177a060323
commit
14d68f09fb
|
|
@ -1,16 +1,17 @@
|
|||
<template>
|
||||
<a-config-provider>
|
||||
<!-- <a-config-provider>
|
||||
<router-view v-slot="{ Component }">
|
||||
<page-403 v-if="Math.random() > 0.999"></page-403>
|
||||
<component v-else :is="Component"></component>
|
||||
</router-view>
|
||||
</a-config-provider>
|
||||
<!-- <div>
|
||||
</a-config-provider> -->
|
||||
<div>
|
||||
<my-editor></my-editor>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MyEditor from './components/editor/index.vue';
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import Option from "./option.vue";
|
||||
import Render from "./render.vue";
|
||||
import { Font } from "./interface";
|
||||
export * from "./interface";
|
||||
|
||||
export const FontRender = Render;
|
||||
|
||||
export const FontOption = Option;
|
||||
|
||||
export const font: Font = {
|
||||
content: "请输入文字",
|
||||
family: "microsoft yahei",
|
||||
size: 14,
|
||||
color: "#000000",
|
||||
bold: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
align: 3,
|
||||
};
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import { CSSProperties, StyleValue } from "vue";
|
||||
|
||||
export interface Font {
|
||||
/**
|
||||
* 文字内容
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* 字体
|
||||
*/
|
||||
family: string;
|
||||
/**
|
||||
* 字号(px)
|
||||
*/
|
||||
size: number;
|
||||
/**
|
||||
* 颜色(16进制)
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* 是否加粗
|
||||
*/
|
||||
bold: boolean;
|
||||
/**
|
||||
* 是否斜体
|
||||
*/
|
||||
italic: boolean;
|
||||
/**
|
||||
* 是否下划线
|
||||
*/
|
||||
underline: boolean;
|
||||
/**
|
||||
* 对齐方式
|
||||
*/
|
||||
align: number;
|
||||
}
|
||||
|
||||
export const AlignOptions = [
|
||||
{
|
||||
label: "居上",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: "居下",
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: "居中",
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: "居左",
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
label: "居右",
|
||||
value: 5,
|
||||
},
|
||||
];
|
||||
|
||||
export const FontFamilyOptions = [
|
||||
{
|
||||
label: "微软雅黑",
|
||||
value: "microsoft yahei",
|
||||
},
|
||||
{
|
||||
label: "黑体",
|
||||
value: "gothic",
|
||||
},
|
||||
{
|
||||
label: "宋体",
|
||||
value: "simsun",
|
||||
},
|
||||
{
|
||||
label: "Arial",
|
||||
value: "arial",
|
||||
},
|
||||
];
|
||||
|
||||
export const getFontStyle = (font: Font) => {
|
||||
const { size, family: fontFamily, color, bold, italic, underline, align } = font;
|
||||
const fontSize = `${size}px`;
|
||||
const fontWeight = bold ? "bold" : "normal";
|
||||
const fontStyle = italic ? "italic" : "normal";
|
||||
const textDecoration = underline ? "underline" : "none";
|
||||
let textAlign = "left";
|
||||
let verticalAlign = "middle";
|
||||
|
||||
switch (align) {
|
||||
case 1:
|
||||
textAlign = "center";
|
||||
verticalAlign = "top";
|
||||
break;
|
||||
case 2:
|
||||
textAlign = "center";
|
||||
verticalAlign = "bottom";
|
||||
break;
|
||||
case 3:
|
||||
textAlign = "center";
|
||||
verticalAlign = "middle";
|
||||
break;
|
||||
case 4:
|
||||
textAlign = "left";
|
||||
verticalAlign = "middle";
|
||||
break;
|
||||
case 5:
|
||||
textAlign = "right";
|
||||
verticalAlign = "middle";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
textDecoration,
|
||||
color,
|
||||
textAlign,
|
||||
verticalAlign,
|
||||
} as CSSProperties;
|
||||
};
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-form-item label="内容">
|
||||
<a-textarea v-model="data.content" placeholder="输入内容..."></a-textarea>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="颜色">
|
||||
<input-color v-model="data.color"></input-color>
|
||||
</a-form-item>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="字体">
|
||||
<a-select v-model="data.family" :options="FontFamilyOptions" class="w-full overflow-hidden"> </a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="大小">
|
||||
<a-input-number v-model="data.size" :min="12"> </a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<a-form-item label="样式">
|
||||
<div class="h-8 flex items-center justify-between">
|
||||
<a-tag
|
||||
class="cursor-pointer !h-7"
|
||||
:color="data.bold ? 'blue' : ''"
|
||||
:bordered="data.bold ? true : false"
|
||||
@click="data.bold = !data.bold"
|
||||
>
|
||||
<i class="icon-park-outline-text-bold"></i>
|
||||
</a-tag>
|
||||
<a-tag
|
||||
class="!h-7 cursor-pointer"
|
||||
:color="data.italic ? 'blue' : ''"
|
||||
:bordered="data.italic ? true : false"
|
||||
@click="data.italic = !data.italic"
|
||||
>
|
||||
<i class="icon-park-outline-text-italic"></i>
|
||||
</a-tag>
|
||||
<a-tag
|
||||
class="!h-7 cursor-pointer"
|
||||
:color="data.underline ? 'blue' : ''"
|
||||
:bordered="data.underline ? true : false"
|
||||
@click="data.underline = !data.underline"
|
||||
>
|
||||
<i class="icon-park-outline-text-underline"></i>
|
||||
</a-tag>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="方向">
|
||||
<a-select v-model="data.align" :options="AlignOptions"></a-select>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import InputColor from "../../components/InputColor.vue";
|
||||
import { Font, AlignOptions, FontFamilyOptions } from "./interface";
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Font>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
./types
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div class="w-full h-full table overflow-hidden" :style="style">
|
||||
<div class="table-cell break-all" :style="{ verticalAlign: style.verticalAlign }">
|
||||
<slot>
|
||||
{{ data.content }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType, computed } from "vue";
|
||||
import { Font, getFontStyle } from "./interface";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Font>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const style = computed(() => {
|
||||
return getFontStyle(props.data);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
./types
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import Text from "./text";
|
||||
|
||||
export const BlockerMap = {
|
||||
[Text.initial.type]: Text,
|
||||
[Text.type]: Text,
|
||||
};
|
||||
|
||||
export const getBlockerRender = (type: string) => {
|
||||
return BlockerMap[type].render;
|
||||
}
|
||||
};
|
||||
|
||||
export default BlockerMap;
|
||||
|
|
@ -1,27 +1,44 @@
|
|||
import { defineBlocker } from '../../config'
|
||||
import Option from './option.vue'
|
||||
import Render from './render.vue'
|
||||
export * from './interface'
|
||||
import { defineBlocker } from "../../config";
|
||||
import Render from "./render.vue";
|
||||
import Option from "./option.vue";
|
||||
import { TextData } from "./interface";
|
||||
|
||||
export default defineBlocker({
|
||||
export default defineBlocker<TextData>({
|
||||
type: "text",
|
||||
icon: "icon-park-outline-text",
|
||||
title: "文本组件",
|
||||
description: "文字",
|
||||
render: Render,
|
||||
option: Option,
|
||||
initial: {
|
||||
id: "",
|
||||
type: "text",
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 50,
|
||||
h: 50,
|
||||
w: 300,
|
||||
h: 100,
|
||||
xFixed: false,
|
||||
yFixed: false,
|
||||
bgImage: "",
|
||||
bgColor: "",
|
||||
data: {},
|
||||
meta: {},
|
||||
actived: false,
|
||||
resizable: true,
|
||||
draggable: true,
|
||||
data: {
|
||||
marquee: false,
|
||||
marqueeSpeed: 1,
|
||||
marqueeDirection: "left",
|
||||
fontCh: {
|
||||
content: "请输入文字",
|
||||
family: "微软雅黑",
|
||||
size: 14,
|
||||
color: "#000000",
|
||||
bold: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
align: 3,
|
||||
},
|
||||
render: Render,
|
||||
option: Option,
|
||||
})
|
||||
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,87 +1,43 @@
|
|||
export interface Text {
|
||||
import { Font } from "../font";
|
||||
|
||||
export interface TextData {
|
||||
/**
|
||||
* 文字内容
|
||||
* 是否滚动
|
||||
*/
|
||||
text: string;
|
||||
marquee?: boolean;
|
||||
/**
|
||||
* 字体
|
||||
* 滚动速度
|
||||
*/
|
||||
family: string;
|
||||
marqueeSpeed?: number;
|
||||
/**
|
||||
* 字号(px)
|
||||
* 滚动方向
|
||||
*/
|
||||
size: number;
|
||||
marqueeDirection?: "left" | "right" | "up" | "down";
|
||||
/**
|
||||
* 颜色(16进制)
|
||||
* 内容(中文)
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* 是否加粗
|
||||
*/
|
||||
bold: boolean;
|
||||
/**
|
||||
* 是否斜体
|
||||
*/
|
||||
italic: boolean;
|
||||
/**
|
||||
* 是否下划线
|
||||
*/
|
||||
underline: boolean;
|
||||
/**
|
||||
* 对齐方式
|
||||
*/
|
||||
align: number;
|
||||
fontCh: Font;
|
||||
}
|
||||
|
||||
export const DefaultText: Text = {
|
||||
text: "双击编辑文字",
|
||||
family: "microsoft yahei",
|
||||
size: 14,
|
||||
color: "#000000",
|
||||
bold: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
align: 3,
|
||||
}
|
||||
|
||||
export const TextAlignOptions = [
|
||||
export const DirectionOptions = [
|
||||
{
|
||||
label: "居上",
|
||||
value: 1,
|
||||
icon: "icon-park-outline-arrow-left",
|
||||
tip: "向左滚动",
|
||||
value: "left",
|
||||
},
|
||||
{
|
||||
label: "居下",
|
||||
value: 2,
|
||||
icon: "icon-park-outline-arrow-up",
|
||||
tip: "向上滚动",
|
||||
value: "up",
|
||||
},
|
||||
{
|
||||
label: "居中",
|
||||
value: 3,
|
||||
icon: "icon-park-outline-arrow-down",
|
||||
tip: "向下滚动",
|
||||
value: "down",
|
||||
},
|
||||
{
|
||||
label: "居左",
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
label: "居右",
|
||||
value: 5,
|
||||
},
|
||||
];
|
||||
|
||||
export const TextFamilyOptions = [
|
||||
{
|
||||
label: "微软雅黑111111111",
|
||||
value: "microsoft yahei",
|
||||
},
|
||||
{
|
||||
label: "黑体",
|
||||
value: "gothic",
|
||||
},
|
||||
{
|
||||
label: "宋体",
|
||||
value: "simsun",
|
||||
},
|
||||
{
|
||||
label: "Arial",
|
||||
value: "arial",
|
||||
icon: "icon-park-outline-arrow-right",
|
||||
tip: "向右滚动",
|
||||
value: "right",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,224 @@
|
|||
<template>
|
||||
<div ref="box" class="marquee" :style="marqueeStyle">
|
||||
<p ref="text" class="marquee-text" :style="marqueeTextStyle" v-bind="$attrs" @animationend="reset">
|
||||
<slot></slot>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: "marquee",
|
||||
props: {
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
direction: {
|
||||
type: String as PropType<"up" | "right" | "down" | "left">,
|
||||
default: "left",
|
||||
},
|
||||
delayTime: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
textAnimation: "",
|
||||
timer: null as any,
|
||||
boxClientHeight: 0,
|
||||
boxClientWidth: 0,
|
||||
textClientHeight: 0,
|
||||
textClientWidth: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 容器样式
|
||||
*/
|
||||
marqueeStyle() {
|
||||
const display = this.isVertical(this.direction) ? "block" : "flex";
|
||||
const alignItems = this.isVertical(this.direction) ? "initial" : "center";
|
||||
return { display, alignItems };
|
||||
},
|
||||
|
||||
/**
|
||||
* 文本样式
|
||||
*/
|
||||
marqueeTextStyle() {
|
||||
const transform = this.textTransform;
|
||||
const animation = this.textAnimation;
|
||||
const whiteSpace = this.isHorizontal(this.direction) ? "nowrap" : "normal";
|
||||
const direction = this.direction === "right" ? "rtl" : "ltr";
|
||||
const unicodeBidi = this.direction === "right" ? "bidi-override" : undefined;
|
||||
return {
|
||||
transform,
|
||||
animation,
|
||||
whiteSpace,
|
||||
direction,
|
||||
unicodeBidi,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据容器宽高和文本宽高,动态计算文本的滚动时间
|
||||
*/
|
||||
textDuration() {
|
||||
if (["up", "down"].includes(this.direction)) {
|
||||
return (this.boxClientHeight + this.textClientHeight) / this.speed;
|
||||
}
|
||||
return (this.boxClientWidth + this.textClientWidth) / this.speed;
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据容器宽高和文本宽高,动态计算文本的起始位置
|
||||
*/
|
||||
textTransform() {
|
||||
let transform = "";
|
||||
switch (this.direction) {
|
||||
case "up": {
|
||||
transform = `translateY(${this.boxClientHeight}px)`;
|
||||
break;
|
||||
}
|
||||
case "right": {
|
||||
transform = `translateX(-100%)`;
|
||||
break;
|
||||
}
|
||||
case "down": {
|
||||
transform = `translateY(-100%)`;
|
||||
break;
|
||||
}
|
||||
case "left": {
|
||||
transform = `translateX(${this.boxClientWidth}px)`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return transform;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
speed: "marquee",
|
||||
direction: "marquee",
|
||||
},
|
||||
mounted() {
|
||||
this.marquee();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 主函数,编辑版式时,使用NextTick也获取不到DOM数据,暂用SetTimeout实现
|
||||
*/
|
||||
marquee() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.initClient();
|
||||
this.initStyle();
|
||||
this.startMarquee();
|
||||
}, this.delayTime);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取容器和文本的宽高
|
||||
*/
|
||||
initClient() {
|
||||
const box = this.$refs.box as HTMLElement;
|
||||
this.boxClientHeight = box.clientHeight;
|
||||
this.boxClientWidth = box.clientWidth;
|
||||
const text = this.$refs.text as HTMLElement;
|
||||
this.textClientHeight = text.clientHeight;
|
||||
this.textClientWidth = text.clientWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* 动态设置CSS样式
|
||||
*/
|
||||
initStyle() {
|
||||
const head = document.querySelector("head")!;
|
||||
let style = document.querySelector("#marqueeanimation")!;
|
||||
if (style) {
|
||||
style.parentNode?.removeChild(style);
|
||||
}
|
||||
style = document.createElement("style");
|
||||
style.id = "marqueeanimation";
|
||||
|
||||
let transform = "";
|
||||
switch (this.direction) {
|
||||
case "down":
|
||||
transform = `translateY(${this.boxClientHeight}px)`;
|
||||
break;
|
||||
case "up":
|
||||
transform = `translateY(-100%)`;
|
||||
break;
|
||||
case "right":
|
||||
transform = `translateX(${this.boxClientWidth}px)`;
|
||||
break;
|
||||
case "left":
|
||||
transform = `translateX(-100%)`;
|
||||
break;
|
||||
}
|
||||
|
||||
style.innerHTML = `
|
||||
@keyframes marquee-to-${this.direction} {
|
||||
100% {
|
||||
transform: ${transform}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
head.appendChild(style);
|
||||
},
|
||||
|
||||
/**
|
||||
* 开始滚动文本(设置css的animation属性会自动滚动)
|
||||
*/
|
||||
startMarquee() {
|
||||
this.textAnimation = `marquee-to-${this.direction} ${this.textDuration}s linear`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置并开始下一轮
|
||||
*/
|
||||
reset() {
|
||||
this.$emit("animationend");
|
||||
this.textAnimation = "";
|
||||
this.timer = setTimeout(this.startMarquee, this.delayTime);
|
||||
},
|
||||
|
||||
/**
|
||||
* 是否水平方向滚动
|
||||
*/
|
||||
isVertical(direction: string) {
|
||||
return ["up", "down"].includes(direction);
|
||||
},
|
||||
|
||||
/**
|
||||
* 是否垂直方向滚动
|
||||
*/
|
||||
isHorizontal(direction: string) {
|
||||
return ["left", "right"].includes(direction);
|
||||
},
|
||||
},
|
||||
beforeDestory() {
|
||||
clearTimeout(this.timer);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.marquee {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.marquee-text {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,65 +1,57 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="左侧">
|
||||
<a-input-number v-model="data.x" :min="0" :max="100">
|
||||
<template #prefix>
|
||||
<a-tooltip content="固定水平方向">
|
||||
<i
|
||||
class="cursor-pointer text-gray-400 hover:text-gray-700"
|
||||
:class="data.xFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'"
|
||||
@click="data.xFixed = !data.xFixed"
|
||||
></i>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
<base-option :data="data"></base-option>
|
||||
</div>
|
||||
<div>
|
||||
<a-divider></a-divider>
|
||||
<div class="muti-form-item grid grid-cols-2 gap-4">
|
||||
<a-form-item label="是否滚动">
|
||||
<a-radio-group type="button" v-model="data.data.marquee" class="!w-full">
|
||||
<a-radio :value="false">否</a-radio>
|
||||
<a-radio :value="true">是</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="顶部">
|
||||
<a-input-number v-model="data.y" :min="0" :max="100">
|
||||
<template #prefix>
|
||||
<a-tooltip content="固定垂直方向">
|
||||
<i
|
||||
class="cursor-pointer text-gray-400 hover:text-gray-700"
|
||||
:class="data.yFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'"
|
||||
@click="data.yFixed = !data.yFixed"
|
||||
></i>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
<a-form-item v-show="data.data.marquee" label="滚动速度">
|
||||
<a-input-number v-model="data.data.marqueeSpeed" :min="10" :step="10"></a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="宽度">
|
||||
<a-input-number v-model="data.w" :min="0" :max="100"> </a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="高度">
|
||||
<a-input-number v-model="data.h" :min="0" :max="100"> </a-input-number>
|
||||
<a-form-item v-show="data.data.marquee" label="滚动方向">
|
||||
<a-radio-group type="button" v-model="data.data.marqueeDirection" class="!w-full">
|
||||
<a-radio v-for="item in DirectionOptions" :key="item.value" :value="item.value" class="dir-radio">
|
||||
<i :class="item.icon"></i>
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item label="背景图片">
|
||||
<input-image v-model="data.bgImage"></input-image>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="背景颜色">
|
||||
<a-input v-model="data.bgColor" allow-clear placeholder="无">
|
||||
<template #prefix>
|
||||
<color-picker v-model="data.bgColor"></color-picker>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<div>
|
||||
<a-divider></a-divider>
|
||||
<div class="mb-4 leading-0">
|
||||
<i class="icon-park-outline-text-style"></i>
|
||||
内容(中文)
|
||||
</div>
|
||||
<font-option :data="data.data.fontCh"></font-option>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import InputImage from "../../components/InputImage.vue";
|
||||
import { FontOption } from "../font";
|
||||
import { Block } from "../../config";
|
||||
import { TextData, DirectionOptions } from "./interface";
|
||||
import BaseOption from "../../components/BaseOption.vue";
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Object as PropType<any>,
|
||||
type: Object as PropType<Block<TextData>>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.dir-radio {
|
||||
.arco-radio-button-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,64 +1,31 @@
|
|||
<template>
|
||||
<div class="w-full h-full table overflow-hidden" :style="style">
|
||||
<div class="table-cell break-all" :style="{ verticalAlign: style.verticalAlign }">
|
||||
<slot>
|
||||
{{ data.text }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<ani-marquee
|
||||
v-if="data.data.marquee"
|
||||
:style="style"
|
||||
:speed="data.data.marqueeSpeed"
|
||||
:direction="data.data.marqueeDirection"
|
||||
>
|
||||
{{ data.data.fontCh.content }}
|
||||
</ani-marquee>
|
||||
<font-render v-else :data="data.data.fontCh"></font-render>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import { Text } from "./interface";
|
||||
import { FontRender, getFontStyle } from "../font";
|
||||
import { Block } from "../../config";
|
||||
import { TextData } from "./interface";
|
||||
import AniMarquee from "./marquee.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Text>,
|
||||
type: Object as PropType<Block<TextData>>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const style = computed(() => {
|
||||
const { size, family, color, bold, italic, underline, align } = props.data;
|
||||
|
||||
let textAlign = "left";
|
||||
let verticalAlign = "middle";
|
||||
switch (align) {
|
||||
case 1:
|
||||
textAlign = "center";
|
||||
verticalAlign = "top";
|
||||
break;
|
||||
case 2:
|
||||
textAlign = "center";
|
||||
verticalAlign = "bottom";
|
||||
break;
|
||||
case 3:
|
||||
textAlign = "center";
|
||||
verticalAlign = "middle";
|
||||
break;
|
||||
case 4:
|
||||
textAlign = "left";
|
||||
verticalAlign = "middle";
|
||||
break;
|
||||
case 5:
|
||||
textAlign = "right";
|
||||
verticalAlign = "middle";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
fontFamily: family,
|
||||
fontSize: size + "px",
|
||||
fontWeight: bold ? "bold" : "normal",
|
||||
fontStyle: italic ? "italic" : "normal",
|
||||
textDecoration: underline ? "underline" : "none",
|
||||
color,
|
||||
textAlign,
|
||||
verticalAlign,
|
||||
} as any;
|
||||
return getFontStyle(props.data.data.fontCh);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="左侧">
|
||||
<a-input-number v-model="data.x" :min="0" :max="100">
|
||||
<template #prefix>
|
||||
<a-tooltip content="固定水平方向">
|
||||
<i
|
||||
class="cursor-pointer text-gray-400 hover:text-gray-700"
|
||||
:class="data.xFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'"
|
||||
@click="data.xFixed = !data.xFixed"
|
||||
></i>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="顶部">
|
||||
<a-input-number v-model="data.y" :min="0" :max="100">
|
||||
<template #prefix>
|
||||
<a-tooltip content="固定垂直方向">
|
||||
<i
|
||||
class="cursor-pointer text-gray-400 hover:text-gray-700"
|
||||
:class="data.yFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'"
|
||||
@click="data.yFixed = !data.yFixed"
|
||||
></i>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="宽度">
|
||||
<a-input-number v-model="data.w" :min="0" :max="100"> </a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="高度">
|
||||
<a-input-number v-model="data.h" :min="0" :max="100"> </a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item label="背景图片">
|
||||
<input-image v-model="data.bgImage"></input-image>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="背景颜色">
|
||||
<input-color v-model="data.bgColor"></input-color>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import { Block } from "../config";
|
||||
import InputColor from "./InputColor.vue";
|
||||
import InputImage from "./InputImage.vue";
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Block>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="innerVisible"
|
||||
title="选择图片"
|
||||
title="选择素材"
|
||||
title-align="start"
|
||||
:width="1080"
|
||||
:closable="false"
|
||||
:mask-closable="false"
|
||||
:draggable="true"
|
||||
:ok-button-props="{ disabled: !seleted.length }"
|
||||
>
|
||||
<div class="w-full flex items-center justify-between gap-4">
|
||||
|
|
@ -30,10 +31,8 @@
|
|||
<div class="h-[450px] grid grid-cols-5 grid-rows-2 items-start justify-between gap-4 mt-2">
|
||||
<div
|
||||
v-for="item in images"
|
||||
:key="item.title"
|
||||
:class="{
|
||||
selected: selectedKeys.includes(item.id),
|
||||
}"
|
||||
:key="item.id"
|
||||
:class="{ selected: selectedKeys.includes(item.id) }"
|
||||
class="p-2 border border-transparent rounded"
|
||||
@click="onSelectedImage(item)"
|
||||
>
|
||||
|
|
@ -93,6 +92,13 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "update:visible"]);
|
||||
|
||||
const innerVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit("update:visible", value),
|
||||
});
|
||||
|
||||
const loadData = async () => {
|
||||
const { page, size } = pagination.value;
|
||||
const params = { ...search.value, page, size };
|
||||
|
|
@ -141,19 +147,12 @@ const onSelectedImage = (image: any) => {
|
|||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (value) => {
|
||||
if (!value) return;
|
||||
|
||||
(value) => {
|
||||
if (value) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "update:visible"]);
|
||||
|
||||
const innerVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit("update:visible", value),
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,45 @@
|
|||
import { Component } from "vue";
|
||||
import { Block } from "./block";
|
||||
|
||||
interface Blocker {
|
||||
interface Blocker<T = any> {
|
||||
/**
|
||||
* 组件名称
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* 组件描述
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* 组件类型
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* 组件图标
|
||||
*/
|
||||
icon: string;
|
||||
/**
|
||||
* 组件默认值
|
||||
*/
|
||||
initial: Block;
|
||||
initial: Block<T>;
|
||||
/**
|
||||
* 渲染组件
|
||||
* 编辑时的渲染组件
|
||||
*/
|
||||
render: Component;
|
||||
/**
|
||||
* 配置组件
|
||||
* 配置时的渲染组件
|
||||
*/
|
||||
option: Component;
|
||||
/**
|
||||
* 预览时的渲染组件
|
||||
*/
|
||||
viewer?: Component;
|
||||
/**
|
||||
* 初始化钩子
|
||||
* @param block 组件
|
||||
* @returns
|
||||
*/
|
||||
onInit?: (block: Block) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -21,6 +47,6 @@ interface Blocker {
|
|||
* @param blocker 参数
|
||||
* @returns
|
||||
*/
|
||||
export const defineBlocker = (blocker: Blocker) => {
|
||||
export const defineBlocker = <T>(blocker: Blocker<T>) => {
|
||||
return blocker;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ export interface Context {
|
|||
}>;
|
||||
blocks: Ref<Block[]>;
|
||||
container: Ref<Container>;
|
||||
setCurrentBlock: (block: Block) => void;
|
||||
setCurrentBlock: (block: Block | null) => void;
|
||||
setContainerOrigin: () => void;
|
||||
saveData: () => void;
|
||||
loadData: () => void;
|
||||
}
|
||||
|
||||
export const ContextKey = Symbol('ContextKey') as InjectionKey<Context>;
|
||||
|
|
|
|||
|
|
@ -26,12 +26,21 @@ import PanelLeft from "./panel-left/index.vue";
|
|||
import PanelMain from "./panel-main/index.vue";
|
||||
import PanelRight from "./panel-right/index.vue";
|
||||
|
||||
const blocks = ref<Block[]>([]);
|
||||
|
||||
/**
|
||||
* 运行时上下文
|
||||
*/
|
||||
const current = ref({
|
||||
block: null as Block | null,
|
||||
});
|
||||
|
||||
/**
|
||||
* 组件列表
|
||||
*/
|
||||
const blocks = ref<Block[]>([]);
|
||||
|
||||
/**
|
||||
* 画布容器
|
||||
*/
|
||||
const container = ref<Container>({
|
||||
id: 11,
|
||||
title: "国庆节喜庆版式设计",
|
||||
|
|
@ -45,6 +54,38 @@ const container = ref<Container>({
|
|||
bgColor: "#ffffff",
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
|
||||
/**
|
||||
* 保存数据
|
||||
*/
|
||||
const saveData = () => {
|
||||
const data = {
|
||||
container: container.value,
|
||||
children: blocks.value,
|
||||
};
|
||||
const str = JSON.stringify(data);
|
||||
localStorage.setItem("ANI_EDITOR_DATA", str);
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const loadData = async () => {
|
||||
const str = localStorage.getItem("ANI_EDITOR_DATA");
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
const data = JSON.parse(str);
|
||||
container.value = data.container;
|
||||
blocks.value = data.children;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置当前选中的组件
|
||||
*/
|
||||
const setCurrentBlock = (block: Block | null) => {
|
||||
for (const block of blocks.value) {
|
||||
block.actived = false;
|
||||
|
|
@ -57,19 +98,26 @@ const setCurrentBlock = (block: Block | null) => {
|
|||
current.value.block = block;
|
||||
};
|
||||
|
||||
// 恢复画布到原始比例和远点
|
||||
/**
|
||||
* 恢复画布到原始比例和远点
|
||||
*/
|
||||
const setContainerOrigin = () => {
|
||||
container.value.x = 0;
|
||||
container.value.y = 0;
|
||||
container.value.zoom = 0.7;
|
||||
};
|
||||
|
||||
/**
|
||||
* 提供上下文注入
|
||||
*/
|
||||
provide(ContextKey, {
|
||||
current,
|
||||
container,
|
||||
blocks,
|
||||
setCurrentBlock,
|
||||
setContainerOrigin,
|
||||
loadData,
|
||||
saveData,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
export interface Block<T = any> {
|
||||
id: string;
|
||||
type: string;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
xFixed: boolean;
|
||||
yFixed: boolean;
|
||||
bgImage?: string;
|
||||
bgColor?: string;
|
||||
data: T;
|
||||
meta: Record<string, any>;
|
||||
}
|
||||
|
||||
export const DefaultBlock: Block = {
|
||||
id: "",
|
||||
type: "",
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 50,
|
||||
h: 50,
|
||||
xFixed: false,
|
||||
yFixed: false,
|
||||
bgImage: "",
|
||||
bgColor: "",
|
||||
data: {},
|
||||
meta: {},
|
||||
}
|
||||
|
||||
export interface Container {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
bgImage?: string;
|
||||
bgColor?: string;
|
||||
children: Block[];
|
||||
}
|
||||
|
||||
export interface Current {
|
||||
container: Container;
|
||||
block: Block;
|
||||
}
|
||||
|
||||
export type TextAlign = "left" | "center" | "right";
|
||||
|
||||
export interface TextStyle {
|
||||
text: string;
|
||||
family: string;
|
||||
size: number;
|
||||
color: string;
|
||||
bold: boolean;
|
||||
italic: boolean;
|
||||
underline: boolean;
|
||||
align: TextAlign;
|
||||
}
|
||||
|
||||
export const TextAlignOptions = [
|
||||
{
|
||||
label: "居上",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: "居下",
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: "居中",
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: "居左",
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
label: "居右",
|
||||
value: 5,
|
||||
},
|
||||
];
|
||||
|
||||
export const TextFamilyOptions = [
|
||||
{
|
||||
label: "微软雅黑111111111",
|
||||
value: "microsoft yahei",
|
||||
},
|
||||
{
|
||||
label: "黑体",
|
||||
value: "gothic",
|
||||
},
|
||||
{
|
||||
label: "宋体",
|
||||
value: "simsun",
|
||||
},
|
||||
{
|
||||
label: "Arial",
|
||||
value: "arial",
|
||||
},
|
||||
];
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
<template>
|
||||
<div class="h-full flex items-center justify-between px-4">
|
||||
<div class="text-base group">
|
||||
<a-tag color="green" bordered class="mr-1.5"> 新增 </a-tag>前端编辑器
|
||||
<i class="icon-park-outline-edit text-gray-400 hover:text-gray-700 ml-1"></i>
|
||||
<a-tag :color="container.id ? 'blue' : 'green'" bordered class="mr-2">
|
||||
{{ container.id ? "修改" : "新增" }}
|
||||
</a-tag>
|
||||
<ani-texter v-model="container.title"></ani-texter>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a-button> 导出 </a-button>
|
||||
<a-button> 设置 </a-button>
|
||||
<a-dropdown-button type="primary">
|
||||
<a-dropdown-button type="primary" @click="onSaveData">
|
||||
保存
|
||||
<template #content>
|
||||
<a-doption>保存为JSON</a-doption>
|
||||
|
|
@ -19,6 +21,17 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import { ContextKey } from "../config";
|
||||
import AniTexter from "../panel-main/components/texter.vue";
|
||||
|
||||
const { saveData, container } = inject(ContextKey)!;
|
||||
|
||||
const onSaveData = () => {
|
||||
saveData();
|
||||
Message.success("保存成功");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -37,16 +37,20 @@
|
|||
</div>
|
||||
<div v-show="!collapsed">
|
||||
<ul class="list-none px-2 grid gap-2" @dragstart="onDragStart" @dragover="onDragOver">
|
||||
<li v-for="i in 10" class="flex items-center justify-between gap-2 border border-slate-200 text-gray-500 px-2 py-1 rounded cursor-move"
|
||||
<li
|
||||
v-for="item in blocks"
|
||||
:key="item.type"
|
||||
:draggable="true"
|
||||
:data-type="'text'"
|
||||
class="flex items-center justify-between gap-2 bg-gray-100 text-gray-500 px-2 py-1 rounded cursor-move"
|
||||
>
|
||||
<div class="">
|
||||
<i class="icon-park-outline-pic text-base text-gray-500"></i>
|
||||
<i class="text-base text-gray-500" :class="item.icon"></i>
|
||||
</div>
|
||||
<div class="flex-1 leading-0">
|
||||
<div class="">图片</div>
|
||||
<!-- <div class="text-xs text-gray-400 mt-1">image</div> -->
|
||||
<div class="">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -55,17 +59,20 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { BlockerMap } from "../blocks";
|
||||
|
||||
const blocks = Object.values(BlockerMap);
|
||||
const collapsed = ref(false);
|
||||
|
||||
const onDragStart = (e: DragEvent) => {
|
||||
console.log('start');
|
||||
console.log("start");
|
||||
e.dataTransfer?.setData("type", (e.target as HTMLElement).dataset.type!);
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver = (e: Event) => {
|
||||
console.log('over');
|
||||
console.log("over");
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
@resizing="onItemDragOrResize"
|
||||
@activated="setCurrentBlock(data)"
|
||||
>
|
||||
<component :is="BlockerMap[data.type].render" :data="{}" />
|
||||
<component :is="BlockerMap[data.type].render" :data="data" />
|
||||
</drag-resizer>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<span class="text-gray-700">{{ Math.floor(container.x) }} , {{ Math.floor(container.y) }} </span>
|
||||
</span>
|
||||
<span class="text-gray-400 text-xs mr-3">
|
||||
尺寸:
|
||||
画布:
|
||||
<span class="text-gray-700">{{ container.width }} * {{ container.height }} </span>
|
||||
</span>
|
||||
<span class="text-gray-400 text-xs mr-2">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
></i>
|
||||
</span>
|
||||
<span v-else class="inline-flex items-center">
|
||||
<a-input size="small" v-model="descContent" class="!w-96"></a-input>
|
||||
<a-input size="small" v-model="descContent" class="!w-96" v-bind="inputProps"></a-input>
|
||||
<a-button type="text" size="small" @click="onDescEdited" class="ml-2">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-check"></i>
|
||||
|
|
@ -22,11 +22,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import { Input } from "@arco-design/web-vue";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
inputProps: {
|
||||
type: Object as PropType<Partial<InstanceType<typeof Input>["$props"]>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<div
|
||||
class="relative"
|
||||
:style="containerStyle"
|
||||
@click="onClick"
|
||||
@drop="onDragDrop"
|
||||
@dragover.prevent
|
||||
@wheel="onMouseWheel"
|
||||
|
|
@ -22,11 +23,19 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BlockerMap from "../blocks";
|
||||
import { ContextKey } from "../config";
|
||||
import AniBlock from "./components/block.vue";
|
||||
import AniHeader from "./components/header.vue";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
|
||||
const { blocks, container } = inject(ContextKey)!;
|
||||
const { blocks, container, setCurrentBlock } = inject(ContextKey)!;
|
||||
|
||||
const onClick = (e: Event) => {
|
||||
if(e.target === e.currentTarget) {
|
||||
setCurrentBlock(null);
|
||||
}
|
||||
}
|
||||
|
||||
const isStart = ref(false);
|
||||
const position = ref({
|
||||
|
|
@ -88,22 +97,16 @@ const onDragDrop = (e: DragEvent) => {
|
|||
if (!type) {
|
||||
return;
|
||||
}
|
||||
const blocker = BlockerMap[type];
|
||||
const currentIds = blocks.value.map((item) => item.id);
|
||||
const maxId = Math.max(...currentIds.map((item) => parseInt(item)));
|
||||
const id = (maxId + 1).toString();
|
||||
|
||||
blocks.value.push({
|
||||
id: "0",
|
||||
w: 200,
|
||||
h: 100,
|
||||
bgColor: "#0099ff",
|
||||
xFixed: false,
|
||||
yFixed: false,
|
||||
resizable: true,
|
||||
draggable: true,
|
||||
type,
|
||||
...cloneDeep(blocker.initial),
|
||||
id,
|
||||
x: e.offsetX,
|
||||
y: e.offsetY,
|
||||
data: {},
|
||||
meta: {},
|
||||
actived: false,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="左侧">
|
||||
<a-input-number v-model="block.x" :min="0" :max="100">
|
||||
<template #prefix>
|
||||
<a-tooltip content="固定水平方向">
|
||||
<i
|
||||
class="cursor-pointer text-gray-400 hover:text-gray-700"
|
||||
:class="
|
||||
block.xFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'
|
||||
"
|
||||
@click="block.xFixed = !block.xFixed"
|
||||
></i>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="顶部">
|
||||
<a-input-number v-model="block.y" :min="0" :max="100">
|
||||
<template #prefix>
|
||||
<a-tooltip content="固定垂直方向">
|
||||
<i
|
||||
class="cursor-pointer text-gray-400 hover:text-gray-700"
|
||||
:class="
|
||||
block.yFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'
|
||||
"
|
||||
@click="block.yFixed = !block.yFixed"
|
||||
></i>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="宽度">
|
||||
<a-input-number v-model="block.w" :min="0" :max="100"> </a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="高度">
|
||||
<a-input-number v-model="block.h" :min="0" :max="100"> </a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item label="背景图片">
|
||||
<a-input v-model="block.bgImage" class="group w-full" allow-clear placeholder="暂无">
|
||||
<template #prefix>
|
||||
<a-link class="!text-xs">选择</a-link>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="背景颜色">
|
||||
<a-input v-model="block.bgColor" allow-clear placeholder="无">
|
||||
<template #prefix>
|
||||
<color-picker v-model="block.bgColor"></color-picker>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
|
||||
defineProps({
|
||||
block: {
|
||||
type: Object as PropType<any>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -2,16 +2,13 @@
|
|||
<div class="w-[248px] overflow-hidden p-3">
|
||||
<a-radio-group type="button" class="w-full mb-2">
|
||||
<a-radio value="1">基本</a-radio>
|
||||
<a-radio value="2">字体</a-radio>
|
||||
<a-radio value="2">文本</a-radio>
|
||||
</a-radio-group>
|
||||
<a-form :model="block" layout="vertical">
|
||||
<div v-if="current.block" class="muti-form-item mt-2">
|
||||
<component :is="BlockerMap[current.block.type].option" :data="current.block" />
|
||||
</div>
|
||||
<div class="muti-form-item">
|
||||
<a-divider orientation="left">中文设置</a-divider>
|
||||
<text-attr :block="block.textStyle"></text-attr>
|
||||
</div>
|
||||
<a-empty v-else :description="'选择组件后显示'" class="mt-8"></a-empty>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-form-item label="内容">
|
||||
<a-textarea v-model="block.text" placeholder="输入内容..."></a-textarea>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="颜色">
|
||||
<a-input v-model="block.color">
|
||||
<template #prefix>
|
||||
<color-picker v-model="block.color"></color-picker>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="字体">
|
||||
<a-select v-model="block.family" :options="TextFamilyOptions" class="w-full overflow-hidden"> </a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="大小">
|
||||
<a-input-number v-model="block.size" :min="12"> </a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<a-form-item label="样式">
|
||||
<div class="h-8 flex items-center justify-between">
|
||||
<a-tag
|
||||
class="cursor-pointer !h-7"
|
||||
:color="block.bold ? 'blue' : ''"
|
||||
:bordered="block.bold ? true : false"
|
||||
@click="block.bold = !block.bold"
|
||||
>
|
||||
<i class="icon-park-outline-text-bold"></i>
|
||||
</a-tag>
|
||||
<a-tag
|
||||
class="!h-7 cursor-pointer"
|
||||
:color="block.italic ? 'blue' : ''"
|
||||
:bordered="block.italic ? true : false"
|
||||
@click="block.italic = !block.italic"
|
||||
>
|
||||
<i class="icon-park-outline-text-italic"></i>
|
||||
</a-tag>
|
||||
<a-tag
|
||||
class="!h-7 cursor-pointer"
|
||||
:color="block.underline ? 'blue' : ''"
|
||||
:bordered="block.underline ? true : false"
|
||||
@click="block.underline = !block.underline"
|
||||
>
|
||||
<i class="icon-park-outline-text-underline"></i>
|
||||
</a-tag>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="方向">
|
||||
<a-select v-model="block.align" :options="TextAlignOptions"></a-select>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import ColorPicker from "../components/ColorPicker.vue";
|
||||
import { TextAlignOptions, TextFamilyOptions } from "../interface";
|
||||
import { ContextKey } from '../config';
|
||||
|
||||
const context = inject(ContextKey);
|
||||
console.log('ctx', context);
|
||||
|
||||
defineProps({
|
||||
block: {
|
||||
type: Object as PropType<any>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -7,43 +7,34 @@ export {}
|
|||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
|
||||
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
|
||||
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
|
||||
AButton: typeof import('@arco-design/web-vue')['Button']
|
||||
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
||||
ACheckboxGroup: typeof import('@arco-design/web-vue')['CheckboxGroup']
|
||||
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
||||
ADivider: typeof import('@arco-design/web-vue')['Divider']
|
||||
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
||||
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
||||
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
|
||||
ADropdownButton: typeof import('@arco-design/web-vue')['DropdownButton']
|
||||
AEmpty: typeof import('@arco-design/web-vue')['Empty']
|
||||
AForm: typeof import('@arco-design/web-vue')['Form']
|
||||
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
|
||||
AInput: typeof import('@arco-design/web-vue')['Input']
|
||||
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
|
||||
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
|
||||
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch']
|
||||
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
||||
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
|
||||
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
||||
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
||||
ALink: typeof import('@arco-design/web-vue')['Link']
|
||||
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||
AMenuItemGroup: typeof import('@arco-design/web-vue')['MenuItemGroup']
|
||||
AModal: typeof import('@arco-design/web-vue')['Modal']
|
||||
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
||||
APopover: typeof import('@arco-design/web-vue')['Popover']
|
||||
ARadio: typeof import('@arco-design/web-vue')['Radio']
|
||||
ARadioButton: typeof import('@arco-design/web-vue')['RadioButton']
|
||||
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup']
|
||||
AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar']
|
||||
ASelect: typeof import('@arco-design/web-vue')['Select']
|
||||
ASpace: typeof import('@arco-design/web-vue')['Space']
|
||||
ASpin: typeof import('@arco-design/web-vue')['Spin']
|
||||
ATag: typeof import('@arco-design/web-vue')['Tag']
|
||||
ATextarea: typeof import('@arco-design/web-vue')['Textarea']
|
||||
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
|
||||
AUpload: typeof import('@arco-design/web-vue')['Upload']
|
||||
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
|
||||
Block: typeof import('./../components/editor/panel-main/components/block.vue')['default']
|
||||
BlockAttr: typeof import('./../components/editor/panel-right/block-attr.vue')['default']
|
||||
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']
|
||||
|
|
@ -55,14 +46,15 @@ declare module '@vue/runtime-core' {
|
|||
ImagePicker: typeof import('./../components/editor/components/ImagePicker.vue')['default']
|
||||
InputColor: typeof import('./../components/editor/components/InputColor.vue')['default']
|
||||
InputImage: typeof import('./../components/editor/components/InputImage.vue')['default']
|
||||
Option: typeof import('./../components/editor/blocks/text/option.vue')['default']
|
||||
Marquee: typeof import('./../components/editor/blocks/text/marquee.vue')['default']
|
||||
Option: typeof import('./../components/editor/blocks/font/option.vue')['default']
|
||||
Page403: typeof import('./../components/error/page-403.vue')['default']
|
||||
PanelHeader: typeof import('./../components/editor/panel-header/index.vue')['default']
|
||||
PanelLeft: typeof import('./../components/editor/panel-left/index.vue')['default']
|
||||
PanelMain: typeof import('./../components/editor/panel-main/index.vue')['default']
|
||||
PanelRight: typeof import('./../components/editor/panel-right/index.vue')['default']
|
||||
Preview: typeof import('./../components/editor/preview/index.vue')['default']
|
||||
Render: typeof import('./../components/editor/blocks/text/render.vue')['default']
|
||||
Render: typeof import('./../components/editor/blocks/font/render.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
TextAttr: typeof import('./../components/editor/panel-right/text-attr.vue')['default']
|
||||
|
|
|
|||
Loading…
Reference in New Issue