feat: 优化部分代码

master
绝弹 2023-10-09 22:41:15 +08:00
parent 95dfbc6cdb
commit 79e016fbd7
17 changed files with 1227 additions and 144 deletions

View File

@ -1,22 +1,22 @@
<!-- 修改自: https://github.com/zuley/vue-color-picker --> <!-- 修改自: https://github.com/zuley/vue-color-picker -->
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from "vue";
import { onClickOutside } from "@vueuse/core"; import { onClickOutside } from "@vueuse/core";
import { computed, ref } from "vue";
const props = withDefaults( const props = defineProps({
defineProps<{ modelValue: {
// type: String,
modelValue: string; },
// defaultColor: {
defaultColor?: string; type: String,
// default: "#000000",
disabled?: boolean; },
}>(), disabled: {
{ type: Boolean,
defaultColor: "#000000", default: false,
} },
); });
const emits = defineEmits<{ const emits = defineEmits<{
(e: "update:modelValue", value: string): void; (e: "update:modelValue", value: string): void;
@ -112,7 +112,7 @@ const triggerHtml5Color = () => {
html5ColorEl.value?.click(); html5ColorEl.value?.click();
}; };
// //
const updataValue = (value: string) => { const updataValue = (value: string = "") => {
emits("update:modelValue", value); emits("update:modelValue", value);
emits("change", value); emits("change", value);
openStatus.value = false; openStatus.value = false;
@ -196,7 +196,7 @@ const gradient = (startColor: string, endColor: string, step: number) => {
</div> </div>
</div> </div>
<div class="bd"> <div class="bd">
<h3>主题颜色</h3> <h3 class="section-title">主题颜色</h3>
<ul class="tColor"> <ul class="tColor">
<li <li
v-for="(color, index) of tColor" v-for="(color, index) of tColor"
@ -221,7 +221,7 @@ const gradient = (startColor: string, endColor: string, step: number) => {
</ul> </ul>
</li> </li>
</ul> </ul>
<h3>标准颜色</h3> <h3 class="section-title">标准颜色</h3>
<ul class="tColor"> <ul class="tColor">
<li <li
v-for="(color, index) of bColor" v-for="(color, index) of bColor"
@ -247,6 +247,7 @@ const gradient = (startColor: string, endColor: string, step: number) => {
font-size: 14px; font-size: 14px;
display: inline-block; display: inline-block;
outline: none; outline: none;
ul, ul,
li, li,
ol { ol {
@ -276,6 +277,7 @@ const gradient = (startColor: string, endColor: string, step: number) => {
opacity: 0; opacity: 0;
transition: all 0.3s ease; transition: all 0.3s ease;
box-sizing: content-box; box-sizing: content-box;
z-index: 99999;
h3 { h3 {
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
@ -295,7 +297,6 @@ const gradient = (startColor: string, endColor: string, step: number) => {
.box.open { .box.open {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
z-index: 1;
} }
.hd { .hd {
overflow: hidden; overflow: hidden;
@ -314,6 +315,9 @@ const gradient = (startColor: string, endColor: string, step: number) => {
cursor: pointer; cursor: pointer;
color: #333; color: #333;
} }
.section-title {
color: #899;
}
} }
.tColor { .tColor {
li { li {

View File

@ -0,0 +1,907 @@
<!-- 修改自https://github.com/kirillmurashov/vue-drag-resize -->
<template>
<div
class="vdr"
:style="positionStyle"
:class="`${active || isActive ? 'active' : 'inactive'} ${contentClass ? contentClass : ''}`"
@mousedown="bodyDown($event)"
@touchstart="bodyDown($event)"
@touchend="up($event)"
>
<div :style="sizeStyle" class="content-container" ref="container">
<slot></slot>
</div>
<div
v-for="stick in sticks"
class="vdr-stick"
:class="['vdr-stick-' + stick, isResizable ? '' : 'not-resizable']"
@mousedown.stop.prevent="stickDown(stick, $event)"
@touchstart.stop.prevent="stickDown(stick, $event)"
:style="vdrStick(stick)"
></div>
</div>
</template>
<script>
const styleMapping = {
y: {
t: "top",
m: "marginTop",
b: "bottom",
},
x: {
l: "left",
m: "marginLeft",
r: "right",
},
};
function addEvents(events) {
events.forEach((cb, eventName) => {
document.documentElement.addEventListener(eventName, cb);
});
}
function removeEvents(events) {
events.forEach((cb, eventName) => {
document.documentElement.removeEventListener(eventName, cb);
});
}
export default {
name: "vue-drag-resize",
emits: ["clicked", "dragging", "dragstop", "resizing", "resizestop", "activated", "deactivated"],
props: {
stickSize: {
type: Number,
default: 8,
},
parentScaleX: {
type: Number,
default: 1,
},
parentScaleY: {
type: Number,
default: 1,
},
isActive: {
type: Boolean,
default: false,
},
preventActiveBehavior: {
type: Boolean,
default: false,
},
isDraggable: {
type: Boolean,
default: true,
},
isResizable: {
type: Boolean,
default: true,
},
aspectRatio: {
type: Boolean,
default: false,
},
parentLimitation: {
type: Boolean,
default: false,
},
snapToGrid: {
type: Boolean,
default: false,
},
gridX: {
type: Number,
default: 50,
validator(val) {
return val >= 0;
},
},
gridY: {
type: Number,
default: 50,
validator(val) {
return val >= 0;
},
},
parentW: {
type: Number,
default: 0,
validator(val) {
return val >= 0;
},
},
parentH: {
type: Number,
default: 0,
validator(val) {
return val >= 0;
},
},
w: {
type: [String, Number],
default: 200,
validator(val) {
return typeof val === "string" ? val === "auto" : val >= 0;
},
},
h: {
type: [String, Number],
default: 200,
validator(val) {
return typeof val === "string" ? val === "auto" : val >= 0;
},
},
minw: {
type: Number,
default: 50,
validator(val) {
return val >= 0;
},
},
minh: {
type: Number,
default: 50,
validator(val) {
return val >= 0;
},
},
x: {
type: Number,
default: 0,
validator(val) {
return typeof val === "number";
},
},
y: {
type: Number,
default: 0,
validator(val) {
return typeof val === "number";
},
},
z: {
type: [String, Number],
default: "auto",
validator(val) {
return typeof val === "string" ? val === "auto" : val >= 0;
},
},
dragHandle: {
type: String,
default: null,
},
dragCancel: {
type: String,
default: null,
},
sticks: {
type: Array,
default() {
return ["tl", "tm", "tr", "mr", "br", "bm", "bl", "ml"];
},
},
axis: {
type: String,
default: "both",
validator(val) {
return ["x", "y", "both", "none"].indexOf(val) !== -1;
},
},
contentClass: {
type: String,
required: false,
default: "",
},
},
data() {
return {
fixAspectRatio: null,
active: null,
zIndex: null,
parentWidth: null,
parentHeight: null,
left: null,
top: null,
right: null,
bottom: null,
minHeight: null,
};
},
beforeCreate() {
this.stickDrag = false;
this.bodyDrag = false;
this.dimensionsBeforeMove = { pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0 };
this.limits = {
left: { min: null, max: null },
right: { min: null, max: null },
top: { min: null, max: null },
bottom: { min: null, max: null },
};
this.currentStick = null;
},
mounted() {
this.parentElement = this.$el.parentNode;
this.parentWidth = this.parentW ? this.parentW : this.parentElement.clientWidth;
this.parentHeight = this.parentH ? this.parentH : this.parentElement.clientHeight;
this.left = this.x;
this.top = this.y;
this.right = this.parentWidth - (this.w === "auto" ? this.$refs.container.scrollWidth : this.w) - this.left;
this.bottom = this.parentHeight - (this.h === "auto" ? this.$refs.container.scrollHeight : this.h) - this.top;
this.domEvents = new Map([
["mousemove", this.move],
["mouseup", this.up],
["mouseleave", this.up],
["mousedown", this.deselect],
["touchmove", this.move],
["touchend", this.up],
["touchcancel", this.up],
["touchstart", this.up],
]);
addEvents(this.domEvents);
if (this.dragHandle) {
[...this.$el.querySelectorAll(this.dragHandle)].forEach((dragHandle) => {
dragHandle.setAttribute("data-drag-handle", this._uid);
});
}
if (this.dragCancel) {
[...this.$el.querySelectorAll(this.dragCancel)].forEach((cancelHandle) => {
cancelHandle.setAttribute("data-drag-cancel", this._uid);
});
}
},
beforeDestroy() {
removeEvents(this.domEvents);
},
methods: {
deselect() {
if (this.preventActiveBehavior) {
return;
}
this.active = false;
},
move(ev) {
if (!this.stickDrag && !this.bodyDrag) {
return;
}
ev.stopPropagation();
const pageX = typeof ev.pageX !== "undefined" ? ev.pageX : ev.touches[0].pageX;
const pageY = typeof ev.pageY !== "undefined" ? ev.pageY : ev.touches[0].pageY;
const { dimensionsBeforeMove } = this;
const delta = {
x: (dimensionsBeforeMove.pointerX - pageX) / this.parentScaleX,
y: (dimensionsBeforeMove.pointerY - pageY) / this.parentScaleY,
};
if (this.stickDrag) {
this.stickMove(delta);
}
if (this.bodyDrag) {
if (this.axis === "x") {
delta.y = 0;
} else if (this.axis === "y") {
delta.x = 0;
} else if (this.axis === "none") {
return;
}
this.bodyMove(delta);
}
},
up(ev) {
if (this.stickDrag) {
this.stickUp(ev);
} else if (this.bodyDrag) {
this.bodyUp(ev);
}
},
bodyDown(ev) {
const { target, button } = ev;
if (!this.preventActiveBehavior) {
this.active = true;
}
if (button && button !== 0) {
return;
}
this.$emit("clicked", ev);
if (!this.active) {
return;
}
if (this.dragHandle && target.getAttribute("data-drag-handle") !== this._uid.toString()) {
return;
}
if (this.dragCancel && target.getAttribute("data-drag-cancel") === this._uid.toString()) {
return;
}
if (typeof ev.stopPropagation !== "undefined") {
ev.stopPropagation();
}
if (typeof ev.preventDefault !== "undefined") {
ev.preventDefault();
}
if (this.isDraggable) {
this.bodyDrag = true;
}
const pointerX = typeof ev.pageX !== "undefined" ? ev.pageX : ev.touches[0].pageX;
const pointerY = typeof ev.pageY !== "undefined" ? ev.pageY : ev.touches[0].pageY;
this.saveDimensionsBeforeMove({ pointerX, pointerY });
if (this.parentLimitation) {
this.limits = this.calcDragLimitation();
}
},
bodyMove(delta) {
const { dimensionsBeforeMove, parentWidth, parentHeight, gridX, gridY, width, height } = this;
let newTop = dimensionsBeforeMove.top - delta.y;
let newBottom = dimensionsBeforeMove.bottom + delta.y;
let newLeft = dimensionsBeforeMove.left - delta.x;
let newRight = dimensionsBeforeMove.right + delta.x;
if (this.snapToGrid) {
let alignTop = true;
let alignLeft = true;
let diffT = newTop - Math.floor(newTop / gridY) * gridY;
let diffB = parentHeight - newBottom - Math.floor((parentHeight - newBottom) / gridY) * gridY;
let diffL = newLeft - Math.floor(newLeft / gridX) * gridX;
let diffR = parentWidth - newRight - Math.floor((parentWidth - newRight) / gridX) * gridX;
if (diffT > gridY / 2) {
diffT -= gridY;
}
if (diffB > gridY / 2) {
diffB -= gridY;
}
if (diffL > gridX / 2) {
diffL -= gridX;
}
if (diffR > gridX / 2) {
diffR -= gridX;
}
if (Math.abs(diffB) < Math.abs(diffT)) {
alignTop = false;
}
if (Math.abs(diffR) < Math.abs(diffL)) {
alignLeft = false;
}
newTop -= alignTop ? diffT : diffB;
newBottom = parentHeight - height - newTop;
newLeft -= alignLeft ? diffL : diffR;
newRight = parentWidth - width - newLeft;
}
({
newLeft: this.left,
newRight: this.right,
newTop: this.top,
newBottom: this.bottom,
} = this.rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom }));
this.$emit("dragging", this.rect);
},
bodyUp() {
this.bodyDrag = false;
this.$emit("dragging", this.rect);
this.$emit("dragstop", this.rect);
this.dimensionsBeforeMove = { pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0 };
this.limits = {
left: { min: null, max: null },
right: { min: null, max: null },
top: { min: null, max: null },
bottom: { min: null, max: null },
};
},
stickDown(stick, ev, force = false) {
if ((!this.isResizable || !this.active) && !force) {
return;
}
this.stickDrag = true;
const pointerX = typeof ev.pageX !== "undefined" ? ev.pageX : ev.touches[0].pageX;
const pointerY = typeof ev.pageY !== "undefined" ? ev.pageY : ev.touches[0].pageY;
this.saveDimensionsBeforeMove({ pointerX, pointerY });
this.currentStick = stick;
this.limits = this.calcResizeLimits();
},
saveDimensionsBeforeMove({ pointerX, pointerY }) {
this.dimensionsBeforeMove.pointerX = pointerX;
this.dimensionsBeforeMove.pointerY = pointerY;
this.dimensionsBeforeMove.left = this.left;
this.dimensionsBeforeMove.right = this.right;
this.dimensionsBeforeMove.top = this.top;
this.dimensionsBeforeMove.bottom = this.bottom;
this.dimensionsBeforeMove.width = this.width;
this.dimensionsBeforeMove.height = this.height;
this.aspectFactor = this.width / this.height;
},
stickMove(delta) {
const { currentStick, dimensionsBeforeMove, gridY, gridX, snapToGrid, parentHeight, parentWidth } = this;
let newTop = dimensionsBeforeMove.top;
let newBottom = dimensionsBeforeMove.bottom;
let newLeft = dimensionsBeforeMove.left;
let newRight = dimensionsBeforeMove.right;
switch (currentStick[0]) {
case "b":
newBottom = dimensionsBeforeMove.bottom + delta.y;
if (snapToGrid) {
newBottom = parentHeight - Math.round((parentHeight - newBottom) / gridY) * gridY;
}
break;
case "t":
newTop = dimensionsBeforeMove.top - delta.y;
if (snapToGrid) {
newTop = Math.round(newTop / gridY) * gridY;
}
break;
default:
break;
}
switch (currentStick[1]) {
case "r":
newRight = dimensionsBeforeMove.right + delta.x;
if (snapToGrid) {
newRight = parentWidth - Math.round((parentWidth - newRight) / gridX) * gridX;
}
break;
case "l":
newLeft = dimensionsBeforeMove.left - delta.x;
if (snapToGrid) {
newLeft = Math.round(newLeft / gridX) * gridX;
}
break;
default:
break;
}
({ newLeft, newRight, newTop, newBottom } = this.rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom }));
if (this.aspectRatio) {
({ newLeft, newRight, newTop, newBottom } = this.rectCorrectionByAspectRatio({
newLeft,
newRight,
newTop,
newBottom,
}));
}
this.left = newLeft;
this.right = newRight;
this.top = newTop;
this.bottom = newBottom;
this.$emit("resizing", this.rect);
},
stickUp() {
this.stickDrag = false;
this.dimensionsBeforeMove = {
pointerX: 0,
pointerY: 0,
x: 0,
y: 0,
w: 0,
h: 0,
};
this.limits = {
left: { min: null, max: null },
right: { min: null, max: null },
top: { min: null, max: null },
bottom: { min: null, max: null },
};
this.$emit("resizing", this.rect);
this.$emit("resizestop", this.rect);
},
calcDragLimitation() {
const { parentWidth, parentHeight } = this;
return {
left: { min: 0, max: parentWidth - this.width },
right: { min: 0, max: parentWidth - this.width },
top: { min: 0, max: parentHeight - this.height },
bottom: { min: 0, max: parentHeight - this.height },
};
},
calcResizeLimits() {
const { aspectFactor, width, height, bottom, top, left, right } = this;
let { minh: minHeight, minw: minWidth } = this;
const parentLim = this.parentLimitation ? 0 : null;
if (this.aspectRatio) {
if (minWidth / minHeight > aspectFactor) {
minHeight = minWidth / aspectFactor;
} else {
minWidth = aspectFactor * minHeight;
}
}
const limits = {
left: { min: parentLim, max: left + (width - minWidth) },
right: { min: parentLim, max: right + (width - minWidth) },
top: { min: parentLim, max: top + (height - minHeight) },
bottom: { min: parentLim, max: bottom + (height - minHeight) },
};
if (this.aspectRatio) {
const aspectLimits = {
left: {
min: left - Math.min(top, bottom) * aspectFactor * 2,
max: left + ((height - minHeight) / 2) * aspectFactor * 2,
},
right: {
min: right - Math.min(top, bottom) * aspectFactor * 2,
max: right + ((height - minHeight) / 2) * aspectFactor * 2,
},
top: {
min: top - (Math.min(left, right) / aspectFactor) * 2,
max: top + ((width - minWidth) / 2 / aspectFactor) * 2,
},
bottom: {
min: bottom - (Math.min(left, right) / aspectFactor) * 2,
max: bottom + ((width - minWidth) / 2 / aspectFactor) * 2,
},
};
if (this.currentStick[0] === "m") {
limits.left = {
min: Math.max(limits.left.min, aspectLimits.left.min),
max: Math.min(limits.left.max, aspectLimits.left.max),
};
limits.right = {
min: Math.max(limits.right.min, aspectLimits.right.min),
max: Math.min(limits.right.max, aspectLimits.right.max),
};
} else if (this.currentStick[1] === "m") {
limits.top = {
min: Math.max(limits.top.min, aspectLimits.top.min),
max: Math.min(limits.top.max, aspectLimits.top.max),
};
limits.bottom = {
min: Math.max(limits.bottom.min, aspectLimits.bottom.min),
max: Math.min(limits.bottom.max, aspectLimits.bottom.max),
};
}
}
return limits;
},
sideCorrectionByLimit(limit, current) {
let value = current;
if (limit.min !== null && current < limit.min) {
value = limit.min;
} else if (limit.max !== null && limit.max < current) {
value = limit.max;
}
return value;
},
rectCorrectionByLimit(rect) {
const { limits } = this;
let { newRight, newLeft, newBottom, newTop } = rect;
newLeft = this.sideCorrectionByLimit(limits.left, newLeft);
newRight = this.sideCorrectionByLimit(limits.right, newRight);
newTop = this.sideCorrectionByLimit(limits.top, newTop);
newBottom = this.sideCorrectionByLimit(limits.bottom, newBottom);
return {
newLeft,
newRight,
newTop,
newBottom,
};
},
rectCorrectionByAspectRatio(rect) {
let { newLeft, newRight, newTop, newBottom } = rect;
const { parentWidth, parentHeight, currentStick, aspectFactor, dimensionsBeforeMove } = this;
let newWidth = parentWidth - newLeft - newRight;
let newHeight = parentHeight - newTop - newBottom;
if (currentStick[1] === "m") {
const deltaHeight = newHeight - dimensionsBeforeMove.height;
newLeft -= (deltaHeight * aspectFactor) / 2;
newRight -= (deltaHeight * aspectFactor) / 2;
} else if (currentStick[0] === "m") {
const deltaWidth = newWidth - dimensionsBeforeMove.width;
newTop -= deltaWidth / aspectFactor / 2;
newBottom -= deltaWidth / aspectFactor / 2;
} else if (newWidth / newHeight > aspectFactor) {
newWidth = aspectFactor * newHeight;
if (currentStick[1] === "l") {
newLeft = parentWidth - newRight - newWidth;
} else {
newRight = parentWidth - newLeft - newWidth;
}
} else {
newHeight = newWidth / aspectFactor;
if (currentStick[0] === "t") {
newTop = parentHeight - newBottom - newHeight;
} else {
newBottom = parentHeight - newTop - newHeight;
}
}
return { newLeft, newRight, newTop, newBottom };
},
},
computed: {
positionStyle() {
return {
top: this.top + "px",
left: this.left + "px",
zIndex: this.zIndex,
};
},
sizeStyle() {
return {
width: this.w == "auto" ? "auto" : this.width + "px",
height: this.h == "auto" ? "auto" : this.height + "px",
};
},
vdrStick() {
return (stick) => {
const stickStyle = {
width: `${this.stickSize / this.parentScaleX}px`,
height: `${this.stickSize / this.parentScaleY}px`,
};
stickStyle[styleMapping.y[stick[0]]] = `${this.stickSize / this.parentScaleX / -2}px`;
stickStyle[styleMapping.x[stick[1]]] = `${this.stickSize / this.parentScaleX / -2}px`;
return stickStyle;
};
},
width() {
return this.parentWidth - this.left - this.right;
},
height() {
return this.parentHeight - this.top - this.bottom;
},
rect() {
return {
left: Math.round(this.left),
top: Math.round(this.top),
width: Math.round(this.width),
height: Math.round(this.height),
};
},
},
watch: {
active(isActive) {
if (isActive) {
this.$emit("activated");
} else {
this.$emit("deactivated");
}
},
isActive: {
immediate: true,
handler(val) {
this.active = val;
},
},
z: {
immediate: true,
handler(val) {
if (val >= 0 || val === "auto") {
this.zIndex = val;
}
},
},
x: {
handler(newVal, oldVal) {
if (this.stickDrag || this.bodyDrag || newVal === this.left) {
return;
}
const delta = oldVal - newVal;
this.bodyDown({ pageX: this.left, pageY: this.top });
this.bodyMove({ x: delta, y: 0 });
this.$nextTick(() => {
this.bodyUp();
});
},
},
y: {
handler(newVal, oldVal) {
if (this.stickDrag || this.bodyDrag || newVal === this.top) {
return;
}
const delta = oldVal - newVal;
this.bodyDown({ pageX: this.left, pageY: this.top });
this.bodyMove({ x: 0, y: delta });
this.$nextTick(() => {
this.bodyUp();
});
},
},
w: {
handler(newVal, oldVal) {
if (this.stickDrag || this.bodyDrag || newVal === this.width) {
return;
}
const stick = "mr";
const delta = oldVal - newVal;
this.stickDown(stick, { pageX: this.right, pageY: this.top + this.height / 2 }, true);
this.stickMove({ x: delta, y: 0 });
this.$nextTick(() => {
this.stickUp();
});
},
},
h: {
handler(newVal, oldVal) {
if (this.stickDrag || this.bodyDrag || newVal === this.height) {
return;
}
const stick = "bm";
const delta = oldVal - newVal;
this.stickDown(stick, { pageX: this.left + this.width / 2, pageY: this.bottom }, true);
this.stickMove({ x: 0, y: delta });
this.$nextTick(() => {
this.stickUp();
});
},
},
parentW(val) {
this.right = val - this.width - this.left;
this.parentWidth = val;
},
parentH(val) {
this.bottom = val - this.height - this.top;
this.parentHeight = val;
},
},
};
</script>
<style>
.vdr {
position: absolute;
box-sizing: border-box;
}
.vdr.active:before {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
outline: 1px dashed #d6d6d6;
}
.vdr-stick {
box-sizing: border-box;
position: absolute;
font-size: 1px;
background: #ffffff;
border: 1px solid #6c6c6c;
box-shadow: 0 0 2px #bbb;
}
.inactive .vdr-stick {
display: none;
}
.vdr-stick-tl,
.vdr-stick-br {
cursor: nwse-resize;
}
.vdr-stick-tm,
.vdr-stick-bm {
left: 50%;
cursor: ns-resize;
}
.vdr-stick-tr,
.vdr-stick-bl {
cursor: nesw-resize;
}
.vdr-stick-ml,
.vdr-stick-mr {
top: 50%;
cursor: ew-resize;
}
.vdr-stick.not-resizable {
display: none;
}
.content-container {
display: block;
position: relative;
}
</style>

View File

@ -0,0 +1 @@
export const ContextKey = Symbol('ContextKey');

View File

@ -20,10 +20,20 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ContextKey } from './config';
import PanelHeader from "./panel-header/index.vue";
import PanelLeft from "./panel-left/index.vue"; import PanelLeft from "./panel-left/index.vue";
import PanelMain from "./panel-main/index.vue"; import PanelMain from "./panel-main/index.vue";
import PanelRight from "./panel-right/index.vue"; import PanelRight from "./panel-right/index.vue";
import PanelHeader from "./panel-header/index.vue";
const context = reactive({
current: {
}
})
provide(ContextKey, {})
</script> </script>
<style lang="less"> <style lang="less">
@ -55,5 +65,9 @@ import PanelHeader from "./panel-header/index.vue";
.arco-form-item-content-flex { .arco-form-item-content-flex {
display: block; display: block;
} }
.arco-divider-text-left {
left: 0;
padding-left: 0;
}
} }
</style> </style>

View File

@ -5,12 +5,29 @@ export interface Block<T = any> {
y: number; y: number;
w: number; w: number;
h: number; h: number;
xFixed: boolean;
yFixed: boolean;
bgImage?: string; bgImage?: string;
bgColor?: string; bgColor?: string;
data: T; data: T;
meta: Record<string, any>; 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 { export interface Container {
id: string; id: string;
type: string; type: string;

View File

@ -1,87 +1,6 @@
export interface Text { import Option from './option.vue'
/** import Render from './render.vue'
* export * from './interface'
*/
text: string;
/**
*
*/
family: string;
/**
* (px)
*/
size: number;
/**
* (16)
*/
color: string;
/**
*
*/
bold: boolean;
/**
*
*/
italic: boolean;
/**
* 线
*/
underline: boolean;
/**
*
*/
align: number;
}
export const DefaultText = { export { Option, Render }
text: "双击编辑文字",
family: "microsoft yahei",
size: 14,
color: "#000000",
bold: false,
italic: false,
underline: false,
align: 3,
}
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",
},
];

View File

@ -0,0 +1,87 @@
export interface Text {
/**
*
*/
text: string;
/**
*
*/
family: string;
/**
* (px)
*/
size: number;
/**
* (16)
*/
color: string;
/**
*
*/
bold: boolean;
/**
*
*/
italic: boolean;
/**
* 线
*/
underline: boolean;
/**
*
*/
align: number;
}
export const DefaultText: Text = {
text: "双击编辑文字",
family: "microsoft yahei",
size: 14,
color: "#000000",
bold: false,
italic: false,
underline: false,
align: 3,
}
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",
},
];

View File

@ -9,7 +9,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Text } from "./index.ts"; import { PropType } from "vue";
import { Text } from "./interface";
const props = defineProps({ const props = defineProps({
data: { data: {
@ -57,7 +58,7 @@ const style = computed(() => {
color, color,
textAlign, textAlign,
verticalAlign, verticalAlign,
}; } as any;
}); });
</script> </script>

View File

@ -1,10 +1,20 @@
<template> <template>
<div class="h-full flex items-center justify-between px-4"> <div class="h-full flex items-center justify-between px-4">
<div class="text-base">前端编辑器</div> <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>
</div>
<div class="flex gap-2"> <div class="flex gap-2">
<a-button> 导出 </a-button>
<a-button> 设置 </a-button> <a-button> 设置 </a-button>
<a-button type="primary"> 立即保存 </a-button> <a-dropdown-button type="primary">
<a-button>退出</a-button> 保存
<template #content>
<a-doption>保存为JSON</a-doption>
<a-doption>保存为图片</a-doption>
</template>
</a-dropdown-button>
<a-button status="danger">退出</a-button>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div class="h-full grid grid-cols-[auto_1fr]" :style="{ width: !collapsed ? '248px' : undefined }">
<div class="border-r border-slate-200"> <div class="h-full grid grid-rows-[1fr_auto] border-r border-slate-200">
<a-menu :collapsed="true"> <a-menu :collapsed="true">
<a-menu-item key="0_0"> <a-menu-item key="0_0">
<template #icon> <template #icon>
@ -27,11 +27,32 @@
Menu 4 Menu 4
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
<div class="w-full justify-center gap-1 grid text-center pb-4">
<a-tooltip content="帮助" position="right">
<a-button type="text">
<template #icon>
<i class="icon-park-outline-info text-lg text-gray-400 hover:text-gray-700"></i>
</template>
</a-button>
</a-tooltip>
<a-tooltip :content="collapsed ? '展开' : '折叠'" position="right">
<a-button type="text" @click="collapsed = !collapsed">
<template #icon>
<i
class="text-lg text-gray-400 hover:text-gray-700"
:class="collapsed ? 'icon-park-outline-expand-left' : 'icon-park-outline-expand-right'"
></i>
</template>
</a-button>
</a-tooltip>
</div>
</div> </div>
<div></div> <div></div>
</div> </div>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
const collapsed = ref(false);
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,6 +1,54 @@
<template> <template>
<div class="h-full grid grid-rows-[auto_1fr]"> <div class="h-full grid grid-rows-[auto_1fr]">
<div class="h-10 bg-white"></div> <div class="h-10 flex items-center justify-between gap-4 px-4 bg-white">
<div>
<span class="group">
<span class="text-gray-400">描述: </span>
国庆节上午9:00-10:00版式设计
<i
class="!hidden !group-hover:inline-block icon-park-outline-edit text-gray-400 hover:text-gray-700 ml-1"
></i>
</span>
</div>
<div class="flex items-center">
<span class="text-gray-400 text-xs mr-2">
尺寸
<span class="text-gray-700">1920 * 1080</span>
</span>
<span class="text-gray-400 text-xs mr-2">
比例
<span class="text-gray-700">100%</span>
</span>
<span class="text-gray-400 text-xs mr-2">
组件
<span class="text-gray-700">3 </span>
</span>
<a-tooltip content="预览" position="bottom">
<a-button type="text">
<template #icon>
<i class="icon-park-outline-play text-base !text-gray-600"></i>
</template>
</a-button>
</a-tooltip>
<a-popover position="br" trigger="click">
<a-button type="text">
<template #icon>
<i class="icon-park-outline-config text-base !text-gray-600"></i>
</template>
</a-button>
<template #content>
<span>
背景图片:
<a-link>选择</a-link>
</span>
<span class="inline-flex items-center">
背景颜色
<color-picker></color-picker>
</span>
</template>
</a-popover>
</div>
</div>
<div class="h-full p-5 px-6"> <div class="h-full p-5 px-6">
<div class="bg-white h-full relative"> <div class="bg-white h-full relative">
<drag-resizer <drag-resizer
@ -21,7 +69,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import DragResizer from "vue-drag-resize"; import ColorPicker from "../components/ColorPicker.vue";
import DragResizer from "../components/DragResizer.vue";
import TextRender from "../items/text/render.vue"; import TextRender from "../items/text/render.vue";
const item = ref({ const item = ref({
@ -36,7 +85,7 @@ const item = ref({
draggable: true, draggable: true,
}); });
const onItemDragOrResize = (rect) => { const onItemDragOrResize = (rect: any) => {
item.value.x = rect.left; item.value.x = rect.left;
item.value.y = rect.top; item.value.y = rect.top;
item.value.w = rect.width; item.value.w = rect.width;
@ -44,13 +93,13 @@ const onItemDragOrResize = (rect) => {
}; };
const textCh = { const textCh = {
text: "文本", text: "进站前请准备好车票或乘车二维码。进站前请主动配合车站工作人员,接受安检,准备好自己的车票或乘车二维码。车站检票闸机会扫描并验证车票或乘车二维码,验证通过后方可进站。",
family: "simsun", family: "",
bold: false, bold: false,
italic: false, italic: false,
underline: false, underline: false,
size: 14, size: 14,
color: "#33cc99", color: "#ffffff",
align: 3, align: 3,
}; };
</script> </script>

View File

@ -61,11 +61,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Block } from "../interface.ts"; import { PropType } from "vue";
defineProps({ defineProps({
block: { block: {
type: Object as PropType<Block>, type: Object as PropType<any>,
required: true, required: true,
}, },
}); });

View File

@ -6,9 +6,11 @@
</a-radio-group> </a-radio-group>
<a-form :model="block" layout="vertical"> <a-form :model="block" layout="vertical">
<div class="muti-form-item mt-2"> <div class="muti-form-item mt-2">
<a-divider orientation="left">基本设置</a-divider>
<block-attr :block="item"></block-attr> <block-attr :block="item"></block-attr>
</div> </div>
<div class="muti-form-item"> <div class="muti-form-item">
<a-divider orientation="left">中文设置</a-divider>
<text-attr :block="block.textStyle"></text-attr> <text-attr :block="block.textStyle"></text-attr>
</div> </div>
</a-form> </a-form>
@ -19,7 +21,7 @@
import BlockAttr from "./block-attr.vue"; import BlockAttr from "./block-attr.vue";
import TextAttr from "./text-attr.vue"; import TextAttr from "./text-attr.vue";
const item = ref({ const item = ref<any>({
x: 0, x: 0,
y: 0, y: 0,
w: 200, w: 200,

View File

@ -1,5 +1,9 @@
<template> <template>
<div> <div>
<a-form-item label="内容">
<a-textarea v-model="block.text" placeholder="输入内容..."></a-textarea>
</a-form-item>
<a-form-item label="颜色"> <a-form-item label="颜色">
<a-input v-model="block.color"> <a-input v-model="block.color">
<template #prefix> <template #prefix>
@ -50,12 +54,16 @@
<a-select v-model="block.align" :options="TextAlignOptions"></a-select> <a-select v-model="block.align" :options="TextAlignOptions"></a-select>
</a-form-item> </a-form-item>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { TextAlignOptions, TextFamilyOptions } from "../interface.ts"; import { PropType } from "vue";
import ColorPicker from "../components/ColorPicker.vue"; import ColorPicker from "../components/ColorPicker.vue";
import { TextAlignOptions, TextFamilyOptions } from "../interface";
defineProps({ defineProps({
block: { block: {
type: Object as PropType<any>, type: Object as PropType<any>,

View File

@ -0,0 +1,58 @@
<template>
<a-modal v-model:visible="modal.visible" title="上传文件" title-align="start" :footer="false">
<a-upload :custom-request="upload" draggable action="/api/v1/upload"></a-upload>
</a-modal>
</template>
<script setup lang="ts">
import { api } from "@/api";
import { RequestOption } from "@arco-design/web-vue";
import axios from "axios";
import { reactive } from "vue";
const modal = reactive({
visible: false,
});
const upload = (option: RequestOption) => {
const { fileItem, onError, onProgress, onSuccess } = option;
const source = axios.CancelToken.source();
if (fileItem.file) {
api.upload
.addFile(
{
file: fileItem.file,
},
{
onUploadProgress(e) {
let percent = 0;
if (e.total && e.total > 0) {
percent = e.loaded / e.total;
}
onProgress(percent, e as any);
},
cancelToken: source.token,
}
)
.then((res) => {
onSuccess(res);
})
.catch((e) => {
onError(e);
});
}
return {
abort() {
source.cancel();
},
};
};
defineExpose({
open: () => {
modal.visible = true;
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,4 @@
export const useModal = (modal: any) => {
}

View File

@ -7,62 +7,43 @@ export {}
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
AAlert: typeof import('@arco-design/web-vue')['Alert']
AAvatar: typeof import('@arco-design/web-vue')['Avatar'] 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'] AButton: typeof import('@arco-design/web-vue')['Button']
ACard: typeof import('@arco-design/web-vue')['Card']
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
ACheckboxGroup: typeof import('@arco-design/web-vue')['CheckboxGroup']
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
ADatePicker: typeof import('@arco-design/web-vue')['DatePicker']
ADivider: typeof import('@arco-design/web-vue')['Divider'] ADivider: typeof import('@arco-design/web-vue')['Divider']
ADoption: typeof import('@arco-design/web-vue')['Doption'] ADoption: typeof import('@arco-design/web-vue')['Doption']
ADrawer: typeof import('@arco-design/web-vue')['Drawer'] ADrawer: typeof import('@arco-design/web-vue')['Drawer']
ADropdown: typeof import('@arco-design/web-vue')['Dropdown'] ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
ADropdownButton: typeof import('@arco-design/web-vue')['DropdownButton']
AForm: typeof import('@arco-design/web-vue')['Form'] AForm: typeof import('@arco-design/web-vue')['Form']
AFormItem: typeof import('@arco-design/web-vue')['FormItem'] AFormItem: typeof import('@arco-design/web-vue')['FormItem']
AImage: typeof import('@arco-design/web-vue')['Image']
AInput: typeof import('@arco-design/web-vue')['Input'] AInput: typeof import('@arco-design/web-vue')['Input']
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber'] AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch'] AInputSearch: typeof import('@arco-design/web-vue')['InputSearch']
ALayout: typeof import('@arco-design/web-vue')['Layout'] ALayout: typeof import('@arco-design/web-vue')['Layout']
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent'] ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader'] ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider'] ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
ALink: typeof import('@arco-design/web-vue')['Link'] ALink: typeof import('@arco-design/web-vue')['Link']
AList: typeof import('@arco-design/web-vue')['List']
AListItem: typeof import('@arco-design/web-vue')['ListItem']
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta']
AMenu: typeof import('@arco-design/web-vue')['Menu'] AMenu: typeof import('@arco-design/web-vue')['Menu']
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem'] AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
AMenuItemGroup: typeof import('@arco-design/web-vue')['MenuItemGroup'] AMenuItemGroup: typeof import('@arco-design/web-vue')['MenuItemGroup']
AModal: typeof import('@arco-design/web-vue')['Modal'] 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'] ARadio: typeof import('@arco-design/web-vue')['Radio']
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup'] ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup']
AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar'] AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar']
ASelect: typeof import('@arco-design/web-vue')['Select'] ASelect: typeof import('@arco-design/web-vue')['Select']
ASlider: typeof import('@arco-design/web-vue')['Slider']
ASpace: typeof import('@arco-design/web-vue')['Space']
ASpin: typeof import('@arco-design/web-vue')['Spin'] ASpin: typeof import('@arco-design/web-vue')['Spin']
ASwitch: typeof import('@arco-design/web-vue')['Switch']
ATabPane: typeof import('@arco-design/web-vue')['TabPane']
ATabs: typeof import('@arco-design/web-vue')['Tabs']
ATag: typeof import('@arco-design/web-vue')['Tag'] ATag: typeof import('@arco-design/web-vue')['Tag']
ATextarea: typeof import('@arco-design/web-vue')['Textarea'] ATextarea: typeof import('@arco-design/web-vue')['Textarea']
ATooltip: typeof import('@arco-design/web-vue')['Tooltip'] ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
ATree: typeof import('@arco-design/web-vue')['Tree']
AUpload: typeof import('@arco-design/web-vue')['Upload']
BlockAttr: typeof import('./../components/editor/panel-right/block-attr.vue')['default'] BlockAttr: typeof import('./../components/editor/panel-right/block-attr.vue')['default']
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default'] BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']
BreadPage: typeof import('./../components/breadcrumb/bread-page.vue')['default'] BreadPage: typeof import('./../components/breadcrumb/bread-page.vue')['default']
ColorPicker: typeof import('./../components/editor/components/ColorPicker.vue')['default'] ColorPicker: typeof import('./../components/editor/components/ColorPicker.vue')['default']
DragResizer: typeof import('./../components/editor/components/DragResizer.vue')['default']
Editor: typeof import('./../components/editor/index.vue')['default'] Editor: typeof import('./../components/editor/index.vue')['default']
Option: typeof import('./../components/editor/items/text/option.vue')['default'] Option: typeof import('./../components/editor/items/text/option.vue')['default']
Options: typeof import('./../components/editor/items/text/options.vue')['default']
Page403: typeof import('./../components/error/page-403.vue')['default'] Page403: typeof import('./../components/error/page-403.vue')['default']
PanelHeader: typeof import('./../components/editor/panel-header/index.vue')['default'] PanelHeader: typeof import('./../components/editor/panel-header/index.vue')['default']
PanelLeft: typeof import('./../components/editor/panel-left/index.vue')['default'] PanelLeft: typeof import('./../components/editor/panel-left/index.vue')['default']