插件
Code Inspector
页面开发提效的神器,点击页面上的 DOM,Code Inspector能够自动打开你的 IDE 并将光标定位到 DOM 对应的源代码位置
- 安装
npm install code-inspector-plugin -D
- 使用
// webpack.config.js
const { codeInspectorPlugin } = require("code-inspector-plugin");
module.exports = () => ({
plugins: [
codeInspectorPlugin({
bundler: "webpack",
}),
],
});
// vite.config.js
import { defineConfig } from "vite";
import { codeInspectorPlugin } from "code-inspector-plugin";
export default defineConfig({
plugins: [
codeInspectorPlugin({
bundler: "vite",
}),
],
});
vue-draggable-plus
官网:https://vue-draggable-plus.pages.dev/en/
Options 继承自 Sortablejs 所有的配置项,具体请查看 Sortablejs 官方文档 https://github.com/SortableJS/Sortable?tab=readme-ov-file#options
注意
拖拽排序模块,支持 Vue>=v3 或 Vue >=2.7
使用中遇到的问题
- 已存在指定元素,提示用户元素已存在,并且拦截拖拽,要使用
onMove事件进行拦截看SortableJS文档 看SortableJS文档 看SortableJS文档
<template>
<VueDraggable
class="drag"
v-model="group.components"
:sort="false"
:group="{ name: 'form', pull: 'clone', put: false }"
@start="isStart = true"
@end="isStart = false"
:clone="clone"
:onMove="onLeftMove"
>
<div :data-type="cp.name" class="inner" v-for="(cp, id) in group.components" :key="id">
<i :class="cp.icon"></i>
<span>{{ cp.title }}</span>
</div>
</VueDraggable>
</template>
<script>
import VueDraggable from "vue-draggable-plus";
import { debounce } from "@/utils";
export default {
components: { VueDraggable },
methods: {
onLeftMove: debounce(
function (event) {
if (event.data.name === "SerialNumber") {
if (!this._formConfig.hasSerialNumber) {
return true;
} else {
this.$message.error("流水号组件只能添加一次!");
return false;
}
}
return true;
},
300,
true
),
},
};
</script>
- 禁止从分组 1 中的某个元素拖拽到分组 2 中:例如,组件库中存在分栏布局组件、明细表组件, 其中
分栏布局组件不能拖拽到明细表组件中,同时也不能拖拽到自己中;明细表组件不能拖拽到分栏布局组件中,同时也不能拖拽到自己中。
解决方案
给
组件库添加:data-type="cp.name"、分别配置分栏布局组件、明细表组件的 :group="{ name: 'form', pull: true, put: put }"在
put方法中进行校验
分栏布局组件
<template>
<el-row :gutter="Number(gutter)">
<el-col :span="24 / number" v-for="(cols, ri) in _items" :key="ri + '_row'">
<VueDraggable
class="l-drag-from"
v-model="_items[ri]"
:group="{ name: 'form', pull: true, put: put }"
:animation="150"
@start="draggableStart"
@end="draggableEnd"
@add="handleAdd"
>
<el-form-item
v-for="(cp, id) in cols"
:key="id"
:label="cp.title"
:required="cp.props.required"
class="l-form-item"
@click.native.stop="selectItem(cp)"
:style="getSelectedClass(cp)"
:class="{ 'w-form-cp-nlb1': cp.props.hideLabel }"
:data-type="cp.name"
:show-message="false"
>
<span class="l-option" v-if="active && active.id === cp.id">
<i class="el-icon-delete" @click="delItem(ri, id)"></i>
</span>
<form-design-render :config="cp" :active.sync="_active" class="form-design" :globalConfig="globalConfig" />
</el-form-item>
</VueDraggable>
</el-col>
</el-row>
</template>
<script>
export default {
methods: {
put(to, from, dragEl, event) {
const type = dragEl.dataset.type;
switch (type) {
case "SpanLayout":
return false;
default:
return true;
}
},
},
};
</script>
明细表组件
<template>
<VueDraggable
class="l-drag-from"
v-model="config.columns"
:group="{ name: 'form', pull: true, put: put }"
filter=".w-tb-op"
target=".vxe-header--row"
:animation="150"
:key="key"
@start="draggableStart"
@end="draggableEnd"
@add="handleAdd"
ghostClass="w-f-cp-select"
>
<vxe-table
:data="tableData"
:border="showBorder"
ref="tableRef"
@header-cell-click="ck"
:header-cell-class-name="getHeaderClass"
header-row-class-name="vxe-Draggable"
empty-text="👆拖拽字段到上方列内"
align="center"
>
<vxe-column v-for="col in config.columns" :field="col.id" :title="col.title" :key="col.id" min-width="100">
<template #header>
<div>
<!-- <tooltip :message="col.title"></tooltip> -->
{{ col.title }}
<span style="color: #f56c6c" v-if="col.props.required">*</span>
</div>
<div class="l-option" v-if="_active && _active.id === col.id">
<i class="el-icon-delete" @click.stop="delItem(col.id)"></i>
</div>
</template>
</vxe-column>
<vxe-column title="操作" fixed="right" class="w-tb-op" width="120"></vxe-column>
</vxe-table>
</VueDraggable>
</template>
<script>
export default {
methods: {
put(to, from, dragEl, event) {
const type = dragEl.dataset.type;
switch (type) {
case "TableList":
case "SpanLayout":
case "SerialNumber":
case "Description":
case "SegmentationLabel":
return false;
default:
return true;
}
},
},
};
</script>
效果图:


组件库
<template>
<VueDraggable
class="drag"
v-model="group.components"
:sort="false"
:group="{ name: 'form', pull: 'clone', put: false }"
@start="isStart = true"
@end="isStart = false"
:clone="clone"
:onMove="onLeftMove"
>
<div :data-type="cp.name" class="inner" v-for="(cp, id) in group.components" :key="id">
<i :class="cp.icon"></i>
<span>{{ cp.title }}</span>
</div>
</VueDraggable>
</template>
<script>
import VueDraggable from "vue-draggable-plus";
import { debounce } from "@/utils";
export default {
components: { VueDraggable },
methods: {
onLeftMove: debounce(
function (event) {
if (event.data.name === "SerialNumber") {
if (!this._formConfig.hasSerialNumber) {
return true;
} else {
this.$message.error("流水号组件只能添加一次!");
return false;
}
}
return true;
},
300,
true
),
},
};
</script>
组件容器
<template>
<el-main class="layout-main">
<div class="tool-nav">
<div>
<el-tooltip class="item" effect="dark" content="预览表单" placement="top">
<i class="el-icon-view" @click="viewForms"></i>
</el-tooltip>
</div>
</div>
<div class="work-form">
<div class="tip" v-if="_value.length === 0 && !isStart">👈 请在左侧选择控件并拖至此处</div>
<el-form
v-else
:label-width="_formConfig.labelWidth + 'px'"
:label-position="_formConfig.labelPosition"
:size="_formConfig.size"
@submit.native.prevent
>
<VueDraggable
class="drag-from"
v-model="_value"
group="form"
:animation="150"
@start="draggableStart"
@end="drag = false"
@add="handleAdd"
>
<el-form-item
v-for="(cp, id) in _value"
:key="id"
:label="cp.title"
:required="cp.props.required"
class="form-item"
@click.native="selectItem(cp)"
:style="getSelectedClass(cp)"
:class="{ 'w-form-cp-nlb': cp.props.hideLabel }"
:data-type="cp.name"
>
<span class="option" v-if="active && active.id === cp.id">
<i class="el-icon-delete" @click="del(id)"></i>
</span>
<form-design-render
class="form-design"
:config="cp"
:value.sync="cp.value"
:active.sync="active"
:globalConfig="_formConfig"
/>
</el-form-item>
</VueDraggable>
</el-form>
</div>
</el-main>
</template>
spanLayout 分栏布局组件
<template>
<div class="rtc-span-layout">
<div v-if="mode === 'DESIGN'">
<el-row :gutter="Number(gutter)">
<el-col :span="24 / number" v-for="(cols, ri) in _items" :key="ri + '_row'">
<VueDraggable
class="l-drag-from"
v-model="_items[ri]"
:group="{ name: 'form', pull: true, put: put }"
:animation="150"
@start="draggableStart"
@end="draggableEnd"
@add="handleAdd"
>
<el-form-item
v-for="(cp, id) in cols"
:key="id"
:label="cp.title"
:required="cp.props.required"
class="l-form-item"
@click.native.stop="selectItem(cp)"
:style="getSelectedClass(cp)"
:class="{ 'w-form-cp-nlb1': cp.props.hideLabel }"
:data-type="cp.name"
:show-message="false"
>
<span class="l-option" v-if="active && active.id === cp.id">
<i class="el-icon-delete" @click="delItem(ri, id)"></i>
</span>
<form-design-render
:config="cp"
:active.sync="_active"
class="form-design"
:globalConfig="globalConfig"
/>
</el-form-item>
</VueDraggable>
</el-col>
</el-row>
</div>
<div v-else class="bottom">
<el-row :gutter="Number(gutter)">
<el-col :span="24 / number" v-for="(cols, ri) in _items" :key="ri + '_row'">
<template v-for="(item, id) in cols">
<el-form-item
:prop="item.id"
:label="item.title"
v-if="getIsShow(item.id)"
:required="item.props.required"
:key="item.name + id"
:class="{ 'w-form-cp-nlb1': item.props.hideLabel }"
:show-message="false"
>
<form-design-render
:value.sync="item.value"
:mode="mode"
:config="item"
:PermissionList="PermissionList"
:globalConfig="globalConfig"
/>
</el-form-item>
<!-- <form-design-render
:key="item.name + id"
v-else
v-model="_value"
:mode="mode"
:config="item"
/> -->
</template>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import { VueDraggable } from "vue-draggable-plus";
import FormDesignRender from "@/views/userCenter/formDesign/admin/layout/form/FormDesignRender.vue";
import componentMinxins from "../ComponentMinxins";
import { mapMutations, mapState } from "vuex";
export default {
mixins: [componentMinxins],
name: "SpanLayout",
components: { VueDraggable, FormDesignRender },
props: {
value: {
default: null,
},
items: {
type: Array,
default: () => {
return [];
},
},
number: {
type: Number,
default: 2,
},
gutter: {
type: Number | String,
default: 5,
},
globalConfig: {
type: Object,
default: () => ({}),
},
},
computed: {
_items: {
get() {
return this.items;
},
set(val) {
this.items = val;
},
},
isRouteName() {
return this.$route.name == "statisticsFormDetails";
},
...mapState("formDesign", ["selectFormItem", "nodeMap"]),
},
watch: {
number: {
handler() {
this.reloadSpan();
},
},
},
methods: {
...mapMutations("formDesign", ["setSelectFormItem"]),
getIsShow(id) {
if (this.isRouteName) {
if (this.PermissionList.length) {
let obj = this.PermissionList.find((item) => item.Uid == id);
if (obj) {
return obj.Status != 3;
} else {
return true;
}
} else {
return true;
}
} else {
return true;
}
},
reloadSpan() {
//初始化分栏数据值
if (this.number > this.items.length) {
for (let i = 0; i < this.number; i++) {
if (!this.items[i]) {
this._items.push([]);
}
}
} else {
this._items.length = this.number;
}
},
selectItem(cp) {
this._active = cp;
},
getSelectedClass(cp) {
return this._active && this._active.id === cp.id ? "border: 1px dashed #1fa3b0" : "";
},
draggableStart() {
this._active = {};
},
draggableEnd() {},
put(to, from, dragEl, event) {
const type = dragEl.dataset.type;
switch (type) {
case "SpanLayout":
return false;
default:
return true;
}
},
async handleAdd(e) {
console.log(99, e);
if (e.from._prevClass == "vxe-header--row vxe-Draggable") {
e.clonedData.id = this.getId();
}
if (e.clonedData.name == "SpanLayout") {
// this._items.forEach(item => {
// let index = item.findIndex(item1 => item1.name == "SpanLayout");
// index > -1 && item.splice(index, 1);
// });
// this.$message.error("分栏布局组件不能添加分栏布局!");
} else if (e.clonedData.name == "SerialNumber") {
if (e.data.name === "SerialNumber" && !this.globalConfig.hasSerialNumber) {
this.globalConfig.hasSerialNumber = true;
}
}
},
getId() {
return (
"field" +
(Math.floor(Math.random() * (99999 - 10000)) + 10000).toString() +
new Date().getTime().toString().substring(5)
);
},
delItem(ri, index) {
this.$confirm("删除组件将会连带删除包含该组件的条件以及相关设置,是否继续?", "提示", {
confirmButtonText: "确 定",
cancelButtonText: "取 消",
type: "error",
})
.then(() => {
if (this._items[ri][index].name === "SpanLayout") {
//删除的是分栏则遍历删除分栏内所有子组件
this._items[ri][index].props.items.forEach((item) => {
this.removeFormItemAbout(item);
});
this._items[ri][index].props.items.length = 0;
} else {
if (this._items[ri][index].name === "SerialNumber") {
this.globalConfig.hasSerialNumber = false;
}
this.removeFormItemAbout(this._items[ri][index]);
}
this._items[ri].splice(index, 1);
// this.setSelectFormItem(null);
this._active = {};
})
.catch(() => {});
},
async removeFormItemAbout(item) {
this.nodeMap.forEach((node) => {
//搜寻条件,进行移除
if (node.type === "CONDITION") {
node.props.groups.forEach((group) => {
let i = group.cids.remove(item.id);
if (i > -1) {
//从子条件移除
group.conditions.splice(i, 1);
}
});
}
//搜寻权限,进行移除
if (node.type === "ROOT" || node.type === "APPROVAL" || node.type === "CC") {
node.props.formPerms.removeByKey("id", item.id);
}
});
},
},
};
</script>
<style lang="scss" scoped>
/*去除表单项的label名称*/
>>> .w-form-cp-nlb1 {
.el-form-item__label {
display: none;
}
.el-form-item__content {
margin-left: 0 !important;
}
}
>>> .el-row {
width: 100%;
.el-col {
margin-bottom: 0;
}
}
.choose {
border: 1px dashed #1fa3b0 !important;
}
.l-drag-from {
min-height: 50px;
background-color: rgb(245, 246, 246);
.l-form-item {
cursor: grab;
// background: #ffffff;
border: 1px solid transparent;
margin-bottom: 5px;
&:hover {
border: 1px dashed #1fa3b0;
}
}
}
.l-option {
position: absolute;
bottom: 0;
right: 0;
z-index: 10;
background: #1fa3b0;
border-radius: 2px;
height: 23px;
line-height: 23px;
i {
font-size: small;
cursor: pointer;
color: #fff;
padding: 5px;
}
&:hover {
background: #62bec7;
}
}
.bottom {
.el-form-item {
margin-bottom: 22px !important;
}
}
>>> .el-rate {
height: 100% !important;
}
</style>
tableList 明细表组件
<template>
<div>
<div v-if="mode === 'DESIGN'">
<VueDraggable
class="l-drag-from"
v-model="config.columns"
:group="{ name: 'form', pull: true, put: put }"
filter=".w-tb-op"
target=".vxe-header--row"
:animation="150"
:key="key"
@start="draggableStart"
@end="draggableEnd"
@add="handleAdd"
ghostClass="w-f-cp-select"
>
<vxe-table
:data="tableData"
:border="showBorder"
ref="tableRef"
@header-cell-click="ck"
:header-cell-class-name="getHeaderClass"
header-row-class-name="vxe-Draggable"
empty-text="👆拖拽字段到上方列内"
align="center"
>
<vxe-column v-for="col in config.columns" :field="col.id" :title="col.title" :key="col.id" min-width="100">
<template #header>
<div>
<!-- <tooltip :message="col.title"></tooltip> -->
{{ col.title }}
<span style="color: #f56c6c" v-if="col.props.required">*</span>
</div>
<div class="l-option" v-if="_active && _active.id === col.id">
<i class="el-icon-delete" @click.stop="delItem(col.id)"></i>
</div>
</template>
</vxe-column>
<vxe-column title="操作" fixed="right" class="w-tb-op" width="120"></vxe-column>
</vxe-table>
</VueDraggable>
</div>
<div v-else-if="mode === 'print'" class="print1">
<table border="0">
<tr>
<template v-for="(item, index) in tableProps">
<th :key="index">
{{ item.title }}
</th>
</template>
</tr>
<tr v-for="(item, index) in _value" :key="index">
<td v-for="(item1, index1) in tableProps" :key="index1">
<form-design-render
:value.sync="item[item1.id]"
:PermissionList="PermissionList"
:mode="mode"
:config="item1"
:isTable="true"
/>
</td>
</tr>
</table>
</div>
<div v-else>
<vxe-table
:data="_value"
:border="showBorder"
:header-cell-class-name="getHeaderClass1"
align="center"
:column-config="{ resizable: true }"
>
<template v-for="col in tableProps">
<vxe-column :field="col.id" v-if="getIsShow(col.id)" :title="col.title" :key="col.id" min-width="100">
<template #header>
<div>
{{ col.title }}
<span style="color: #f56c6c" v-if="col.props.required">*</span>
</div>
</template>
<template #default="{ row, column: currentColumn }">
<form-design-render
:class="{
'valid-error': showError(col, row[col.id]),
}"
:value.sync="row[currentColumn.field]"
:PermissionList="PermissionList"
:mode="mode"
:config="col"
:isTable="true"
/>
</template>
</vxe-column>
</template>
<vxe-column title="操作" fixed="right" width="120" v-if="isShow != 1 && Status != 'approval'">
<template #header>
<span>操作</span>
<el-button
icon="el-icon-plus"
@click="addRow"
size="small"
circle
:globalConfig="globalConfig"
class="w-tb-op-add"
/>
</template>
<template #default="{ row }">
<el-button size="mini" type="text" @click="delRow(row)">删除</el-button>
</template>
</vxe-column>
</vxe-table>
</div>
</div>
</template>
<script>
import { VueDraggable } from "vue-draggable-plus";
import { ValueType } from "../ComponentsConfigExport";
import FormDesignRender from "@/views/userCenter/formDesign/admin/layout/form/FormDesignRender";
import componentMinxins from "../ComponentMinxins";
import { deepClone } from "@/utils";
export default {
mixins: [componentMinxins],
name: "TableList",
components: { VueDraggable, FormDesignRender },
props: {
value: {
type: Array,
default: () => {
return [];
},
},
placeholder: {
type: String,
default: "添加数据",
},
showBorder: {
type: Boolean,
default: true,
},
maxSize: {
type: Number,
default: 0,
},
rowLayout: {
type: Boolean,
default: true,
},
config: {
type: Object,
default: () => {},
},
globalConfig: {
type: Object,
default: () => ({}),
},
},
watch: {
"config.columns": {
handler() {
this.key++;
},
deep: true,
},
},
created() {
if (!Array.isArray(this.value)) {
this._value = [];
}
},
data() {
return {
select: null,
drag: false,
ValueType,
tbCellStyle: {
padding: "10px 0",
},
key: 0,
tableData: [],
};
},
computed: {
isRouteName() {
return this.$route.name == "statisticsFormDetails";
},
tableProps() {
return this.config.columns.filter((item) => {
if (this.isRouteName) {
if (this.PermissionList.length) {
let obj = this.PermissionList.find((item1) => item1.Uid == item.id);
if (obj) {
return obj.Status != 3;
} else {
return true;
}
} else {
return true;
}
} else {
return true;
}
});
},
},
methods: {
getIsShow(id) {
if (this.isRouteName) {
if (this.PermissionList.length) {
let obj = this.PermissionList.find((item) => item.Uid == id);
if (obj) {
return obj.Status != 3;
} else {
return true;
}
} else {
return true;
}
} else {
return true;
}
},
showError(col, val) {
if (col.props.required) {
switch (col.valueType) {
case ValueType.dept:
case ValueType.user:
case ValueType.dateRange:
case ValueType.array:
return !(Array.isArray(val) && val.length > 0);
default:
return !val;
}
}
return false;
},
put(to, from, dragEl, event) {
const type = dragEl.dataset.type;
switch (type) {
case "TableList":
case "SpanLayout":
case "SerialNumber":
case "Description":
case "SegmentationLabel":
return false;
default:
return true;
}
},
handleAdd(e) {
console.log(e);
e.clonedData.id = this.getId();
// if (e.clonedData.name == "SerialNumber") {
// if (
// e.from._prevClass == "drag-from" ||
// e.from._prevClass == "l-drag-from"
// ) {
// this.globalConfig.hasSerialNumber = false;
// }
// this.config.columns.splice(e.newIndex, 1);
// this.$message.error("明细表组件不能添加流水号!");
// } else if (e.clonedData.name == "SpanLayout") {
// this.config.columns.splice(e.newIndex, 1);
// this.$message.error("明细表组件不能添加分栏组件!");
// } else if (e.clonedData.name == "TableList") {
// this.config.columns.splice(e.newIndex, 1);
// this.$message.error("明细表组件不能添加明细表!");
// }
},
getId() {
return (
"field" +
(Math.floor(Math.random() * (99999 - 10000)) + 10000).toString() +
new Date().getTime().toString().substring(5)
);
},
copyData(i, row) {
this._value.push(deepClone(row));
},
delRow(row) {
let index = this._value.findIndex((item) => item._X_ROW_KEY == row._X_ROW_KEY);
this._value.splice(index, 1);
},
addRow() {
let row = {};
this.config.columns.forEach((col) => {
this.$set(row, col.id, deepClone(col.value));
});
this._value.push(row);
this.$set(this, "_value", this._value);
},
delItem(id) {
this.$confirm("删除组件将会连带删除包含该组件的条件以及相关设置,是否继续?", "提示", {
confirmButtonText: "确 定",
cancelButtonText: "取 消",
type: "error",
})
.then(() => {
let index = this.config.columns.findIndex((item) => item.id == id);
this.config.columns.splice(index, 1);
this._active = {};
})
.catch(() => {});
},
selectItem(cp) {
this._active = cp;
},
ck({ column, $event }) {
$event.stopPropagation();
if (column.property) {
this._active = this.config.columns.find((item) => item.id == column.property);
}
},
getHeaderClass({ column }) {
if (!column.property) {
return "w-tb-op"; // 返回 class 名称
} else if (column.property === this._active.id) {
return "w-form-cp-active"; // 返回 class 名称
}
return "";
},
getHeaderClass1({ column }) {
if (!column.property) {
return "w-tb-op"; // 返回 class 名称
}
return "";
},
draggableStart() {
this._active = {};
},
draggableEnd(evt) {
this.$nextTick(() => {
this.key++;
this.$refs.tableRef.reloadData([]);
this.$refs.tableRef.refreshColumn();
});
// setTimeout(() => {
// this.key++
// }, 300)
// const tableRef = this.$refs.tableRef
// tableRef.reloadColumn(this.config.columns)
// console.log(this.config.columns)
// const newColumns = [...this.config.columns];
// const [moved] = newColumns.splice(evt.oldIndex, 1);
// newColumns.splice(evt.newIndex, 0, moved);
// this.config.columns = newColumns; // 必须赋值新数组触发响应式
},
getSelectedClass(cp) {
return this._active && this._active.id === cp.id ? "border-left: 4px solid #f56c6c" : "";
},
validate(call) {
if (this.rowLayout) {
let result = true;
for (let i = 0; i < this.columns.length; i++) {
if (this.columns[i].props.required) {
for (let j = 0; j < this._value.length; j++) {
result = !this.showError(this.columns[i], this._value[j][this.columns[i].id]);
if (!result) {
call(false);
return;
}
}
}
}
call(result);
} else {
let success = 0;
this._value.forEach((v, i) => {
let formRef = this.$refs[`table-form-${i}`];
if (formRef && Array.isArray(formRef) && formRef.length > 0) {
formRef[0].validate((valid) => {
if (valid) {
success++;
}
});
}
});
if (success === this._value.length) {
call(true);
} else {
call(false);
}
}
},
},
};
</script>
<style lang="scss" scoped>
>>> .valid-error {
.el-input__inner {
border-color: #f56c6c;
}
}
.choose {
border: 1px dashed #1fa3b0 !important;
}
.l-option {
position: absolute;
bottom: 0;
right: 0;
z-index: 10;
background: #1fa3b0;
border-radius: 2px;
i {
font-size: small;
cursor: pointer;
color: #fff;
padding: 5px;
}
&:hover {
background: #62bec7;
}
}
>>> .w-form-cp-active {
border: 1px dashed #1fa3b0;
.cell {
overflow: visible;
}
}
>>> .w-form-d-item {
position: relative;
padding: 5px;
margin: 2px;
border: 1px dashed white;
&:hover {
border: 1px dashed #1fa3b0;
}
}
>>> .vxe-cell {
white-space: normal;
}
.w-tb-op {
position: relative;
.w-tb-op-add {
position: absolute;
top: 5px;
right: 5px;
}
}
.w-f-cp-select {
border-radius: 2px;
border: 1px dashed #1fa3b0 !important;
padding: 15px 0;
width: 100px;
}
>>> .el-rate {
height: 100% !important;
}
.print1 {
table {
font-size: 16px;
width: 100%;
border-collapse: collapse;
padding: 2px;
tr {
vertical-align: middle;
th {
text-align: left;
border: 1px solid #464648;
padding: 5px 10px;
width: 20%;
}
td {
text-align: left;
border: 1px solid #464648;
padding: 5px 10px;
}
}
}
}
</style>
canvas-editor
基于 canvas/svg 的富文本编辑器
