Compare commits
2 Commits
66e9cf9512
...
836b33ad64
| Author | SHA1 | Date |
|---|---|---|
|
|
836b33ad64 | |
|
|
33d6c81a55 |
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/locales",
|
||||
"src/locales/lang",
|
||||
"public/resource/tinymce/langs"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -81,11 +81,15 @@
|
|||
"@vueuse/core": "^10.7.1",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"ant-design-vue": "^4.0.8",
|
||||
"bpmn-js": "^17.0.2",
|
||||
"bpmn-js-properties-panel": "^5.13.0",
|
||||
"bpmn-js-token-simulation": "^0.33.1",
|
||||
"axios": "^1.6.4",
|
||||
"codemirror": "^5.65.16",
|
||||
"cropperjs": "^1.6.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"diagram-js": "^14.1.0",
|
||||
"driver.js": "^1.3.1",
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "^2.6.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,229 @@
|
|||
const customDrawStyles = [
|
||||
{
|
||||
id: 'gl-draw-polygon-fill-inactive',
|
||||
type: 'fill',
|
||||
filter: [
|
||||
'all',
|
||||
['==', 'active', 'false'],
|
||||
['==', '$type', 'Polygon'],
|
||||
['!=', 'mode', 'static'],
|
||||
],
|
||||
paint: {
|
||||
'fill-color': '#3bb2d0',
|
||||
'fill-outline-color': '#6495ed',
|
||||
'fill-opacity': 0.5,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-fill-active',
|
||||
type: 'fill',
|
||||
filter: ['all', ['==', 'active', 'true'], ['==', '$type', 'Polygon']],
|
||||
paint: {
|
||||
'fill-color': '#fbb03b',
|
||||
'fill-outline-color': '#fbb03b',
|
||||
'fill-opacity': 0.5,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-midpoint',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'midpoint']],
|
||||
paint: {
|
||||
'circle-radius': 3,
|
||||
'circle-color': '#fbb03b',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-stroke-inactive',
|
||||
type: 'line',
|
||||
filter: [
|
||||
'all',
|
||||
['==', 'active', 'false'],
|
||||
['==', '$type', 'Polygon'],
|
||||
['!=', 'mode', 'static'],
|
||||
],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': '#3bb2d0',
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-stroke-active',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', 'active', 'true'], ['==', '$type', 'Polygon']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': '#fbb03b',
|
||||
'line-dasharray': [0.2, 2],
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-line-inactive',
|
||||
type: 'line',
|
||||
filter: [
|
||||
'all',
|
||||
['==', 'active', 'false'],
|
||||
['==', '$type', 'LineString'],
|
||||
['!=', 'mode', 'static'],
|
||||
['!=', 'user_isSnapGuide', 'true'],
|
||||
],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': '#3bb2d0',
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-line-active',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', '$type', 'LineString'], ['==', 'active', 'true']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': '#fbb03b',
|
||||
'line-dasharray': [0.2, 2],
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-and-line-vertex-stroke-inactive',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']],
|
||||
paint: {
|
||||
'circle-radius': 5,
|
||||
'circle-color': '#fff',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-and-line-vertex-inactive',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']],
|
||||
paint: {
|
||||
'circle-radius': 3,
|
||||
'circle-color': '#fbb03b',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-point-point-stroke-inactive',
|
||||
type: 'circle',
|
||||
filter: [
|
||||
'all',
|
||||
['==', 'active', 'false'],
|
||||
['==', '$type', 'Point'],
|
||||
['==', 'meta', 'feature'],
|
||||
['!=', 'mode', 'static'],
|
||||
],
|
||||
paint: {
|
||||
'circle-radius': 5,
|
||||
'circle-opacity': 1,
|
||||
'circle-color': '#fff',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-point-inactive',
|
||||
type: 'circle',
|
||||
filter: [
|
||||
'all',
|
||||
['==', 'active', 'false'],
|
||||
['==', '$type', 'Point'],
|
||||
['==', 'meta', 'feature'],
|
||||
['!=', 'mode', 'static'],
|
||||
],
|
||||
paint: {
|
||||
'circle-radius': 3,
|
||||
'circle-color': '#3bb2d0',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-point-stroke-active',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', '$type', 'Point'], ['==', 'active', 'true'], ['!=', 'meta', 'midpoint']],
|
||||
paint: {
|
||||
'circle-radius': 7,
|
||||
'circle-color': '#fff',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-point-active',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', '$type', 'Point'], ['!=', 'meta', 'midpoint'], ['==', 'active', 'true']],
|
||||
paint: {
|
||||
'circle-radius': 5,
|
||||
'circle-color': '#fbb03b',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-fill-static',
|
||||
type: 'fill',
|
||||
filter: ['all', ['==', 'mode', 'static'], ['==', '$type', 'Polygon']],
|
||||
paint: {
|
||||
'fill-color': '#404040',
|
||||
'fill-outline-color': '#404040',
|
||||
'fill-opacity': 0.1,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-stroke-static',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', 'mode', 'static'], ['==', '$type', 'Polygon']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': '#404040',
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-line-static',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', 'mode', 'static'], ['==', '$type', 'LineString']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': '#404040',
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-point-static',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'mode', 'static'], ['==', '$type', 'Point']],
|
||||
paint: {
|
||||
'circle-radius': 5,
|
||||
'circle-color': '#404040',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'guide',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', '$type', 'LineString'], ['==', 'user_isSnapGuide', 'true']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': '#c00c00',
|
||||
'line-width': 1,
|
||||
'line-dasharray': [5, 5],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export { customDrawStyles };
|
||||
|
|
@ -0,0 +1,652 @@
|
|||
<template>
|
||||
<div class="map-container">
|
||||
<div :id="mapContainerName" class="map-box"></div>
|
||||
<a-input-search class="inputbox" v-model:value="address" enter-button placeholder="请输入地址" @search="searchAddress" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
defineProps,
|
||||
defineEmits,
|
||||
reactive,
|
||||
ref,
|
||||
defineExpose,
|
||||
watch,
|
||||
inject,
|
||||
} from 'vue';
|
||||
import mapboxgl, { Map, Popup } from 'mapbox-gl';
|
||||
// 图形绘制工具类
|
||||
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
||||
import { generateUUID, getGeometryCenter } from '../src/tool';
|
||||
import 'mapbox-extensions/dist/index.css';
|
||||
import U from 'mapbox-gl-utils';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import * as turf from '@turf/turf';
|
||||
import '../src/index.less';
|
||||
import { MapboxConfig, MapboxDefaultStyle, MapControlConfig } from '../src/config';
|
||||
import { MP } from '../src/MP';
|
||||
import {
|
||||
SnapPolygonMode,
|
||||
SnapPointMode,
|
||||
SnapLineMode,
|
||||
SnapModeDrawStyles,
|
||||
SnapDirectSelect,
|
||||
} from 'mapbox-gl-draw-snap-mode';
|
||||
import { customDrawStyles } from '../Styles/Styles';
|
||||
import { WktToGeojson, GeojsonToWkt } from '../src/WktGeojsonTransform';
|
||||
import { getPolygonCenter } from '@/api/tiankongdi/index'
|
||||
import { message } from 'ant-design-vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const mapContainerName = ref<String>();
|
||||
mapContainerName.value = 'mapContainer-' + generateUUID();
|
||||
const props = defineProps(['geoms','id','isRead']);
|
||||
const emit = defineEmits(['handlerDrawComplete'])
|
||||
|
||||
const address = ref()
|
||||
const searchAddress = ()=>{
|
||||
console.log('213',address.value)
|
||||
axios({
|
||||
method: 'get',
|
||||
url: `https://restapi.amap.com/v3/geocode/geo?key=4f992c089f9496201f6e4ea39ff3ab60&address=`+address.value,
|
||||
}).then((res) => {
|
||||
if(res.data){
|
||||
let location = res.data.geocodes[0].location.split(',')
|
||||
map.flyTo({
|
||||
center: location,
|
||||
zoom: 13,
|
||||
bearing: 0,
|
||||
speed: 1, // 飞行速度
|
||||
curve: 2, // 飞行曲线
|
||||
essential: true,
|
||||
easing(t) {
|
||||
// 飞行动画函数
|
||||
return t;
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// 定义地图容器
|
||||
let map: Map;
|
||||
let drawTool: any;
|
||||
let clickPoisition: Array<number> = [];
|
||||
let mp: any = null;
|
||||
let geojson = reactive({
|
||||
geojson: {},
|
||||
});
|
||||
let drawing = ref(false);
|
||||
let geoms = ref<any>([])
|
||||
let hasPolygon = false;
|
||||
onMounted(() => {
|
||||
mapboxgl.accessToken = MapboxConfig.ACCESS_TOKEN;
|
||||
map = initMap();
|
||||
map.on('load', () => {
|
||||
//挂载mapbox-gl-utils
|
||||
U.init(map);
|
||||
mp = new MP(map)
|
||||
// 初始化绘图空间
|
||||
handlerInitDrawTool();
|
||||
map.on('click', (e) => {
|
||||
clickPoisition = e.lngLat;
|
||||
});
|
||||
// 设置绘图监听事件
|
||||
map.on('draw.create', function (e) {
|
||||
if (hasPolygon) {
|
||||
drawTool.delete(e.features[0].id);
|
||||
message.warning("只能绘制一个图斑,请先删除已有的!");
|
||||
return;
|
||||
}
|
||||
hasPolygon = true;
|
||||
handlerDealFeature(e.features[0]);
|
||||
});
|
||||
map.on('draw.update', function (e) {
|
||||
handlerDealFeature(e.features[0]);
|
||||
});
|
||||
map.on('draw.delete', function (e) {
|
||||
hasPolygon = false;
|
||||
handlerDeleteFeature(e.features[0]);
|
||||
});
|
||||
map.on("draw.selectionchange", (e) => {
|
||||
e.features.forEach(feature => {
|
||||
if (feature.properties.user_static) {
|
||||
drawTool.changeMode("simple_select", { featureIds: [] }); // 取消选中
|
||||
}
|
||||
});
|
||||
});
|
||||
let filter = '"RelationId"=\'' + props.id + "'";
|
||||
// getPolygonCenter({ tablename: 'idle_shp', filter: filter }).then(res => {
|
||||
// if(res.length > 0){
|
||||
// try {
|
||||
// let geojson = WktToGeojson(res[0].centroid_point);
|
||||
// map.flyTo({
|
||||
// center: geojson.coordinates,
|
||||
// zoom: 17.2,
|
||||
// bearing: 0,
|
||||
// speed: 1, // 飞行速度
|
||||
// curve: 2, // 飞行曲线
|
||||
// essential: true,
|
||||
// easing(t) {
|
||||
// // 飞行动画函数
|
||||
// return t;
|
||||
// },
|
||||
// });
|
||||
// } catch (e) {
|
||||
// console.log(e)
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
});
|
||||
});
|
||||
// 移除地图实例
|
||||
onUnmounted(() => {
|
||||
map ? map.remove() : null;
|
||||
});
|
||||
// 初始化地图 返回地图实例
|
||||
const initMap = () => {
|
||||
return new mapboxgl.Map({
|
||||
container: mapContainerName.value,
|
||||
language: 'zh-cmn',
|
||||
projection: 'equirectangular', // wgs84参考系
|
||||
style: MapboxDefaultStyle,
|
||||
minZoom: 1,
|
||||
maxZoom: 24,
|
||||
zoom: 10,
|
||||
pitch: 0,
|
||||
center: [118.340253, 35.092481],
|
||||
});
|
||||
};
|
||||
// 初始化绘图空间
|
||||
const handlerInitDrawTool = (feature, bool) => {
|
||||
geojson.geojson = feature;
|
||||
|
||||
if (drawTool) {
|
||||
drawTool.deleteAll();
|
||||
if (feature.features) {
|
||||
drawTool.set(geojson.geojson);
|
||||
}
|
||||
} else {
|
||||
drawTool = new MapboxDraw({
|
||||
displayControlsDefault: false,
|
||||
controls: {
|
||||
point: props.isRead? false: true,
|
||||
polygon: props.isRead? false: true, // 仅显示“绘制多边形”按钮
|
||||
trash: props.isRead? false: true // 仅显示“删除”按钮
|
||||
},
|
||||
modes: {
|
||||
...MapboxDraw.modes,
|
||||
draw_point: SnapPointMode,
|
||||
draw_polygon: SnapPolygonMode,
|
||||
draw_line_string: SnapLineMode,
|
||||
direct_select: SnapDirectSelect,
|
||||
},
|
||||
styles: customDrawStyles,
|
||||
userProperties: true,
|
||||
snap: true,
|
||||
snapOptions: {
|
||||
snapPx: 12, // defaults to 15
|
||||
snapToMidPoints: true, // defaults to false
|
||||
snapVertexPriorityDistance: 0.0025, // defaults to 1.25
|
||||
},
|
||||
guides: false,
|
||||
});
|
||||
map.addControl(drawTool, 'top-right');
|
||||
setTimeout(() => {
|
||||
document.querySelector(".mapbox-gl-draw_polygon")?.setAttribute("title", "绘制图斑");
|
||||
document.querySelector(".mapbox-gl-draw_trash")?.setAttribute("title", "删除图斑");
|
||||
}, 500);
|
||||
// let featureList:any = []
|
||||
if(props.geoms){
|
||||
props.geoms.forEach(item => {
|
||||
const geojsonPolygon = WktToGeojson(item.geom)
|
||||
const featureId = `polygon-${generateUUID()}`;
|
||||
let properties = {}
|
||||
if(props.isRead){
|
||||
properties = { user_static: true}
|
||||
}
|
||||
const feature = {
|
||||
id: featureId,
|
||||
type: "Feature",
|
||||
properties,
|
||||
geometry: geojsonPolygon
|
||||
};
|
||||
geoms.value.push(feature)
|
||||
// featureList.push(feature)
|
||||
hasPolygon = true
|
||||
drawTool.add(feature);
|
||||
})
|
||||
}
|
||||
|
||||
// drawTool.add(featureList);
|
||||
}
|
||||
drawing.value = true;
|
||||
};
|
||||
|
||||
// 数据绘制完成判断
|
||||
const handlerDealFeature = (feature) => {
|
||||
if(map.getLayer('area-label')){
|
||||
map.removeLayer('area-label');
|
||||
map.removeSource('area-label');
|
||||
}
|
||||
let area = turf.area(feature);
|
||||
const centroid = turf.centroid(feature);
|
||||
let labelText = `${area.toFixed(2)} m²`;
|
||||
map.addLayer({
|
||||
id: `area-label`, // 确保 ID 唯一
|
||||
type: "symbol",
|
||||
source: {
|
||||
type: "geojson",
|
||||
data: centroid
|
||||
},
|
||||
// layout: {
|
||||
// "text-field": labelText, // 显示面积
|
||||
// "text-size": 14,
|
||||
// "text-anchor": "center"
|
||||
// },
|
||||
paint: {
|
||||
"text-color": "#ff0000"
|
||||
}
|
||||
});
|
||||
let existFeature = geoms.value.find((item, index) => {
|
||||
return item.id == feature.id;
|
||||
});
|
||||
if (existFeature) {
|
||||
// 如果查找到了 则替换数据
|
||||
for (let i = 0; i < geoms.value.length; i++) {
|
||||
if (geoms.value[i].id == feature.id) {
|
||||
geoms.value[i] = feature;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果没找到数据则添加到数组
|
||||
geoms.value.push(feature);
|
||||
}
|
||||
// 自动将数据返回给父组件
|
||||
handlerDrawComplete();
|
||||
};
|
||||
// 删除数据
|
||||
const handlerDeleteFeature = (feature) => {
|
||||
if(map.getLayer('area-label')){
|
||||
map.removeLayer('area-label');
|
||||
map.removeSource('area-label');
|
||||
}
|
||||
for (let i = 0; i < geoms.value.length; i++) {
|
||||
if (geoms.value[i].id == feature.id) {
|
||||
geoms.value.splice(i, 1);
|
||||
}
|
||||
}
|
||||
handlerDrawComplete();
|
||||
};
|
||||
const handlerDrawComplete = () => {
|
||||
let arr = [];
|
||||
geoms.value.forEach((item, index) => {
|
||||
let wktStr = GeojsonToWkt(item.geometry);
|
||||
let area = turf.area(item).toFixed(2)
|
||||
let center = turf.center(item)
|
||||
let obj = {
|
||||
center: center.geometry.coordinates,
|
||||
geom: wktStr,
|
||||
};
|
||||
arr.push(obj);
|
||||
});
|
||||
emit('handlerDrawComplete', arr);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cloud-query-div {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 10px;
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cloud-query-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.map-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layer-control-center {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.layer-control-center p {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.layer-control-center .ant-checkbox-wrapper {
|
||||
}
|
||||
|
||||
.draw-control-center {
|
||||
position: absolute;
|
||||
padding: 8px;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.draw-control-center .draw-btn {
|
||||
float: left;
|
||||
margin: 0px 7px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.draw-control-center .draw-btn:hover {
|
||||
background-color: rgb(0 0 0/5%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group:not(:empty) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group {
|
||||
padding: 6px;
|
||||
border-radius: 12px;
|
||||
top: 5px;
|
||||
right: 0px;
|
||||
}
|
||||
.mapbox-gl-draw_ctrl-draw-btn {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-top-right {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button + button {
|
||||
border: 0px;
|
||||
margin: 0px 6px;
|
||||
}
|
||||
|
||||
.mapbox-gl-draw_ctrl-draw-btn:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.mapbox-gl-draw_polygon {
|
||||
background-image: url(/polygon.png);
|
||||
background-size: 100% 100%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.mapbox-gl-draw_point {
|
||||
background-image: url(/point.png);
|
||||
background-size: 100% 100%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.mapbox-gl-draw_line {
|
||||
background-image: url(/line.png);
|
||||
background-size: 100% 100%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 0px 6px;
|
||||
}
|
||||
|
||||
.mapbox-gl-draw_trash {
|
||||
background-image: url(/del.png);
|
||||
background-size: 100% 100%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.mapbox-gl-draw_combine {
|
||||
background-image: url(/combine.png);
|
||||
background-size: 100% 100%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.mapbox-gl-draw_uncombine {
|
||||
background-image: url(/uncombine.png);
|
||||
background-size: 100% 100%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.jas-ctrl-measure {
|
||||
position: relative;
|
||||
top: 6px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.jas-ctrl-measure-item {
|
||||
height: 22px;
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.layer-item {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.layer-item:hover {
|
||||
background: #c7dcf580;
|
||||
}
|
||||
|
||||
::v-deep .ant-collapse-content-box {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
::v-deep .jas-ctrl-extend-desktop-container {
|
||||
width: 320px !important;
|
||||
}
|
||||
|
||||
.position-by-lnglat {
|
||||
height: 29px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 131px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
.to-location {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/location.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.picture-azimuth {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/is_show_picture.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.picture-azimuth-active {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/not_show_picture.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.draw-polygon {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/draw_polygon.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.split-line {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/split_polygon.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.split-polygon {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/split_polygon_polygon.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.to-location-input {
|
||||
padding: 16px;
|
||||
padding-right: 4px;
|
||||
width: 418px;
|
||||
min-height: 60px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
right: 10px;
|
||||
z-index: 999999;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
||||
.location-operation {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.location-item-list-coantienr {
|
||||
width: 100%;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
.location-item {
|
||||
line-height: 20px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.split-panel-item:hover {
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.cloudqueryNotice {
|
||||
background: rgba(0, 0, 0, 0.53);
|
||||
padding: 0px 14px;
|
||||
border-radius: 6px;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 5vw;
|
||||
width: 700px;
|
||||
color: #fff;
|
||||
z-index: 10;
|
||||
.cloudquery-title {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
.cloudquery-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.cloudquery-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
width: 130px;
|
||||
}
|
||||
img {
|
||||
width: 34px;
|
||||
height: 29px;
|
||||
}
|
||||
.cloudquery-btn {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.line {
|
||||
background: #ededed;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
}
|
||||
.anticon.anticon-close {
|
||||
height: 30px;
|
||||
}
|
||||
button {
|
||||
width: 70px;
|
||||
height: 26px;
|
||||
background: linear-gradient(-74deg, #086dec, #0b4bdd);
|
||||
box-shadow: 3px 4px 5px 1px rgba(13, 13, 13, 0.05);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.title-box {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.inputbox{
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
left: 20px;
|
||||
top: 10px;
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { withInstall } from '@/utils';
|
||||
import getAddressDetails from './index.vue'
|
||||
|
||||
export const AddressDetails = withInstall(getAddressDetails);
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-input v-model:value="address" placeholder="" @click="showMaps" />
|
||||
<a-row class="containerbox" v-if="lngLat.length>0">
|
||||
<a-col :span="12" class="flex">
|
||||
<div class="labelbox">经度</div>
|
||||
<a-input class="latbox" v-model:value="lngLat[0]" disabled />
|
||||
</a-col>
|
||||
<a-col :span="12" class="flex">
|
||||
<div class="labelbox">维度</div>
|
||||
<a-input class="latbox" v-model:value="lngLat[1]" disabled />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<a-modal v-model:visible="mapsVisible" title="位置选择" @cancel="handleCancel" @ok="handleOk" width="800px" height="500px" >
|
||||
<div class="modalbox">
|
||||
<MapboxMap @handlerDrawComplete="handlerDrawComplete"></MapboxMap>
|
||||
</div>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, defineAsyncComponent } from 'vue';
|
||||
import MapboxMap from './components/map.vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const address = ref('')
|
||||
const mapsVisible = ref(false)
|
||||
const lngLat = ref([])
|
||||
const paramsData = ref()
|
||||
const showMaps = ()=>{
|
||||
console.log('asdddd')
|
||||
mapsVisible.value = true
|
||||
}
|
||||
const handleCancel = ()=>{
|
||||
paramsData.value = null
|
||||
}
|
||||
const handleOk = ()=>{
|
||||
if(paramsData.value){
|
||||
lngLat.value = paramsData.value.center
|
||||
axios({
|
||||
method: 'get',
|
||||
url: `https://restapi.amap.com/v3/geocode/regeo?output=json&location=`+paramsData.value.center+`&key=4f992c089f9496201f6e4ea39ff3ab60&radius=1000&extensions=base`,
|
||||
}).then((res) => {
|
||||
if(res.data){
|
||||
address.value = res.data.regeocode.formatted_address
|
||||
}
|
||||
});
|
||||
}else{
|
||||
lngLat.value = []
|
||||
address.value = ''
|
||||
}
|
||||
mapsVisible.value = false
|
||||
}
|
||||
const handlerDrawComplete = (e)=>{
|
||||
paramsData.value = e[0]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.modalbox{
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
}
|
||||
.flex{
|
||||
display: flex;
|
||||
}
|
||||
.containerbox{
|
||||
margin-left: -100px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.labelbox{
|
||||
width: 110px;
|
||||
text-align: end;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
}
|
||||
.latbox{
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -0,0 +1,554 @@
|
|||
import U from 'mapbox-gl-utils';
|
||||
import * as turf from '@turf/turf';
|
||||
|
||||
type EventType = 'Point' | 'LineString' | 'Polygon';
|
||||
interface GenerateGeoJSONDataInterface {
|
||||
lng: number;
|
||||
lat: number;
|
||||
}
|
||||
|
||||
interface FeatureCollection {
|
||||
type: string;
|
||||
features: Array<any>;
|
||||
}
|
||||
|
||||
const CIRCLE_STYLE = {
|
||||
'circle-color': '#6495ED',
|
||||
'circle-radius': 8,
|
||||
'circle-stroke-width': 2,
|
||||
'circle-stroke-color': '#ffffff',
|
||||
};
|
||||
const LINE_STYLE = {
|
||||
'line-color': '#6495ED',
|
||||
'line-width': 3,
|
||||
};
|
||||
const LINE_IS_DRAW_STYLE = {
|
||||
'line-dasharray': [2, 2],
|
||||
'line-color': '#00FA9A',
|
||||
'line-width': 3,
|
||||
};
|
||||
const POLYGON_STYLE = {
|
||||
'fill-color': '#FAFAD2',
|
||||
'fill-opacity': 0.6,
|
||||
};
|
||||
|
||||
function typeOf(obj: any): any {
|
||||
const toString: any = Object.prototype.toString;
|
||||
const map: any = {
|
||||
'[object Boolean]': 'boolean',
|
||||
'[object Number]': 'number',
|
||||
'[object String]': 'string',
|
||||
'[object Function]': 'function',
|
||||
'[object Array]': 'array',
|
||||
'[object Date]': 'date',
|
||||
'[object RegExp]': 'regExp',
|
||||
'[object Undefined]': 'undefined',
|
||||
'[object Null]': 'null',
|
||||
'[object Object]': 'object',
|
||||
};
|
||||
return map[toString.call(obj)];
|
||||
}
|
||||
|
||||
function deepClone(data: any): any {
|
||||
// 获取传入拷贝函数的数据类型
|
||||
const type = typeOf(data);
|
||||
// 定义一个返回any类型的数据
|
||||
let reData: any;
|
||||
// 递归遍历一个array类型数据,
|
||||
if (type === 'array') {
|
||||
reData = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
reData.push(deepClone(data[i]));
|
||||
}
|
||||
} else if (type === 'object') {
|
||||
//递归遍历一个object类型数据
|
||||
reData = {};
|
||||
for (const i in data) {
|
||||
reData[i] = deepClone(data[i]);
|
||||
}
|
||||
} else {
|
||||
// 返回基本数据类型
|
||||
return data;
|
||||
}
|
||||
// 将any类型的数据return出去,作为deepClone的结果
|
||||
return reData;
|
||||
}
|
||||
|
||||
class BaseMP {
|
||||
map: any;
|
||||
listeners: {};
|
||||
constructor(map: any) {
|
||||
this.map = map;
|
||||
// 初始化mapbox工具类
|
||||
U.init(this.map);
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
//监听
|
||||
on(event: EventType, callback: void) {
|
||||
console.log('on', event);
|
||||
this.listeners[event] = callback;
|
||||
}
|
||||
|
||||
off(event: EventType) {
|
||||
console.log('off', event);
|
||||
if (this.listeners[event]) {
|
||||
delete this.listeners[event];
|
||||
}
|
||||
}
|
||||
|
||||
emit(event: EventType, data: any) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event](data);
|
||||
}
|
||||
}
|
||||
|
||||
//防抖函数
|
||||
debounce = (func: Function, delay: number) => {
|
||||
let timer: any = null;
|
||||
return (...args: any) => {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
|
||||
//切换鼠标样式
|
||||
_changeMouseCursor = (cursor: string = 'pointer') => {
|
||||
this.map.getCanvas().style.cursor = cursor;
|
||||
};
|
||||
|
||||
//生成geoJson
|
||||
_generateGeoJSON(data: GenerateGeoJSONDataInterface[], geometryType: EventType) {
|
||||
/**
|
||||
* // 生成点类型的GeoJSON
|
||||
* var data = [{lng: -122.4194, lat: 37.7749}, {lng: -122.408, lat: 37.791}, {lng: -122.431, lat: 37.769}];
|
||||
* var geoJSON = generateGeoJSON(data, "Point");
|
||||
*
|
||||
* // 生成线类型的GeoJSON
|
||||
* var data = [{lng: -122.4194, lat: 37.7749}, {lng: -122.408, lat: 37.791}, {lng: -122.431, lat: 37.769}];
|
||||
* var geoJSON = generateGeoJSON(data, "LineString");
|
||||
*
|
||||
* // 生成面类型的GeoJSON
|
||||
* var data = [{lng: -122.4194, lat: 37.7749}, {lng: -122.408, lat: 37.791}, {lng: -122.431, lat: 37.769}];
|
||||
* var geoJSON = generateGeoJSON(data, "Polygon");
|
||||
* **/
|
||||
|
||||
const featureCollection: FeatureCollection = {
|
||||
type: 'FeatureCollection',
|
||||
features: [],
|
||||
};
|
||||
switch (geometryType) {
|
||||
case 'Point':
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const feature = {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: geometryType,
|
||||
coordinates: [data[i].lng, data[i].lat],
|
||||
},
|
||||
properties: {},
|
||||
};
|
||||
featureCollection.features.push(feature);
|
||||
}
|
||||
break;
|
||||
case 'LineString':
|
||||
const lineFeature = {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: geometryType,
|
||||
coordinates: [],
|
||||
},
|
||||
properties: {},
|
||||
};
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
lineFeature['geometry']['coordinates'].push([data[i].lng, data[i].lat]);
|
||||
}
|
||||
featureCollection.features.push(lineFeature);
|
||||
break;
|
||||
case 'Polygon':
|
||||
const polygonFeature = {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: geometryType,
|
||||
coordinates: [[]],
|
||||
},
|
||||
properties: {},
|
||||
};
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
polygonFeature['geometry']['coordinates'][0].push([data[i].lng, data[i].lat]);
|
||||
}
|
||||
featureCollection.features.push(polygonFeature);
|
||||
break;
|
||||
}
|
||||
return featureCollection;
|
||||
}
|
||||
}
|
||||
|
||||
export class MP extends BaseMP {
|
||||
drawModelChoose: { LineString: string; Point: string; Polygon: string; DEFAULT: string };
|
||||
drawModel: string;
|
||||
drawLocal: never[];
|
||||
drawCurrentId: {
|
||||
point: string; //点
|
||||
line: string; //线
|
||||
lastLine: string; //鼠标位置的点
|
||||
polygon: string;
|
||||
};
|
||||
currentMouseLocation: null;
|
||||
correctionMouseLocation: any;
|
||||
clickCount: number;
|
||||
clickTimeout: any;
|
||||
constructor(map) {
|
||||
super(map);
|
||||
this.drawModelChoose = {
|
||||
LineString: 'LineString',
|
||||
Point: 'Point',
|
||||
Polygon: 'Polygon',
|
||||
DEFAULT: 'default',
|
||||
};
|
||||
this.drawModel = this.drawModelChoose.DEFAULT;
|
||||
this.drawLocal = [];
|
||||
this.drawCurrentId = {
|
||||
point: 'current-draw-point', //点
|
||||
line: 'current-draw-line', //线
|
||||
lastLine: 'current-draw-last-line', //鼠标位置的点
|
||||
polygon: 'current-draw-polygon',
|
||||
};
|
||||
this.currentMouseLocation = null; //当前鼠标位置
|
||||
this.correctionMouseLocation = null; //校正后的鼠标位置
|
||||
this.clickCount = 0; // 定义一个计数器
|
||||
this.clickTimeout = null; // 定义一个超时变量
|
||||
}
|
||||
|
||||
//添加或更新source
|
||||
_addOrUpdateSource = (gS, id) => {
|
||||
if (this.map.getSource(id)) {
|
||||
this.map.getSource(id).setData(gS);
|
||||
} else {
|
||||
this.map.U.addGeoJSON(id, gS);
|
||||
}
|
||||
};
|
||||
// 添加layer,有则不操作
|
||||
_addLayer = (layerId, sourceId, type, style) => {
|
||||
// 如果图层不存在,添加图层。如果存在,不做操作
|
||||
if (!this.map.getLayer(sourceId)) {
|
||||
switch (type) {
|
||||
case this.drawModelChoose.Point:
|
||||
//addCircleLayer
|
||||
this.map.U.addCircleLayer(layerId, sourceId, style);
|
||||
break;
|
||||
case this.drawModelChoose.LineString:
|
||||
//addLineLayer
|
||||
this.map.U.addLineLayer(layerId, sourceId, style);
|
||||
break;
|
||||
case this.drawModelChoose.Polygon:
|
||||
//addPolygonLayer
|
||||
this.map.U.addFillLayer(layerId, sourceId, style);
|
||||
break;
|
||||
default:
|
||||
console.log('layer类型错误');
|
||||
}
|
||||
}
|
||||
};
|
||||
_currentDrawSource = () => {
|
||||
// currentGSP 点 currentGSL 线 currentGSPL 面
|
||||
const currentGSP = this._generateGeoJSON(this.drawLocal, 'Point');
|
||||
this._addOrUpdateSource(currentGSP, this.drawCurrentId.point);
|
||||
|
||||
if (
|
||||
this.drawModel === this.drawModelChoose.LineString ||
|
||||
this.drawModel === this.drawModelChoose.Polygon
|
||||
) {
|
||||
const currentGSL = this._generateGeoJSON(this.drawLocal, 'LineString');
|
||||
this._addOrUpdateSource(currentGSL, this.drawCurrentId.line);
|
||||
}
|
||||
if (this.drawModel === this.drawModelChoose.Polygon) {
|
||||
const lastGSPL = this._generateGeoJSON(this.drawLocal, 'Polygon');
|
||||
this._addOrUpdateSource(lastGSPL, this.drawCurrentId.polygon);
|
||||
}
|
||||
};
|
||||
//绘制当前的layer
|
||||
_currentDrawLayer = () => {
|
||||
// 如果是点,直接绘制点
|
||||
this._addLayer(this.drawCurrentId.point, this.drawCurrentId.point, 'Point', CIRCLE_STYLE);
|
||||
// 如果是线,在点的基础上,加上线
|
||||
// 如果是面,在线的基础上,加上面
|
||||
if (
|
||||
this.drawModel === this.drawModelChoose.LineString ||
|
||||
this.drawModel === this.drawModelChoose.Polygon
|
||||
) {
|
||||
this._addLayer(this.drawCurrentId.line, this.drawCurrentId.line, 'LineString', LINE_STYLE);
|
||||
}
|
||||
if (this.drawModel === this.drawModelChoose.Polygon) {
|
||||
this._addLayer(
|
||||
this.drawCurrentId.polygon,
|
||||
this.drawCurrentId.polygon,
|
||||
'Polygon',
|
||||
POLYGON_STYLE,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
//最后动态的一笔
|
||||
_currentDrawLastLine = () => {
|
||||
if (
|
||||
this.drawModel === this.drawModelChoose.LineString ||
|
||||
this.drawModel === this.drawModelChoose.Polygon
|
||||
) {
|
||||
if (!this.drawLocal.length) {
|
||||
this.map.U.removeLayer(this.drawCurrentId.lastLine);
|
||||
this.map.U.removeSource(this.drawCurrentId.lastLine);
|
||||
return false;
|
||||
}
|
||||
const startPoint = this.drawLocal[this.drawLocal.length - 1];
|
||||
const endPoint = this.getDrawEndPoint();
|
||||
|
||||
// 添加动态线
|
||||
const lastGSL = this._generateGeoJSON([startPoint, endPoint], 'LineString');
|
||||
this.crossesLine(this.drawLocal, [startPoint, endPoint]);
|
||||
this._addOrUpdateSource(lastGSL, this.drawCurrentId.lastLine);
|
||||
this._addLayer(
|
||||
this.drawCurrentId.lastLine,
|
||||
this.drawCurrentId.lastLine,
|
||||
'LineString',
|
||||
LINE_IS_DRAW_STYLE,
|
||||
);
|
||||
}
|
||||
if (this.drawModel === this.drawModelChoose.Polygon) {
|
||||
this._currentDrawLastPolygon();
|
||||
}
|
||||
};
|
||||
//获取矫正的最后点
|
||||
getDrawEndPoint = () => {
|
||||
let endPoint = null;
|
||||
if (this.nearlyClientDistant()) {
|
||||
endPoint = this.correctionMouseLocation;
|
||||
} else {
|
||||
endPoint = this.currentMouseLocation;
|
||||
}
|
||||
return endPoint;
|
||||
};
|
||||
//最后动态的一笔,跟随面生成
|
||||
_currentDrawLastPolygon = () => {
|
||||
// 添加动态面
|
||||
const endPoint = this.getDrawEndPoint();
|
||||
const drawLocalCopy = deepClone(this.drawLocal);
|
||||
drawLocalCopy.push(endPoint);
|
||||
drawLocalCopy.push(drawLocalCopy[0]);
|
||||
const lastGSPL = this._generateGeoJSON(drawLocalCopy, 'Polygon');
|
||||
this._addOrUpdateSource(lastGSPL, this.drawCurrentId.polygon);
|
||||
this._addLayer(
|
||||
this.drawCurrentId.polygon,
|
||||
this.drawCurrentId.polygon,
|
||||
'Polygon',
|
||||
POLYGON_STYLE,
|
||||
);
|
||||
};
|
||||
|
||||
//鼠标点击
|
||||
clickHandler = (e) => {
|
||||
const _this = this;
|
||||
this.clickCount++; // 每次单击事件计数器加1
|
||||
if (this.clickCount === 1) {
|
||||
// 如果是第一次单击
|
||||
this.clickTimeout = setTimeout(function () {
|
||||
// 启动超时变量
|
||||
// 单击事件
|
||||
_this.clickCount = 0; // 计数器清零
|
||||
if (_this.nearlyClientDistant()) {
|
||||
_this.drawLocal.push(_this.correctionMouseLocation);
|
||||
// 如果是第一个点,那么就结束绘制
|
||||
// todo 画面第一点后结束
|
||||
if (
|
||||
_this.drawModel === _this.drawModelChoose.Polygon &&
|
||||
_this.correctionMouseLocation === _this.drawLocal[0]
|
||||
) {
|
||||
_this._currentDrawSource();
|
||||
_this._currentDrawLayer();
|
||||
_this.emit(_this.drawModel, _this.drawLocal);
|
||||
_this.finishDraw();
|
||||
return false;
|
||||
} else {
|
||||
_this.drawLocal.pop();
|
||||
}
|
||||
} else {
|
||||
_this.drawLocal.push(e.lngLat);
|
||||
}
|
||||
_this._currentDrawSource();
|
||||
_this._currentDrawLayer();
|
||||
if (_this.drawModel === _this.drawModelChoose.Point) {
|
||||
_this.drawIsFinish();
|
||||
}
|
||||
}, 200); // 设置超时时间为300毫秒
|
||||
} else {
|
||||
// 如果不是第一次单击
|
||||
clearTimeout(this.clickTimeout); // 清除超时变量
|
||||
this.clickCount = 0; // 计数器清零
|
||||
// 在这里编写双击事件的操作
|
||||
|
||||
// 鼠标双击事件
|
||||
|
||||
if (this.drawModel === this.drawModelChoose.LineString) {
|
||||
_this.drawLocal.push(e.lngLat);
|
||||
_this._currentDrawSource();
|
||||
_this._currentDrawLayer();
|
||||
this.drawIsFinish();
|
||||
}
|
||||
|
||||
if (this.drawModel === this.drawModelChoose.Polygon) {
|
||||
_this.drawLocal.push(e.lngLat);
|
||||
_this._currentDrawSource();
|
||||
_this._currentDrawLayer();
|
||||
this.drawIsFinish();
|
||||
}
|
||||
}
|
||||
};
|
||||
//右键点击
|
||||
contextmenuHandler = (e) => {
|
||||
if (this.drawLocal.length < 1) {
|
||||
return false;
|
||||
}
|
||||
this.drawLocal.pop();
|
||||
this._currentDrawSource();
|
||||
this._currentDrawLayer();
|
||||
this._currentDrawLastLine();
|
||||
};
|
||||
// 鼠标移动
|
||||
mousemoveHandler = this.debounce((e) => {
|
||||
this.currentMouseLocation = e.lngLat;
|
||||
this._currentDrawLastLine();
|
||||
}, 5);
|
||||
// 判断是否吸附,距离12像素
|
||||
nearlyClientDistant = () => {
|
||||
// 判断屏幕距离,鼠标吸附
|
||||
let _this = this;
|
||||
let mousePoint = [this.currentMouseLocation.lng, this.currentMouseLocation.lat];
|
||||
let closestFeature = null;
|
||||
let closestDistance = Infinity;
|
||||
let threshold = 10; // 阈值,单位为像素
|
||||
// 遍历所有特征,找到距离最近的特征
|
||||
let clientPosition = _this.map.project(mousePoint);
|
||||
this.drawLocal.forEach(function (feature) {
|
||||
_this.map.project([feature.lng, feature.lat]);
|
||||
let featurePosition = _this.map.project([feature.lng, feature.lat]);
|
||||
// 计算两个点在屏幕上的像素距离
|
||||
let distance = Math.sqrt(
|
||||
Math.pow(clientPosition.x - featurePosition.x, 2) +
|
||||
Math.pow(clientPosition.y - featurePosition.y, 2),
|
||||
);
|
||||
if (distance < closestDistance && distance < threshold) {
|
||||
closestFeature = feature;
|
||||
closestDistance = distance;
|
||||
}
|
||||
});
|
||||
// 如果距离小于阈值,则将标注吸附到特征上
|
||||
if (closestFeature) {
|
||||
this._changeMouseCursor('pointer');
|
||||
this.correctionMouseLocation = closestFeature;
|
||||
return true;
|
||||
} else {
|
||||
// console.log('范围外')
|
||||
this._changeMouseCursor('crosshair');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// 判断是否相交 true 相交 false 不相交
|
||||
crossesLine = (line1, line2) => {
|
||||
let _this = this;
|
||||
if (this.drawLocal.length >= 3) {
|
||||
let _line1 = line1.map((e) => {
|
||||
return [e.lng, e.lat];
|
||||
});
|
||||
_line1.pop();
|
||||
let _line2 = line2.map((e) => {
|
||||
return [e.lng, e.lat];
|
||||
});
|
||||
//判断是否有交叉
|
||||
let intersect = turf.lineIntersect(turf.lineString(_line1), turf.lineString(_line2));
|
||||
if (intersect.features.length > 0) {
|
||||
let coordinates = intersect.features[0].geometry.coordinates;
|
||||
//判断是否交叉在点上
|
||||
const isExist = _line1.some(
|
||||
(item) => item[0] === coordinates[0] && item[1] === coordinates[1],
|
||||
);
|
||||
if (isExist) {
|
||||
// console.log('no cross,穿过了交点')
|
||||
_this.map.U.setProperty(this.drawCurrentId.lastLine, 'line-color', '#00FA9A');
|
||||
_this.map.on('click', this.clickHandler);
|
||||
} else {
|
||||
// console.log('cross')
|
||||
_this.map.U.setProperty(this.drawCurrentId.lastLine, 'line-color', '#DC143C');
|
||||
_this._changeMouseCursor('no-drop');
|
||||
_this.map.off('click', this.clickHandler);
|
||||
}
|
||||
} else {
|
||||
// console.log('no cross,不沾边')
|
||||
_this.map.U.setProperty(this.drawCurrentId.lastLine, 'line-color', '#00FA9A');
|
||||
_this.map.on('click', this.clickHandler);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
drawStart = () => {
|
||||
//每次绘制都要初始化数据
|
||||
this.drawLocal = [];
|
||||
this.deleteDraw();
|
||||
//禁用鼠标双击放大事件
|
||||
this.map.doubleClickZoom.disable();
|
||||
this._changeMouseCursor('crosshair');
|
||||
this.map.on('click', this.clickHandler);
|
||||
this.map.on('contextmenu', this.contextmenuHandler);
|
||||
this.map.on('mousemove', this.mousemoveHandler);
|
||||
};
|
||||
draw = (shape) => {
|
||||
if(this.drawModelChoose[shape]) {
|
||||
this.drawModel = this.drawModelChoose[shape];
|
||||
this.drawStart();
|
||||
} else {
|
||||
console.log(`暂无${shape}类型`);
|
||||
}
|
||||
};
|
||||
drawIsFinish = () => {
|
||||
let _this = this;
|
||||
this.emit(this.drawModel, this.drawLocal);
|
||||
this.finishDraw();
|
||||
//恢复双击放大功能
|
||||
setTimeout(() => {
|
||||
_this.map.doubleClickZoom.enable();
|
||||
}, 10);
|
||||
};
|
||||
// 结束绘制线和面
|
||||
finishDraw = () => {
|
||||
this.map.U.removeSource(this.drawCurrentId.lastLine);
|
||||
this.map.U.removeLayer(this.drawCurrentId.lastLine);
|
||||
this.drawModel = this.drawModelChoose.DEFAULT;
|
||||
this.unDraw();
|
||||
};
|
||||
unDraw = () => {
|
||||
// console.log('推出绘制模式');
|
||||
this.map.off('click', this.clickHandler);
|
||||
this.map.off('contextmenu', this.contextmenuHandler);
|
||||
this.map.off('mousemove', this.mousemoveHandler);
|
||||
this._changeMouseCursor('pointer');
|
||||
this.drawLocal = [];
|
||||
};
|
||||
//删除绘制的内容
|
||||
deleteDraw = () => {
|
||||
this.map.U.removeSource([
|
||||
this.drawCurrentId.line,
|
||||
this.drawCurrentId.point,
|
||||
this.drawCurrentId.lastLine,
|
||||
this.drawCurrentId.polygon,
|
||||
]);
|
||||
this.map.U.removeLayer([
|
||||
this.drawCurrentId.line,
|
||||
this.drawCurrentId.point,
|
||||
this.drawCurrentId.lastLine,
|
||||
this.drawCurrentId.polygon,
|
||||
]);
|
||||
this.unDraw();
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import WKT from "terraformer-wkt-parser"
|
||||
import { wktToGeoJSON,geojsonToWKT } from "@terraformer/wkt"
|
||||
|
||||
|
||||
|
||||
const WktToGeojson = (wktData)=> {
|
||||
// return WKT.parse(wktData)
|
||||
return wktToGeoJSON(wktData);
|
||||
}
|
||||
|
||||
const GeojsonToWkt = (geojsonData)=> {
|
||||
// return WKT.convert(geojsonData)
|
||||
console.log("geojsonData",geojsonData)
|
||||
return geojsonToWKT(geojsonData)
|
||||
}
|
||||
|
||||
|
||||
const removeZM = (geoJSON) => {
|
||||
|
||||
function removeZMFromCoords(coords) {
|
||||
return coords.map((coord) => [coord[0], coord[1]]); // 只保留 X 和 Y
|
||||
}
|
||||
|
||||
switch (geoJSON.type) {
|
||||
case 'Point':
|
||||
geoJSON.coordinates = removeZMFromCoords([geoJSON.coordinates]).flat();
|
||||
break;
|
||||
case 'LineString':
|
||||
case 'MultiPoint':
|
||||
geoJSON.coordinates = removeZMFromCoords(geoJSON.coordinates);
|
||||
break;
|
||||
case 'Polygon':
|
||||
case 'MultiLineString':
|
||||
geoJSON.coordinates = geoJSON.coordinates.map((ring) => removeZMFromCoords(ring));
|
||||
break;
|
||||
case 'MultiPolygon':
|
||||
geoJSON.coordinates = geoJSON.coordinates.map((polygon) =>
|
||||
polygon.map((ring) => removeZMFromCoords(ring))
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported geometry type: ${geoJSON.type}`);
|
||||
}
|
||||
return geoJSON;
|
||||
}
|
||||
|
||||
export {WktToGeojson,GeojsonToWkt,removeZM}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
export enum MapboxConfig {
|
||||
ACCESS_TOKEN = 'pk.eyJ1IjoiemhhbmcxMjM4ODk5OSIsImEiOiJja3N5Ync1cXcyMTR2Mm9xempmbGE4MnBtIn0.R-j78CRvbs6JZG-MDSoh8Q',
|
||||
// ACCESS_TOKEN = "1234",
|
||||
TDT_TOKEN = 'b6585bc41ee16251dbe6b1af64f375d9',
|
||||
// add more config options here
|
||||
}
|
||||
export const MapboxDefaultStyle = {
|
||||
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
|
||||
version: 8,
|
||||
sources: {
|
||||
'dianzi': {
|
||||
type: 'raster',
|
||||
tiles: [
|
||||
`https://t0.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=${MapboxConfig.TDT_TOKEN}`,
|
||||
],
|
||||
tileSize: 256,
|
||||
minzoom:18,
|
||||
maxzoom:24,
|
||||
},
|
||||
'dianzi-biaozhu': {
|
||||
type: 'raster',
|
||||
tiles: [
|
||||
`https://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${MapboxConfig.TDT_TOKEN}`,
|
||||
],
|
||||
tileSize: 256,
|
||||
},
|
||||
'raster-tiles': {
|
||||
type: 'raster',
|
||||
tiles: [
|
||||
`https://t0.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${MapboxConfig.TDT_TOKEN}`,
|
||||
],
|
||||
tileSize: 256,
|
||||
minzoom:0,
|
||||
maxzoom:18,
|
||||
},
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
id: 'dianzi-biaozhu',
|
||||
type: 'raster',
|
||||
source: 'dianzi-biaozhu',
|
||||
layout: {
|
||||
visibility: 'visible',
|
||||
},
|
||||
},{
|
||||
id: 'tdt-img-tiles',
|
||||
type: 'raster',
|
||||
source: 'raster-tiles',
|
||||
minzoom: 0,
|
||||
maxzoom: 18,
|
||||
},{
|
||||
id: 'dianzi',
|
||||
type: 'raster',
|
||||
source: 'dianzi',
|
||||
layout: {
|
||||
visibility: 'visible',
|
||||
},
|
||||
minzoom: 18,
|
||||
maxzoom: 24,
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
export const MapControlConfig = {
|
||||
DrawPoint: {
|
||||
handler: 'handlerDrawPoint',
|
||||
icon: '/point.png',
|
||||
title: '绘制点',
|
||||
},
|
||||
DrawLineString: {
|
||||
handler: 'handlerDrawLineString',
|
||||
icon: '/line.png',
|
||||
title: '绘制线',
|
||||
},
|
||||
DrawPolygon: {
|
||||
handler: 'handlerDrawPolygon',
|
||||
icon: '/polygon.png',
|
||||
title: '绘制面',
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
.mapboxgl-ctrl-logo {
|
||||
display: none !important;
|
||||
}
|
||||
.map-container {
|
||||
position: relative;
|
||||
}
|
||||
.map-box,
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.map-control {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
display: flex;
|
||||
}
|
||||
.map-control img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
.mapboxgl-ctrl-logo {
|
||||
display: none !important;
|
||||
}
|
||||
.map-container{
|
||||
position: relative;
|
||||
}
|
||||
.map-box,
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.map-control {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
img:hover {
|
||||
scale: 1.2;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { ControlOutlined } from '@ant-design/icons-vue';
|
||||
import * as turf from '@turf/turf'
|
||||
|
||||
// js生成UUID
|
||||
const generateUUID = ()=>{
|
||||
|
||||
var d = new Date().getTime(); //Timestamp
|
||||
var d2 = (performance && performance.now && (performance.now()*1000)) || 0;
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random() * 16;
|
||||
if(d > 0) {
|
||||
r = (d + r)%16 | 0;
|
||||
d = Math.floor(d/16);
|
||||
} else {
|
||||
r = (d2 + r)%16 | 0;
|
||||
d2 = Math.floor(d2/16);
|
||||
}
|
||||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
// js 针对后端post接口参数拼接到url的情况 将json参数转换为 ?key1=value1&key2=value2
|
||||
const ObjectToUrl = (obj)=>{
|
||||
let params = "?";
|
||||
for(let item in obj){
|
||||
params+=item+"="+obj[item]+"&"
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
// turf获取几何图形的中心
|
||||
const getGeometryCenter = (geometry)=>{
|
||||
let coordinates = [];
|
||||
switch(geometry.geometry.type.toUpperCase()){
|
||||
case "POINT":
|
||||
|
||||
break;
|
||||
case "MULTIPOINT":
|
||||
|
||||
break;
|
||||
case "LINESTRING":
|
||||
|
||||
break;
|
||||
case "MULTILINESTRING":
|
||||
|
||||
break;
|
||||
case "POLYGON":
|
||||
coordinates = geometry.geometry.coordinates
|
||||
break;
|
||||
case "MULTIPOLYGON":
|
||||
coordinates = geometry.geometry.coordinates[0]
|
||||
break;
|
||||
}
|
||||
|
||||
// let polygon = turf.polygon(coordinates);
|
||||
// let center = turf.centerOfMass(polygon);
|
||||
return [coordinates[0][0][0],coordinates[0][0][1]];
|
||||
// return [117.732878836452,35.1320944773393]
|
||||
}
|
||||
|
||||
export { generateUUID,ObjectToUrl,getGeometryCenter }
|
||||
|
|
@ -19,7 +19,6 @@
|
|||
>
|
||||
{{ btnText ? btnText : t('component.cropper.selectImage') }}
|
||||
</a-button>
|
||||
|
||||
<CropperModal
|
||||
@register="register"
|
||||
@upload-success="handleUploadSuccess"
|
||||
|
|
|
|||
|
|
@ -24,7 +24,12 @@
|
|||
<div :class="`${prefixCls}-toolbar`">
|
||||
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
|
||||
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
|
||||
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
|
||||
<a-button
|
||||
size="small"
|
||||
preIcon="ant-design:upload-outlined"
|
||||
:icon="h(UploadOutlined)"
|
||||
type="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Upload>
|
||||
<Space>
|
||||
|
|
@ -32,6 +37,7 @@
|
|||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:reload-outlined"
|
||||
:icon="h(ReloadOutlined)"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('reset')"
|
||||
|
|
@ -41,6 +47,7 @@
|
|||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:rotate-left-outlined"
|
||||
:icon="h(RotateLeftOutlined)"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('rotate', -45)"
|
||||
|
|
@ -50,6 +57,7 @@
|
|||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:rotate-right-outlined"
|
||||
:icon="h(RotateRightOutlined)"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('rotate', 45)"
|
||||
|
|
@ -59,6 +67,7 @@
|
|||
<a-button
|
||||
type="primary"
|
||||
preIcon="vaadin:arrows-long-h"
|
||||
:icon="h(ColumnWidthOutlined)"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('scaleX')"
|
||||
|
|
@ -68,6 +77,7 @@
|
|||
<a-button
|
||||
type="primary"
|
||||
preIcon="vaadin:arrows-long-v"
|
||||
:icon="h(ColumnHeightOutlined)"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('scaleY')"
|
||||
|
|
@ -77,6 +87,7 @@
|
|||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:zoom-in-outlined"
|
||||
:icon="h(ZoomInOutlined)"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('zoom', 0.1)"
|
||||
|
|
@ -86,6 +97,7 @@
|
|||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:zoom-out-outlined"
|
||||
:icon="h(ZoomOutOutlined)"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('zoom', -0.1)"
|
||||
|
|
@ -113,7 +125,7 @@
|
|||
<script lang="ts" setup>
|
||||
import type { CropendResult, Cropper } from './typing';
|
||||
|
||||
import { ref, PropType } from 'vue';
|
||||
import { ref, PropType, h, watch } from 'vue';
|
||||
import CropperImage from './Cropper.vue';
|
||||
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
|
||||
import { useDesign } from '@/hooks/web/useDesign';
|
||||
|
|
@ -121,6 +133,16 @@
|
|||
import { dataURLtoBlob } from '@/utils/file/base64Conver';
|
||||
import { isFunction } from '@/utils/is';
|
||||
import { useI18n } from '@/hooks/web/useI18n';
|
||||
import {
|
||||
UploadOutlined,
|
||||
ReloadOutlined,
|
||||
RotateLeftOutlined,
|
||||
ZoomOutOutlined,
|
||||
ZoomInOutlined,
|
||||
RotateRightOutlined,
|
||||
ColumnHeightOutlined,
|
||||
ColumnWidthOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
type apiFunParams = { file: Blob; name: string; filename: string };
|
||||
|
||||
|
|
@ -134,11 +156,17 @@
|
|||
src: { type: String },
|
||||
size: { type: Number },
|
||||
});
|
||||
|
||||
console.log(props.src);
|
||||
const src = ref(props.src || '');
|
||||
watch(
|
||||
() => props.src,
|
||||
(v: string) => {
|
||||
src.value = v;
|
||||
},
|
||||
);
|
||||
const emit = defineEmits(['uploadSuccess', 'uploadError', 'register']);
|
||||
|
||||
let filename = '';
|
||||
const src = ref(props.src || '');
|
||||
const previewSource = ref('');
|
||||
const cropper = ref<Cropper>();
|
||||
let scaleX = 1;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ import ApiTreeSelect from './components/ApiTreeSelect.vue';
|
|||
import ApiCascader from './components/ApiCascader.vue';
|
||||
import ApiTransfer from './components/ApiTransfer.vue';
|
||||
import { BasicUpload, ImageUpload,VideoUpload,FileUpload } from '@/components/Upload';
|
||||
import {Location } from '@/components/Map'
|
||||
import {AddressDetails} from '@/components/AddressDetails'
|
||||
import { StrengthMeter } from '@/components/StrengthMeter';
|
||||
import { IconPicker } from '@/components/Icon';
|
||||
import { CountdownInput } from '@/components/CountDown';
|
||||
|
|
@ -47,6 +49,8 @@ componentMap.set('AutoComplete', AutoComplete);
|
|||
componentMap.set('ImageUpload', ImageUpload);
|
||||
componentMap.set("VideoUpload",VideoUpload);
|
||||
componentMap.set("FileUpload",FileUpload);
|
||||
componentMap.set("Location",Location);
|
||||
componentMap.set("AddressDetails",AddressDetails);
|
||||
componentMap.set('Select', Select);
|
||||
componentMap.set('ApiSelect', ApiSelect);
|
||||
componentMap.set('ApiTree', ApiTree);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,279 @@
|
|||
<template>
|
||||
<div v-if="tableData.ifShow">
|
||||
<div style="display: flex; margin-bottom: 10px">
|
||||
<div style="margin-left: 10px">
|
||||
<a-radio-group
|
||||
:disabled="tableData.componentProps.disabled"
|
||||
v-model:value="noTitleKey"
|
||||
:options="tableData.componentProps.options"
|
||||
@change="onTabChange($event, tableData.field)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <a-card
|
||||
style="width: 100%"
|
||||
v-for="(item,index) in tableData.componentProps.options"
|
||||
:key="index"
|
||||
v-show="noTitleKey === item.value"
|
||||
:title="item.label"
|
||||
> -->
|
||||
<a-card style="width: 100%">
|
||||
<BasicForm ref="cardRef" @register="registerForm" @click="changeForm" @change="changeForm">
|
||||
<template #CardGroup>
|
||||
<template v-if="Object.keys(childItem).length > 0">
|
||||
<CardGourp
|
||||
:data="childItem"
|
||||
:parentValue="props.parentValue"
|
||||
:formData="props.formData"
|
||||
v-if="childItem"
|
||||
:callModal="props.callModal"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { BasicForm, useForm } from '@/components/Form';
|
||||
import { CardGourp } from './index';
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import { subTableStore } from '@/store/modules/subTable';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const emits = defineEmits(['changeRadioVal']);
|
||||
|
||||
const subTableDataStore = subTableStore();
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
parentValue: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
formData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
callModal: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
});
|
||||
const noTitleKey = ref(0);
|
||||
const formColumns = ref([]);
|
||||
const tabListNoTitle: any = ref([]);
|
||||
const tableData = props.data;
|
||||
const childGourp = ref([]);
|
||||
const childItem = ref({});
|
||||
let cardFormData = props.formData;
|
||||
const nowTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'));
|
||||
const userName = localStorage.getItem('fireUserLoginName');
|
||||
const cardRef = ref<any>();
|
||||
if (props.callModal) {
|
||||
tableData.ifShow = true;
|
||||
}
|
||||
const [
|
||||
registerForm,
|
||||
{ getFieldsValue, setFieldsValue, updateSchema, resetFields, validate, clearValidate },
|
||||
] = useForm({
|
||||
labelWidth: 100,
|
||||
schemas: formColumns.value,
|
||||
showActionButtonGroup: false,
|
||||
baseColProps: { lg: 24, md: 24 },
|
||||
});
|
||||
watch(
|
||||
() => subTableDataStore.getToSetGroupData,
|
||||
() => {
|
||||
if (subTableDataStore.getToSetGroupData) {
|
||||
if (Object.keys(subTableDataStore.getGroupData).includes(tableData.field)) {
|
||||
noTitleKey.value = subTableDataStore.getGroupData[tableData.field];
|
||||
if (tableData.ifShow) {
|
||||
onTabChange({ target: { value: noTitleKey.value } }, tableData.field);
|
||||
}
|
||||
} else {
|
||||
noTitleKey.value = tableData.componentProps.options[0].value;
|
||||
if (tableData.ifShow) {
|
||||
onTabChange({ target: { value: noTitleKey.value } }, tableData.field);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.formData,
|
||||
(newVal) => {
|
||||
cardFormData = {...newVal};
|
||||
let groupData = subTableDataStore.getGroupData;
|
||||
Object.keys(groupData).forEach((item) => {
|
||||
if(groupData[item]){
|
||||
cardFormData[item] = groupData[item];
|
||||
}
|
||||
})
|
||||
formColumns.value.forEach((element) => {
|
||||
for (const key in cardFormData) {
|
||||
if (element.field == key) {
|
||||
var obj = {};
|
||||
obj[key] = cardFormData[key];
|
||||
subTableDataStore.setGroupData(obj);
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
setFieldsValue(subTableDataStore.getGroupData);
|
||||
}, 10);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
const onTabChange = (event, field) => {
|
||||
clearValidate();
|
||||
let clearGroupDataKey = []
|
||||
let value = event.target.value;
|
||||
subTableDataStore.setOneGroupData(field, value);
|
||||
noTitleKey.value = value;
|
||||
var currentIndex = (childGourp.value || []).findIndex((element) => element.index === value);
|
||||
|
||||
if (currentIndex == -1) {
|
||||
childItem.value = {};
|
||||
} else {
|
||||
childItem.value = childGourp.value[currentIndex];
|
||||
}
|
||||
formColumns.value.forEach((element) => {
|
||||
if (element.component === 'CardGroup') {
|
||||
element.slot = 'CardGroup';
|
||||
}
|
||||
element.show = false;
|
||||
updateSchema([{ field: element.field, show: false }]);
|
||||
if (element.index == value) {
|
||||
element.show = true;
|
||||
if (element.requiredString) {
|
||||
element.itemProps.required = true;
|
||||
updateSchema([{ field: element.field, itemProps: { required: true } }]);
|
||||
}
|
||||
updateSchema([{ field: element.field, show: true }]);
|
||||
} else {
|
||||
clearGroupDataKey.push(element.field)
|
||||
element.itemProps.required = false;
|
||||
// delete element.itemProps.required;
|
||||
clearValidate(element.field);
|
||||
updateSchema([{ field: element.field, itemProps: { required: false } }]);
|
||||
// updateSchema([{ field: element.field, itemProps: element.itemProps }]);
|
||||
}
|
||||
});
|
||||
subTableDataStore.clearGroupDataKeyList(clearGroupDataKey)
|
||||
setTimeout(() => {
|
||||
resetFields();
|
||||
setFieldsValue(subTableDataStore.getGroupData);
|
||||
}, 10);
|
||||
emits('changeRadioVal');
|
||||
};
|
||||
if (tableData.componentProps) {
|
||||
tableData.componentProps.options.forEach((element, index) => {
|
||||
tabListNoTitle.value.push({
|
||||
key: index,
|
||||
tab: element.label,
|
||||
...element,
|
||||
index: index,
|
||||
});
|
||||
element.children.forEach((childElement) => {
|
||||
formColumns.value.push({
|
||||
parentValue: element.field,
|
||||
...childElement,
|
||||
show: index == 0 ? true : false,
|
||||
index: element.value,
|
||||
requiredString: childElement.itemProps.required,
|
||||
});
|
||||
if (childElement.component == 'CardGroup') {
|
||||
childGourp.value.push({
|
||||
...childElement,
|
||||
index: element.value,
|
||||
});
|
||||
}
|
||||
if (['createuser', 'modifyuser'].includes(childElement.type)) {
|
||||
var obj = {};
|
||||
obj[childElement.field] = userName;
|
||||
subTableDataStore.setGroupData(obj);
|
||||
}
|
||||
if (['createtime', 'modifytime'].includes(childElement.type)) {
|
||||
var obj = {};
|
||||
obj[childElement.field] = nowTime.value;
|
||||
subTableDataStore.setGroupData(obj);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
nextTick(() => {
|
||||
if (Object.keys(subTableDataStore.getGroupData).includes(tableData.field)) {
|
||||
noTitleKey.value = subTableDataStore.getGroupData[tableData.field];
|
||||
if (tableData.ifShow) {
|
||||
onTabChange({ target: { value: noTitleKey.value } }, tableData.field);
|
||||
}
|
||||
} else {
|
||||
noTitleKey.value = tableData.componentProps.options[0].value;
|
||||
if (tableData.ifShow) {
|
||||
onTabChange({ target: { value: noTitleKey.value } }, tableData.field);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (tableData.ifShow) {
|
||||
setTimeout(() => {
|
||||
resetFields();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function changeForm() {
|
||||
setTimeout(() => {
|
||||
let data = getFieldsValue();
|
||||
console.log(data,'changeForm');
|
||||
let result = {};
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (key.indexOf('card_group') == -1) {
|
||||
result[key] = data[key];
|
||||
}
|
||||
});
|
||||
subTableDataStore.setGroupData(result);
|
||||
}, 500);
|
||||
}
|
||||
// 提交时,下拉选数据存到pinia中
|
||||
// 提交时,获取最新的file_upload组件的数据
|
||||
function submitChangeFrom() {
|
||||
setTimeout(() => {
|
||||
let data = getFieldsValue();
|
||||
let oldDefaultGroupData = subTableDataStore.getOldDefaultGroupData;
|
||||
let result = {};
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (key.indexOf('_select') !== -1 || key.indexOf('_upload') !== -1) {
|
||||
if(oldDefaultGroupData[key] != data[key]){
|
||||
result[key] = data[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log(result,'submitChangeFrom')
|
||||
subTableDataStore.setGroupData(result);
|
||||
}, 100)
|
||||
}
|
||||
|
||||
async function verify() {
|
||||
const data = await validate()
|
||||
.then(async (data) => {
|
||||
return true;
|
||||
})
|
||||
.catch(async (err) => {
|
||||
console.log(err);
|
||||
if (err.errorFields.length > 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
defineExpose({
|
||||
verify,
|
||||
changeForm,
|
||||
submitChangeFrom,
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export { default as FormViewer } from './index.vue';
|
||||
export { default as SubTable } from './subTable.vue';
|
||||
export { default as CardGourp } from './cardGourp.vue';
|
||||
|
|
@ -0,0 +1,629 @@
|
|||
<template>
|
||||
<div class="my-form-viewer">
|
||||
<div v-show="tabsColumns.length > 1">
|
||||
<a-tabs v-model:activeKey="activeTabsKey" style="width: 100%" @change="tabsChange">
|
||||
<a-tab-pane v-for="(colItem, index) in tabsColumns" :tab="colItem.label" :key="index">
|
||||
<BasicForm :ref="`tabsFormRef${index}`" @register="registerForm" :key="index">
|
||||
<template #CardGroup>
|
||||
<CardGourp
|
||||
v-if="cardGroupData.length > 0 && cardGroupData[index]"
|
||||
:data="cardGroupData[index]"
|
||||
:formData="cardGourpFormData"
|
||||
:parentValue="cardGroupData[index].field"
|
||||
ref="groupRef"
|
||||
@changeRadioVal="radioVal"
|
||||
/>
|
||||
</template>
|
||||
</BasicForm>
|
||||
<subTable ref="subTableRef" :data="subTableColumns[index]" :tabsKey="tabsKey" />
|
||||
|
||||
<template v-for="(item, useIndex) in createOrModifyList[index]" :key="useIndex">
|
||||
<CreateOrModifyComponent :data="item" />
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<BasicForm
|
||||
ref="formRef"
|
||||
@register="registerForm"
|
||||
v-if="formModalVisible && tabsColumns.length < 1"
|
||||
>
|
||||
<template #CardGroup>
|
||||
<CardGourp
|
||||
v-if="cardGroupData.length > 0"
|
||||
:data="cardGroupData[0]"
|
||||
:formData="cardGourpFormData"
|
||||
:parentValue="cardGroupData[0].field"
|
||||
ref="groupRef"
|
||||
@changeRadioVal="radioVal"
|
||||
/>
|
||||
</template>
|
||||
</BasicForm>
|
||||
<!-- 设计子表 -->
|
||||
<subTable v-if="formModalVisible && tabsColumns.length < 1" :data="subTableColumns[0]" />
|
||||
<!-- 卡片 -->
|
||||
<template v-for="(item, index) in cardLayout" :key="index">
|
||||
<a-row style="width: 100%">
|
||||
<a-col :span="item?.colProps?.span || 24" style="padding: 10px">
|
||||
<a-card
|
||||
:title="item.label"
|
||||
:class="
|
||||
item.shadow === 'always' ? 'card-always' : item.shadow === 'hover' ? 'card-hover' : ''
|
||||
"
|
||||
>
|
||||
<template v-for="(childItem, childIndex) in item.columns[0].children" :key="childIndex">
|
||||
<a-row style="width: 100%; margin-bottom: 10px">
|
||||
<a-col :span="childItem?.colProps?.span || 24">
|
||||
<CallModalCardFormItem
|
||||
:data="childItem"
|
||||
:dataKey="item.field"
|
||||
:values="cardValues"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<!-- todo 创建/修改 人/时间 -->
|
||||
<template v-if="tabsColumns.length < 1">
|
||||
<template v-for="(item, index) in createOrModifyList[0]" :key="index">
|
||||
<CreateOrModifyComponent :data="item" />
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, defineProps, defineEmits, getCurrentInstance, nextTick } from 'vue';
|
||||
import { BasicForm, useForm } from '@/components/Form';
|
||||
import { functionGetFormDataFormScheme, LoadFormScheme } from '@/api/demo/formScheme';
|
||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import CallModalCardFormItem from '@/views/demo/onlineform/formCall/CallModalFormItem/index.vue';
|
||||
import CreateOrModifyComponent from '@/views/demo/onlineform/formCall/CreateOrModifyComponent/index.vue';
|
||||
import { SubTable, CardGourp } from './index';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { cardNestStructure } from '@/views/demo/onlineform/util.ts';
|
||||
import { subTableStore } from '@/store/modules/subTable';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const emit = defineEmits(['getFormSuccess']);
|
||||
const subTableDataStore = subTableStore();
|
||||
const tabsFormRef0 = ref();
|
||||
const tabsFormRef1 = ref();
|
||||
const tabsFormRef2 = ref();
|
||||
const tabsFormRef3 = ref();
|
||||
const tabsFormRef4 = ref();
|
||||
const formRef = ref();
|
||||
const activeTabsKey = ref();
|
||||
let formColumns: FormSchema[] = [];
|
||||
const props = defineProps({
|
||||
formConfig: Object,
|
||||
processId: String,
|
||||
formVerison: String,
|
||||
formRelationId: String,
|
||||
flowFormData: Object,
|
||||
instanceInfo: Object,
|
||||
issueId: String,
|
||||
isDetail: Boolean,
|
||||
});
|
||||
const subTableId = ref(null);
|
||||
const subTableColumns: any = ref([]);
|
||||
const subTableDB = ref([]);
|
||||
const cardLayout = ref([]);
|
||||
const cardValues = ref({});
|
||||
const createOrModifyList = ref([]);
|
||||
const formModalVisible = ref(false);
|
||||
const tabsColumns: any = ref([]);
|
||||
const infoUseSubTableData = ref();
|
||||
const infoUseMainTableData = ref({});
|
||||
const tabsKey = ref(0);
|
||||
const keyValue = ref('');
|
||||
const FieldsValue = ref({});
|
||||
const subTableRef = ref<any>();
|
||||
const cardGroupData = ref([]);
|
||||
const cardGourpFormData = ref({});
|
||||
subTableDataStore.clearGoupData();
|
||||
const nowTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'));
|
||||
const userName = localStorage.getItem('fireUserLoginName');
|
||||
const getCardLayoutKey = (dataList, key) => {
|
||||
dataList.forEach((item) => {
|
||||
if (item.component === 'Card') {
|
||||
getCardLayoutKey(item.columns[0].children, key);
|
||||
} else if (item.type === 'subTable') {
|
||||
cardValues.value[key][item.field] = [];
|
||||
} else {
|
||||
cardValues.value[key][item.field] = '';
|
||||
}
|
||||
});
|
||||
};
|
||||
const [
|
||||
registerForm,
|
||||
{ getFieldsValue, setFieldsValue, updateSchema, resetFields, validate, clearValidate },
|
||||
] = useForm({
|
||||
labelWidth: 100,
|
||||
schemas: formColumns,
|
||||
showActionButtonGroup: false,
|
||||
actionColOptions: {
|
||||
span: 24,
|
||||
},
|
||||
});
|
||||
async function getFormHistory() {
|
||||
const data = await LoadFormScheme({
|
||||
schemeId: props.formVerison,
|
||||
});
|
||||
if (data) {
|
||||
const scheme = JSON.parse(data.scheme);
|
||||
scheme.formInfo.tabList.forEach((tabElement, index) => {
|
||||
createOrModifyList.value.push([]);
|
||||
tabElement.schemas.forEach((element) => {
|
||||
//流程设置的表单显示和编辑与表单做关联
|
||||
props.formConfig.forEach((configElement) => {
|
||||
if (configElement.field == element.field) {
|
||||
element.componentProps.disabled = !configElement.disabled;
|
||||
element.ifShow = configElement.ifShow;
|
||||
if (configElement.required) {
|
||||
element.itemProps.required = configElement.required;
|
||||
}
|
||||
}
|
||||
if (
|
||||
props.isDetail &&
|
||||
['createuser', 'createtime', 'modifyuser', 'modifytime'].includes(element.type)
|
||||
) {
|
||||
element.ifShow = true;
|
||||
element.component = 'Input';
|
||||
element.componentProps.disabled = true;
|
||||
}
|
||||
if (element.columns) {
|
||||
element.columns.forEach((child) => {
|
||||
child.children.forEach((t) => {
|
||||
if (configElement.field == t.field) {
|
||||
t.componentProps.disabled = !configElement.disabled;
|
||||
t.ifShow = configElement.ifShow;
|
||||
if (configElement.required) {
|
||||
t.itemProps.required = configElement.required;
|
||||
}
|
||||
}
|
||||
if (
|
||||
props.isDetail &&
|
||||
['createuser', 'createtime', 'modifyuser', 'modifytime'].includes(t.type)
|
||||
) {
|
||||
t.ifShow = true;
|
||||
t.component = 'Input';
|
||||
t.componentProps.disabled = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
subTableColumns.value = [];
|
||||
// 将card组件嵌套起来
|
||||
scheme.formInfo.tabList = cardNestStructure(scheme.formInfo.tabList);
|
||||
subTableDB.value = scheme.db;
|
||||
let disDetail = false;
|
||||
let tableColumns = [];
|
||||
scheme.formInfo.tabList.forEach((tabElement, index) => {
|
||||
subTableColumns.value.push({
|
||||
indexValue: index,
|
||||
parentFileId: '',
|
||||
child: [],
|
||||
multiterm: '',
|
||||
});
|
||||
tabElement.schemas.forEach((element) => {
|
||||
if (element.field == props.formRelationId) {
|
||||
keyValue.value = element.componentProps.fieldName;
|
||||
getFormDetail(scheme.formInfo.tabList);
|
||||
disDetail = true;
|
||||
}
|
||||
if (element.component === 'InputGuid') {
|
||||
element.ifShow = false;
|
||||
}
|
||||
// 验证规则
|
||||
if (element.rules !== undefined) {
|
||||
let myString = element.rules[0].pattern;
|
||||
const lastCharacter = myString.charAt(myString.length - 1);
|
||||
if (lastCharacter === 'i' || lastCharacter === 's') {
|
||||
element.rules[0].pattern = new RegExp(
|
||||
element.rules[0].pattern.slice(1, -2),
|
||||
lastCharacter,
|
||||
);
|
||||
} else {
|
||||
element.rules[0].pattern = new RegExp(element.rules[0].pattern.slice(1, -1));
|
||||
}
|
||||
}
|
||||
// 卡片布局
|
||||
if (element.component === 'Card') {
|
||||
cardLayout.value.push(element);
|
||||
cardValues.value[element.field] = {};
|
||||
getCardLayoutKey(element.columns[0].children, element.field);
|
||||
}
|
||||
if (['createuser', 'createtime', 'modifyuser', 'modifytime'].includes(element.type)) {
|
||||
createOrModifyList.value[index].push(element);
|
||||
}
|
||||
// 选项卡
|
||||
if (element.type === 'subTable') {
|
||||
subTableId.value = element.field;
|
||||
subTableColumns.value[index].parentFileId = element.field;
|
||||
(subTableColumns.value[index].multiterm = element.componentProps.multiterm),
|
||||
tableColumns.push({
|
||||
parentFileId: element.field,
|
||||
child: [],
|
||||
dataTable: element.componentProps.dataTable,
|
||||
});
|
||||
element.columns.forEach((itemColumn) => {
|
||||
itemColumn.children.forEach((itemColumnChild) => {
|
||||
subTableColumns.value[index].child.push({
|
||||
key: itemColumnChild.field,
|
||||
title: itemColumnChild.label,
|
||||
dataIndex: itemColumnChild.field,
|
||||
...itemColumnChild,
|
||||
width: 120,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
// 卡片组布局
|
||||
if (element.component === 'CardGroup') {
|
||||
element.slot = 'CardGroup';
|
||||
if (cardGroupData.value.length !== index) {
|
||||
cardGroupData.value.push(null);
|
||||
}
|
||||
cardGroupData.value.push(element);
|
||||
// cardGroupData.value.push({
|
||||
// ...element,
|
||||
// });
|
||||
}
|
||||
//栅格
|
||||
if (element.component === 'Grid' && element.label === '栅格布局') {
|
||||
element.columns.forEach((itemColumn) => {
|
||||
itemColumn.children.forEach((itemColumnChild) => {
|
||||
itemColumnChild.colProps.span = itemColumn.span;
|
||||
formColumns.push({
|
||||
parentValue: index,
|
||||
...itemColumnChild,
|
||||
show: index == 0 ? true : false,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
formColumns.push({
|
||||
parentValue: index,
|
||||
...element,
|
||||
show: index == 0 ? true : false,
|
||||
});
|
||||
});
|
||||
//
|
||||
if (tabElement.text != '') {
|
||||
tabsColumns.value.push({
|
||||
label: tabElement.text,
|
||||
value: index,
|
||||
children: tabElement.schemas,
|
||||
});
|
||||
}
|
||||
});
|
||||
formModalVisible.value = true;
|
||||
setTimeout(() => {
|
||||
subTableDataStore.setTableData(tableColumns);
|
||||
resetFields();
|
||||
if (!disDetail) {
|
||||
if (props.flowFormData) {
|
||||
if (props.flowFormData.mapGeom) {
|
||||
props.flowFormData.MapGeom = props.flowFormData.mapGeom;
|
||||
}
|
||||
console.log('flowFormDataAfter', props.flowFormData);
|
||||
setFieldsValue(props.flowFormData);
|
||||
}
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
function tabsChange(e) {
|
||||
tabsKey.value = e;
|
||||
formColumns.forEach((element) => {
|
||||
element.show = false;
|
||||
if (element.parentValue == e) {
|
||||
element.show = true;
|
||||
}
|
||||
});
|
||||
// 切换时保存值
|
||||
// const values = await validate();
|
||||
const values = getFieldsValue();
|
||||
if (Object.keys(FieldsValue.value).length == 0) {
|
||||
FieldsValue.value = values;
|
||||
}
|
||||
for (const key in values) {
|
||||
for (const fieKey in FieldsValue.value) {
|
||||
if (key == fieKey) {
|
||||
if (values[key] != undefined) {
|
||||
FieldsValue.value[key] = values[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
setFieldsValue(FieldsValue.value);
|
||||
clearValidate();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
async function getFormDetail(element) {
|
||||
var instance = props.instanceInfo;
|
||||
const querys = {
|
||||
id: props.formVerison,
|
||||
key: keyValue.value,
|
||||
keyValue: instance.pkeyValue,
|
||||
};
|
||||
const data = await functionGetFormDataFormScheme(querys);
|
||||
let obj = new Object();
|
||||
for (var i in data) {
|
||||
subTableDB.value.forEach((element) => {
|
||||
if (element.type == 'chlid') {
|
||||
subTableDataStore.getTableData.forEach((element) => {
|
||||
if (element.dataTable == i) {
|
||||
subTableDataStore.setSingleData(element.parentFileId, data[i]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (var j in data[i]) {
|
||||
Object.assign(obj, data[i][j]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
cardGourpFormData.value = obj;
|
||||
let cardGroupKeys = [];
|
||||
element.forEach((tabItem) => {
|
||||
tabItem.schemas.forEach((elItem) => {
|
||||
if (elItem.component == 'CardGroup' && elItem.field.indexOf('guid') == -1) {
|
||||
cardGroupKeys = cardGroupGetKey(elItem, cardGroupKeys);
|
||||
}
|
||||
});
|
||||
});
|
||||
let setGroupDataObj = {};
|
||||
Object.keys(obj).forEach((itemKey) => {
|
||||
if (cardGroupKeys.includes(itemKey)) {
|
||||
setGroupDataObj[itemKey] = obj[itemKey];
|
||||
}
|
||||
});
|
||||
subTableDataStore.setGroupData(setGroupDataObj);
|
||||
subTableDataStore.setOldDefaultGroupData(setGroupDataObj);
|
||||
subTableDataStore.setToSetGroupData();
|
||||
FieldsValue.value = obj;
|
||||
setFieldsValue({
|
||||
...obj,
|
||||
});
|
||||
if (subTableDB.value.length > 1) {
|
||||
let childTableName = subTableDB.value.find((item) => item.type === 'chlid').name;
|
||||
let mainTableName = subTableDB.value.find((item) => item.type === 'main').name;
|
||||
infoUseSubTableData.value = data[childTableName].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
key: uuidv4(),
|
||||
};
|
||||
});
|
||||
data[mainTableName].forEach((item) => {
|
||||
infoUseMainTableData.value = { ...infoUseMainTableData.value, ...item };
|
||||
});
|
||||
}
|
||||
if (Object.keys(cardValues.value).length > 0) {
|
||||
Object.keys(cardValues.value).forEach((cardItem) => {
|
||||
let cardItemKeyList = Object.keys(cardValues.value[cardItem]);
|
||||
Object.keys(infoUseMainTableData.value).forEach((item) => {
|
||||
if (cardItemKeyList.includes(item)) {
|
||||
cardValues.value[cardItem][item] = infoUseMainTableData.value[item];
|
||||
}
|
||||
});
|
||||
// todo
|
||||
cardItemKeyList.forEach((item) => {
|
||||
if (item.indexOf('grid') !== -1) {
|
||||
cardValues.value[cardItem][item] = infoUseSubTableData.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
let mainTable = subTableDB.value.find((item) => item.type === 'main').name;
|
||||
emit('getFormSuccess', data[mainTable][0]);
|
||||
}
|
||||
const groupRef = ref();
|
||||
const cardGroupGetKey = (element, resultList) => {
|
||||
resultList.push(element.field);
|
||||
element.componentProps.options.forEach((groupItem) => {
|
||||
groupItem.children.forEach((childItem) => {
|
||||
resultList.push(childItem.field);
|
||||
if (childItem.component == 'CardGroup' && childItem.field.indexOf('guid') == -1) {
|
||||
resultList = cardGroupGetKey(childItem, resultList);
|
||||
}
|
||||
});
|
||||
});
|
||||
return resultList;
|
||||
};
|
||||
async function getForm() {
|
||||
try {
|
||||
// 验证卡片组必填项
|
||||
if (groupRef.value) {
|
||||
if (groupRef.value.length > 0) {
|
||||
for (let index = 0; index < groupRef.value.length; index++) {
|
||||
await groupRef.value[index].submitChangeFrom();
|
||||
if (!(await groupRef.value[index].verify())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await groupRef.value.submitChangeFrom();
|
||||
if (!(await groupRef.value.verify())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
let resultObj = {};
|
||||
let values;
|
||||
switch (activeTabsKey.value) {
|
||||
case 0:
|
||||
resultObj = proxy.$refs.tabsFormRef0[0].getFieldsValue();
|
||||
proxy.$refs.tabsFormRef0[0].setFieldsValue({
|
||||
...resultObj,
|
||||
...subTableDataStore.getGroupData,
|
||||
});
|
||||
await proxy.$refs.tabsFormRef0[0].validate();
|
||||
values = proxy.$refs.tabsFormRef0[0].getFieldsValue();
|
||||
break;
|
||||
case 1:
|
||||
resultObj = proxy.$refs.tabsFormRef1[0].getFieldsValue();
|
||||
proxy.$refs.tabsFormRef1[0].setFieldsValue({
|
||||
...resultObj,
|
||||
...subTableDataStore.getGroupData,
|
||||
});
|
||||
await proxy.$refs.tabsFormRef1[0].validate();
|
||||
values = proxy.$refs.tabsFormRef1[0].getFieldsValue();
|
||||
console.log('resultValue111', JSON.parse(JSON.stringify(values)));
|
||||
break;
|
||||
case 2:
|
||||
resultObj = proxy.$refs.tabsFormRef2[0].getFieldsValue();
|
||||
proxy.$refs.tabsFormRef2[0].setFieldsValue({
|
||||
...resultObj,
|
||||
...subTableDataStore.getGroupData,
|
||||
});
|
||||
await proxy.$refs.tabsFormRef2[0].validate();
|
||||
values = proxy.$refs.tabsFormRef2[0].getFieldsValue();
|
||||
break;
|
||||
case 3:
|
||||
resultObj = proxy.$refs.tabsFormRef3[0].getFieldsValue();
|
||||
proxy.$refs.tabsFormRef3[0].setFieldsValue({
|
||||
...resultObj,
|
||||
...subTableDataStore.getGroupData,
|
||||
});
|
||||
await proxy.$refs.tabsFormRef3[0].validate();
|
||||
values = proxy.$refs.tabsFormRef3[0].getFieldsValue();
|
||||
break;
|
||||
case 4:
|
||||
resultObj = proxy.$refs.tabsFormRef4[0].getFieldsValue();
|
||||
proxy.$refs.tabsFormRef4[0].setFieldsValue({
|
||||
...resultObj,
|
||||
...subTableDataStore.getGroupData,
|
||||
});
|
||||
await proxy.$refs.tabsFormRef4[0].validate();
|
||||
values = proxy.$refs.tabsFormRef4[0].getFieldsValue();
|
||||
break;
|
||||
case 5:
|
||||
resultObj = proxy.$refs.tabsFormRef5[0].getFieldsValue();
|
||||
proxy.$refs.tabsFormRef5[0].setFieldsValue({
|
||||
...resultObj,
|
||||
...subTableDataStore.getGroupData,
|
||||
});
|
||||
await proxy.$refs.tabsFormRef5[0].validate();
|
||||
values = proxy.$refs.tabsFormRef5[0].getFieldsValue();
|
||||
break;
|
||||
case 6:
|
||||
resultObj = proxy.$refs.tabsFormRef6[0].getFieldsValue();
|
||||
proxy.$refs.tabsFormRef6[0].setFieldsValue({
|
||||
...resultObj,
|
||||
...subTableDataStore.getGroupData,
|
||||
});
|
||||
await proxy.$refs.tabsFormRef6[0].validate();
|
||||
values = proxy.$refs.tabsFormRef6[0].getFieldsValue();
|
||||
break;
|
||||
default:
|
||||
resultObj = getFieldsValue();
|
||||
setFieldsValue({
|
||||
...resultObj,
|
||||
...subTableDataStore.getGroupData,
|
||||
});
|
||||
values = await validate();
|
||||
break;
|
||||
}
|
||||
for (const key in values) {
|
||||
for (const fieKey in FieldsValue.value) {
|
||||
if (key == fieKey) {
|
||||
if (values[key] == null || values[key] == undefined) {
|
||||
values[key] = FieldsValue.value[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let cardValueList = Object.values(cardValues.value);
|
||||
cardValueList.forEach((item) => {
|
||||
values = { ...values, ...item };
|
||||
});
|
||||
let query = values;
|
||||
// 存储子表的信息
|
||||
if (subTableDataStore.getTableData.length > 0) {
|
||||
subTableDataStore.getTableData.forEach((item) => {
|
||||
query[item.parentFileId] = JSON.stringify(item.child);
|
||||
});
|
||||
}
|
||||
// 储存卡片组信息
|
||||
setTimeout(() => {
|
||||
if (Object.keys(subTableDataStore.getGroupData).length > 0) {
|
||||
for (const key in subTableDataStore.getGroupData) {
|
||||
query[key] = subTableDataStore.getGroupData[key];
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
// 存储创建人、创建时间等内容
|
||||
if (createOrModifyList.value.length > 0) {
|
||||
createOrModifyList.value.forEach((childTab) => {
|
||||
childTab.forEach((item) => {
|
||||
if (!item.componentProps.disabled) {
|
||||
if (item.type == 'createuser' || item.type == 'modifyuser') {
|
||||
query[item.field] = userName;
|
||||
} else if (item.type == 'createtime' || item.type == 'modifytime') {
|
||||
query[item.field] = nowTime.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
console.log(query);
|
||||
return query;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
getForm,
|
||||
});
|
||||
onMounted(() => {
|
||||
if (props.flowFormData) {
|
||||
FieldsValue.value = props.flowFormData;
|
||||
}
|
||||
if (props.formVerison) {
|
||||
getFormHistory();
|
||||
}
|
||||
// tabsFormRef.value && tabsFormRef.value.clearValidate();
|
||||
// formRef.value && formRef.value.clearValidate();
|
||||
});
|
||||
function radioVal() {
|
||||
console.log('radioVal');
|
||||
clearValidate();
|
||||
//切换卡片组选项时,卡片组的值赋值给form,优化卡片组必填验证不通过
|
||||
setFieldsValue({
|
||||
...subTableDataStore.getGroupData,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.my-process-designer {
|
||||
width: 100%;
|
||||
}
|
||||
.my-form-viewer {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: calc(100vh - 350px);
|
||||
}
|
||||
::v-deep .ant-tabs .ant-tabs-nav-wrap {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
background-color: @component-background;
|
||||
border-bottom: 1px solid rgba(5, 5, 5, 0.06);
|
||||
}
|
||||
::v-deep .ant-tabs-content-holder {
|
||||
margin-top: 40px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<div v-if="columns.length > 1">
|
||||
<a-table
|
||||
class="sub-table"
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="false"
|
||||
:scroll="scrollValue"
|
||||
>
|
||||
<!-- v-if="props.data.multiterm" -->
|
||||
<template #headerCell="{ column, record }">
|
||||
<template v-if="column.key === 'setting'">
|
||||
<PlusOutlined class="icon-button" @click="addListItem" v-if="!isDetail" />
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'setting'">
|
||||
<DeleteOutlined
|
||||
class="icon-button"
|
||||
@click="delListItem(column, record)"
|
||||
v-if="!isDetail"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<FormItem :data="column" :record="record" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- <BasicForm ref="subTableRef" @register="registerForm" @change="changeData" v-else /> -->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import FormItem from '@/views/demo/onlineform/formCall/ShowFormModal/FormItem/index.vue';
|
||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { subTableStore } from '@/store/modules/subTable';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
import { BasicForm, useForm } from '@/components/Form';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const subTableDataStore = subTableStore();
|
||||
const scrollValue = ref();
|
||||
const columns: any = ref([
|
||||
{
|
||||
dataIndex: 'setting',
|
||||
key: 'setting',
|
||||
fixed: 'left',
|
||||
width: 60,
|
||||
},
|
||||
]);
|
||||
const tableData: any = ref([]);
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
tabsKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const nowTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'));
|
||||
const userName = localStorage.getItem('fireUserLoginName');
|
||||
const [registerForm, { getFieldsValue, setFieldsValue, updateSchema, resetFields, validate }] =
|
||||
useForm({
|
||||
labelWidth: 100,
|
||||
schemas: columns,
|
||||
showActionButtonGroup: false,
|
||||
baseColProps: { lg: 24, md: 24 },
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
subTableDataStore.getTableData.forEach((element) => {
|
||||
if (element.parentFileId == props.data.parentFileId) {
|
||||
tableData.value = element.child;
|
||||
}
|
||||
});
|
||||
});
|
||||
props.data.child.forEach((element) => {
|
||||
if (
|
||||
!['createuser', 'modifyuser', 'createtime', 'modifytime'].includes(element.type) &&
|
||||
element.component != 'InputGuid'
|
||||
) {
|
||||
columns.value.push({
|
||||
...element,
|
||||
});
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => tableData.value,
|
||||
(newVal) => {
|
||||
subTableDataStore.setSingleData(props.data.parentFileId, newVal);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
scrollValue.value = { x: (columns.value.length - 1) * 140, y: 300 };
|
||||
|
||||
const addListItem = () => {
|
||||
if (!props.data.multiterm && tableData.value.length >= 1) {
|
||||
createMessage.error('单行模式,只允许一条数据!');
|
||||
return;
|
||||
}
|
||||
let keyValue = uuidv4();
|
||||
let emptyItem = { key: keyValue };
|
||||
props.data.child.map((item) => {
|
||||
if (item.component == 'InputGuid') {
|
||||
emptyItem[item.field] = keyValue;
|
||||
} else if (item.type == 'createuser' || item.type == 'modifyuser') {
|
||||
emptyItem[item.field] = userName;
|
||||
} else if (item.type == 'createtime' || item.type == 'modifytime') {
|
||||
emptyItem[item.field] = nowTime.value;
|
||||
} else {
|
||||
emptyItem[item.field] = '';
|
||||
}
|
||||
});
|
||||
tableData.value.push(emptyItem);
|
||||
};
|
||||
const delListItem = (column, record) => {
|
||||
tableData.value = tableData.value.filter((item) => item.key != record.key);
|
||||
};
|
||||
function getData() {
|
||||
return tableData.value;
|
||||
}
|
||||
defineExpose({
|
||||
getData,
|
||||
});
|
||||
function changeData() {
|
||||
console.log(tableData.value);
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { withInstall } from '@/utils';
|
||||
import getLocation from './src/components/Location.vue'
|
||||
|
||||
export const Location = withInstall(getLocation);
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mapContainer" :id="'mapContainer' + mapRandom">
|
||||
<div class="refresh-button">
|
||||
<ReloadOutlined @click="onrefresh" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import mapboxgl, { Map, Popup } from 'mapbox-gl';
|
||||
import { ref, toRefs, watch, onMounted } from 'vue';
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue';
|
||||
import type { UploadFile, UploadProps } from 'ant-design-vue';
|
||||
import { Modal, Upload } from 'ant-design-vue';
|
||||
import { on } from '@/utils/domUtils';
|
||||
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
import { isArray, isFunction, isObject, isString } from '@/utils/is';
|
||||
import { warn } from '@/utils/log';
|
||||
import { useI18n } from '@/hooks/web/useI18n';
|
||||
import { useUploadType } from '../hooks/useUpload';
|
||||
import { uploadContainerProps } from '../props';
|
||||
import { isImgTypeByName } from '../helper';
|
||||
import { UploadResultStatus } from '@/components/Upload/src/types/typing';
|
||||
import { parse } from 'path';
|
||||
|
||||
defineOptions({ name: 'ImageUpload' });
|
||||
const mapRandom = ref(parseInt(Math.random() * 100000).toString());
|
||||
|
||||
const emit = defineEmits(['change', 'update:value', 'delete']);
|
||||
const props = defineProps({
|
||||
...uploadContainerProps,
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const { createMessage } = useMessage();
|
||||
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||
const isInnerOperate = ref<boolean>(false);
|
||||
const { getStringAccept } = useUploadType({
|
||||
acceptRef: accept,
|
||||
helpTextRef: helpText,
|
||||
maxNumberRef: maxNumber,
|
||||
maxSizeRef: maxSize,
|
||||
});
|
||||
const previewOpen = ref<boolean>(false);
|
||||
const previewImage = ref<string>('');
|
||||
const previewTitle = ref<string>('');
|
||||
|
||||
const fileList = ref<UploadProps['fileList']>([]);
|
||||
const isLtMsg = ref<boolean>(true);
|
||||
const isActMsg = ref<boolean>(true);
|
||||
|
||||
let map: Map;
|
||||
|
||||
onMounted(() => {
|
||||
mapboxgl.accessToken = "pk.eyJ1IjoieHVqaW5nbGlhbmciLCJhIjoiY2w3bzFzZnZqMjdieTN1cG92N2I1d2huOSJ9.aQqMz4S-cTziUYizIH_gNg"
|
||||
map = initMap();
|
||||
map.on('load', function () {
|
||||
refreshLocation();
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(v) => {
|
||||
if (isInnerOperate.value) {
|
||||
isInnerOperate.value = false;
|
||||
return;
|
||||
}
|
||||
if (v) {
|
||||
let value: string[] = [];
|
||||
if (isArray(v)) {
|
||||
value = v;
|
||||
} else {
|
||||
value.push(v);
|
||||
}
|
||||
fileList.value = value.map((item, i) => {
|
||||
if (item && isString(item)) {
|
||||
return {
|
||||
uid: -i + '',
|
||||
name: item.substring(item.lastIndexOf('/') + 1),
|
||||
status: 'done',
|
||||
url: item,
|
||||
};
|
||||
} else if (item && isObject(item)) {
|
||||
return item;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}) as UploadProps['fileList'];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const initMap = () => {
|
||||
return new mapboxgl.Map({
|
||||
container: 'mapContainer' + mapRandom.value,
|
||||
language: 'zh-cmn',
|
||||
projection: 'equirectangular', // wgs84参考系
|
||||
style: {
|
||||
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
|
||||
version: 8,
|
||||
sources: {
|
||||
'raster-tiles': {
|
||||
type: 'raster',
|
||||
tiles: [
|
||||
`https://t0.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=b6585bc41ee16251dbe6b1af64f375d9`,
|
||||
],
|
||||
tileSize: 256,
|
||||
},
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
id: 'tdt-img-tiles',
|
||||
type: 'raster',
|
||||
source: 'raster-tiles',
|
||||
minzoom: 0,
|
||||
maxzoom: 18,
|
||||
},
|
||||
],
|
||||
},
|
||||
maxZoom: 22,
|
||||
minZoom: 6,
|
||||
zoom: 15,
|
||||
center: [118.298906, 35.135013],
|
||||
});
|
||||
};
|
||||
|
||||
const refreshLocation = () => {
|
||||
map.addSource('points', {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [118.298906, 35.135013],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
// Add a circle layer
|
||||
map.addLayer({
|
||||
id: 'circle',
|
||||
type: 'circle',
|
||||
source: 'points',
|
||||
paint: {
|
||||
'circle-color': '#409EFF',
|
||||
'circle-radius': 6,
|
||||
'circle-stroke-width': 3,
|
||||
'circle-stroke-color': '#ffffff',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onrefresh = () => {
|
||||
createMessage.success(t('component.map.refreshSuccess'));
|
||||
};
|
||||
|
||||
function getBase64<T extends string | ArrayBuffer | null>(file: File) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
resolve(reader.result as T);
|
||||
};
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
const handlePreview = async (file: UploadFile) => {
|
||||
console.log('fileEEEEE', file);
|
||||
if (!file.url && !file.preview) {
|
||||
file.preview = await getBase64<string>(file.originFileObj!);
|
||||
}
|
||||
previewImage.value = file.url || file.preview || '';
|
||||
previewOpen.value = true;
|
||||
previewTitle.value =
|
||||
file.name || previewImage.value.substring(previewImage.value.lastIndexOf('/') + 1);
|
||||
};
|
||||
|
||||
const handleRemove = async (file: UploadFile) => {
|
||||
if (fileList.value) {
|
||||
const index = fileList.value.findIndex((item) => item.uid === file.uid);
|
||||
index !== -1 && fileList.value.splice(index, 1);
|
||||
const value = getValue();
|
||||
isInnerOperate.value = true;
|
||||
emit('change', value);
|
||||
emit('delete', file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
previewOpen.value = false;
|
||||
previewTitle.value = '';
|
||||
};
|
||||
|
||||
const beforeUpload = (file: File) => {
|
||||
const { maxSize, accept } = props;
|
||||
const { name } = file;
|
||||
const isAct = isImgTypeByName(name);
|
||||
if (!isAct) {
|
||||
createMessage.error(t('component.upload.acceptUpload', [accept]));
|
||||
isActMsg.value = false;
|
||||
// 防止弹出多个错误提示
|
||||
setTimeout(() => (isActMsg.value = true), 1000);
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 > maxSize;
|
||||
if (isLt) {
|
||||
createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
|
||||
isLtMsg.value = false;
|
||||
// 防止弹出多个错误提示
|
||||
setTimeout(() => (isLtMsg.value = true), 1000);
|
||||
}
|
||||
return (isAct && !isLt) || Upload.LIST_IGNORE;
|
||||
};
|
||||
|
||||
async function customRequest(info: UploadRequestOption<any>) {
|
||||
const { api } = props;
|
||||
if (!api || !isFunction(api)) {
|
||||
return warn('upload api must exist and be a function');
|
||||
}
|
||||
try {
|
||||
const res = await props.api?.({
|
||||
data: {
|
||||
...(props.uploadParams || {}),
|
||||
},
|
||||
file: info.file,
|
||||
name: props.name,
|
||||
filename: props.filename,
|
||||
});
|
||||
info.onSuccess!(res.data);
|
||||
const value = getValue();
|
||||
isInnerOperate.value = true;
|
||||
emit('change', value);
|
||||
} catch (e: any) {
|
||||
console.log(e);
|
||||
info.onError!(e);
|
||||
}
|
||||
}
|
||||
|
||||
function getValue() {
|
||||
const list = (fileList.value || [])
|
||||
.filter((item) => item?.status === UploadResultStatus.DONE)
|
||||
.map((item: any) => {
|
||||
return item?.url || item?.response?.url;
|
||||
});
|
||||
return props.multiple ? list : list.length > 0 ? list[0] : '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ant-upload-select-picture-card i {
|
||||
color: #999;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.ant-upload-select-picture-card .ant-upload-text {
|
||||
margin-top: 8px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.mapContainer {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.refresh-button {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
import type { BasicColumn, ActionItem } from '@/components/Table';
|
||||
import { FileBasicColumn, FileItem, PreviewFileItem, UploadResultStatus } from '../types/typing';
|
||||
import { isImgTypeByName } from '../helper';
|
||||
import { Progress, Tag } from 'ant-design-vue';
|
||||
import TableAction from '@/components/Table/src/components/TableAction.vue';
|
||||
import ThumbUrl from './ThumbUrl.vue';
|
||||
import { useI18n } from '@/hooks/web/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 文件上传列表
|
||||
export function createTableColumns(): FileBasicColumn[] {
|
||||
return [
|
||||
{
|
||||
dataIndex: 'thumbUrl',
|
||||
title: t('component.upload.legend'),
|
||||
width: 100,
|
||||
customRender: ({ record }) => {
|
||||
const { thumbUrl } = (record as FileItem) || {};
|
||||
return thumbUrl && <ThumbUrl fileUrl={thumbUrl} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: t('component.upload.fileName'),
|
||||
align: 'left',
|
||||
customRender: ({ text, record }) => {
|
||||
const { percent, status: uploadStatus } = (record as FileItem) || {};
|
||||
let status: 'normal' | 'exception' | 'active' | 'success' = 'normal';
|
||||
if (uploadStatus === UploadResultStatus.ERROR) {
|
||||
status = 'exception';
|
||||
} else if (uploadStatus === UploadResultStatus.UPLOADING) {
|
||||
status = 'active';
|
||||
} else if (uploadStatus === UploadResultStatus.SUCCESS) {
|
||||
status = 'success';
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<p class="truncate mb-1 max-w-[280px]" title={text}>
|
||||
{text}
|
||||
</p>
|
||||
<Progress percent={percent} size="small" status={status} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'size',
|
||||
title: t('component.upload.fileSize'),
|
||||
width: 100,
|
||||
customRender: ({ text = 0 }) => {
|
||||
return text && (text / 1024).toFixed(2) + 'KB';
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: t('component.upload.fileStatue'),
|
||||
width: 100,
|
||||
customRender: ({ text }) => {
|
||||
if (text === UploadResultStatus.SUCCESS) {
|
||||
return <Tag color="green">{() => t('component.upload.uploadSuccess')}</Tag>;
|
||||
} else if (text === UploadResultStatus.ERROR) {
|
||||
return <Tag color="red">{() => t('component.upload.uploadError')}</Tag>;
|
||||
} else if (text === UploadResultStatus.UPLOADING) {
|
||||
return <Tag color="blue">{() => t('component.upload.uploading')}</Tag>;
|
||||
}
|
||||
|
||||
return text || t('component.upload.pending');
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
export function createActionColumn(handleRemove: Function): FileBasicColumn {
|
||||
return {
|
||||
width: 120,
|
||||
title: t('component.upload.operating'),
|
||||
dataIndex: 'action',
|
||||
fixed: false,
|
||||
customRender: ({ record }) => {
|
||||
const actions: ActionItem[] = [
|
||||
{
|
||||
label: t('component.upload.del'),
|
||||
color: 'error',
|
||||
onClick: handleRemove.bind(null, record),
|
||||
},
|
||||
];
|
||||
return <TableAction actions={actions} outside={true} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
// 文件预览列表
|
||||
export function createPreviewColumns(): BasicColumn[] {
|
||||
return [
|
||||
{
|
||||
dataIndex: 'url',
|
||||
title: t('component.upload.legend'),
|
||||
width: 100,
|
||||
customRender: ({ record }) => {
|
||||
const { url } = (record as PreviewFileItem) || {};
|
||||
return isImgTypeByName(url) && <ThumbUrl fileUrl={url} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: t('component.upload.fileName'),
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function createPreviewActionColumn({
|
||||
handleRemove,
|
||||
handleDownload,
|
||||
}: {
|
||||
handleRemove: Fn;
|
||||
handleDownload: Fn;
|
||||
}): BasicColumn {
|
||||
return {
|
||||
width: 160,
|
||||
title: t('component.upload.operating'),
|
||||
dataIndex: 'action',
|
||||
fixed: false,
|
||||
customRender: ({ record }) => {
|
||||
const actions: ActionItem[] = [
|
||||
{
|
||||
label: t('component.upload.del'),
|
||||
color: 'error',
|
||||
onClick: handleRemove.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: t('component.upload.download'),
|
||||
onClick: handleDownload.bind(null, record),
|
||||
},
|
||||
];
|
||||
|
||||
return <TableAction actions={actions} outside={true} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
export function checkFileType(file: File, accepts: string[]) {
|
||||
const newTypes = accepts.join('|');
|
||||
// const reg = /\.(jpg|jpeg|png|gif|txt|doc|docx|xls|xlsx|xml)$/i;
|
||||
const reg = new RegExp('\\.(' + newTypes + ')$', 'i');
|
||||
|
||||
return reg.test(file.name);
|
||||
}
|
||||
|
||||
export function checkImgType(file: File) {
|
||||
return isImgTypeByName(file.name);
|
||||
}
|
||||
|
||||
export function isImgTypeByName(name: string) {
|
||||
return /\.(jpg|jpeg|png|gif|webp)$/i.test(name);
|
||||
}
|
||||
|
||||
|
||||
export function isVideoTypeByName(name: string) {
|
||||
return /\.(mp4|mov|avi)$/i.test(name);
|
||||
}
|
||||
|
||||
export function getBase64WithFile(file: File) {
|
||||
return new Promise<{
|
||||
result: string;
|
||||
file: File;
|
||||
}>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve({ result: reader.result as string, file });
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { Ref, unref, computed } from 'vue';
|
||||
import { useI18n } from '@/hooks/web/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
export function useUploadType({
|
||||
acceptRef,
|
||||
helpTextRef,
|
||||
maxNumberRef,
|
||||
maxSizeRef,
|
||||
}: {
|
||||
acceptRef: Ref<string[]>;
|
||||
helpTextRef: Ref<string>;
|
||||
maxNumberRef: Ref<number>;
|
||||
maxSizeRef: Ref<number>;
|
||||
}) {
|
||||
// 文件类型限制
|
||||
const getAccept = computed(() => {
|
||||
const accept = unref(acceptRef);
|
||||
if (accept && accept.length > 0) {
|
||||
return accept;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const getStringAccept = computed(() => {
|
||||
return unref(getAccept)
|
||||
.map((item) => {
|
||||
if (item.indexOf('/') > 0 || item.startsWith('.')) {
|
||||
return item;
|
||||
} else {
|
||||
return `.${item}`;
|
||||
}
|
||||
})
|
||||
.join(',');
|
||||
});
|
||||
|
||||
// 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。
|
||||
const getHelpText = computed(() => {
|
||||
const helpText = unref(helpTextRef);
|
||||
if (helpText) {
|
||||
return helpText;
|
||||
}
|
||||
const helpTexts: string[] = [];
|
||||
|
||||
const accept = unref(acceptRef);
|
||||
if (accept.length > 0) {
|
||||
helpTexts.push(t('component.upload.accept', [accept.join(',')]));
|
||||
}
|
||||
|
||||
const maxSize = unref(maxSizeRef);
|
||||
if (maxSize) {
|
||||
helpTexts.push(t('component.upload.maxSize', [maxSize]));
|
||||
}
|
||||
|
||||
const maxNumber = unref(maxNumberRef);
|
||||
if (maxNumber && maxNumber !== Infinity) {
|
||||
helpTexts.push(t('component.upload.maxNumber', [maxNumber]));
|
||||
}
|
||||
return helpTexts.join(',');
|
||||
});
|
||||
return { getAccept, getStringAccept, getHelpText };
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
import type { PropType } from 'vue';
|
||||
import { FileBasicColumn } from './types/typing';
|
||||
|
||||
import type { Options } from 'sortablejs';
|
||||
|
||||
import { Merge } from '@/utils/types';
|
||||
|
||||
type SortableOptions = Merge<
|
||||
Omit<Options, 'onEnd'>,
|
||||
{
|
||||
onAfterEnd?: <T = any, R = any>(params: T) => R;
|
||||
// ...可扩展
|
||||
}
|
||||
>;
|
||||
|
||||
type ListType = 'text' | 'picture' | 'picture-card';
|
||||
|
||||
export const basicProps = {
|
||||
listType: {
|
||||
type: String as PropType<ListType>,
|
||||
default: 'picture-card',
|
||||
},
|
||||
helpText: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
// 文件最大多少MB
|
||||
maxSize: {
|
||||
type: Number as PropType<number>,
|
||||
default: 2,
|
||||
},
|
||||
// 最大数量的文件,Infinity不限制
|
||||
maxNumber: {
|
||||
type: Number as PropType<number>,
|
||||
default: 1,
|
||||
},
|
||||
// 根据后缀,或者其他
|
||||
accept: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
uploadParams: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}),
|
||||
},
|
||||
api: {
|
||||
type: Function as PropType<PromiseFn>,
|
||||
default: null,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String as PropType<string>,
|
||||
default: 'file',
|
||||
},
|
||||
filename: {
|
||||
type: String as PropType<string>,
|
||||
default: null,
|
||||
},
|
||||
fileListOpenDrag: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
fileListDragOptions: {
|
||||
type: Object as PropType<SortableOptions>,
|
||||
default: () => ({}),
|
||||
},
|
||||
};
|
||||
|
||||
export const uploadContainerProps = {
|
||||
value: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
...basicProps,
|
||||
showPreviewNumber: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
emptyHidePreview: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const previewProps = {
|
||||
value: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
};
|
||||
|
||||
export const fileListProps = {
|
||||
columns: {
|
||||
type: Array as PropType<FileBasicColumn[]>,
|
||||
default: null,
|
||||
},
|
||||
actionColumn: {
|
||||
type: Object as PropType<FileBasicColumn>,
|
||||
default: null,
|
||||
},
|
||||
dataSource: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: null,
|
||||
},
|
||||
openDrag: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
dragOptions: {
|
||||
type: Object as PropType<SortableOptions>,
|
||||
default: () => ({}),
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { BasicColumn } from '@/components/Table';
|
||||
import { UploadApiResult } from '@/api/sys/model/uploadModel';
|
||||
|
||||
export enum UploadResultStatus {
|
||||
DONE = 'done',
|
||||
SUCCESS = 'success',
|
||||
ERROR = 'error',
|
||||
UPLOADING = 'uploading',
|
||||
}
|
||||
|
||||
export interface FileItem {
|
||||
thumbUrl?: string;
|
||||
name: string;
|
||||
size: string | number;
|
||||
type?: string;
|
||||
percent: number;
|
||||
file: File;
|
||||
status?: UploadResultStatus;
|
||||
response?: UploadApiResult;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export interface PreviewFileItem {
|
||||
url: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface FileBasicColumn extends Omit<BasicColumn, 'customRender'> {
|
||||
/**
|
||||
* Renderer of the table cell. The return value should be a VNode, or an object for colSpan/rowSpan config
|
||||
* @type Function | ScopedSlot
|
||||
*/
|
||||
customRender?: Function;
|
||||
/**
|
||||
* Title of this column
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* Display field of the data record, could be set like a.b.c
|
||||
* @type string
|
||||
*/
|
||||
dataIndex: string;
|
||||
}
|
||||
|
|
@ -147,6 +147,8 @@ import { message } from 'ant-design-vue';
|
|||
drawTool = new MapboxDraw({
|
||||
displayControlsDefault: false,
|
||||
controls: {
|
||||
point: props.isRead? false: true,
|
||||
line: props.isRead? false: true,
|
||||
polygon: props.isRead? false: true, // 仅显示“绘制多边形”按钮
|
||||
trash: props.isRead? false: true // 仅显示“删除”按钮
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,65 +1,32 @@
|
|||
<template>
|
||||
<div class="map-container">
|
||||
<div id="mapContainer" class="map-box"></div>
|
||||
<!-- <div class="map-control">
|
||||
<img
|
||||
v-for="(item, index) in nextMapControl"
|
||||
:key="index"
|
||||
:src="item.icon"
|
||||
:title="item.title"
|
||||
@click="handlerMapControlClick(item.handler)"
|
||||
/>
|
||||
<img v-show="nextMapControl.length > 0" @click="handlerUnDraw" src="/del.png" title="清除" />
|
||||
</div> -->
|
||||
|
||||
<!-- layer list -->
|
||||
<div class="layer-workspace" :style="{'left':showLayerList?'0px':'-356px'}">
|
||||
<LayerComponent
|
||||
@changeOpenModal="changeOpenModal"
|
||||
@changeOpenInsertShpModal="changeOpenInsertShpModal"
|
||||
:layerList="layerList"
|
||||
@clearLayerList="handlerClearLayerList"
|
||||
@handlerLayerShowChange="handlerLayerControler"
|
||||
@handlerGetTableList="handlerGetTableList"
|
||||
/>
|
||||
<div class="close-layer-workspace" @click="changeLayerList">
|
||||
<MenuFoldOutlined v-if="showLayerList" />
|
||||
<MenuUnfoldOutlined v-else />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<LayerControl @draw="handlerDrawPolygon" :style="{'left':showLayerList?'386px':'20px'}" />
|
||||
<UseModal v-model:openModal="openModal" @changeOpenModal="changeOpenModal" @handlerAddToLayerList="handlerAddToLayerList" />
|
||||
<InsertShp v-model:openModal="insertShpModal" />
|
||||
<!-- <AddLayer v-model:openModal="insertShpModal" /> -->
|
||||
|
||||
<!-- table data list -->
|
||||
<DataListComponent v-if="DataTableList" />
|
||||
|
||||
<!-- attribute -->
|
||||
<RightShowInfo :openModal="openRightInfo" />
|
||||
|
||||
|
||||
<div :id="mapContainerName" class="map-box"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, defineProps, reactive, ref, defineExpose } from 'vue';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
import { Map, Popup } from 'mapbox-gl';
|
||||
import heatGeoJson from './lib/data.json'
|
||||
|
||||
import {
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
defineProps,
|
||||
defineEmits,
|
||||
reactive,
|
||||
ref,
|
||||
defineExpose,
|
||||
watch,
|
||||
inject,
|
||||
} from 'vue';
|
||||
import mapboxgl, { Map, Popup } from 'mapbox-gl';
|
||||
// 图形绘制工具类
|
||||
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
||||
|
||||
import { generateUUID, getGeometryCenter } from './src/tool';
|
||||
import 'mapbox-extensions/dist/index.css';
|
||||
import U from 'mapbox-gl-utils';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import * as turf from '@turf/turf';
|
||||
import './src/index.less';
|
||||
import { MapboxConfig, MapboxDefaultStyle, MapControlConfig } from './src/config';
|
||||
import { MP } from './src/MP';
|
||||
import { DrawingType } from '@/enums/mapEnum';
|
||||
import {MenuFoldOutlined,MenuUnfoldOutlined} from '@ant-design/icons-vue'
|
||||
import {
|
||||
SnapPolygonMode,
|
||||
SnapPointMode,
|
||||
|
|
@ -67,297 +34,122 @@
|
|||
SnapModeDrawStyles,
|
||||
SnapDirectSelect,
|
||||
} from 'mapbox-gl-draw-snap-mode';
|
||||
import LayerComponent from './LayerComponent/index.vue';
|
||||
import LayerControl from './LayerControl/index.vue';
|
||||
import UseModal from './Modal/index.vue';
|
||||
import InsertShp from './InsertShp/index.vue';
|
||||
import AddLayer from './AddLayer/index.vue';
|
||||
import DataListComponent from './DataListComponent/index.vue';
|
||||
import RightShowInfo from './RightShowInfo/index.vue';
|
||||
import { customDrawStyles } from './Styles/Styles';
|
||||
import { WktToGeojson, GeojsonToWkt } from './src/WktGeojsonTransform';
|
||||
import { getPolygonCenter } from '@/api/tiankongdi/index'
|
||||
import { message } from 'ant-design-vue';
|
||||
const mapContainerName = ref<String>();
|
||||
mapContainerName.value = 'mapContainer-' + generateUUID();
|
||||
const props = defineProps(['geoms','id','isRead']);
|
||||
const emit = defineEmits(['handlerDrawComplete'])
|
||||
|
||||
import proj4 from 'proj4';
|
||||
// let fromProjection = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs +type=crs";
|
||||
let fromProjection = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=39500000 +y_0=0 +ellps=GRS80 +units=m +no_defs +type=crs"
|
||||
// let toProjection = "+proj=longlat +ellps=GRS80 +no_defs +type=crs"
|
||||
let toProjection = "+proj=longlat +datum=WGS84 +no_defs +type=crs";
|
||||
|
||||
// let coordinates = [39591762.54875996,3908085.3169043385];
|
||||
let coordinates = [39592654.90645946,3909001.5344353705]
|
||||
const converted = proj4(fromProjection, toProjection, coordinates);
|
||||
// alert(converted)
|
||||
|
||||
const openModal = ref(false);
|
||||
const insertShpModal = ref(false);
|
||||
const openRightInfo = ref(false);
|
||||
const changeOpenModal = (value) => {
|
||||
openModal.value = value;
|
||||
};
|
||||
const changeOpenInsertShpModal = (value) => {
|
||||
insertShpModal.value = value;
|
||||
};
|
||||
|
||||
// layer list
|
||||
const showLayerList = ref<boolean>(false);
|
||||
const changeLayerList = ()=>{
|
||||
showLayerList.value = !showLayerList.value;
|
||||
}
|
||||
|
||||
const layerList = ref<[]>([]);
|
||||
const handlerAddToLayerList = (value) => {
|
||||
layerList.value.push(value);
|
||||
console.log("layerList.value",layerList.value);
|
||||
}
|
||||
|
||||
const handlerClearLayerList = () => {
|
||||
layerList.value = [];
|
||||
}
|
||||
|
||||
// map参数类型
|
||||
interface MapboxOptionsInterface {
|
||||
mapOptions: mapboxgl.MapboxOptions;
|
||||
control: DrawingType[];
|
||||
}
|
||||
const props = defineProps<MapboxOptionsInterface>();
|
||||
|
||||
let nextMapControl: Array<any> = reactive([]);
|
||||
nextMapControl = props.control
|
||||
? props.control.map((item) => {
|
||||
console.log('item::: ', item);
|
||||
return MapControlConfig[item];
|
||||
})
|
||||
: [];
|
||||
|
||||
console.log('nextMapControl::: ', nextMapControl);
|
||||
|
||||
// 定义地图容器
|
||||
let map: Map;
|
||||
let popup: Popup;
|
||||
let drawTool: any;
|
||||
let clickPoisition: Array<number> = [];
|
||||
let selectFeature: Object = {};
|
||||
let mp: any = null;
|
||||
|
||||
const { createConfirm, createMessage } = useMessage();
|
||||
|
||||
// 定义地图回调emit
|
||||
// 地图加载完成回调
|
||||
const emit = defineEmits(['mapOnLoad', 'mapDraw']);
|
||||
|
||||
let geojson = reactive({
|
||||
geojson: {},
|
||||
});
|
||||
let drawing = ref(false);
|
||||
let geoms = ref<any>([])
|
||||
let hasPolygon = false;
|
||||
onMounted(() => {
|
||||
mapboxgl.accessToken = MapboxConfig.ACCESS_TOKEN;
|
||||
|
||||
map = initMap();
|
||||
map.on('load', () => {
|
||||
|
||||
|
||||
|
||||
// map.addLayer({
|
||||
// 'id': 'wms-test-layer',
|
||||
// 'type': 'raster',
|
||||
// 'source': {
|
||||
// 'type': 'raster',
|
||||
// 'tiles': [
|
||||
// // "http://175.27.168.120:8080/geoserver/feixian/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fjpeg&TRANSPARENT=true&LAYERS=feixian%3Ayingxiang_17&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A4548&STYLES=&WIDTH=728&HEIGHT=768&BBOX=586801.5388788335%2C3908183.1422136417%2C590270.7583843566%2C3911848.0123377703",
|
||||
// // "http://175.27.168.120:8080/geoserver/feixian/wms?service=WMS&version=1.1.0&request=GetMap&layers=feixian:yingxiang_17&styles=&bbox={bbox-epsg-3857}&width=256&height=256&srs=EPSG:3857&format=image/png&TRANSPARENT=TRUE"
|
||||
// "http://60.213.14.14:8060/geoserver/feixian/wms?service=WMS&version=1.1.0&request=GetMap&layers=feixian:yingxiang&styles=&bbox={bbox-epsg-3857}&width=256&height=256&srs=EPSG:3857&format=image/png&TRANSPARENT=TRUE"
|
||||
// ],
|
||||
// 'tileSize': 256
|
||||
// },
|
||||
// 'paint': {}
|
||||
// });
|
||||
|
||||
map.addSource('radar', {
|
||||
'type': 'image',
|
||||
// 'url': 'http://175.27.168.120:8080/geoserver/feixian/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fjpeg&TRANSPARENT=true&LAYERS=feixian%3Ayingxiang_17&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A4548&STYLES=&WIDTH=728&HEIGHT=768&BBOX=586801.5388788335%2C3908183.1422136417%2C590270.7583843566%2C3911848.0123377703',
|
||||
// 'url':"http://175.27.168.120:8080/geoserver/feixian/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fjpeg&TRANSPARENT=true&LAYERS=feixian%3Ayingxiang_17&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A4548&STYLES=&WIDTH=727&HEIGHT=768&BBOX=591035.9133569683%2C3908525.08342436%2C591469.2675472646%2C3908982.893941982",
|
||||
'url':"http://60.213.14.14:8060/geoserver/feixian/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fjpeg&TRANSPARENT=true&LAYERS=feixian%3Ayingxiang&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A4527&STYLES=&WIDTH=1024&HEIGHT=1024&BBOX=39591762.54875996%2C3908085.3169043385%2C39592654.90645946%2C3909001.5344353705",
|
||||
'coordinates': [
|
||||
|
||||
|
||||
// EPSG:4548
|
||||
// [118.00090749819051,35.30579563806707],
|
||||
// [118.0057224793592,35.30579563806707],
|
||||
// [118.0057224793592,35.30170937770571],
|
||||
// [118.00090749819051,35.30170937770571],
|
||||
|
||||
// EPSG:4527
|
||||
// [118.00884630303432,35.3058545162185],
|
||||
// [118.01875909202998,35.3058545162185],
|
||||
// [118.01875909202998,35.29767983003392],
|
||||
// [118.00884630303432,35.29767983003392],
|
||||
|
||||
[118.00884630303432,35.30585451621851],
|
||||
[118.01875909202998,35.30585451621851],
|
||||
[118.01875909202998,35.297679830033914],
|
||||
[118.00884630303432,35.297679830033914],
|
||||
|
||||
// [39591762.54875996,3909001.5344353705],
|
||||
// [39592654.90645946,3909001.5344353705],
|
||||
// [39592654.90645946,3908085.3169043385],
|
||||
// [39591762.54875996,3908085.3169043385],
|
||||
]
|
||||
});
|
||||
map.addLayer({
|
||||
id: 'radar-layer',
|
||||
'type': 'raster',
|
||||
'source': 'radar',
|
||||
'paint': {
|
||||
'raster-fade-duration': 0
|
||||
}
|
||||
});
|
||||
|
||||
map.addSource('earthquakes', {
|
||||
'type': 'geojson',
|
||||
'data': heatGeoJson
|
||||
});
|
||||
|
||||
map.addLayer(
|
||||
{
|
||||
'id': 'earthquakes-heat',
|
||||
'type': 'heatmap',
|
||||
'source': 'earthquakes',
|
||||
// 'maxzoom': 18,
|
||||
'minzoom':11,
|
||||
'paint': {
|
||||
// Increase the heatmap weight based on frequency and property magnitude
|
||||
'heatmap-weight': [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
['get', 'mag'],
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
// Increase the heatmap color weight weight by zoom level
|
||||
// heatmap-intensity is a multiplier on top of heatmap-weight
|
||||
'heatmap-intensity': [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
['zoom'],
|
||||
0,
|
||||
1,
|
||||
9,
|
||||
3
|
||||
],
|
||||
// Color ramp for heatmap. Domain is 0 (low) to 1 (high).
|
||||
// Begin color ramp at 0-stop with a 0-transparancy color
|
||||
// to create a blur-like effect.
|
||||
'heatmap-color': [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
['heatmap-density'],
|
||||
0,
|
||||
'rgba(33,102,172,0)',
|
||||
0.2,
|
||||
'rgb(103,169,207)',
|
||||
0.4,
|
||||
'rgb(209,229,240)',
|
||||
0.6,
|
||||
'rgb(253,219,199)',
|
||||
0.8,
|
||||
'rgb(239,138,98)',
|
||||
1,
|
||||
'rgb(178,24,43)'
|
||||
],
|
||||
// Adjust the heatmap radius by zoom level
|
||||
'heatmap-radius': [
|
||||
'interpolate',['linear'],['zoom'],
|
||||
3,
|
||||
6,
|
||||
9,
|
||||
20
|
||||
],
|
||||
// Transition from heatmap to circle layer by zoom level
|
||||
'heatmap-opacity': [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
['zoom'],
|
||||
7,
|
||||
1,
|
||||
8,
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
//挂载mapbox-gl-utils
|
||||
// U.init(map);
|
||||
mp = new MP(map);
|
||||
emit('mapOnLoad', map);
|
||||
|
||||
U.init(map);
|
||||
mp = new MP(map)
|
||||
// 初始化绘图空间
|
||||
handlerInitDrawTool();
|
||||
|
||||
map.on('click', (e) => {
|
||||
console.log('click',e)
|
||||
clickPoisition = e.lngLat;
|
||||
});
|
||||
|
||||
map.on('draw.selectionchange', (e) => {
|
||||
handlerCopyToTargetLayer(e);
|
||||
// 设置绘图监听事件
|
||||
map.on('draw.create', function (e) {
|
||||
if (hasPolygon) {
|
||||
drawTool.delete(e.features[0].id);
|
||||
message.warning("只能绘制一个图斑,请先删除已有的!");
|
||||
return;
|
||||
}
|
||||
hasPolygon = true;
|
||||
handlerDealFeature(e.features[0]);
|
||||
});
|
||||
map.on('draw.update', function (e) {
|
||||
handlerDealFeature(e.features[0]);
|
||||
});
|
||||
map.on('draw.delete', function (e) {
|
||||
hasPolygon = false;
|
||||
handlerDeleteFeature(e.features[0]);
|
||||
});
|
||||
map.on("draw.selectionchange", (e) => {
|
||||
e.features.forEach(feature => {
|
||||
if (feature.properties.user_static) {
|
||||
drawTool.changeMode("simple_select", { featureIds: [] }); // 取消选中
|
||||
}
|
||||
});
|
||||
});
|
||||
let filter = '"RelationId"=\'' + props.id + "'";
|
||||
getPolygonCenter({ tablename: 'idle_shp', filter: filter }).then(res => {
|
||||
if(res.length > 0){
|
||||
try {
|
||||
let geojson = WktToGeojson(res[0].centroid_point);
|
||||
map.flyTo({
|
||||
center: geojson.coordinates,
|
||||
zoom: 17.2,
|
||||
bearing: 0,
|
||||
speed: 1, // 飞行速度
|
||||
curve: 2, // 飞行曲线
|
||||
essential: true,
|
||||
easing(t) {
|
||||
// 飞行动画函数
|
||||
return t;
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.handlerCopyFeature = handlerCopyFeature;
|
||||
});
|
||||
});
|
||||
// 销毁地图
|
||||
// 移除地图实例
|
||||
onUnmounted(() => {
|
||||
map ? map.remove() : null;
|
||||
});
|
||||
// 初始化地图
|
||||
// 返回地图实例
|
||||
// 初始化地图 返回地图实例
|
||||
const initMap = () => {
|
||||
return new mapboxgl.Map({
|
||||
container: 'mapContainer',
|
||||
container: mapContainerName.value,
|
||||
language: 'zh-cmn',
|
||||
projection: 'equirectangular', // wgs84参考系
|
||||
style: MapboxDefaultStyle,
|
||||
maxZoom: 22,
|
||||
minZoom: 1,
|
||||
maxZoom: 24,
|
||||
zoom: 10,
|
||||
// ...props.mapOptions,
|
||||
center:[117.984425,35.270654],
|
||||
pitch: 0,
|
||||
center: [118.340253, 35.092481],
|
||||
});
|
||||
};
|
||||
|
||||
const handlerMapControlClick = (handler: string) => {
|
||||
handler === 'handlerDrawPoint' && handlerDrawPoint();
|
||||
handler === 'handlerDrawLineString' && handlerDrawLineString();
|
||||
handler === 'handlerDrawPolygon' && handlerDrawPolygon();
|
||||
};
|
||||
//绘制点
|
||||
const handlerDrawPoint = () => {
|
||||
mp.draw('Point');
|
||||
mp.on('Point', function (e) {
|
||||
emit('mapDraw', 'Point', e);
|
||||
});
|
||||
};
|
||||
//绘制线
|
||||
const handlerDrawLineString = () => {
|
||||
mp.draw('LineString');
|
||||
mp.on('LineString', function (e) {
|
||||
emit('mapDraw', 'LineString', e);
|
||||
});
|
||||
};
|
||||
//绘制面
|
||||
const handlerDrawPolygon = () => {
|
||||
mp.draw('Polygon');
|
||||
mp.on('Polygon', function (e) {
|
||||
emit('mapDraw', 'Polygon', e);
|
||||
});
|
||||
};
|
||||
//删除标记
|
||||
const handlerUnDraw = () => {
|
||||
mp.deleteDraw();
|
||||
emit('mapDraw', 'cancel');
|
||||
};
|
||||
|
||||
// 初始化绘图空间
|
||||
const handlerInitDrawTool = () => {
|
||||
let drawTool = new MapboxDraw({
|
||||
const handlerInitDrawTool = (feature, bool) => {
|
||||
geojson.geojson = feature;
|
||||
|
||||
if (drawTool) {
|
||||
drawTool.deleteAll();
|
||||
if (feature.features) {
|
||||
drawTool.set(geojson.geojson);
|
||||
}
|
||||
} else {
|
||||
drawTool = new MapboxDraw({
|
||||
displayControlsDefault: false,
|
||||
controls: {
|
||||
polygon: props.isRead? false: true, // 仅显示“绘制多边形”按钮
|
||||
trash: props.isRead? false: true // 仅显示“删除”按钮
|
||||
},
|
||||
modes: {
|
||||
...MapboxDraw.modes,
|
||||
draw_point: SnapPointMode,
|
||||
|
|
@ -365,106 +157,168 @@
|
|||
draw_line_string: SnapLineMode,
|
||||
direct_select: SnapDirectSelect,
|
||||
},
|
||||
// Styling guides
|
||||
styles: SnapModeDrawStyles,
|
||||
styles: customDrawStyles,
|
||||
userProperties: true,
|
||||
// Config snapping features
|
||||
snap: true,
|
||||
snapOptions: {
|
||||
snapPx: 15, // defaults to 15
|
||||
snapPx: 12, // defaults to 15
|
||||
snapToMidPoints: true, // defaults to false
|
||||
snapVertexPriorityDistance: 0.0025, // defaults to 1.25
|
||||
},
|
||||
guides: true,
|
||||
guides: false,
|
||||
});
|
||||
|
||||
// map.addControl(drawTool, 'top-right');
|
||||
};
|
||||
|
||||
// 将图斑复制到指定图层
|
||||
const handlerCopyToTargetLayer = (e) => {
|
||||
if (e.features.length > 0) {
|
||||
if (popup) {
|
||||
popup.remove();
|
||||
popup = null;
|
||||
map.addControl(drawTool, 'top-right');
|
||||
setTimeout(() => {
|
||||
document.querySelector(".mapbox-gl-draw_polygon")?.setAttribute("title", "绘制图斑");
|
||||
document.querySelector(".mapbox-gl-draw_trash")?.setAttribute("title", "删除图斑");
|
||||
}, 500);
|
||||
// let featureList:any = []
|
||||
props.geoms.forEach(item => {
|
||||
const geojsonPolygon = WktToGeojson(item.geom)
|
||||
console.log('geojsonPolygon',geojsonPolygon)
|
||||
const featureId = `polygon-${generateUUID()}`;
|
||||
let properties = {}
|
||||
if(props.isRead){
|
||||
properties = { user_static: true}
|
||||
}
|
||||
|
||||
selectFeature = e.features[0];
|
||||
|
||||
popup = new mapboxgl.Popup({
|
||||
closeButton: false,
|
||||
closeOnClick: false,
|
||||
});
|
||||
|
||||
// 设置 popup 的位置和内容
|
||||
popup
|
||||
.setLngLat(clickPoisition)
|
||||
.setHTML(
|
||||
`
|
||||
<div style="color:#333;padding:3px 12px;cursor:pointer;" type="primary" icon="el-icon-search" onclick="handlerCopyFeature();">复制当前图斑</div>`,
|
||||
)
|
||||
.addTo(map);
|
||||
} else {
|
||||
popup.remove();
|
||||
const feature = {
|
||||
id: featureId,
|
||||
type: "Feature",
|
||||
properties,
|
||||
geometry: geojsonPolygon
|
||||
};
|
||||
geoms.value.push(feature)
|
||||
// featureList.push(feature)
|
||||
hasPolygon = true
|
||||
drawTool.add(feature);
|
||||
})
|
||||
// console.log('featureList',featureList)
|
||||
// drawTool.add(featureList);
|
||||
}
|
||||
drawing.value = true;
|
||||
};
|
||||
|
||||
const handlerCopyFeature = () => {
|
||||
console.log(selectFeature);
|
||||
popup.remove();
|
||||
createMessage.success('复制成功!');
|
||||
};
|
||||
|
||||
const handlerRenderLayer = (layer) => {
|
||||
console.log('layerInfo', layer);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
handlerRenderLayer,
|
||||
// 数据绘制完成判断
|
||||
const handlerDealFeature = (feature) => {
|
||||
if(map.getLayer('area-label')){
|
||||
map.removeLayer('area-label');
|
||||
map.removeSource('area-label');
|
||||
}
|
||||
let area = turf.area(feature);
|
||||
const centroid = turf.centroid(feature);
|
||||
let labelText = `${area.toFixed(2)} m²`;
|
||||
map.addLayer({
|
||||
id: `area-label`, // 确保 ID 唯一
|
||||
type: "symbol",
|
||||
source: {
|
||||
type: "geojson",
|
||||
data: centroid
|
||||
},
|
||||
layout: {
|
||||
"text-field": labelText, // 显示面积
|
||||
"text-size": 14,
|
||||
"text-anchor": "center"
|
||||
},
|
||||
paint: {
|
||||
"text-color": "#ff0000"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const handlerLayerControler = (layerInfo) => {
|
||||
|
||||
layerInfo.layer = layerInfo.layer? layerInfo.layer : JSON.parse(layerInfo.style);
|
||||
|
||||
if (map.getSource(layerInfo.layer.id)) {
|
||||
if (layerInfo.checked) {
|
||||
map.setLayoutProperty(layerInfo.layer.id, 'visibility', 'visible');
|
||||
} else {
|
||||
map.setLayoutProperty(layerInfo.layer.id, 'visibility', 'none');
|
||||
let existFeature = geoms.value.find((item, index) => {
|
||||
return item.id == feature.id;
|
||||
});
|
||||
if (existFeature) {
|
||||
// 如果查找到了 则替换数据
|
||||
for (let i = 0; i < geoms.value.length; i++) {
|
||||
if (geoms.value[i].id == feature.id) {
|
||||
geoms.value[i] = feature;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
map.addLayer(layerInfo.layer);
|
||||
map.on('click', layerInfo.layer.id, function (e) {
|
||||
handlerPreviewFeatureInfo(e);
|
||||
// 如果没找到数据则添加到数组
|
||||
geoms.value.push(feature);
|
||||
}
|
||||
// 自动将数据返回给父组件
|
||||
handlerDrawComplete();
|
||||
};
|
||||
// 删除数据
|
||||
const handlerDeleteFeature = (feature) => {
|
||||
if(map.getLayer('area-label')){
|
||||
map.removeLayer('area-label');
|
||||
map.removeSource('area-label');
|
||||
}
|
||||
for (let i = 0; i < geoms.value.length; i++) {
|
||||
if (geoms.value[i].id == feature.id) {
|
||||
geoms.value.splice(i, 1);
|
||||
}
|
||||
}
|
||||
handlerDrawComplete();
|
||||
};
|
||||
const handlerDrawComplete = () => {
|
||||
let arr = [];
|
||||
geoms.value.forEach((item, index) => {
|
||||
let wktStr = GeojsonToWkt(item.geometry);
|
||||
let area = turf.area(item).toFixed(2)
|
||||
let obj = {
|
||||
area: area,
|
||||
geom: wktStr,
|
||||
};
|
||||
arr.push(obj);
|
||||
});
|
||||
}
|
||||
emit('handlerDrawComplete', arr);
|
||||
};
|
||||
|
||||
// 图斑属性查看
|
||||
const handlerPreviewFeatureInfo = (e) => {
|
||||
if (e.features) {
|
||||
openRightInfo.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const DataTableList = ref<Boolean>(false);
|
||||
const handlerGetTableList = ()=>{
|
||||
DataTableList.value = true;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cloud-query-div {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 10px;
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cloud-query-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.map-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layer-control-center {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.layer-control-center p {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.layer-control-center .ant-checkbox-wrapper {
|
||||
}
|
||||
|
||||
.draw-control-center {
|
||||
position: absolute;
|
||||
padding: 7px;
|
||||
padding: 8px;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background: #ffffff;
|
||||
|
|
@ -473,7 +327,7 @@
|
|||
|
||||
.draw-control-center .draw-btn {
|
||||
float: left;
|
||||
margin: 0px 6px;
|
||||
margin: 0px 7px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
|
@ -488,11 +342,10 @@
|
|||
}
|
||||
|
||||
.mapboxgl-ctrl-group {
|
||||
padding: 10px;
|
||||
padding: 6px;
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
right: 140px;
|
||||
top: 5px;
|
||||
right: 0px;
|
||||
}
|
||||
.mapbox-gl-draw_ctrl-draw-btn {
|
||||
width: 20px !important;
|
||||
|
|
@ -501,6 +354,7 @@
|
|||
}
|
||||
|
||||
.mapboxgl-ctrl-top-right {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group button + button {
|
||||
|
|
@ -552,24 +406,211 @@
|
|||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.layer-workspace{
|
||||
position:absolute;
|
||||
top:0px;
|
||||
left:0px;
|
||||
height:100%;
|
||||
|
||||
.jas-ctrl-measure {
|
||||
position: relative;
|
||||
top: 6px;
|
||||
right: 10px;
|
||||
}
|
||||
.close-layer-workspace{
|
||||
|
||||
.jas-ctrl-measure-item {
|
||||
height: 22px;
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.layer-item {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.layer-item:hover {
|
||||
background: #c7dcf580;
|
||||
}
|
||||
|
||||
::v-deep .ant-collapse-content-box {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
::v-deep .jas-ctrl-extend-desktop-container {
|
||||
width: 320px !important;
|
||||
}
|
||||
|
||||
.position-by-lnglat {
|
||||
height: 29px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
bottom:20px;
|
||||
left:355px;
|
||||
width:30px;
|
||||
height:30px;
|
||||
background:#e0e5ed;
|
||||
z-index:9999;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
border-top-right-radius:4px;
|
||||
border-bottom-right-radius:4px;
|
||||
top: 10px;
|
||||
right: 131px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
.to-location {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/location.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.picture-azimuth {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/is_show_picture.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.picture-azimuth-active {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/not_show_picture.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.draw-polygon {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/draw_polygon.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.split-line {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/split_polygon.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.split-polygon {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
float: left;
|
||||
background: url(/map/split_polygon_polygon.png);
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.to-location-input {
|
||||
padding: 16px;
|
||||
padding-right: 4px;
|
||||
width: 418px;
|
||||
min-height: 60px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
right: 10px;
|
||||
z-index: 999999;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
||||
.location-operation {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.location-item-list-coantienr {
|
||||
width: 100%;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
.location-item {
|
||||
line-height: 20px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.split-panel-item:hover {
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.cloudqueryNotice {
|
||||
background: rgba(0, 0, 0, 0.53);
|
||||
padding: 0px 14px;
|
||||
border-radius: 6px;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 5vw;
|
||||
width: 700px;
|
||||
color: #fff;
|
||||
z-index: 10;
|
||||
.cloudquery-title {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
.cloudquery-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.cloudquery-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
width: 130px;
|
||||
}
|
||||
img {
|
||||
width: 34px;
|
||||
height: 29px;
|
||||
}
|
||||
.cloudquery-btn {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.line {
|
||||
background: #ededed;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
}
|
||||
.anticon.anticon-close {
|
||||
height: 30px;
|
||||
}
|
||||
button {
|
||||
width: 70px;
|
||||
height: 26px;
|
||||
background: linear-gradient(-74deg, #086dec, #0b4bdd);
|
||||
box-shadow: 3px 4px 5px 1px rgba(13, 13, 13, 0.05);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.title-box {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
<template>
|
||||
<div class="process-design" :style="'display: flex; height:' + data.height">
|
||||
<bpmn-process-designer
|
||||
v-model="data.xmlString"
|
||||
v-bind="data.controlForm"
|
||||
keyboard
|
||||
ref="processDesigner"
|
||||
:events="[
|
||||
'element.click',
|
||||
'connection.added',
|
||||
'connection.removed',
|
||||
'shape.removed',
|
||||
'selection.changed',
|
||||
]"
|
||||
@connection-added="connectionAdded"
|
||||
@connection-removed="connectionRemoved"
|
||||
@shape-removed="shapeRemoved"
|
||||
@element-click="elementClick"
|
||||
@init-finished="initModeler"
|
||||
@save="onSaveProcess"
|
||||
:schemeCode="data.schemeCode"
|
||||
:pageFlow="data.pageFlow"
|
||||
:pageType="data.pageType"
|
||||
/>
|
||||
<!-- 属性面板 -->
|
||||
<bmpn-process-penal
|
||||
:bpmn-modeler="data.modeler"
|
||||
:prefix="data.controlForm.prefix"
|
||||
class="process-panel"
|
||||
ref="processPanel"
|
||||
:schemeCode="data.schemeCode"
|
||||
:pageView="data.pageView"
|
||||
:pageType="data.pageType"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, defineProps, defineEmits, ref, watch } from 'vue';
|
||||
import './package/theme/index.scss';
|
||||
// 流程面板和属性面板
|
||||
import { BpmnProcessDesigner, BmpnProcessPenal } from './package/index';
|
||||
import 'highlight.js/styles/atom-one-dark-reasonable.css';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const emit = defineEmits(['save']);
|
||||
// Vue.use(vuePlugin);
|
||||
const props = defineProps({
|
||||
// 编辑模板,code供详情接口使用
|
||||
schemeCode: {
|
||||
type: String,
|
||||
},
|
||||
// 预览历史版本wfData
|
||||
pageView: String,
|
||||
// 分辨是不是预览历史版本detial
|
||||
pageType: String,
|
||||
// 流程图
|
||||
pageFlow: String,
|
||||
bpmnXml: String,
|
||||
});
|
||||
const data = reactive({
|
||||
height: document.documentElement.clientHeight - 144.5 + 'px;',
|
||||
xmlString: props.bpmnXml,
|
||||
modeler: null,
|
||||
controlForm: {
|
||||
prefix: 'flowable',
|
||||
},
|
||||
element: null,
|
||||
schemeCode: props.schemeCode,
|
||||
pageView: props.pageView,
|
||||
pageType: props.pageType,
|
||||
pageFlow: props.pageFlow,
|
||||
});
|
||||
watch(
|
||||
() => props.pageView,
|
||||
(newVal) => {
|
||||
data.pageView = newVal;
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.pageFlow,
|
||||
(newVal) => {
|
||||
data.pageFlow = newVal;
|
||||
},
|
||||
);
|
||||
if (props.pageType == 'detail') {
|
||||
data.height = document.documentElement.clientHeight - 294.5 + 'px;';
|
||||
}
|
||||
const processPanel = ref<any>();
|
||||
const processDesigner = ref<any>();
|
||||
async function getFlow() {
|
||||
let panel = await processPanel.value.getPanel();
|
||||
let flow = await processDesigner.value.getFlow();
|
||||
panel.scheme.flowContent = flow;
|
||||
return panel;
|
||||
// return {
|
||||
// panel:panel,
|
||||
// flowContent:flow
|
||||
// };
|
||||
}
|
||||
async function validateFlow() {
|
||||
let res = await processPanel.value.validatePanel();
|
||||
return res;
|
||||
}
|
||||
defineExpose({
|
||||
getFlow,
|
||||
validateFlow,
|
||||
});
|
||||
function connectionAdded(node) {
|
||||
console.log('connectionAdded');
|
||||
data.element = node;
|
||||
if (node.type == 'bpmn:SequenceFlow') {
|
||||
const element = {
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
lineConditions: '',
|
||||
from: node.source.id,
|
||||
isInit: true,
|
||||
to: node.target.id,
|
||||
};
|
||||
flowWfDataStore.setWfDataNode(element);
|
||||
}
|
||||
}
|
||||
function connectionRemoved(node) {
|
||||
flowWfDataStore.deleteWfData(node);
|
||||
}
|
||||
function shapeRemoved(node) {
|
||||
flowWfDataStore.deleteWfData(node);
|
||||
}
|
||||
function elementClick(element) {
|
||||
data.element = element;
|
||||
}
|
||||
function initModeler(modeler) {
|
||||
setTimeout(() => {
|
||||
data.modeler = modeler;
|
||||
}, 10);
|
||||
}
|
||||
function onSaveProcess(saveData) {
|
||||
emit('save', saveData);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
body {
|
||||
overflow: auto !important;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
body * {
|
||||
/* 滚动条 */
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: #fff;
|
||||
/*滚动条的背景颜色*/
|
||||
-webkit-border-radius: 0;
|
||||
/*滚动条的圆角宽度*/
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
/*滚动条的宽度*/
|
||||
height: 8px;
|
||||
/*滚动条的高度*/
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:vertical {
|
||||
/*垂直滚动条的样式*/
|
||||
height: 50px;
|
||||
background-color: rgba(153, 153, 153, 0.5);
|
||||
-webkit-border-radius: 4px;
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: -2px;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
/*滚动条的hover样式*/
|
||||
background-color: rgba(159, 159, 159, 0.3);
|
||||
-webkit-border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
/*滚动条的hover样式*/
|
||||
background-color: rgba(159, 159, 159, 0.5);
|
||||
-webkit-border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.process-panel {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
<template>
|
||||
<div class="my-process-designer">
|
||||
<div class="my-process-designer__header">
|
||||
<slot name="control-header"></slot>
|
||||
<template v-if="!$slots['control-header']">
|
||||
<div slot="content">
|
||||
<!-- <a-button :type="headerButtonType" :icon="h(SaveOutlined)" @click="onSave" class="ml-2">保存流程 </a-button> -->
|
||||
<!-- <a-button :type="headerButtonType" @click="previewProcessXML" class="ml-2">预览XML</a-button> -->
|
||||
<!-- <a-button :type="headerButtonType" @click="previewProcessJson" class="ml-2">预览JSON</a-button> -->
|
||||
<!-- <a-button :type="headerButtonType" @click="getFlow" class="ml-2">获取json</a-button> -->
|
||||
|
||||
<a-space>
|
||||
<a-tooltip placement="bottom" class="ml-2" title="缩小视图">
|
||||
<a-button
|
||||
:disabled="process.defaultZoom <= 0.3"
|
||||
:icon="h(ZoomOutOutlined)"
|
||||
@click="processZoomOut()"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-button>{{ Math.floor(process.defaultZoom * 10 * 10) + '%' }}</a-button>
|
||||
<a-tooltip placement="bottom" title="放大视图">
|
||||
<a-button
|
||||
:disabled="process.defaultZoom >= 3.9"
|
||||
:icon="h(ZoomInOutlined)"
|
||||
@click="processZoomIn()"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" title="撤销" class="ml-2">
|
||||
<a-button :icon="h(RotateLeftOutlined)" @click="processUndo()" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" title="恢复">
|
||||
<a-button :icon="h(RotateRightOutlined)" @click="processRedo()" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" title="重新绘制">
|
||||
<a-button :icon="h(ClearOutlined)" @click="processRestart()" />
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
<div> </div>
|
||||
</div>
|
||||
<div class="my-process-designer__container">
|
||||
<div class="my-process-designer__canvas" ref="bpmn-canvas" id="bpmn-canvas"></div>
|
||||
</div>
|
||||
<a-modal v-model:open="process.previewModelVisible" width="60%" title="预览">
|
||||
<highlightjs
|
||||
:language="process.previewType"
|
||||
:code="process.previewResult"
|
||||
style="height: 60vh"
|
||||
/>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, provide, reactive, onMounted, defineProps, defineEmits, watch, nextTick } from 'vue';
|
||||
import {
|
||||
ZoomOutOutlined,
|
||||
ZoomInOutlined,
|
||||
RotateLeftOutlined,
|
||||
RotateRightOutlined,
|
||||
ClearOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
// 生产环境时优化
|
||||
import BpmnModeler from 'bpmn-js/lib/Modeler';
|
||||
import DefaultEmptyXML from './plugins/defaultEmpty';
|
||||
// 翻译方法(汉化)
|
||||
import customTranslate from './plugins/translate/customTranslate';
|
||||
import { getDetail } from '@/api/sys/WFSchemeInfo';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
// 引入json转换与高亮
|
||||
import convert from 'xml-js';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const emit = defineEmits([
|
||||
'init-finished',
|
||||
'event',
|
||||
'commandStack-changed',
|
||||
'input',
|
||||
'change',
|
||||
'canvas-viewbox-changed',
|
||||
'destroy',
|
||||
'save',
|
||||
'element-click',
|
||||
'connection-added',
|
||||
'connection-removed',
|
||||
'connection-changed',
|
||||
]);
|
||||
interface processType {
|
||||
defaultZoom: number;
|
||||
previewModelVisible: boolean;
|
||||
simulationStatus: boolean;
|
||||
previewResult: string;
|
||||
previewType: string;
|
||||
recoverable: boolean;
|
||||
revocable: boolean;
|
||||
bpmnModeler: any;
|
||||
}
|
||||
const process: processType = reactive({
|
||||
defaultZoom: 1,
|
||||
previewModelVisible: false,
|
||||
simulationStatus: false,
|
||||
previewResult: '',
|
||||
previewType: 'xml',
|
||||
recoverable: false,
|
||||
revocable: false,
|
||||
bpmnModeler: null,
|
||||
});
|
||||
const props = defineProps({
|
||||
processId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
processName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: String, // xml 字符串
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'flowable',
|
||||
},
|
||||
events: {
|
||||
type: Array,
|
||||
default: () => ['element.click'],
|
||||
},
|
||||
headerButtonSize: {
|
||||
type: String,
|
||||
default: 'small',
|
||||
validator: (value: any) => ['default', 'medium', 'small', 'mini'].indexOf(value) !== -1,
|
||||
},
|
||||
headerButtonType: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
validator: (value: any) =>
|
||||
['default', 'primary', 'success', 'warning', 'danger', 'info'].indexOf(value) !== -1,
|
||||
},
|
||||
schemeCode: String,
|
||||
pageFlow: String,
|
||||
pageType: String,
|
||||
});
|
||||
watch(
|
||||
() => props.pageFlow,
|
||||
(newVal) => {
|
||||
createNewDiagram(newVal);
|
||||
},
|
||||
);
|
||||
onMounted(() => {
|
||||
initBpmnModeler();
|
||||
createNewDiagram(props.value);
|
||||
if (props.schemeCode) {
|
||||
getDetailInfo(1);
|
||||
}
|
||||
if (props.pageType == 'detail') {
|
||||
createNewDiagram(props.pageFlow);
|
||||
}
|
||||
});
|
||||
async function getDetailInfo(a) {
|
||||
let data = await getDetail({ code: props.schemeCode });
|
||||
if (a == 1) {
|
||||
createNewDiagram(data.scheme.flowContent);
|
||||
}
|
||||
}
|
||||
// function onSave() {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// if (process.bpmnModeler == null) {
|
||||
// reject();
|
||||
// }
|
||||
// process.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
// emit('save', xml);
|
||||
// resolve(xml);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
function initBpmnModeler() {
|
||||
if (process.bpmnModeler) return;
|
||||
process.bpmnModeler = new BpmnModeler({
|
||||
container: '#bpmn-canvas',
|
||||
additionalModules: [
|
||||
{
|
||||
translate: ['value', customTranslate],
|
||||
},
|
||||
],
|
||||
});
|
||||
emit('init-finished', process.bpmnModeler);
|
||||
initModelListeners();
|
||||
}
|
||||
function initModelListeners() {
|
||||
const EventBus = process.bpmnModeler.get('eventBus');
|
||||
// 注册需要的监听事件, 将. 替换为 - , 避免解析异常
|
||||
props.events.forEach((event) => {
|
||||
EventBus.on(event, function (eventObj) {
|
||||
provide('wfdesign', eventObj);
|
||||
let eventName = event.replace('.', '-');
|
||||
let element = eventObj ? eventObj.element : null;
|
||||
emit(eventName, element, eventObj);
|
||||
emit('event', eventName, element, eventObj);
|
||||
// 修改脚本节点的名称
|
||||
// if (
|
||||
// eventObj.newSelection &&
|
||||
// eventObj.newSelection.length > 0 &&
|
||||
// eventObj.newSelection[0].type === 'bpmn:ScriptTask'
|
||||
// ) {
|
||||
// let name = eventObj.newSelection[0].di.bpmnElement.name
|
||||
// ? eventObj.newSelection[0].di.bpmnElement.name
|
||||
// : '';
|
||||
// name = '脚本节点';
|
||||
// nextTick(function () {
|
||||
// const modeling = process.bpmnModeler.get('modeling');
|
||||
// if (eventObj.newSelection[0].type == 'label') return;
|
||||
// //除了task节点,其他节点都会生成一个label容器
|
||||
// //所以不需要再给label节点添加,不然无法显示文字
|
||||
// modeling.updateProperties(eventObj.newSelection[0], { name });
|
||||
// flowWfDataStore.setWfDataName(eventObj.newSelection[0].id, name);
|
||||
// });
|
||||
// }
|
||||
});
|
||||
});
|
||||
process.bpmnModeler.on('element.click', ({ element }) => {
|
||||
if (element) {
|
||||
flowWfDataStore.setWfDataName(element.di.bpmnElement.id, element.di.bpmnElement.name);
|
||||
}
|
||||
});
|
||||
// 监听图形改变返回xml
|
||||
EventBus.on('commandStack.changed', async (event) => {
|
||||
console.log('commandStack.changed');
|
||||
console.log(event);
|
||||
await process.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
const json = JSON.parse(convert.xml2json(xml, { spaces: 2 }));
|
||||
const flowElements = json.elements[0].elements[0].elements;
|
||||
console.log(flowElements);
|
||||
for (var j = 0; j < flowElements.length; j++) {
|
||||
if (flowElements[j].attributes.name) {
|
||||
flowWfDataStore.setWfDataName(
|
||||
flowElements[j].attributes.id,
|
||||
flowElements[j].attributes.name,
|
||||
);
|
||||
}
|
||||
if (flowElements[j].name == 'bpmn2:sequenceFlow') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(flowElements[j].attributes.id);
|
||||
if (currentNode) {
|
||||
flowWfDataStore.updataWfDataNode(
|
||||
flowElements[j].attributes.id,
|
||||
'from',
|
||||
flowElements[j].attributes.sourceRef,
|
||||
);
|
||||
flowWfDataStore.updataWfDataNode(
|
||||
flowElements[j].attributes.id,
|
||||
'to',
|
||||
flowElements[j].attributes.targetRef,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// try {
|
||||
// process.recoverable = process.bpmnModeler.get('commandStack').canRedo();
|
||||
// process.revocable = process.bpmnModeler.get('commandStack').canUndo();
|
||||
// let { xml } = await process.bpmnModeler.saveXML({ format: true });
|
||||
// emit('commandStack-changed', event);
|
||||
// emit('input', xml);
|
||||
// emit('change', xml);
|
||||
// } catch (e) {
|
||||
// console.error(`[Process Designer Warn]: ${e.message || e}`);
|
||||
// }
|
||||
});
|
||||
// 监听视图缩放变化
|
||||
process.bpmnModeler.on('canvas.viewbox.changed', ({ viewbox }) => {
|
||||
emit('canvas-viewbox-changed', { viewbox });
|
||||
const { scale } = viewbox;
|
||||
process.defaultZoom = Math.floor(scale * 100) / 100;
|
||||
});
|
||||
process.bpmnModeler.on('shape.added', (e) => {
|
||||
let addElement = e.element;
|
||||
let name = addElement.di.bpmnElement.name ? addElement.di.bpmnElement.name : '';
|
||||
if (name == '') {
|
||||
console.log(addElement.type);
|
||||
switch (addElement.type) {
|
||||
case 'bpmn:Task':
|
||||
name = '任务节点';
|
||||
nextTick(function () {
|
||||
const modeling = process.bpmnModeler.get('modeling');
|
||||
if (addElement.type == 'label') return;
|
||||
//除了task节点,其他节点都会生成一个label容器
|
||||
//所以不需要再给label节点添加,不然无法显示文字
|
||||
modeling.updateProperties(addElement, { name });
|
||||
flowWfDataStore.setWfDataName(addElement.id, name);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/* 创建新的流程图 */
|
||||
async function createNewDiagram(xml) {
|
||||
// 将字符串转换成图显示出来
|
||||
let newId = props.processId || `Process_${new Date().getTime()}`;
|
||||
let newName = props.processName || `业务流程_${new Date().getTime()}`;
|
||||
let xmlString = xml || DefaultEmptyXML(newId, newName, props.prefix);
|
||||
try {
|
||||
let { warnings } = await process.bpmnModeler.importXML(xmlString);
|
||||
if (warnings && warnings.length) {
|
||||
warnings.forEach((warn) => console.warn(warn));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[Process Designer Warn]: ${e.message || e}`);
|
||||
}
|
||||
}
|
||||
|
||||
function processRedo() {
|
||||
process.bpmnModeler.get('commandStack').redo();
|
||||
}
|
||||
function processUndo() {
|
||||
process.bpmnModeler.get('commandStack').undo();
|
||||
}
|
||||
function processZoomIn(zoomStep = 0.1) {
|
||||
let newZoom = Math.floor(process.defaultZoom * 100 + zoomStep * 100) / 100;
|
||||
if (newZoom > 4) {
|
||||
throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4');
|
||||
}
|
||||
process.defaultZoom = newZoom;
|
||||
process.bpmnModeler.get('canvas').zoom(process.defaultZoom);
|
||||
}
|
||||
function processZoomOut(zoomStep = 0.1) {
|
||||
let newZoom = Math.floor(process.defaultZoom * 100 - zoomStep * 100) / 100;
|
||||
if (newZoom < 0.2) {
|
||||
throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2');
|
||||
}
|
||||
process.defaultZoom = newZoom;
|
||||
process.bpmnModeler.get('canvas').zoom(process.defaultZoom);
|
||||
}
|
||||
function processRestart() {
|
||||
process.recoverable = false;
|
||||
process.revocable = false;
|
||||
createNewDiagram(null).then(() => process.bpmnModeler.get('canvas').zoom(1, 'auto'));
|
||||
flowWfDataStore.setWfDataAll([]);
|
||||
}
|
||||
/*----------------------------- 方法结束 ---------------------------------*/
|
||||
// function previewProcessXML() {
|
||||
// // process.bpmnModeler.get('elementRegistry').get(图形id)
|
||||
// // 通过图形id获取图形shape节点对象
|
||||
// process.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
// console.log(xml);
|
||||
// process.previewResult = xml;
|
||||
// process.previewType = 'xml';
|
||||
// process.previewModelVisible = true;
|
||||
// });
|
||||
// }
|
||||
// function previewProcessJson() {
|
||||
// process.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
// process.previewResult = convert.xml2json(xml, { spaces: 2 });
|
||||
// console.log(process.previewResult);
|
||||
// process.previewType = 'json';
|
||||
// process.previewModelVisible = true;
|
||||
// });
|
||||
// }
|
||||
async function getFlow() {
|
||||
let flowContent;
|
||||
let flowElements;
|
||||
await process.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
flowContent = xml;
|
||||
const json = JSON.parse(convert.xml2json(xml, { spaces: 2 }));
|
||||
flowElements = json.elements[0].elements[0].elements;
|
||||
flowWfDataStore.setElments(flowElements);
|
||||
});
|
||||
return flowContent;
|
||||
}
|
||||
defineExpose({
|
||||
getFlow,
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
::v-deep .bjs-container a {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import BpmnProcessDesigner from './ProcessDesigner.vue';
|
||||
|
||||
BpmnProcessDesigner.install = function (Vue) {
|
||||
Vue.component(BpmnProcessDesigner.name, BpmnProcessDesigner);
|
||||
};
|
||||
|
||||
export default BpmnProcessDesigner;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
export default (key, name, type) => {
|
||||
if (!type) type = "camunda";
|
||||
const TYPE_TARGET = {
|
||||
activiti: "http://activiti.org/bpmn",
|
||||
camunda: "http://bpmn.io/schema/bpmn",
|
||||
flowable: "http://flowable.org/bpmn"
|
||||
};
|
||||
return `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn2:definitions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
||||
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
|
||||
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
|
||||
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
|
||||
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
|
||||
id="diagram_${key}"
|
||||
targetNamespace="${TYPE_TARGET[type]}">
|
||||
<bpmn2:process id="${key}" name="${name}" isExecutable="true">
|
||||
</bpmn2:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${key}">
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn2:definitions>
|
||||
`;
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import translations from './zh.ts'
|
||||
|
||||
export default function customTranslate(template, replacements) {
|
||||
replacements = replacements || {}
|
||||
template = translations[template] || template
|
||||
return template.replace(/{([^}]+)}/g, function (_, key) {
|
||||
return replacements[key] || '{' + key + '}'
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
export default {
|
||||
'Activate hand tool': '激活抓手工具',
|
||||
'Activate lasso tool': '激活套索工具',
|
||||
'Activate create/remove space tool': '激活创建/删除空间工具',
|
||||
'Activate global connect tool': '激活全局连接工具',
|
||||
'Create start event': '创建开始事件',
|
||||
'Create end event': '创建结束事件',
|
||||
'Create task': '创建任务',
|
||||
'Create user task': '创建用户任务',
|
||||
'Create gateway': '创建网关',
|
||||
'Create data object reference': '创建数据对象',
|
||||
'Create data store reference': '创建数据存储',
|
||||
'Create group': '创建分组',
|
||||
'Create intermediate/boundary event': '创建中间/边界事件',
|
||||
'Create expanded sub-process': '创建扩展子过程',
|
||||
'Create pool/participant': '创建池/参与者',
|
||||
'Change element': '修改类型',
|
||||
Delete: '移除',
|
||||
'Append end event': '追加结束事件',
|
||||
'Append gateway': '追加网关',
|
||||
'Append task': '追加任务',
|
||||
'Append intermediate/boundary event': '追加中间抛出事件/边界事件',
|
||||
'Add text annotation': '添加 text annotation',
|
||||
'Connect using association': '使用关联连接',
|
||||
'Connect to other element': '消息关联',
|
||||
'Start event': '开始事件',
|
||||
'End event': '结束事件',
|
||||
'Message intermediate catch event': '消息中间捕获事件',
|
||||
'Message intermediate throw event': '消息中间抛出事件',
|
||||
'Timer intermediate catch event': '定时中间捕获事件',
|
||||
'Escalation intermediate throw event': '升级中间抛出事件',
|
||||
'Conditional intermediate catch event': '条件中间捕获事件',
|
||||
'Link intermediate catch event': '链接中间捕获事件',
|
||||
'Link intermediate throw event': '链接中间抛出事件',
|
||||
'Compensation intermediate throw event': '补偿中间抛出事件',
|
||||
'Signal intermediate catch event': '信号中间捕获事件',
|
||||
'Signal intermediate throw event': '信号中间抛出事件',
|
||||
Task: '任务',
|
||||
'User task': '用户任务',
|
||||
'Service task': '服务任务',
|
||||
'Send task': '发送任务',
|
||||
'Receive task': '、接收任务',
|
||||
'Manual task': '手动任务',
|
||||
'Business rule task': '业务规则任务',
|
||||
'Script task': '脚本任务',
|
||||
'Call activity': '调用活动',
|
||||
'Sub-process (collapsed)': '子流程(已折叠)',
|
||||
'Sub-process (expanded)': '子流程(扩大)',
|
||||
'Intermediate throw event': '中间抛出事件',
|
||||
'Message start event': '消息开始事件',
|
||||
'Timer start event': '定时开始事件',
|
||||
'Conditional start event': '条件开始事件',
|
||||
'Signal start event': '信号开始事件',
|
||||
'Exclusive gateway': '排他网关',
|
||||
'Parallel gateway': '并行网关',
|
||||
'Inclusive gateway': '包容网关',
|
||||
'Complex gateway': '复杂网关',
|
||||
'Event-based gateway': '事件网关',
|
||||
'Message end event': '消息结束事件',
|
||||
'Escalation end event': '升级结束事件',
|
||||
'Error end event': '错误结束事件',
|
||||
'Compensation end event': '补偿结束事件',
|
||||
'Signal end event': '信号结束事件',
|
||||
'Terminate end event': '终止结束事件',
|
||||
|
||||
Process: '业务流程',
|
||||
'Append EndEvent': '追加结束事件',
|
||||
'Append Gateway': '追加网关',
|
||||
'Append Task': '追加任务',
|
||||
'Append Intermediate/Boundary Event': '追加中间抛出事件/边界事件',
|
||||
'Activate the global connect tool': '激活全局连接工具',
|
||||
'Append {type}': '添加 {type}',
|
||||
'Add Lane above': '在上面添加道',
|
||||
'Divide into two Lanes': '分割成两个道',
|
||||
'Divide into three Lanes': '分割成三个道',
|
||||
'Add Lane below': '在下面添加道',
|
||||
'Append compensation activity': '追加补偿活动',
|
||||
'Change type': '修改类型',
|
||||
'Connect using Association': '使用关联连接',
|
||||
'Connect using Sequence/MessageFlow or Association': '使用顺序/消息流或者关联连接',
|
||||
'Connect using DataInputAssociation': '使用数据输入关联连接',
|
||||
Remove: '移除',
|
||||
'Activate the hand tool': '激活抓手工具',
|
||||
'Activate the lasso tool': '激活套索工具',
|
||||
'Activate the create/remove space tool': '激活创建/删除空间工具',
|
||||
'Create expanded SubProcess': '创建扩展子过程',
|
||||
'Create IntermediateThrowEvent/BoundaryEvent': '创建中间抛出事件/边界事件',
|
||||
'Create Pool/Participant': '创建池/参与者',
|
||||
'Parallel Multi Instance': '并行多重事件',
|
||||
'Sequential Multi Instance': '时序多重事件',
|
||||
DataObjectReference: '数据对象参考',
|
||||
DataStoreReference: '数据存储参考',
|
||||
Loop: '循环',
|
||||
'Ad-hoc': '即席',
|
||||
'Create {type}': '创建 {type}',
|
||||
'Create StartEvent': '创建开始事件',
|
||||
'Create EndEvent': '创建结束事件',
|
||||
'Create Task': '创建任务',
|
||||
'Create User Task': '创建用户任务',
|
||||
'Create Gateway': '创建网关',
|
||||
'Create DataObjectReference': '创建数据对象',
|
||||
'Create DataStoreReference': '创建数据存储',
|
||||
'Create Group': '创建分组',
|
||||
'Create Intermediate/Boundary Event': '创建中间/边界事件',
|
||||
'Message Start Event': '消息开始事件',
|
||||
'Timer Start Event': '定时开始事件',
|
||||
'Conditional Start Event': '条件开始事件',
|
||||
'Signal Start Event': '信号开始事件',
|
||||
'Error Start Event': '错误开始事件',
|
||||
'Escalation Start Event': '升级开始事件',
|
||||
'Compensation Start Event': '补偿开始事件',
|
||||
'Message Start Event (non-interrupting)': '消息开始事件(非中断)',
|
||||
'Timer Start Event (non-interrupting)': '定时开始事件(非中断)',
|
||||
'Conditional Start Event (non-interrupting)': '条件开始事件(非中断)',
|
||||
'Signal Start Event (non-interrupting)': '信号开始事件(非中断)',
|
||||
'Escalation Start Event (non-interrupting)': '升级开始事件(非中断)',
|
||||
'Message Intermediate Catch Event': '消息中间捕获事件',
|
||||
'Message Intermediate Throw Event': '消息中间抛出事件',
|
||||
'Timer Intermediate Catch Event': '定时中间捕获事件',
|
||||
'Escalation Intermediate Throw Event': '升级中间抛出事件',
|
||||
'Conditional Intermediate Catch Event': '条件中间捕获事件',
|
||||
'Link Intermediate Catch Event': '链接中间捕获事件',
|
||||
'Link Intermediate Throw Event': '链接中间抛出事件',
|
||||
'Compensation Intermediate Throw Event': '补偿中间抛出事件',
|
||||
'Signal Intermediate Catch Event': '信号中间捕获事件',
|
||||
'Signal Intermediate Throw Event': '信号中间抛出事件',
|
||||
'Collapsed Pool': '折叠池',
|
||||
'Expanded Pool': '展开池',
|
||||
'no parent for {element} in {parent}': '在{parent}里,{element}没有父类',
|
||||
'no shape type specified': '没有指定的形状类型',
|
||||
'flow elements must be children of pools/participants': '流元素必须是池/参与者的子类',
|
||||
'out of bounds release': 'out of bounds release',
|
||||
'more than {count} child lanes': '子道大于{count} ',
|
||||
'element required': '元素不能为空',
|
||||
'diagram not part of bpmn:Definitions': '流程图不符合bpmn规范',
|
||||
'no diagram to display': '没有可展示的流程图',
|
||||
'no process or collaboration to display': '没有可展示的流程/协作',
|
||||
'element {element} referenced by {referenced}#{property} not yet drawn':
|
||||
'由{referenced}#{property}引用的{element}元素仍未绘制',
|
||||
'already rendered {element}': '{element} 已被渲染',
|
||||
'failed to import {element}': '导入{element}失败',
|
||||
Id: '编号',
|
||||
Name: '名称',
|
||||
General: '常规',
|
||||
Details: '详情',
|
||||
'Message Name': '消息名称',
|
||||
Message: '消息',
|
||||
Initiator: '创建者',
|
||||
'Asynchronous Continuations': '持续异步',
|
||||
'Asynchronous Before': '异步前',
|
||||
'Asynchronous After': '异步后',
|
||||
'Job Configuration': '工作配置',
|
||||
Exclusive: '排除',
|
||||
'Job Priority': '工作优先级',
|
||||
'Retry Time Cycle': '重试时间周期',
|
||||
Documentation: '文档',
|
||||
'Element Documentation': '元素文档',
|
||||
'History Configuration': '历史配置',
|
||||
'History Time To Live': '历史的生存时间',
|
||||
Forms: '表单',
|
||||
'Form Key': '表单key',
|
||||
'Form Fields': '表单字段',
|
||||
'Business Key': '业务key',
|
||||
'Form Field': '表单字段',
|
||||
ID: '编号',
|
||||
Type: '类型',
|
||||
Label: '名称',
|
||||
'Default Value': '默认值',
|
||||
'Default Flow': '默认流转路径',
|
||||
'Conditional Flow': '条件流转路径',
|
||||
'Sequence Flow': '普通流转路径',
|
||||
Validation: '校验',
|
||||
'Add Constraint': '添加约束',
|
||||
Config: '配置',
|
||||
Properties: '属性',
|
||||
'Add Property': '添加属性',
|
||||
Value: '值',
|
||||
Listeners: '监听器',
|
||||
'Execution Listener': '执行监听',
|
||||
'Event Type': '事件类型',
|
||||
'Listener Type': '监听器类型',
|
||||
'Java Class': 'Java类',
|
||||
Expression: '表达式',
|
||||
'Must provide a value': '必须提供一个值',
|
||||
'Delegate Expression': '代理表达式',
|
||||
Script: '脚本',
|
||||
'Script Format': '脚本格式',
|
||||
'Script Type': '脚本类型',
|
||||
'Inline Script': '内联脚本',
|
||||
'External Script': '外部脚本',
|
||||
Resource: '资源',
|
||||
'Field Injection': '字段注入',
|
||||
Extensions: '扩展',
|
||||
'Input/Output': '输入/输出',
|
||||
'Input Parameters': '输入参数',
|
||||
'Output Parameters': '输出参数',
|
||||
Parameters: '参数',
|
||||
'Output Parameter': '输出参数',
|
||||
'Timer Definition Type': '定时器定义类型',
|
||||
'Timer Definition': '定时器定义',
|
||||
Date: '日期',
|
||||
Duration: '持续',
|
||||
Cycle: '循环',
|
||||
Signal: '信号',
|
||||
'Signal Name': '信号名称',
|
||||
Escalation: '升级',
|
||||
Error: '错误',
|
||||
'Link Name': '链接名称',
|
||||
Condition: '条件名称',
|
||||
'Variable Name': '变量名称',
|
||||
'Variable Event': '变量事件',
|
||||
'Specify more than one variable change event as a comma separated list.':
|
||||
'多个变量事件以逗号隔开',
|
||||
'Wait for Completion': '等待完成',
|
||||
'Activity Ref': '活动参考',
|
||||
'Version Tag': '版本标签',
|
||||
Executable: '可执行文件',
|
||||
'External Task Configuration': '扩展任务配置',
|
||||
'Task Priority': '任务优先级',
|
||||
External: '外部',
|
||||
Connector: '连接器',
|
||||
'Must configure Connector': '必须配置连接器',
|
||||
'Connector Id': '连接器编号',
|
||||
Implementation: '实现方式',
|
||||
'Field Injections': '字段注入',
|
||||
Fields: '字段',
|
||||
'Result Variable': '结果变量',
|
||||
Topic: '主题',
|
||||
'Configure Connector': '配置连接器',
|
||||
'Input Parameter': '输入参数',
|
||||
Assignee: '代理人',
|
||||
'Candidate Users': '候选用户',
|
||||
'Candidate Groups': '候选组',
|
||||
'Due Date': '到期时间',
|
||||
'Follow Up Date': '跟踪日期',
|
||||
Priority: '优先级',
|
||||
'The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
|
||||
'跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
|
||||
'The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
|
||||
'跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
|
||||
Variables: '变量',
|
||||
'Candidate Starter Configuration': '候选人起动器配置',
|
||||
'Candidate Starter Groups': '候选人起动器组',
|
||||
'This maps to the process definition key.': '这映射到流程定义键。',
|
||||
'Candidate Starter Users': '候选人起动器的用户',
|
||||
'Specify more than one user as a comma separated list.': '指定多个用户作为逗号分隔的列表。',
|
||||
'Tasklist Configuration': 'Tasklist配置',
|
||||
Startable: '启动',
|
||||
'Specify more than one group as a comma separated list.': '指定多个组作为逗号分隔的列表。',
|
||||
'Execution listeners': '执行监听器',
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
const hljs = require("highlight.js/lib/core");
|
||||
hljs.registerLanguage("xml", require("highlight.js/lib/languages/xml"));
|
||||
hljs.registerLanguage("json", require("highlight.js/lib/languages/json"));
|
||||
|
||||
export default hljs;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import BpmnProcessDesigner from "./designer/index.ts";
|
||||
import BmpnProcessPenal from "./penal";
|
||||
|
||||
export {
|
||||
BpmnProcessDesigner,
|
||||
BmpnProcessPenal
|
||||
}
|
||||
|
|
@ -0,0 +1,591 @@
|
|||
<template>
|
||||
<div :class="prefixCls" style="width: 30%">
|
||||
<a-tabs v-model:activeKey="configActiveName">
|
||||
<a-tab-pane :tab="data.wfNodeName" key="1">
|
||||
<!-- 开始节点 -->
|
||||
<start-event-option
|
||||
ref="startRef"
|
||||
:element="data.currentWfNode"
|
||||
:pageType="props.pageType"
|
||||
:schemeCode="props.schemeCode"
|
||||
:class="data.currentWfNode.type == 'bpmn:StartEvent' ? '' : 'hidden'"
|
||||
/>
|
||||
<!-- 审核节点 -->
|
||||
<user-task-option
|
||||
ref="taskRef"
|
||||
:element="data.currentWfNode"
|
||||
:pageType="props.pageType"
|
||||
:class="data.currentWfNode.type == 'bpmn:Task' ? '' : 'hidden'"
|
||||
/>
|
||||
<!-- 结束节点 -->
|
||||
<end-event-option
|
||||
ref="endRef"
|
||||
:element="data.currentWfNode"
|
||||
:pageType="props.pageType"
|
||||
:class="data.currentWfNode.type == 'bpmn:EndEvent' ? '' : 'hidden'"
|
||||
/>
|
||||
<!-- 子流程 -->
|
||||
<subprocess-option
|
||||
ref="subprocessRef"
|
||||
:element="data.currentWfNode"
|
||||
:pageType="props.pageType"
|
||||
:class="data.currentWfNode.type == 'bpmn:SubProcess' ? '' : 'hidden'"
|
||||
/>
|
||||
<!-- 排他网关 -->
|
||||
<exclusive-gateway-option
|
||||
ref="exclusiveGatewayRef"
|
||||
:element="data.currentWfNode"
|
||||
:pageType="props.pageType"
|
||||
:class="data.currentWfNode.type == 'bpmn:ExclusiveGateway' ? '' : 'hidden'"
|
||||
/>
|
||||
<!-- 并行网关 -->
|
||||
<parallel-gateway-option
|
||||
ref="parallelGatewayRef"
|
||||
:element="data.currentWfNode"
|
||||
:pageType="props.pageType"
|
||||
:class="data.currentWfNode.type == 'bpmn:ParallelGateway' ? '' : 'hidden'"
|
||||
/>
|
||||
<!-- 包容网关 -->
|
||||
<!-- "bpmn:InclusiveGateway" -->
|
||||
<inclusive-gateway-option
|
||||
ref="inclusiveGatewayRef"
|
||||
:element="data.currentWfNode"
|
||||
:pageType="props.pageType"
|
||||
:class="data.currentWfNode.type == 'bpmn:InclusiveGateway' ? '' : 'hidden'"
|
||||
/>
|
||||
<!-- 线条 -->
|
||||
<myline-option
|
||||
ref="mylineRef"
|
||||
:element="data.currentWfNode"
|
||||
:pageType="props.pageType"
|
||||
:class="data.currentWfNode.type == 'bpmn:SequenceFlow' ? '' : 'hidden'"
|
||||
/>
|
||||
<!-- 脚本节点 -->
|
||||
<script-option
|
||||
ref="scriptRef"
|
||||
:element="data.currentWfNode"
|
||||
:pageType="props.pageType"
|
||||
:class="data.currentWfNode.type == 'bpmn:ScriptTask' ? '' : 'hidden'"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="流程属性" key="2">
|
||||
<shcemeinfo-config ref="shcemeinfoRef" :disabled="true" :pageType="props.pageType" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="发起权限" force-render v-if="props.pageType != 'detail'">
|
||||
<auth-config ref="authRef" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, onMounted, defineProps, watch, ref } from 'vue';
|
||||
import { useDesign } from '@/hooks/web/useDesign';
|
||||
import { lr_AESEncrypt } from '../utils';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
import { getDetail } from '@/api/sys/WFSchemeInfo';
|
||||
// 流程属性
|
||||
// 发起权限
|
||||
// 开始节点
|
||||
// 审核节点
|
||||
// 结束节点
|
||||
// 并行网关
|
||||
// 排他网关
|
||||
// 包容网关
|
||||
// 子流程
|
||||
// 线
|
||||
import {
|
||||
shcemeinfoConfig,
|
||||
authConfig,
|
||||
StartEventOption,
|
||||
userTaskOption,
|
||||
endEventOption,
|
||||
parallelGatewayOption,
|
||||
exclusiveGatewayOption,
|
||||
inclusiveGatewayOption,
|
||||
subprocessOption,
|
||||
mylineOption,
|
||||
scriptOption,
|
||||
} from './page';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const { prefixCls } = useDesign('process-property');
|
||||
const { createMessage } = useMessage();
|
||||
const props = defineProps({
|
||||
bpmnModeler: Object,
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'camunda',
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 520,
|
||||
},
|
||||
element: Object,
|
||||
schemeCode: String,
|
||||
pageType: String,
|
||||
pageView: String,
|
||||
});
|
||||
watch(
|
||||
() => props.pageView,
|
||||
(newVal) => {
|
||||
var content = JSON.parse(newVal);
|
||||
shcemeinfoRef.value.setForm(content);
|
||||
flowWfDataStore.setWfDataAll(content.wfData);
|
||||
},
|
||||
);
|
||||
const startRef = ref<any>();
|
||||
const shcemeinfoRef = ref<any>();
|
||||
const authRef = ref<any>();
|
||||
const taskRef = ref<any>();
|
||||
const endRef = ref<any>();
|
||||
const subprocessRef = ref<any>();
|
||||
const exclusiveGatewayRef = ref<any>();
|
||||
const parallelGatewayRef = ref<any>();
|
||||
const inclusiveGatewayRef = ref<any>();
|
||||
const configActiveName = ref('2');
|
||||
interface dataType {
|
||||
currentWfNode: any;
|
||||
wfNodeName: string;
|
||||
wfData: any;
|
||||
activeTab: string;
|
||||
elementId: string;
|
||||
elementType: string;
|
||||
elementBusinessObject: any; // 元素 businessObject 镜像,提供给需要做判断的组件使用
|
||||
conditionFormVisible: boolean; // 流转条件设置
|
||||
formVisible: boolean; // 表单配置
|
||||
}
|
||||
const data: dataType = reactive({
|
||||
currentWfNode: undefined,
|
||||
wfNodeName: '',
|
||||
wfData: [],
|
||||
activeTab: 'base',
|
||||
elementId: '',
|
||||
elementType: '',
|
||||
elementBusinessObject: {}, // 元素 businessObject 镜像,提供给需要做判断的组件使用
|
||||
conditionFormVisible: false, // 流转条件设置
|
||||
formVisible: false, // 表单配置
|
||||
});
|
||||
function initModels() {
|
||||
setTimeout(() => {
|
||||
getActiveElement();
|
||||
}, 10);
|
||||
}
|
||||
function getActiveElement() {
|
||||
// 初始第一个选中元素 bpmn:Process
|
||||
initFormOnChanged(null);
|
||||
props.bpmnModeler.on('import.done', (e) => {
|
||||
initFormOnChanged(null);
|
||||
});
|
||||
// 监听选择事件,修改当前激活的元素以及表单
|
||||
props.bpmnModeler.on('selection.changed', ({ newSelection }) => {
|
||||
initFormOnChanged(newSelection[0] || null);
|
||||
});
|
||||
props.bpmnModeler.on('element.changed', ({ element }) => {
|
||||
// 保证 修改 "默认流转路径" 类似需要修改多个元素的事件发生的时候,更新表单的元素与原选中元素不一致。
|
||||
if (element && element.id === data.elementId) {
|
||||
initFormOnChanged(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 初始化数据
|
||||
function initFormOnChanged(element) {
|
||||
if (element == null) {
|
||||
configActiveName.value = '2';
|
||||
data.wfNodeName = '';
|
||||
data.currentWfNode = {
|
||||
type: '',
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (element.type == 'bpmn:Process') {
|
||||
data.currentWfNode = {
|
||||
type: element.type,
|
||||
};
|
||||
configActiveName.value = '2';
|
||||
} else {
|
||||
data.currentWfNode = element;
|
||||
configActiveName.value = '1';
|
||||
setNodeData(element.type, element);
|
||||
}
|
||||
}
|
||||
// 设置节点初始数据
|
||||
function setNodeData(name, element) {
|
||||
switch (name) {
|
||||
case 'bpmn:StartEvent':
|
||||
data.wfNodeName = '开始节点';
|
||||
data.currentWfNode = {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
isNextAuditor: false,
|
||||
isCustmerTitle: false,
|
||||
formRelations: [],
|
||||
formType: '1',
|
||||
formCode: '',
|
||||
formVerison: undefined,
|
||||
formRelationId: undefined,
|
||||
formUrl: '',
|
||||
formAppUrl: '',
|
||||
authFields: [],
|
||||
messageType: '',
|
||||
isInit: true,
|
||||
formTitle: '',
|
||||
issueCode: '',
|
||||
mapConfig:{},
|
||||
};
|
||||
break;
|
||||
case 'bpmn:EndEvent':
|
||||
data.wfNodeName = '结束节点';
|
||||
data.currentWfNode = {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
};
|
||||
break;
|
||||
case 'bpmn:Task':
|
||||
data.wfNodeName = '审核节点';
|
||||
data.currentWfNode = {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
isAddSign: false,
|
||||
isTransfer: false,
|
||||
isBatchAudit: false,
|
||||
IsSingleTask: false,
|
||||
autoAgree: '',
|
||||
noAuditor: '1',
|
||||
rejectType: '1',
|
||||
messageType: '',
|
||||
auditUsers: [],
|
||||
lookUsers: [],
|
||||
formRelations: [],
|
||||
formType: '1',
|
||||
formCode: '',
|
||||
formTitle: '',
|
||||
formVerison: undefined,
|
||||
formRelationId: undefined,
|
||||
formUrl: '',
|
||||
formAppUrl: '',
|
||||
authFields: [],
|
||||
name: '审核节点',
|
||||
btnlist: [
|
||||
{
|
||||
code: 'agree',
|
||||
name: '同意',
|
||||
hidden: false,
|
||||
isNextAuditor: false,
|
||||
isSign: false,
|
||||
isSys: true,
|
||||
},
|
||||
{
|
||||
code: 'disagree',
|
||||
name: '驳回',
|
||||
hidden: false,
|
||||
isNextAuditor: false,
|
||||
isSign: false,
|
||||
isSys: true,
|
||||
},
|
||||
],
|
||||
isCountersign: false,
|
||||
isCountersignAll: false,
|
||||
countersignAgian: '1',
|
||||
countersignType: '1',
|
||||
countersignAllType: 100,
|
||||
isOvertimeMessage: false,
|
||||
overtimeMessageType: '',
|
||||
overtimeMessageStart: 1,
|
||||
overtimeMessageInterval: 1,
|
||||
overtimeGo: 12,
|
||||
isInherit: true,
|
||||
isInit: true,
|
||||
issueCode: '',
|
||||
};
|
||||
break;
|
||||
case 'bpmn:SubProcess':
|
||||
data.wfNodeName = '子流程';
|
||||
data.currentWfNode = {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
isAsync: false,
|
||||
wfschemeId: '',
|
||||
wfVersionId: '',
|
||||
isInit: true,
|
||||
};
|
||||
break;
|
||||
case 'bpmn:ExclusiveGateway':
|
||||
data.wfNodeName = '排他网关';
|
||||
data.currentWfNode = {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
conditions: [],
|
||||
isInit: true,
|
||||
};
|
||||
break;
|
||||
case 'bpmn:ParallelGateway':
|
||||
data.wfNodeName = '并行网关';
|
||||
data.currentWfNode = {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
isInit: true,
|
||||
};
|
||||
break;
|
||||
case 'bpmn:InclusiveGateway':
|
||||
data.wfNodeName = '包含网关';
|
||||
data.currentWfNode = {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
conditions: [],
|
||||
isInit: true,
|
||||
};
|
||||
break;
|
||||
case 'bpmn:SequenceFlow':
|
||||
data.wfNodeName = '线条';
|
||||
data.currentWfNode = {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
lineConditions: '',
|
||||
from: element.businessObject.sourceRef.id,
|
||||
isInit: true,
|
||||
to: element.businessObject.targetRef.id,
|
||||
};
|
||||
break;
|
||||
case 'bpmn:ScriptTask':
|
||||
data.wfNodeName = '脚本节点';
|
||||
data.currentWfNode = {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
isInit: true,
|
||||
executeType: '1',
|
||||
sqlDb: '',
|
||||
sqlStr: '',
|
||||
sqlStrRevoke: '',
|
||||
apiUrl: '',
|
||||
apiUrlRevoke: '',
|
||||
ioc: '',
|
||||
iocRevoke: '',
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// 更改flowStore的数据
|
||||
flowWfDataStore.setWfDataNode(data.currentWfNode);
|
||||
}
|
||||
|
||||
async function getDetailInfo() {
|
||||
let data = await getDetail({ code: props.schemeCode });
|
||||
let scheme = JSON.parse(data.scheme.content);
|
||||
let baseinfo = {
|
||||
id: data.schemeinfo.id,
|
||||
code: data.schemeinfo.code,
|
||||
name: data.schemeinfo.name,
|
||||
category: data.schemeinfo.category,
|
||||
enabledMark: data.schemeinfo.enabledMark,
|
||||
mark: data.schemeinfo.mark,
|
||||
isInApp: data.schemeinfo.isInApp,
|
||||
description: data.schemeinfo.description,
|
||||
icon: data.schemeinfo.icon,
|
||||
color: data.schemeinfo.color,
|
||||
schemeId: data.schemeinfo.schemeId,
|
||||
|
||||
undoType: scheme.undoType,
|
||||
undoDbCode: scheme.undoDbCode,
|
||||
undoDbSQL: scheme.undoDbSQL,
|
||||
undoIOCName: scheme.undoIOCName,
|
||||
undoUrl: scheme.undoUrl,
|
||||
|
||||
deleteType: scheme.deleteType,
|
||||
deleteDbCode: scheme.deleteDbCode,
|
||||
deleteDbSQL: scheme.deleteDbSQL,
|
||||
deleteIOCName: scheme.deleteIOCName,
|
||||
deleteUrl: scheme.deleteUrl,
|
||||
|
||||
deleteDraftType: scheme.deleteDraftType,
|
||||
deleteDraftDbCode: scheme.deleteDraftDbCode,
|
||||
deleteDraftDbSQL: scheme.deleteDraftDbSQL,
|
||||
deleteDraftIOCName: scheme.deleteDraftIOCName,
|
||||
deleteDraftUrl: scheme.deleteDraftUrl,
|
||||
};
|
||||
|
||||
let wfData = scheme.wfData;
|
||||
flowWfDataStore.setWfDataAll(wfData);
|
||||
let auth = {
|
||||
authType: data.schemeinfo.authType,
|
||||
authData: data.schemeAuthList.map((t) => {
|
||||
return {
|
||||
id: t.objId,
|
||||
name: t.objName,
|
||||
type: t.objType,
|
||||
};
|
||||
}),
|
||||
};
|
||||
shcemeinfoRef.value.setForm(baseinfo);
|
||||
authRef.value.setForm(auth);
|
||||
}
|
||||
|
||||
async function validatePanel() {
|
||||
let res = await shcemeinfoRef.value.validateForm();
|
||||
if (!res) {
|
||||
configActiveName.value = '2';
|
||||
}
|
||||
return res;
|
||||
}
|
||||
async function getPanel() {
|
||||
// 1.获取流程配置基础属性
|
||||
let baseinfo = await shcemeinfoRef.value.getForm();
|
||||
// 2.获取流程权限信息
|
||||
let auth = await authRef.value.getForm();
|
||||
let wfData = flowWfDataStore.getWfData;
|
||||
var startIndex = (wfData || []).findIndex(
|
||||
(element: { type?: string }) => element.type == 'bpmn:StartEvent',
|
||||
);
|
||||
if (startIndex == -1) {
|
||||
return createMessage.warn('请设置开始节点');
|
||||
}
|
||||
var endIndex = (wfData || []).findIndex(
|
||||
(element: { type?: string }) => element.type == 'bpmn:EndEvent',
|
||||
);
|
||||
if (endIndex == -1) {
|
||||
return createMessage.warn('请设置结束节点');
|
||||
}
|
||||
wfData.forEach(
|
||||
(node: {
|
||||
type?: string;
|
||||
lineConditions?: string;
|
||||
from?: string;
|
||||
messageType?: any;
|
||||
overtimeMessageType?: any;
|
||||
}) => {
|
||||
if (node.type == 'bpmn:SequenceFlow') {
|
||||
if (node.lineConditions == '') {
|
||||
const fromNode:
|
||||
| {
|
||||
type?: string;
|
||||
btnlist?: any;
|
||||
}
|
||||
| any = wfData.find((t: { id: string }) => t.id === node.from);
|
||||
if (fromNode.type == 'bpmn:Task') {
|
||||
if (fromNode.btnlist.findIndex((t) => t.code == 'agree' && !t.hidden) != -1) {
|
||||
node.lineConditions = 'agree'; // 流转条件
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.type == 'bpmn:StartEvent') {
|
||||
if (node.messageType instanceof Array) {
|
||||
if (node.messageType.length == 1) {
|
||||
node.messageType = node.messageType[0];
|
||||
} else {
|
||||
node.messageType = node.messageType.join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.type == 'bpmn:Task') {
|
||||
if (node.overtimeMessageType instanceof Array) {
|
||||
if (node.overtimeMessageType.length == 1) {
|
||||
node.overtimeMessageType = node.overtimeMessageType[0];
|
||||
} else {
|
||||
node.overtimeMessageType = node.overtimeMessageType.join(',');
|
||||
}
|
||||
}
|
||||
if (node.messageType instanceof Array) {
|
||||
if (node.messageType.length == 1) {
|
||||
node.messageType = node.messageType[0];
|
||||
} else {
|
||||
node.messageType = node.messageType.join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let scheme = {
|
||||
wfData: wfData,
|
||||
|
||||
undoType: baseinfo.undoType,
|
||||
undoDbCode: baseinfo.undoDbCode,
|
||||
undoDbSQL: baseinfo.undoDbSQL,
|
||||
undoIOCName: baseinfo.undoIOCName,
|
||||
undoUrl: baseinfo.undoUrl,
|
||||
|
||||
deleteType: baseinfo.deleteType,
|
||||
deleteDbCode: baseinfo.deleteDbCode,
|
||||
deleteDbSQL: baseinfo.deleteDbSQL,
|
||||
deleteIOCName: baseinfo.deleteIOCName,
|
||||
deleteUrl: baseinfo.deleteUrl,
|
||||
|
||||
deleteDraftType: baseinfo.deleteDraftType,
|
||||
deleteDraftDbCode: baseinfo.deleteDraftDbCode,
|
||||
deleteDraftDbSQL: baseinfo.deleteDraftDbSQL,
|
||||
deleteDraftIOCName: baseinfo.deleteDraftIOCName,
|
||||
deleteDraftUrl: baseinfo.deleteDraftUrl,
|
||||
};
|
||||
let dto = {
|
||||
schemeinfo: {
|
||||
id: baseinfo.id,
|
||||
code: baseinfo.code,
|
||||
name: baseinfo.name,
|
||||
category: baseinfo.category,
|
||||
enabledMark: baseinfo.enabledMark,
|
||||
mark: baseinfo.mark,
|
||||
isInApp: baseinfo.isInApp,
|
||||
authType: auth.authType,
|
||||
description: baseinfo.description,
|
||||
icon: baseinfo.icon,
|
||||
color: baseinfo.color,
|
||||
schemeId: baseinfo.schemeId,
|
||||
},
|
||||
schemeAuthList:
|
||||
auth.authType == 1
|
||||
? []
|
||||
: auth.authData.map((t) => {
|
||||
return {
|
||||
objId: t.id,
|
||||
objName: t.name,
|
||||
objType: t.type,
|
||||
};
|
||||
}),
|
||||
scheme: {
|
||||
content: lr_AESEncrypt(JSON.stringify(scheme), 'hc'),
|
||||
},
|
||||
// scheme:scheme
|
||||
};
|
||||
console.log(dto);
|
||||
return dto;
|
||||
}
|
||||
defineExpose({
|
||||
getPanel,
|
||||
validatePanel,
|
||||
});
|
||||
onMounted(() => {
|
||||
initModels();
|
||||
if (props.schemeCode) {
|
||||
getDetailInfo();
|
||||
}
|
||||
if (props.pageType == 'detail') {
|
||||
var content = JSON.parse(props.pageView);
|
||||
shcemeinfoRef.value.setForm(content);
|
||||
flowWfDataStore.setWfDataAll(content.wfData);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-process-property';
|
||||
|
||||
.@{prefix-cls} {
|
||||
background-color: @component-background;
|
||||
}
|
||||
|
||||
::v-deep .ant-tabs {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
::v-deep .ant-tabs-content-holder {
|
||||
overflow-y: auto;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<template>
|
||||
<div class="auth-config">
|
||||
<a-tabs v-model:activeKey="authType" type="card" size="small">
|
||||
<a-tab-pane key="1" tab="所有成员">
|
||||
<a-alert
|
||||
message="权限说明"
|
||||
description="所有人员指不限制流程模版的发起人员,表示每个人都能发起该流程模版。"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="指定成员">
|
||||
<a-space>
|
||||
<a-radio-group>
|
||||
<a-radio-button value="1" @click="handlePostClick">岗位</a-radio-button>
|
||||
<a-radio-button value="2" @click="handleRoleClick">角色</a-radio-button>
|
||||
<a-radio-button value="3" @click="handleAccountClick">用户</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-button danger @click="clearAll">清空</a-button>
|
||||
</a-space>
|
||||
<a-table
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
bordered
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="['name'].includes(column.dataIndex)">
|
||||
<div>
|
||||
{{ text }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="['type'].includes(column.dataIndex)">
|
||||
<div>
|
||||
{{ typeFormat(text) }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'operation'">
|
||||
<a-popconfirm
|
||||
v-if="dataSource.length"
|
||||
title="确定要删除吗?"
|
||||
@confirm="onDelete(record.name)"
|
||||
>
|
||||
<delete-outlined two-tone-color="#eb2f96" />
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-modal
|
||||
width="60%"
|
||||
v-model:open="data.postOpen"
|
||||
wrap-class-name="full-modal-children"
|
||||
title="添加岗位"
|
||||
@ok="postHandleOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<SelectPos ref="posRef" />
|
||||
</a-modal>
|
||||
<a-modal
|
||||
width="60%"
|
||||
v-model:open="data.roleOpen"
|
||||
wrap-class-name="full-modal-children"
|
||||
title="添加角色"
|
||||
@ok="roleHandleOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<SelectRole ref="roleRef" />
|
||||
</a-modal>
|
||||
<a-modal
|
||||
width="60%"
|
||||
v-model:open="data.accountOpen"
|
||||
wrap-class-name="full-modal-children"
|
||||
title="添加角色"
|
||||
@ok="accountHandleOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<SelectAccount ref="accountRef" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { SelectPos } from '@/components/SelectPos/index';
|
||||
import { SelectRole } from '@/components/SelectRole/index';
|
||||
import { SelectAccount } from '@/components/SelectAccount/index';
|
||||
|
||||
let authType = ref('1');
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
},
|
||||
];
|
||||
let dataSource = ref([]);
|
||||
const data = reactive({
|
||||
postOpen: false,
|
||||
roleOpen: false,
|
||||
accountOpen: false,
|
||||
});
|
||||
function typeFormat(type) {
|
||||
type = type.toString();
|
||||
switch (type) {
|
||||
case '1':
|
||||
return '岗位';
|
||||
case '2':
|
||||
return '角色';
|
||||
case '3':
|
||||
return '用户';
|
||||
case '4':
|
||||
return '上下级';
|
||||
case '5':
|
||||
return '节点';
|
||||
case '6':
|
||||
return '表字段';
|
||||
}
|
||||
}
|
||||
function clearAll() {
|
||||
dataSource.value = [];
|
||||
}
|
||||
function addTableData(selectData) {
|
||||
let addData = selectData.filter(
|
||||
(t: { id: string }) =>
|
||||
dataSource.value.findIndex((t2: { id: string }) => t2.id == t.id) == -1,
|
||||
);
|
||||
dataSource.value = dataSource.value.concat(addData);
|
||||
}
|
||||
// 岗位
|
||||
const posRef = ref<any>();
|
||||
|
||||
function handlePostClick() {
|
||||
data.postOpen = true;
|
||||
}
|
||||
function postHandleOk() {
|
||||
let selectData = posRef.value.getRow().map((t) => {
|
||||
return { type: '1', id: t.id, name: t.name };
|
||||
});
|
||||
addTableData(selectData);
|
||||
data.postOpen = false;
|
||||
}
|
||||
// 角色
|
||||
const roleRef = ref<any>();
|
||||
|
||||
function handleRoleClick() {
|
||||
data.roleOpen = true;
|
||||
}
|
||||
function roleHandleOk() {
|
||||
let selectData = roleRef.value.getRow().map((t) => {
|
||||
return { type: '2', id: t.id, name: t.name, condition: '' };
|
||||
});
|
||||
addTableData(selectData);
|
||||
data.roleOpen = false;
|
||||
}
|
||||
// 用户
|
||||
const accountRef = ref<any>();
|
||||
|
||||
function handleAccountClick() {
|
||||
data.accountOpen = true;
|
||||
}
|
||||
function accountHandleOk() {
|
||||
let selectData = accountRef.value.getRow().map((t) => {
|
||||
return { type: '3', id: t.id, name: t.name };
|
||||
});
|
||||
addTableData(selectData);
|
||||
data.accountOpen = false;
|
||||
}
|
||||
function onDelete(key) {
|
||||
dataSource.value = dataSource.value.filter((item: { name: string }) => item.name !== key);
|
||||
}
|
||||
function setForm(data) {
|
||||
authType.value = data.authType.toString();
|
||||
dataSource.value = data.authData;
|
||||
}
|
||||
function getForm() {
|
||||
return {
|
||||
authType: authType.value,
|
||||
authData: dataSource.value,
|
||||
};
|
||||
}
|
||||
defineExpose({
|
||||
getForm,
|
||||
setForm,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.site-space-compact-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .ant-tabs {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
::v-deep .ant-table-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.full-modal-children {
|
||||
.ant-modal {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
height: calc(80vh);
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
height: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
<!-- 开始节点配置 -->
|
||||
<template>
|
||||
<l-layout :top="180" >
|
||||
<template #top>
|
||||
<el-form
|
||||
class="l-form-config"
|
||||
label-width="88px"
|
||||
label-position="left"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item label="节点标识">
|
||||
<el-input v-model="node.id" readonly ></el-input>
|
||||
</el-form-item>
|
||||
<div style="padding:0 16px;">
|
||||
<el-alert
|
||||
title="排他网关说明"
|
||||
type="info"
|
||||
description="排他网关不会等待所有分支汇入才往下执行,只要有分支汇入就会往下执行,出口分支只会执行一条(条件为true,如果多条出口分支条件为true也执行一条)"
|
||||
show-icon
|
||||
:closable="false"
|
||||
>
|
||||
</el-alert>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
<l-layout :top="40">
|
||||
<template #top v-if="!disabled">
|
||||
<div style="padding-left:8px;float:left;" >
|
||||
<el-button-group>
|
||||
<el-button size="mini" icon="el-icon-plus" @click="handleFormulaClick">{{$t('公式')}}</el-button>
|
||||
<el-button size="mini" icon="el-icon-plus" @click="handleSQlClick">{{$t('sql语句')}}</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
<div style="padding-right:8px;float:right;">
|
||||
<el-button size="mini" type="danger" icon="el-icon-delete" @click="handleClearClick">{{$t('清空')}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<l-table :columns="columns" :dataSource="node.conditions" >
|
||||
<template v-slot:type="scope" >
|
||||
{{typeFormat(scope.row.type)}}
|
||||
</template>
|
||||
<l-table-btns v-if="!disabled" :btns="tableBtns" @click="handleTableBtnClick" ></l-table-btns>
|
||||
</l-table>
|
||||
</l-layout>
|
||||
|
||||
|
||||
<l-dialog
|
||||
:title="$t('添加公式条件')"
|
||||
:visible.sync="formulaVisible"
|
||||
:height="480"
|
||||
|
||||
@ok="handleFormulaOk"
|
||||
@closed="handleFormulaClosed"
|
||||
@opened="handleFormulaOpened"
|
||||
>
|
||||
<condition-formula ref="conditionFormula" ></condition-formula>
|
||||
</l-dialog>
|
||||
|
||||
<l-dialog
|
||||
:title="$t('添加sql条件')"
|
||||
:visible.sync="sqlVisible"
|
||||
:height="480"
|
||||
|
||||
@ok="handleSqlOk"
|
||||
@closed="handleSqlClosed"
|
||||
@opened="handleSqlOpened"
|
||||
>
|
||||
<condition-sql ref="conditionSql" ></condition-sql>
|
||||
</l-dialog>
|
||||
|
||||
|
||||
</l-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import conditionFormula from './conditionFormula.vue'
|
||||
import conditionSql from './conditionSql.vue'
|
||||
|
||||
export default {
|
||||
name:'gateway-xor-option',
|
||||
props:{
|
||||
disabled:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
conditionFormula,
|
||||
conditionSql
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tableBtns:[
|
||||
{prop:'Edit',label:'编辑'},
|
||||
{prop:'Delete',label:'删除'}
|
||||
],
|
||||
columns:[
|
||||
{label:'类型',prop:'type',width:'80', align: 'center'},
|
||||
{label:'名称',prop:'name',minWidth:'100'},
|
||||
],
|
||||
tableData:[],
|
||||
|
||||
formulaVisible:false,
|
||||
sqlVisible:false,
|
||||
|
||||
editRow:null,
|
||||
isEdit:false,
|
||||
rowIndex:0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
node(){
|
||||
return this.wfdesign.currentWfNode;
|
||||
}
|
||||
},
|
||||
inject: ["wfdesign"],
|
||||
methods:{
|
||||
typeFormat(type){
|
||||
switch(type){
|
||||
case '1':
|
||||
return '表达式'
|
||||
case '2':
|
||||
return 'sql语句'
|
||||
}
|
||||
},
|
||||
handleTableBtnClick(btn){
|
||||
switch(btn.type){
|
||||
case 'Edit':
|
||||
this.isEdit = true;
|
||||
this.editRow = btn.row;
|
||||
this.rowIndex = btn.rowIndex;
|
||||
if(this.editRow.type == '1'){
|
||||
this.formulaVisible = true;
|
||||
}
|
||||
else{
|
||||
this.sqlVisible = true;
|
||||
}
|
||||
break;
|
||||
case 'Delete':
|
||||
this.node.conditions.splice(btn.rowIndex,1);
|
||||
break;
|
||||
}
|
||||
console.log(btn);
|
||||
//this.tableData.splice(btn.rowIndex,1);
|
||||
},
|
||||
handleFormulaClick(){
|
||||
this.isEdit = false;
|
||||
this.formulaVisible = true;
|
||||
},
|
||||
handleSQlClick(){
|
||||
this.isEdit = false;
|
||||
this.sqlVisible = true;
|
||||
},
|
||||
handleClearClick(){
|
||||
this.node.conditions = [];
|
||||
},
|
||||
handleFormulaOk(){
|
||||
this.$refs.conditionFormula.validateForm(()=>{
|
||||
let formData = this.$refs.conditionFormula.getForm();
|
||||
formData.type = '1';
|
||||
|
||||
if(this.isEdit){
|
||||
this.node.conditions[this.rowIndex] = formData;
|
||||
}
|
||||
else{
|
||||
formData.code = this.$uuid();
|
||||
this.node.conditions.push(formData);
|
||||
}
|
||||
|
||||
|
||||
this.formulaVisible = false;
|
||||
})
|
||||
},
|
||||
handleFormulaOpened(){
|
||||
if(this.isEdit){
|
||||
this.$refs.conditionFormula.setForm(this.editRow);
|
||||
}
|
||||
|
||||
},
|
||||
handleFormulaClosed(){
|
||||
this.$refs.conditionFormula.resetForm();
|
||||
},
|
||||
handleSqlOk(){
|
||||
this.$refs.conditionSql.validateForm(()=>{
|
||||
let formData = this.$refs.conditionSql.getForm();
|
||||
formData.type = '2';
|
||||
|
||||
if(this.isEdit){
|
||||
this.node.conditions[this.rowIndex] = formData;
|
||||
}
|
||||
else{
|
||||
formData.code = this.$uuid();
|
||||
this.node.conditions.push(formData);
|
||||
}
|
||||
|
||||
this.sqlVisible = false;
|
||||
})
|
||||
},
|
||||
handleSqlOpened(){
|
||||
if(this.isEdit){
|
||||
this.$refs.conditionSql.setForm(this.editRow);
|
||||
}
|
||||
},
|
||||
handleSqlClosed(){
|
||||
this.$refs.conditionSql.resetForm();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<!-- 开始节点配置 -->
|
||||
<template>
|
||||
<el-form
|
||||
class="l-form-config"
|
||||
label-width="88px"
|
||||
label-position="left"
|
||||
size="mini">
|
||||
<el-form-item label="节点标识">
|
||||
<el-input v-model="node.id" readonly ></el-input>
|
||||
</el-form-item>
|
||||
<el-divider>执行操作</el-divider>
|
||||
<div style="text-align: center;margin-bottom:16px;" >
|
||||
<el-radio-group v-model="node.executeType" size="mini" :disabled="disabled">
|
||||
<el-radio-button label="1">执行SQL</el-radio-button>
|
||||
<el-radio-button label="2">.NET方法</el-radio-button>
|
||||
<el-radio-button label="3">第三方接口</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<template v-if="node.executeType == '1'" >
|
||||
<div style="padding:0 0 16px 0;">
|
||||
<el-alert
|
||||
title="sql参数说明"
|
||||
type="info"
|
||||
description="参数有 @processId流程进程主键 @code上一步执行码 @userId流程发起人Id @userAccount流程发起人账号 @companyId流程发起人公司 @departmentId流程发起人部门;
|
||||
@userId2上一步审核人Id @userAccount2上一步审核人账号 @companyId2上一步审核人公司 @departmentId2上一步审核人部门;
|
||||
"
|
||||
show-icon
|
||||
:closable="false"
|
||||
>
|
||||
</el-alert>
|
||||
</div>
|
||||
<el-form-item label-width="0">
|
||||
<el-select v-model="node.sqlDb" placeholder="请选择执行SQL数据库" :disabled="disabled">
|
||||
<el-option-group
|
||||
v-for="group in lr_dblinkTree"
|
||||
:key="group.id"
|
||||
:label="group.label">
|
||||
<el-option
|
||||
v-for="item in group.children"
|
||||
:key="item.id"
|
||||
:label="item.label"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="0">
|
||||
<el-input :readonly="disabled" type="textarea" v-model="node.sqlStr" rows="8" placeholder="请填写SQL语句" ></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="0">
|
||||
<el-input :readonly="disabled" type="textarea" v-model="node.sqlStrRevoke" rows="8" placeholder="请填写SQL语句(撤销)" ></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="node.executeType == '2'" >
|
||||
<div style="padding:0 0 16px 0;">
|
||||
<el-alert
|
||||
title="IOC说明"
|
||||
type="info"
|
||||
description="注意:编写一个继承IWorkFlowMethod的类
|
||||
"
|
||||
show-icon
|
||||
:closable="false"
|
||||
>
|
||||
</el-alert>
|
||||
</div>
|
||||
<el-form-item label-width="0">
|
||||
<el-input :readonly="disabled" type="textarea" v-model="node.ioc" rows="4" placeholder="ioc名称" ></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="0">
|
||||
<el-input :readonly="disabled" type="textarea" v-model="node.iocRevoke" rows="4" placeholder="ioc名称(撤销)" ></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="node.executeType == '3'" >
|
||||
<div style="padding:0 0 16px 0;">
|
||||
<el-alert
|
||||
title="接口参数说明"
|
||||
type="info"
|
||||
description="注意:配置支持Post方法的接口,json数据格式。{
|
||||
processId:'流程发起实例主键',userId:'流程发起人Id',userAccount:'流程发起人账号',companyId:'流程发起人公司',departmentId:'流程发起人部门',code:'上一步执行码',
|
||||
userId2:'上一步审核人Id',userAccount2:'上一步审核人账号',companyId2:'上一步审核人公司',departmentId2:'上一步审核人部门'
|
||||
}
|
||||
"
|
||||
show-icon
|
||||
:closable="false"
|
||||
>
|
||||
</el-alert>
|
||||
</div>
|
||||
<el-form-item label-width="0">
|
||||
<el-input :readonly="disabled" type="textarea" v-model="node.apiUrl" rows="4" placeholder="接口地址" ></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="0">
|
||||
<el-input :readonly="disabled" type="textarea" v-model="node.apiUrlRevoke" rows="4" placeholder="接口地址(撤销)" ></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:'script-task-option',
|
||||
props:{
|
||||
disabled:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
node(){
|
||||
return this.wfdesign.currentWfNode;
|
||||
}
|
||||
},
|
||||
inject: ["wfdesign"]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<!-- 开始节点配置 -->
|
||||
<template>
|
||||
<div class="end-event">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="node"
|
||||
labelAlign="left"
|
||||
:disabled="data.componentDisabled"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="节点标识">
|
||||
<a-input v-model:value="node.id" placeholder="请输入" readonly />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch, defineProps, onMounted } from 'vue';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const node: any = ref({});
|
||||
const props = defineProps({
|
||||
element: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
pageType: String,
|
||||
});
|
||||
watch(
|
||||
() => props.element,
|
||||
(newVal) => {
|
||||
if (newVal.type == 'bpmn:EndEvent') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(newVal.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
onMounted(() => {
|
||||
if (props.element.type == 'bpmn:EndEvent') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(props.element.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = props.element;
|
||||
}
|
||||
}
|
||||
});
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const data = reactive({
|
||||
componentDisabled: props.pageType == 'detail' ? true : false,
|
||||
});
|
||||
function getForm() {
|
||||
return node;
|
||||
}
|
||||
defineExpose({
|
||||
getForm,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default as conditionFormula } from './src/conditionFormula.vue';
|
||||
export { default as conditionSql } from './src/conditionSql.vue';
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
<template>
|
||||
<div class="user-task">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="node"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
:disabled="data.componentDisabled"
|
||||
>
|
||||
<a-form-item label="节点标识">
|
||||
<a-input v-model:value="node.id" placeholder="请输入" readonly />
|
||||
</a-form-item>
|
||||
<a-alert
|
||||
message="排他网关说明"
|
||||
description="排他网关不会等待所有分支汇入才往下执行,只要有分支汇入就会往下执行,出口分支只会执行一条(条件为true,如果多条出口分支条件为true也执行一条)"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
<a-space>
|
||||
<a-radio-group>
|
||||
<a-radio-button value="1" @click="handleFormulaClick">公式</a-radio-button>
|
||||
<a-radio-button value="2" @click="handleSQlClick">sql语句</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-button danger @click="handleClearClick">清空</a-button>
|
||||
</a-space>
|
||||
<a-table :columns="data.columns" :data-source="node.conditions" bordered :pagination="false">
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="['name'].includes(column.dataIndex)">
|
||||
<div>
|
||||
{{ text }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="['type'].includes(column.dataIndex)">
|
||||
<div>
|
||||
{{ typeFormat(text) }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'operation'">
|
||||
<a-button @click="onEdit(record)" size="small">编辑</a-button>
|
||||
<a-button danger @click="onDelete(record.id)" size="small" class="ml-2">删除</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form>
|
||||
<a-modal
|
||||
width="40%"
|
||||
v-model:open="data.formulaVisible"
|
||||
title="添加公式条件"
|
||||
@ok="handleFormulaOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<conditionFormula ref="conditionFormulaRef" :propsData="propsData" />
|
||||
</a-modal>
|
||||
<a-modal
|
||||
width="40%"
|
||||
v-model:open="data.sqlVisible"
|
||||
title="添加sql条件"
|
||||
@ok="handleSqlOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<conditionSql ref="conditionSqlRef" :propsData="propsData" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { conditionFormula, conditionSql } from './index';
|
||||
import { reactive, ref, onMounted, watch, defineProps } from 'vue';
|
||||
import { buildGUID } from '@/utils/uuid';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const conditionFormulaRef = ref<any>();
|
||||
const conditionSqlRef = ref<any>();
|
||||
const props = defineProps({
|
||||
element: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
pageType: String,
|
||||
});
|
||||
const node: any = ref({
|
||||
conditions: [],
|
||||
});
|
||||
const propsData = ref();
|
||||
watch(
|
||||
() => props.element,
|
||||
(newVal) => {
|
||||
if (newVal.type == 'bpmn:ExclusiveGateway') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(newVal.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const data = reactive({
|
||||
columns: [
|
||||
{ title: '类型', dataIndex: 'type' },
|
||||
{ title: '名称', dataIndex: 'name', width: '40%' },
|
||||
{ title: '操作', dataIndex: 'operation' },
|
||||
],
|
||||
tableData: [],
|
||||
formulaVisible: false,
|
||||
sqlVisible: false,
|
||||
editRow: null,
|
||||
isEdit: false,
|
||||
rowIndex: 0,
|
||||
componentDisabled: props.pageType == 'detail' ? true : false,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.element.type == 'bpmn:ExclusiveGateway') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(props.element.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = props.element;
|
||||
}
|
||||
}
|
||||
});
|
||||
function typeFormat(type) {
|
||||
switch (type) {
|
||||
case '1':
|
||||
return '表达式';
|
||||
case '2':
|
||||
return 'sql语句';
|
||||
}
|
||||
}
|
||||
|
||||
function handleFormulaClick() {
|
||||
data.formulaVisible = true;
|
||||
data.isEdit = false;
|
||||
propsData.value = {
|
||||
dbCode: '',
|
||||
table: '',
|
||||
columns: [],
|
||||
rfield: '',
|
||||
cfield: '',
|
||||
compareType: '',
|
||||
value: '',
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
function handleSQlClick() {
|
||||
data.sqlVisible = true;
|
||||
data.isEdit = false;
|
||||
propsData.value = {
|
||||
name: '',
|
||||
dbCode: '',
|
||||
sql: '',
|
||||
};
|
||||
}
|
||||
function handleClearClick() {
|
||||
node.value.conditions = [];
|
||||
}
|
||||
async function handleFormulaOk() {
|
||||
conditionFormulaRef.value.validateForm();
|
||||
let obj = conditionFormulaRef.value.getForm();
|
||||
obj.type = '1';
|
||||
if (
|
||||
obj.name == '' ||
|
||||
obj.table == '' ||
|
||||
obj.value == '' ||
|
||||
obj.rfield == '' ||
|
||||
obj.cfield == '' ||
|
||||
obj.compareType == ''
|
||||
) {
|
||||
data.formulaVisible = true;
|
||||
} else {
|
||||
if (data.isEdit) {
|
||||
var currentIndex = node.value.conditions.findIndex(
|
||||
(element) => element.code === propsData.value.code,
|
||||
);
|
||||
node.value.conditions[currentIndex] = obj;
|
||||
} else {
|
||||
obj.code = buildGUID();
|
||||
node.value.conditions.push(obj);
|
||||
}
|
||||
data.formulaVisible = false;
|
||||
}
|
||||
}
|
||||
function handleSqlOk() {
|
||||
conditionSqlRef.value.validateForm();
|
||||
let obj = conditionSqlRef.value.getForm();
|
||||
obj.type = '2';
|
||||
if (obj.name == '' || obj.dbCode == '' || obj.sql == '') {
|
||||
data.sqlVisible = true;
|
||||
} else {
|
||||
if (data.isEdit) {
|
||||
var currentIndex = node.value.conditions.findIndex(
|
||||
(element) => element.code === propsData.value.code,
|
||||
);
|
||||
node.value.conditions[currentIndex] = obj;
|
||||
} else {
|
||||
obj.code = buildGUID();
|
||||
node.value.conditions.push(obj);
|
||||
}
|
||||
data.sqlVisible = false;
|
||||
}
|
||||
}
|
||||
function onEdit(record) {
|
||||
data.isEdit = true;
|
||||
propsData.value = record;
|
||||
if (record.type == 1) {
|
||||
data.formulaVisible = true;
|
||||
} else {
|
||||
data.sqlVisible = true;
|
||||
}
|
||||
}
|
||||
function onDelete(id) {
|
||||
let list = node.value.conditions;
|
||||
var currentIndex = (list || []).findIndex((element) => element.id == id);
|
||||
node.value.conditions.splice(currentIndex, 1);
|
||||
}
|
||||
|
||||
defineExpose({});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
::v-deep .ant-alert-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
::v-deep .ant-table-wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
<template>
|
||||
<div class="l-from-body">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:rules="data.rules"
|
||||
:model="formData"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="条件名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入" />
|
||||
</a-form-item>
|
||||
<a-form-item label="数据库" name="dbCode">
|
||||
<a-select
|
||||
v-model:value="formData.dbCode"
|
||||
placeholder="请选择"
|
||||
:options="sqlOptions"
|
||||
:field-names="{ label: 'text', value: 'id', options: 'childNodes' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="数据表" name="table">
|
||||
<a-space>
|
||||
<a-input v-model:value="formData.table" placeholder="请输入" readonly />
|
||||
<a-button @click="handleTableClick">选择</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="关联流程字段" name="rfield">
|
||||
<a-select
|
||||
v-model:value="formData.rfield"
|
||||
placeholder="请选择"
|
||||
:options="formData.columns"
|
||||
:field-names="{ label: 'name', value: 'dbColumnName' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="比较字段" name="cfield">
|
||||
<a-select
|
||||
v-model:value="formData.cfield"
|
||||
placeholder="请选择"
|
||||
:options="formData.columns"
|
||||
:field-names="{ label: 'name', value: 'dbColumnName' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="比较类型" name="compareType">
|
||||
<a-select
|
||||
v-model:value="formData.compareType"
|
||||
placeholder="请选择"
|
||||
:options="data.options"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="数据值" name="value">
|
||||
<a-input v-model:value="formData.value" placeholder="请输入" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal
|
||||
width="60%"
|
||||
wrap-class-name="full-modal-children"
|
||||
v-model:open="data.tableOpen"
|
||||
title="选择数据表"
|
||||
@ok="tableHandleOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<SelectTable ref="tableRef" :dbCode="formData.dbCode" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { SelectTable } from '@/components/SelectTable/index';
|
||||
import { getTableForms } from '@/api/sys/flowPenal';
|
||||
import { getLoadDataBaseLinkTree } from '@/api/demo/system';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const labelCol = { span: 6 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const formRef = ref<any>();
|
||||
const tableRef = ref<any>();
|
||||
const sqlOptions = ref([]);
|
||||
let formData: any = ref({});
|
||||
const props = defineProps({
|
||||
propsData: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
});
|
||||
formData.value = props.propsData;
|
||||
const data = reactive({
|
||||
rules: {
|
||||
dbCode: [{ required: true, message: '请选择数据库' }],
|
||||
table: [{ required: true, message: '请选择数据表' }],
|
||||
rfield: [{ required: true, message: '请选择关联字段' }],
|
||||
cfield: [{ required: true, message: '请选择比较字段' }],
|
||||
compareType: [{ required: true, message: '请选择比较类型' }],
|
||||
value: [{ required: true, message: '请填写值' }],
|
||||
name: [{ required: true, message: '请填写条件名称' }],
|
||||
},
|
||||
options: [
|
||||
{ value: '1', label: '等于' },
|
||||
{ value: '2', label: '不等于' },
|
||||
{ value: '3', label: '大于' },
|
||||
{ value: '4', label: '大于等于' },
|
||||
{ value: '5', label: '小于' },
|
||||
{ value: '6', label: '小于等于' },
|
||||
{ value: '7', label: '包含' },
|
||||
{ value: '8', label: '不包含' },
|
||||
{ value: '9', label: '包含于' },
|
||||
{ value: '10', label: '不包含于' },
|
||||
],
|
||||
tableOpen: false,
|
||||
});
|
||||
|
||||
async function getSQL() {
|
||||
const data = await getLoadDataBaseLinkTree();
|
||||
let res: any = [];
|
||||
data.forEach((element) => {
|
||||
if (element.childNodes == null) {
|
||||
element.childNodes = [];
|
||||
}
|
||||
let point = {};
|
||||
if (element.id == 'hcsystemdb') {
|
||||
point = {
|
||||
id: '1',
|
||||
text: '常用数据库',
|
||||
childNodes: [
|
||||
{
|
||||
id: element.id,
|
||||
text: element.text,
|
||||
},
|
||||
],
|
||||
};
|
||||
res.push(point);
|
||||
} else {
|
||||
res.push(element);
|
||||
}
|
||||
});
|
||||
sqlOptions.value = res;
|
||||
}
|
||||
function handleTableClick() {
|
||||
if (formData.value.dbCode == '') {
|
||||
return createMessage.warning('请先选择数据库');
|
||||
}
|
||||
data.tableOpen = true;
|
||||
}
|
||||
function tableHandleOk() {
|
||||
console.log(tableRef.value.getRow());
|
||||
const obj = tableRef.value.getRow();
|
||||
formData.value.table = obj[0].tableName;
|
||||
handleTableChange(obj[0].tableName);
|
||||
data.tableOpen = false;
|
||||
}
|
||||
// 选择数据表,把获取表头
|
||||
async function handleTableChange(table) {
|
||||
var querys = {
|
||||
dbCode: formData.value.dbCode,
|
||||
tableNames: table,
|
||||
};
|
||||
const obj = await getTableForms(querys);
|
||||
let columnsData = obj[0].db_codecolumnsList;
|
||||
columnsData.forEach((element) => {
|
||||
element.name = element.dbColumnName + '(' + element.description + ')';
|
||||
});
|
||||
formData.value.columns = columnsData;
|
||||
}
|
||||
function setForm(data) {
|
||||
formData.value = data;
|
||||
}
|
||||
// 校验表单
|
||||
function validateForm() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
return true;
|
||||
})
|
||||
.catch(async () => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
function getForm() {
|
||||
return formData.value;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validateForm,
|
||||
getForm,
|
||||
setForm,
|
||||
});
|
||||
onMounted(() => {
|
||||
getSQL();
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.l-from-body {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.full-modal-children {
|
||||
.ant-modal {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
height: calc(80vh);
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
height: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div class="l-from-body">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:rules="data.rules"
|
||||
:model="formData"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="条件名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入" />
|
||||
</a-form-item>
|
||||
<a-form-item label="数据库" name="dbCode">
|
||||
<a-select
|
||||
v-model:value="formData.dbCode"
|
||||
placeholder="请选择"
|
||||
:options="sqlOptions"
|
||||
:field-names="{ label: 'text', value: 'id', options: 'childNodes' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="SQL语句" name="sql">
|
||||
<a-textarea v-model:value="formData.sql" placeholder="请输入" :rows="4" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { getLoadDataBaseLinkTree } from '@/api/demo/system';
|
||||
|
||||
const labelCol = { span: 5 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const formRef = ref<any>();
|
||||
let formData: any = ref({});
|
||||
const props = defineProps({
|
||||
propsData: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
});
|
||||
formData.value = props.propsData;
|
||||
const data = reactive({
|
||||
rules: {
|
||||
dbCode: [{ required: true, message: '请选择数据库' }],
|
||||
sql: [{ required: true, message: '请填写sql语句' }],
|
||||
name: [{ required: true, message: '请填写条件名称' }],
|
||||
},
|
||||
});
|
||||
const sqlOptions = ref([]);
|
||||
async function getSQL() {
|
||||
const data = await getLoadDataBaseLinkTree();
|
||||
let res: any = [];
|
||||
data.forEach((element) => {
|
||||
if (element.childNodes == null) {
|
||||
element.childNodes = [];
|
||||
}
|
||||
let point = {};
|
||||
if (element.id == 'hcsystemdb') {
|
||||
point = {
|
||||
id: '1',
|
||||
text: '常用数据库',
|
||||
childNodes: [
|
||||
{
|
||||
id: element.id,
|
||||
text: element.text,
|
||||
},
|
||||
],
|
||||
};
|
||||
res.push(point);
|
||||
} else {
|
||||
res.push(element);
|
||||
}
|
||||
});
|
||||
sqlOptions.value = res;
|
||||
}
|
||||
function setForm(data) {
|
||||
formData.value = data;
|
||||
}
|
||||
// 校验表单
|
||||
function validateForm() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
return true;
|
||||
})
|
||||
.catch(async () => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
function getForm() {
|
||||
return formData.value;
|
||||
}
|
||||
defineExpose({
|
||||
getForm,
|
||||
validateForm,
|
||||
setForm,
|
||||
});
|
||||
onMounted(() => {
|
||||
getSQL();
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.l-from-body {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
<template>
|
||||
<div class="user-task">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="node"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
:disabled="data.componentDisabled"
|
||||
>
|
||||
<a-form-item label="节点标识">
|
||||
<a-input v-model:value="node.id" placeholder="请输入" readonly />
|
||||
</a-form-item>
|
||||
<a-alert
|
||||
message="包容网关说明"
|
||||
description="包容网关会等待所有分支汇入才往下执行,出口分支能执行多条(条件为true)"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
<a-space>
|
||||
<a-radio-group>
|
||||
<a-radio-button value="1" @click="handleFormulaClick">公式</a-radio-button>
|
||||
<a-radio-button value="2" @click="handleSQlClick">sql语句</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-button danger @click="handleClearClick">清空</a-button>
|
||||
</a-space>
|
||||
<a-table :columns="data.columns" :data-source="node.conditions" bordered :pagination="false">
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="['name'].includes(column.dataIndex)">
|
||||
<div>
|
||||
{{ text }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="['type'].includes(column.dataIndex)">
|
||||
<div>
|
||||
{{ typeFormat(text) }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'operation'">
|
||||
<a-button @click="onEdit(record)" size="small">编辑</a-button>
|
||||
<a-button danger @click="onDelete(record.id)" size="small" class="ml-2">删除</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form>
|
||||
<a-modal
|
||||
width="40%"
|
||||
v-model:open="data.formulaVisible"
|
||||
title="添加公式条件"
|
||||
@ok="handleFormulaOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<conditionFormula ref="conditionFormulaRef" :propsData="propsData" />
|
||||
</a-modal>
|
||||
<a-modal
|
||||
width="40%"
|
||||
v-model:open="data.sqlVisible"
|
||||
title="添加sql条件"
|
||||
@ok="handleSqlOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<conditionSql ref="conditionSqlRef" :propsData="propsData" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { conditionFormula, conditionSql } from '../exclusiveGateway/index';
|
||||
import { reactive, ref, onMounted, watch, defineProps } from 'vue';
|
||||
import { buildGUID } from '@/utils/uuid';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const conditionFormulaRef = ref<any>();
|
||||
const conditionSqlRef = ref<any>();
|
||||
const props = defineProps({
|
||||
element: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
schemeCode: String,
|
||||
pageType: String,
|
||||
pageView: String,
|
||||
});
|
||||
const propsData = ref();
|
||||
const node: any = ref({
|
||||
conditions: [],
|
||||
id: '',
|
||||
});
|
||||
watch(
|
||||
() => props.element,
|
||||
(newVal) => {
|
||||
if (newVal.type == 'bpmn:InclusiveGateway') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(newVal.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const data = reactive({
|
||||
columns: [
|
||||
{ title: '类型', dataIndex: 'type' },
|
||||
{ title: '名称', dataIndex: 'name', width: '40%' },
|
||||
{ title: '操作', dataIndex: 'operation' },
|
||||
],
|
||||
tableData: [],
|
||||
formulaVisible: false,
|
||||
sqlVisible: false,
|
||||
editRow: null,
|
||||
rowIndex: 0,
|
||||
isEdit: false,
|
||||
componentDisabled: props.pageType == 'detail' ? true : false,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.element.type == 'bpmn:InclusiveGateway') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(props.element.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = props.element;
|
||||
}
|
||||
}
|
||||
});
|
||||
function typeFormat(type) {
|
||||
switch (type) {
|
||||
case '1':
|
||||
return '表达式';
|
||||
case '2':
|
||||
return 'sql语句';
|
||||
}
|
||||
}
|
||||
|
||||
function handleFormulaClick() {
|
||||
data.formulaVisible = true;
|
||||
data.isEdit = false;
|
||||
propsData.value = {
|
||||
dbCode: '',
|
||||
table: '',
|
||||
columns: [],
|
||||
rfield: '',
|
||||
cfield: '',
|
||||
compareType: '',
|
||||
value: '',
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
function handleSQlClick() {
|
||||
data.sqlVisible = true;
|
||||
data.isEdit = false;
|
||||
propsData.value = {
|
||||
name: '',
|
||||
dbCode: '',
|
||||
sql: '',
|
||||
};
|
||||
}
|
||||
function handleClearClick() {
|
||||
node.conditions = [];
|
||||
}
|
||||
async function handleFormulaOk() {
|
||||
conditionFormulaRef.value.validateForm();
|
||||
let obj = conditionFormulaRef.value.getForm();
|
||||
obj.type = '1';
|
||||
if (
|
||||
obj.name == '' ||
|
||||
obj.table == '' ||
|
||||
obj.value == '' ||
|
||||
obj.rfield == '' ||
|
||||
obj.cfield == '' ||
|
||||
obj.compareType == ''
|
||||
) {
|
||||
data.formulaVisible = true;
|
||||
} else {
|
||||
if (data.isEdit) {
|
||||
node.value.conditions[data.rowIndex] = obj;
|
||||
} else {
|
||||
obj.code = buildGUID();
|
||||
node.value.conditions.push(obj);
|
||||
}
|
||||
data.formulaVisible = false;
|
||||
}
|
||||
}
|
||||
function handleSqlOk() {
|
||||
conditionSqlRef.value.validateForm();
|
||||
let obj = conditionSqlRef.value.getForm();
|
||||
obj.type = '2';
|
||||
if (obj.name == '' || obj.dbCode == '' || obj.sql == '') {
|
||||
data.sqlVisible = true;
|
||||
} else {
|
||||
if (data.isEdit) {
|
||||
node.value.conditions[data.rowIndex] = obj;
|
||||
} else {
|
||||
obj.code = buildGUID();
|
||||
node.value.conditions.push(obj);
|
||||
}
|
||||
data.sqlVisible = false;
|
||||
}
|
||||
}
|
||||
function onEdit(record) {
|
||||
data.isEdit = true;
|
||||
propsData.value = record;
|
||||
if (record.type == 1) {
|
||||
data.formulaVisible = true;
|
||||
} else {
|
||||
data.sqlVisible = true;
|
||||
}
|
||||
}
|
||||
function onDelete(id) {
|
||||
let list = node.value.conditions;
|
||||
var currentIndex = (list || []).findIndex((element) => element.id == id);
|
||||
node.value.conditions.splice(currentIndex, 1);
|
||||
}
|
||||
|
||||
defineExpose({});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
::v-deep .ant-alert-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
::v-deep .ant-table-wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import BpmnPropertiesPanel from "./PropertiesPanel.vue";
|
||||
|
||||
BpmnPropertiesPanel.install = function(Vue) {
|
||||
Vue.component(BpmnPropertiesPanel.name, BpmnPropertiesPanel);
|
||||
};
|
||||
|
||||
export default BpmnPropertiesPanel;
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<!-- 开始节点配置 -->
|
||||
<template>
|
||||
<div class="start-event">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="node"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
:disabled="data.componentDisabled"
|
||||
>
|
||||
<a-form-item label="节点标识">
|
||||
<a-input v-model:value="node.id" placeholder="请输入" readonly />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="data.conditionsOptions && data.conditionsOptions.length > 0"
|
||||
label="流转条件"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="node.lineConditions"
|
||||
placeholder="请选择"
|
||||
:options="data.conditionsOptions"
|
||||
:field-names="{ label: 'name', value: 'code' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, defineProps, onMounted, watch } from 'vue';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const props = defineProps({
|
||||
element: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
pageType: String,
|
||||
});
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
let node: any = ref({});
|
||||
const data = reactive({
|
||||
conditionsOptions: [],
|
||||
componentDisabled: props.pageType == 'detail' ? true : false,
|
||||
});
|
||||
watch(
|
||||
() => props.element,
|
||||
(newVal) => {
|
||||
if (newVal.type == 'bpmn:SequenceFlow') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(newVal.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
node.value.from = newVal.from;
|
||||
node.value.to = newVal.to;
|
||||
} else {
|
||||
node.value = newVal;
|
||||
}
|
||||
getConditions();
|
||||
}
|
||||
},
|
||||
);
|
||||
onMounted(() => {
|
||||
if (props.element.type == 'bpmn:SequenceFlow') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(props.element.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = props.element;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function getConditions() {
|
||||
/**
|
||||
ExclusiveGateway:'排他网关',
|
||||
Task:'审核节点',
|
||||
*/
|
||||
let wfdata = flowWfDataStore.getWfData;
|
||||
wfdata.forEach((element: { id: string; conditions: any; btnlist: any; type: string }) => {
|
||||
if (element.id == node.value.from) {
|
||||
if (element.type == 'bpmn:ExclusiveGateway') {
|
||||
data.conditionsOptions = element.conditions;
|
||||
} else if (element.type == 'bpmn:Task') {
|
||||
data.conditionsOptions = element.btnlist.filter((t) => !t.hidden);
|
||||
} else {
|
||||
data.conditionsOptions = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
export { default as shcemeinfoConfig } from './shcemeInfo/index.vue';
|
||||
export { default as authConfig } from './auth/index.vue';
|
||||
export { default as StartEventOption } from './startEvent/index.vue';
|
||||
export { default as userTaskOption } from './userTask/index.vue';
|
||||
export { default as endEventOption } from './endEvent/index.vue';
|
||||
export { default as parallelGatewayOption } from './parallelGateway/index.vue';
|
||||
export { default as exclusiveGatewayOption } from './exclusiveGateway/index.vue';
|
||||
export { default as inclusiveGatewayOption } from './inclusiveGateway/index.vue';
|
||||
export { default as subprocessOption } from './subprocess/index.vue';
|
||||
export { default as mylineOption } from './myline/index.vue';
|
||||
export { default as scriptOption } from './script/index.vue';
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
<!-- 开始节点配置 -->
|
||||
<template>
|
||||
<div class="user-task">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="node"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
:disabled="data.componentDisabled"
|
||||
>
|
||||
<a-form-item label="节点标识">
|
||||
<a-input v-model:value="node.id" placeholder="请输入" readonly />
|
||||
</a-form-item>
|
||||
<a-alert
|
||||
message="并行网关说明"
|
||||
description="并行网关会等待所有分支汇入才往下执行,所有出口分支都会被执行"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, onMounted, watch, defineProps } from 'vue';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const props = defineProps({
|
||||
element: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
schemeCode: String,
|
||||
pageType: String,
|
||||
pageView: String,
|
||||
});
|
||||
const node: any = ref({});
|
||||
watch(
|
||||
() => props.element,
|
||||
(newVal) => {
|
||||
if (newVal.type == 'bpmn:ParallelGateway') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(newVal.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const data = reactive({
|
||||
componentDisabled: props.pageType == 'detail' ? true : false,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.element.type == 'bpmn:ParallelGateway') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(props.element.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = props.element;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
::v-deep .ant-alert-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
::v-deep .ant-table-wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
<template>
|
||||
<div class="start-event">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="node"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
:disabled="data.componentDisabled"
|
||||
>
|
||||
<a-form-item label="节点标识">
|
||||
<a-input v-model:value="node.id" placeholder="请输入" readonly />
|
||||
</a-form-item>
|
||||
<a-divider plain="true">执行操作</a-divider>
|
||||
<a-tabs v-model:activeKey="node.executeType" type="card" size="small" centered="true">
|
||||
<a-tab-pane key="1" tab="执行SQL">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-alert
|
||||
message="sql参数说明"
|
||||
description="参数有 @processId流程进程主键 @code上一步执行码 @userId流程发起人Id @userAccount流程发起人账号 @companyId流程发起人公司 @departmentId流程发起人部门;
|
||||
@userId2上一步审核人Id @userAccount2上一步审核人账号 @companyId2上一步审核人公司 @departmentId2上一步审核人部门;
|
||||
"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
<a-space-compact block>
|
||||
<a-select
|
||||
v-model:value="node.sqlDb"
|
||||
placeholder="请选择执行SQL数据库"
|
||||
:options="sqlOptions"
|
||||
:field-names="{ label: 'text', value: 'id', options: 'childNodes' }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="node.sqlStr"
|
||||
placeholder="请填写SQL语句"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="node.sqlStrRevoke"
|
||||
placeholder="请填写SQL语句(撤销)"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab=".NET方法">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-alert
|
||||
message="IOC说明"
|
||||
description="注意:编写一个继承IWorkFlowMethod的类
|
||||
"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="node.ioc"
|
||||
placeholder="IOC名称"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="node.iocRevoke"
|
||||
placeholder="IOC名称(撤销)"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="第三方接口">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-alert
|
||||
message="接口参数说明"
|
||||
description="注意:配置支持Post方法的接口,json数据格式。{
|
||||
processId:'流程发起实例主键',userId:'流程发起人Id',userAccount:'流程发起人账号',companyId:'流程发起人公司',departmentId:'流程发起人部门',code:'上一步执行码',
|
||||
userId2:'上一步审核人Id',userAccount2:'上一步审核人账号',companyId2:'上一步审核人公司',departmentId2:'上一步审核人部门'
|
||||
}
|
||||
"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="node.apiUrl"
|
||||
placeholder="接口地址"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="node.apiUrlRevoke"
|
||||
placeholder="接口地址(撤销)"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, defineProps, ref, watch, onMounted } from 'vue';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
import { getLoadDataBaseLinkTree } from '@/api/demo/system';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
// 表单选择声明
|
||||
const props = defineProps({
|
||||
element: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
pageType: String,
|
||||
});
|
||||
interface dataType {
|
||||
columns: any;
|
||||
formRelations: any;
|
||||
elementData: any;
|
||||
componentDisabled: boolean;
|
||||
formOpen: boolean;
|
||||
formVerisons: any;
|
||||
formName: string;
|
||||
}
|
||||
const data: dataType = reactive({
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'label',
|
||||
},
|
||||
{
|
||||
title: '字段',
|
||||
dataIndex: 'field',
|
||||
},
|
||||
{
|
||||
title: '必填',
|
||||
dataIndex: 'required',
|
||||
},
|
||||
{
|
||||
title: '编辑',
|
||||
dataIndex: 'disabled',
|
||||
},
|
||||
{
|
||||
title: '查看',
|
||||
dataIndex: 'ifShow',
|
||||
},
|
||||
],
|
||||
formRelations: [],
|
||||
elementData: props.element,
|
||||
componentDisabled: props.pageType == 'detail' ? true : false,
|
||||
formOpen: false,
|
||||
formVerisons: [],
|
||||
formName: '',
|
||||
});
|
||||
let node: any = ref({});
|
||||
watch(
|
||||
() => props.element,
|
||||
(newVal) => {
|
||||
if (newVal.type == 'bpmn:ScriptTask') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(newVal.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
const sqlOptions = ref([]);
|
||||
async function getSQL() {
|
||||
const data = await getLoadDataBaseLinkTree();
|
||||
let res: any = [];
|
||||
data.forEach((element) => {
|
||||
if (element.childNodes == null) {
|
||||
element.childNodes = [];
|
||||
}
|
||||
let point = {};
|
||||
if (element.id == 'hcsystemdb') {
|
||||
point = {
|
||||
id: '1',
|
||||
text: '常用数据库',
|
||||
childNodes: [
|
||||
{
|
||||
id: element.id,
|
||||
text: element.text,
|
||||
},
|
||||
],
|
||||
};
|
||||
res.push(point);
|
||||
} else {
|
||||
res.push(element);
|
||||
}
|
||||
});
|
||||
sqlOptions.value = res;
|
||||
}
|
||||
onMounted(() => {
|
||||
getSQL();
|
||||
if (props.element.type == 'bpmn:ScriptTask') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(props.element.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = props.element;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.site-space-compact-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .ant-tabs {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
::v-deep .ant-space {
|
||||
width: 90%;
|
||||
margin-left: 5%;
|
||||
|
||||
.ant-space-item {
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
width: 20%;
|
||||
}
|
||||
.ant-btn-dangerous {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.l-rblock {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
.l-page-pane {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 794px;
|
||||
overflow: hidden auto;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
margin: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
.addDataBaseTableBox {
|
||||
border: 1px dashed #f0f0f0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin-top: -20px;
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
.connectTableTitle {
|
||||
padding-top: 20px;
|
||||
}
|
||||
.formLine {
|
||||
height: 50px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,340 @@
|
|||
<template>
|
||||
<div class="shceme-info">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:rules="rules"
|
||||
:model="formState"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
:disabled="data.componentDisabled"
|
||||
>
|
||||
<template v-if="!data.componentDisabled">
|
||||
<a-form-item label="模板编号" name="code">
|
||||
<a-input v-model:value="formState.code" placeholder="请输入" />
|
||||
</a-form-item>
|
||||
<a-form-item label="模板名称" name="name">
|
||||
<a-input v-model:value="formState.name" placeholder="请输入" />
|
||||
</a-form-item>
|
||||
<a-form-item label="模板图标" name="icon">
|
||||
<IconPicker v-model:value="formState.icon" />
|
||||
</a-form-item>
|
||||
<a-form-item label="图标颜色" name="color">
|
||||
<a-input
|
||||
type="color"
|
||||
:key="formState.color"
|
||||
:default-value="formState.color"
|
||||
v-model="formState.color"
|
||||
placeholder="请输入"
|
||||
@change="(e) => colorChange(e.target.value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="模板分类" name="category">
|
||||
<a-select
|
||||
v-model:value="formState.category"
|
||||
placeholder="请选择"
|
||||
:options="data.optionsType"
|
||||
:field-names="{ label: 'itemName', value: 'itemName' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="我的任务创建">
|
||||
<a-radio-group v-model:value="formState.mark" name="radioGroup">
|
||||
<a-radio
|
||||
v-for="(item, index) in data.optionsNotOrOk"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-radio
|
||||
>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="移动端创建">
|
||||
<a-radio-group v-model:value="formState.isInApp" name="radioGroup">
|
||||
<a-radio
|
||||
v-for="(item, index) in data.optionsNotOrOk"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-radio
|
||||
>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-textarea
|
||||
v-model:value="formState.description"
|
||||
placeholder="请填写备注"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</template>
|
||||
<a-divider plain="true">撤销操作</a-divider>
|
||||
<a-tabs v-model:activeKey="formState.undoType" type="card" size="small" centered="true">
|
||||
<a-tab-pane key="1" tab="执行SQL">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-select
|
||||
v-model:value="formState.undoDbCode"
|
||||
placeholder="请选择执行SQL数据库"
|
||||
:options="sqlOptions"
|
||||
:field-names="{ label: 'text', value: 'id', options: 'childNodes' }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="formState.undoDbSQL"
|
||||
placeholder="请填写SQL语句,参数有 @processId流程进程主键 @userId用户Id @userAccount用户账号 @companyId用户公司 @departmentId用户部门"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab=".NET方法">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="formState.undoIOCName"
|
||||
placeholder="请填写.NET方法IOC别名"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="第三方接口">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="formState.undoUrl"
|
||||
placeholder="请填写第三方接口地址(POST),JSON 格式,参数有 userId用户Id,userAccount用户账号,companyId用户公司,departmentId用户部门"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<a-divider plain="true">作废操作</a-divider>
|
||||
<a-tabs v-model:activeKey="formState.deleteType" type="card" size="small" centered="true">
|
||||
<a-tab-pane key="1" tab="执行SQL">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-select
|
||||
v-model:value="formState.deleteDbCode"
|
||||
placeholder="请选择执行SQL数据库"
|
||||
:options="sqlOptions"
|
||||
:field-names="{ label: 'text', value: 'id', options: 'childNodes' }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="formState.deleteDbSQL"
|
||||
placeholder="请填写SQL语句,参数有 @processId流程进程主键 @userId用户Id @userAccount用户账号 @companyId用户公司 @departmentId用户部门"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab=".NET方法">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="formState.deleteIOCName"
|
||||
placeholder="请填写.NET方法IOC别名"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="第三方接口">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="formState.undoUrl"
|
||||
placeholder="请填写第三方接口地址(POST),JSON 格式,参数有 userId用户Id,userAccount用户账号,companyId用户公司,departmentId用户部门"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<a-divider plain="true">删除草稿</a-divider>
|
||||
<a-tabs
|
||||
v-model:activeKey="formState.deleteDraftType"
|
||||
type="card"
|
||||
size="small"
|
||||
centered="true"
|
||||
>
|
||||
<a-tab-pane key="1" tab="执行SQL">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-select
|
||||
v-model:value="formState.deleteDraftDbCode"
|
||||
placeholder="请选择执行SQL数据库"
|
||||
:options="sqlOptions"
|
||||
:field-names="{ label: 'text', value: 'id', options: 'childNodes' }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="formState.deleteDraftDbSQL"
|
||||
placeholder="请填写SQL语句,参数有 @processId流程进程主键 @userId用户Id @userAccount用户账号 @companyId用户公司 @departmentId用户部门"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab=".NET方法">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="formState.deleteDraftIOCName"
|
||||
placeholder="请填写.NET方法IOC别名"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="第三方接口">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-textarea
|
||||
v-model:value="formState.undoUrl"
|
||||
placeholder="请填写第三方接口地址(POST),JSON 格式,参数有 userId用户Id,userAccount用户账号,companyId用户公司,departmentId用户部门"
|
||||
:auto-size="{ minRows: 5, maxRows: 8 }"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, onMounted, defineProps } from 'vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import { IconPicker } from '@/components/Icon/index';
|
||||
import { getLoad } from '@/api/sys/sysDataItemDetail';
|
||||
import { getLoadDataBaseLinkTree } from '@/api/demo/system';
|
||||
|
||||
const formRef = ref();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const props = defineProps({
|
||||
pageType: String,
|
||||
});
|
||||
const data = reactive({
|
||||
optionsNotOrOk: [
|
||||
{ label: '允许', value: 1 },
|
||||
{ label: '不允许', value: 2 },
|
||||
],
|
||||
optionsType: [],
|
||||
componentDisabled: props.pageType == 'detail' ? true : false,
|
||||
});
|
||||
const sqlOptions = ref([]);
|
||||
|
||||
let formState = ref({
|
||||
code: '',
|
||||
name: '',
|
||||
category: '',
|
||||
enabledMark: 1,
|
||||
mark: 1,
|
||||
isInApp: 2,
|
||||
description: '',
|
||||
titleRules: '',
|
||||
undoType: '1',
|
||||
undoDbCode: '',
|
||||
undoDbSQL: '',
|
||||
undoIOCName: '',
|
||||
undoUrl: '',
|
||||
deleteType: '1',
|
||||
deleteDbCode: '',
|
||||
deleteDbSQL: '',
|
||||
deleteIOCName: '',
|
||||
deleteUrl: '',
|
||||
deleteDraftType: '1',
|
||||
deleteDraftDbCode: '',
|
||||
deleteDraftDbSQL: '',
|
||||
deleteDraftIOCName: '',
|
||||
deleteDraftUrl: '',
|
||||
icon: 'ant-design:appstore-outlined',
|
||||
color: '#409EFF',
|
||||
});
|
||||
const rules: Record<string, Rule[]> = {
|
||||
code: [
|
||||
{ required: true, message: '请输入模板编号' },
|
||||
// { validator: lr_existDbFiled, keyValue: () => { return data.formData.f_Id }, tableName: 'lr_wf_schemeinfo', keyName: 'f_Id', trigger: 'blur' }
|
||||
],
|
||||
name: [{ required: true, message: '请输入模板名称' }],
|
||||
category: [{ required: true, message: '请选择模板分类' }],
|
||||
icon: [{ required: true, message: '请选择图标' }],
|
||||
color: [{ required: true, message: '请选择颜色' }],
|
||||
};
|
||||
function colorChange(color) {
|
||||
formState.value.color = color;
|
||||
}
|
||||
async function getSQL() {
|
||||
const data = await getLoadDataBaseLinkTree();
|
||||
let res: any = [];
|
||||
data.forEach((element) => {
|
||||
if (element.childNodes == null) {
|
||||
element.childNodes = [];
|
||||
}
|
||||
let point = {};
|
||||
if (element.id == 'hcsystemdb') {
|
||||
point = {
|
||||
id: '1',
|
||||
text: '常用数据库',
|
||||
childNodes: [
|
||||
{
|
||||
id: element.id,
|
||||
text: element.text,
|
||||
},
|
||||
],
|
||||
};
|
||||
res.push(point);
|
||||
} else {
|
||||
res.push(element);
|
||||
}
|
||||
});
|
||||
sqlOptions.value = res;
|
||||
}
|
||||
async function fetch() {
|
||||
let list = await getLoad({ code: 'FlowSort' });
|
||||
data.optionsType = list;
|
||||
}
|
||||
async function validateForm() {
|
||||
let res = await formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
return false;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
function setForm(data) {
|
||||
formState.value = data;
|
||||
}
|
||||
function getForm() {
|
||||
return formState.value;
|
||||
}
|
||||
defineExpose({
|
||||
setForm,
|
||||
getForm,
|
||||
validateForm,
|
||||
});
|
||||
onMounted(() => {
|
||||
fetch();
|
||||
getSQL();
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.site-space-compact-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .ant-tabs {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,523 @@
|
|||
<!-- 开始节点配置 -->
|
||||
<template>
|
||||
<div class="start-event">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="node"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
:disabled="data.componentDisabled"
|
||||
>
|
||||
<a-form-item label="节点标识">
|
||||
<a-input v-model:value="node.id" placeholder="请输入" readonly />
|
||||
</a-form-item>
|
||||
<a-form-item label="下一审核人">
|
||||
<a-switch v-model:checked="node.isNextAuditor" @change="updateWfData('isNextAuditor')" />
|
||||
</a-form-item>
|
||||
<a-form-item label="自定义标题">
|
||||
<a-switch v-model:checked="node.isCustmerTitle" @change="updateWfData('isCustmerTitle')" />
|
||||
</a-form-item>
|
||||
<a-form-item label="通知策略" name="f_Color">
|
||||
<a-checkbox-group
|
||||
v-model:value="node.messageType"
|
||||
name="checkboxgroup"
|
||||
@change="updateWfData('messageType')"
|
||||
:options="[
|
||||
{ value: '1', label: '短信' },
|
||||
{ value: '2', label: '邮箱' },
|
||||
{ value: '3', label: '微信' },
|
||||
{ value: '4', label: '站内消息' },
|
||||
]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-divider plain="true">表单添加</a-divider>
|
||||
<a-tabs
|
||||
v-model:activeKey="node.formType"
|
||||
type="card"
|
||||
size="small"
|
||||
centered="true"
|
||||
@change="tabsChange"
|
||||
>
|
||||
<a-tab-pane key="1" tab="自定义表单">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-input v-model:value="data.formName" placeholder="请选择表单" readonly />
|
||||
<a-button @click="handleShow">选择</a-button>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-select
|
||||
v-model:value="node.formVerison"
|
||||
@change="custmerformVerisonChange"
|
||||
:options="data.formVerisons"
|
||||
:fieldNames="{ value: 'id', label: 'createDate' }"
|
||||
placeholder="请选择表单版本"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-select
|
||||
v-model:value="node.formRelationId"
|
||||
placeholder="请选择流程关联字段"
|
||||
@change="updateWfData('formRelationId')"
|
||||
:options="data.formRelations"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-input v-model:value="node.formTitle" placeholder="请输入表单标题" />
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-input v-model:value="data.issueName" placeholder="请选择发布表单" readonly />
|
||||
<a-button @click="handleIssueShow">选择</a-button>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="系统表单">
|
||||
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
|
||||
<a-space-compact block>
|
||||
<a-input
|
||||
v-model:value="node.formUrl"
|
||||
placeholder="请输入PC端表单地址"
|
||||
@change="updateWfData('formUrl')"
|
||||
/>
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-input v-model:value="node.formTitle" placeholder="请输入PC端表单标题" />
|
||||
</a-space-compact>
|
||||
<a-space-compact block>
|
||||
<a-input
|
||||
v-model:value="node.formAppUrl"
|
||||
placeholder="请输入APP端表单地址"
|
||||
@change="updateWfData('formAppUrl')"
|
||||
/>
|
||||
</a-space-compact>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<a-divider plain="true">表单字段权限</a-divider>
|
||||
<a-table
|
||||
:columns="data.columns"
|
||||
:data-source="node.authFields"
|
||||
bordered
|
||||
:pagination="false"
|
||||
v-if="node.formType == 1"
|
||||
>
|
||||
<template #headerCell="{ column }">
|
||||
<template v-if="column.dataIndex === 'required'">
|
||||
<a-checkbox v-model:checked="requiredCheck" @change="handleChangeCheck('required',$event)">{{column.title}}</a-checkbox>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'disabled'">
|
||||
<a-checkbox v-model:checked="disabledCheck" @change="handleChangeCheck('disabled',$event)">{{column.title}}</a-checkbox>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'ifShow'">
|
||||
<a-checkbox v-model:checked="ifShowCheck" @change="handleChangeCheck('ifShow',$event)">{{column.title}}</a-checkbox>
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="['label', 'field'].includes(column.dataIndex)">
|
||||
<div>
|
||||
{{ text }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="['required', 'disabled', 'ifShow'].includes(column.dataIndex)">
|
||||
<div>
|
||||
<a-switch v-model:checked="record[column.dataIndex]" size="small" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-table
|
||||
:columns="data.columns"
|
||||
:data-source="node.authFields"
|
||||
bordered
|
||||
:pagination="false"
|
||||
v-else
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="['label', 'fieldName'].includes(column.dataIndex)">
|
||||
<div>
|
||||
<a-input v-model:value="record[column.dataIndex]" placeholder="请输入" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="['required', 'disabled', 'ifShow'].includes(column.dataIndex)">
|
||||
<div>
|
||||
<a-switch v-model:checked="record[column.dataIndex]" size="small" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'operation'">
|
||||
<delete-outlined
|
||||
two-tone-color="#eb2f96"
|
||||
@click="handleDeleteAuthField(record.field)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<a-divider plain="true" />
|
||||
<a-space v-if="node.formType != 1">
|
||||
<a-button
|
||||
@click="handleAddAuthField"
|
||||
width="100%"
|
||||
type="dashed"
|
||||
danger
|
||||
:icon="h(PlusOutlined)"
|
||||
>添加字段</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form>
|
||||
<a-modal
|
||||
width="60%"
|
||||
wrap-class-name="full-modal"
|
||||
v-model:open="data.formOpen"
|
||||
title="选择表单"
|
||||
@ok="formHandleOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<SelectForm ref="formRef" />
|
||||
</a-modal>
|
||||
<a-modal
|
||||
width="60%"
|
||||
wrap-class-name="full-modal"
|
||||
v-model:open="data.issueOpen"
|
||||
title="选择发布表单"
|
||||
@ok="issueHandleOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<SelectIssueForm ref="issueRef" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, defineProps, ref, watch, h, onMounted } from 'vue';
|
||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
import { SelectForm } from '@/components/SelectForm/index';
|
||||
import { SelectIssueForm } from '@/components/SelectIssueForm/index';
|
||||
import { functionGetSchemePageList, functionLoadFormPage } from '@/api/demo/formScheme';
|
||||
import { cardNestStructure } from '@/views/demo/onlineform/util.ts';
|
||||
import { fun_GetPageList } from '@/api/demo/formModule';
|
||||
import { getRFields } from '@/views/demo/workflow/scheme/util.ts'
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const requiredCheck = ref(false)
|
||||
const disabledCheck = ref(false)
|
||||
const ifShowCheck = ref(false)
|
||||
// 表单选择声明
|
||||
const props = defineProps({
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
element: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
schemeCode: String,
|
||||
pageType: String,
|
||||
pageView: String,
|
||||
});
|
||||
interface dataType {
|
||||
columns: any;
|
||||
formRelations: any;
|
||||
elementData: any;
|
||||
componentDisabled: boolean;
|
||||
formOpen: boolean;
|
||||
formVerisons: any;
|
||||
formName: string;
|
||||
issueOpen: boolean;
|
||||
issueName: string;
|
||||
mapConfig:string;
|
||||
}
|
||||
const data: dataType = reactive({
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'label',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '字段',
|
||||
dataIndex: 'fieldName',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '必填',
|
||||
dataIndex: 'required',
|
||||
width: 85,
|
||||
},
|
||||
{
|
||||
title: '编辑',
|
||||
dataIndex: 'disabled',
|
||||
width: 85,
|
||||
},
|
||||
{
|
||||
title: '查看',
|
||||
dataIndex: 'ifShow',
|
||||
width: 85,
|
||||
},
|
||||
],
|
||||
formRelations: [],
|
||||
elementData: props.element,
|
||||
componentDisabled: props.pageType == 'detail' ? true : false,
|
||||
formOpen: false,
|
||||
formVerisons: [],
|
||||
formName: '',
|
||||
issueName: '',
|
||||
issueOpen: false,
|
||||
});
|
||||
let node: any = ref({});
|
||||
watch(
|
||||
() => node.value.formCode,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
getFormList();
|
||||
getIssueFormList();
|
||||
getVersions(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.element,
|
||||
(newVal) => {
|
||||
if (newVal.type == 'bpmn:StartEvent') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(newVal.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => node.value.formType,
|
||||
(newVal) => {
|
||||
if (newVal == 1) {
|
||||
data.columns = data.columns.filter((item) => item.dataIndex !== 'operation');
|
||||
} else {
|
||||
data.columns.push({
|
||||
title: '',
|
||||
dataIndex: 'operation',
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
onMounted(() => {
|
||||
if (props.element.type == 'bpmn:StartEvent') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(props.element.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = props.element;
|
||||
}
|
||||
}
|
||||
});
|
||||
// 编辑时获取表单名称,表单版本
|
||||
async function getFormList() {
|
||||
const list = await functionLoadFormPage({
|
||||
page: 1,
|
||||
limit: 1000,
|
||||
});
|
||||
list.items.forEach((element) => {
|
||||
if (element.id == node.value.formCode) {
|
||||
data.formName = element.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
// 切换自定义表单和系统表单
|
||||
function tabsChange() {
|
||||
data.formName = '';
|
||||
node.value.formCode = '';
|
||||
node.value.formTitle = '';
|
||||
node.value.formVerison = undefined;
|
||||
node.value.formRelationId = undefined;
|
||||
node.value.formUrl = '';
|
||||
node.value.formAppUrl = '';
|
||||
node.value.authFields = [];
|
||||
node.value.formRelations = [];
|
||||
}
|
||||
// 表单选择打开
|
||||
function handleShow() {
|
||||
data.formOpen = true;
|
||||
}
|
||||
// 表单选择
|
||||
const formRef = ref<any>();
|
||||
async function formHandleOk() {
|
||||
let obj = formRef.value.getRow();
|
||||
node.value.formCode = obj[0].id;
|
||||
data.formName = obj[0].name;
|
||||
node.value.formVerison = undefined;
|
||||
node.value.formRelationId = undefined;
|
||||
node.value.formUrl = '';
|
||||
node.value.formAppUrl = '';
|
||||
node.value.authFields = [];
|
||||
node.value.formRelations = [];
|
||||
getVersions(true);
|
||||
data.formOpen = false;
|
||||
}
|
||||
// 发布表单
|
||||
function handleIssueShow() {
|
||||
data.issueOpen = true;
|
||||
}
|
||||
const issueRef = ref<any>();
|
||||
async function issueHandleOk() {
|
||||
let obj = issueRef.value.getRow();
|
||||
let formPublishInfo = JSON.parse(obj[0].scheme)
|
||||
let mapConfig = formPublishInfo.table.maps;
|
||||
node.value.issueId = obj[0].id;
|
||||
node.value.mapConfig = mapConfig
|
||||
data.issueName = obj[0].name;
|
||||
data.issueOpen = false;
|
||||
data.mapConfig = mapConfig;
|
||||
}
|
||||
// 编辑时获取发布表单名称
|
||||
async function getIssueFormList() {
|
||||
const list = await fun_GetPageList({
|
||||
page: 1,
|
||||
limit: 1000,
|
||||
});
|
||||
list.items.forEach((element) => {
|
||||
if (element.id == node.value.issueId) {
|
||||
data.issueName = element.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function getVersions(isChange) {
|
||||
const list = await functionGetSchemePageList({
|
||||
schemeInfoId: node.value.formCode,
|
||||
});
|
||||
data.formVerisons = list.items;
|
||||
if (node.value.formVerison) {
|
||||
custmerformVerisonChange(node.value.formVerison, isChange);
|
||||
}
|
||||
}
|
||||
// 表单版本更改
|
||||
async function custmerformVerisonChange(val, isChange) {
|
||||
let obj;
|
||||
data.formVerisons.forEach((element) => {
|
||||
if (element.id == val) {
|
||||
obj = element;
|
||||
}
|
||||
});
|
||||
loadFormScheme(obj.scheme, isChange);
|
||||
}
|
||||
function loadFormScheme(strScheme, isChange) {
|
||||
const scheme = JSON.parse(strScheme);
|
||||
let fields: any[] = [];
|
||||
let rfields: {
|
||||
label?: string;
|
||||
value?: string;
|
||||
}[] = [];
|
||||
scheme.formInfo.tabList = cardNestStructure(scheme.formInfo.tabList);
|
||||
scheme.formInfo.tabList.forEach((tabElement) => {
|
||||
const {rFieldList, fieldList} = getRFields(tabElement.schemas,rfields,fields)
|
||||
rfields = rFieldList
|
||||
fields = fieldList
|
||||
});
|
||||
data.formRelations = rfields;
|
||||
if (isChange) {
|
||||
node.value.authFields = fields;
|
||||
}
|
||||
}
|
||||
function updateWfData(key) {
|
||||
// flowWfDataStore.updataWfDataNode(node.value.id, key, node.value[key]);
|
||||
}
|
||||
function handleAddAuthField() {
|
||||
node.value.authFields.push({
|
||||
field: '',
|
||||
label: '',
|
||||
required: true,
|
||||
disabled: true,
|
||||
ifShow: true,
|
||||
});
|
||||
}
|
||||
function handleDeleteAuthField(key) {
|
||||
node.value.authFields = node.value.authFields.filter((item) => item.field !== key);
|
||||
}
|
||||
const handleChangeCheck = (type: string, data) => {
|
||||
let checked = data.target.checked
|
||||
node.value.authFields.forEach(item => {
|
||||
switch (type) {
|
||||
case 'required':
|
||||
item.required = checked
|
||||
break
|
||||
case 'disabled':
|
||||
item.disabled = checked
|
||||
break
|
||||
case 'ifShow':
|
||||
item.ifShow = checked
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.site-space-compact-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .ant-tabs {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
::v-deep .ant-space {
|
||||
width: 90%;
|
||||
margin-left: 5%;
|
||||
|
||||
.ant-space-item {
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
width: 20%;
|
||||
}
|
||||
.ant-btn-dangerous {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ant-checkbox+span){
|
||||
padding-inline-end: 0px;
|
||||
}
|
||||
.l-rblock {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
.l-page-pane {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 794px;
|
||||
overflow: hidden auto;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
margin: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
.addDataBaseTableBox {
|
||||
border: 1px dashed #f0f0f0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin-top: -20px;
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
.connectTableTitle {
|
||||
padding-top: 20px;
|
||||
}
|
||||
.formLine {
|
||||
height: 50px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<!-- 开始节点配置 -->
|
||||
<template>
|
||||
<div class="subprocess">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="node"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
:disabled="data.componentDisabled"
|
||||
>
|
||||
<a-form-item label="节点标识">
|
||||
<a-input v-model:value="node.id" placeholder="请输入" readonly />
|
||||
</a-form-item>
|
||||
<a-form-item label="是否异步">
|
||||
<a-switch v-model:checked="node.isAsync" />
|
||||
</a-form-item>
|
||||
<a-form-item label="流程模版">
|
||||
<a-select
|
||||
v-model:value="node.wfschemeId"
|
||||
:options="list"
|
||||
@change="changeScheme"
|
||||
:fieldNames="{ value: 'id', label: 'name' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="流程版本">
|
||||
<a-select
|
||||
v-model:value="node.wfVersionId"
|
||||
:options="verisons"
|
||||
:fieldNames="{ value: 'id', label: 'createDate' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, defineProps, ref, watch, onMounted } from 'vue';
|
||||
import { getLoad, getVerisonsLoad } from '@/api/sys/WFSchemeInfo';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const props = defineProps({
|
||||
element: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
schemeCode: String,
|
||||
pageType: String,
|
||||
pageView: String,
|
||||
});
|
||||
let node: any = ref({});
|
||||
const data = reactive({
|
||||
componentDisabled: props.pageType == 'detail' ? true : false,
|
||||
});
|
||||
const list: any = ref([]);
|
||||
const verisons: any = ref([]);
|
||||
watch(
|
||||
() => props.element,
|
||||
(newVal) => {
|
||||
if (newVal.type == 'bpmn:SubProcess') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(newVal.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
async function getSchemeList() {
|
||||
const data = await getLoad();
|
||||
list.value = data;
|
||||
}
|
||||
|
||||
async function changeScheme() {
|
||||
const data = await getVerisonsLoad({ id: node.value.wfschemeId });
|
||||
verisons.value = data;
|
||||
}
|
||||
onMounted(() => {
|
||||
getSchemeList();
|
||||
if (props.element.type == 'bpmn:SubProcess') {
|
||||
const currentNode = flowWfDataStore.getWfDataNode(props.element.id);
|
||||
if (currentNode) {
|
||||
node.value = currentNode;
|
||||
} else {
|
||||
node.value = props.element;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({});
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,4 @@
|
|||
export { default as AuditorLevel } from './src/auditorLevel.vue';
|
||||
export { default as AuditorSql } from './src/auditorSql.vue';
|
||||
export { default as AuditorNode } from './src/auditorNode.vue';
|
||||
export { default as ExecuteSQL } from './src/executeSQL.vue';
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div class="l-from-body">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:rules="data.rules"
|
||||
:model="data.formData"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="上下级" name="id">
|
||||
<a-select
|
||||
v-model:value="data.formData.id"
|
||||
placeholder="请选择上下级"
|
||||
:options="data.options"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
const formRef = ref<any>();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const data = reactive({
|
||||
formData: {
|
||||
name: '',
|
||||
type: '4',
|
||||
id: '',
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '请选择上下级' }],
|
||||
},
|
||||
options: [
|
||||
{ value: '1', label: '上一级' },
|
||||
{ value: '2', label: '上二级' },
|
||||
{ value: '3', label: '上三级' },
|
||||
{ value: '4', label: '上四级' },
|
||||
{ value: '5', label: '上五级' },
|
||||
{ value: '6', label: '下一级' },
|
||||
{ value: '7', label: '下二级' },
|
||||
{ value: '8', label: '下三级' },
|
||||
{ value: '9', label: '下四级' },
|
||||
{ value: '10', label: '下五级' },
|
||||
],
|
||||
});
|
||||
function handleChange(val, option) {
|
||||
data.formData.name = option.label;
|
||||
}
|
||||
function validateForm() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
return true;
|
||||
})
|
||||
.catch(async () => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
function getForm() {
|
||||
let rows = data.formData;
|
||||
return rows;
|
||||
}
|
||||
defineExpose({
|
||||
getForm,
|
||||
validateForm,
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.l-from-body {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<div class="l-from-body">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:rules="data.rules"
|
||||
:model="data.formData"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="节点" name="id">
|
||||
<a-select
|
||||
v-model:value="data.formData.id"
|
||||
placeholder="请选择节点"
|
||||
:options="data.options"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, defineProps, watch, onMounted } from 'vue';
|
||||
import { flowStore } from '@/store/modules/flow';
|
||||
|
||||
const flowWfDataStore = flowStore();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
});
|
||||
watch(
|
||||
() => props.id,
|
||||
(newVal) => {
|
||||
getOptions(newVal);
|
||||
},
|
||||
);
|
||||
const formRef = ref<any>();
|
||||
const data = reactive({
|
||||
formData: {
|
||||
name: '',
|
||||
type: '5',
|
||||
id: '',
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '请选择节点' }],
|
||||
},
|
||||
options: [],
|
||||
});
|
||||
onMounted(() => {
|
||||
getOptions(props.id);
|
||||
});
|
||||
function getOptions(nodeId) {
|
||||
/**
|
||||
ExclusiveGateway:'排他网关',
|
||||
Task:'审核节点',
|
||||
*/
|
||||
let wfdata = flowWfDataStore.getWfData;
|
||||
var arr: any = [];
|
||||
wfdata.forEach((element: { type: string; id: string }) => {
|
||||
if (element.type == 'bpmn:Task' && element.id != nodeId) {
|
||||
arr.push(element);
|
||||
}
|
||||
});
|
||||
data.options = arr;
|
||||
}
|
||||
function handleChange(val, option) {
|
||||
data.formData.name = option.name;
|
||||
}
|
||||
function validateForm() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
return true;
|
||||
})
|
||||
.catch(async () => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
function getForm() {
|
||||
let rows = data.formData;
|
||||
return rows;
|
||||
}
|
||||
defineExpose({
|
||||
getForm,
|
||||
validateForm,
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.l-from-body {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
<template>
|
||||
<div class="l-from-body">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:rules="data.rules"
|
||||
:model="formData"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="数据库" name="dbCode">
|
||||
<a-select
|
||||
v-model:value="formData.dbCode"
|
||||
placeholder="请选择"
|
||||
:options="sqlOptions"
|
||||
:field-names="{ label: 'text', value: 'id', options: 'childNodes' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="数据表" name="table">
|
||||
<a-space>
|
||||
<a-input v-model:value="formData.table" placeholder="请输入" readonly />
|
||||
<a-button @click="handleTableClick">选择</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="关联流程字段" name="rfield">
|
||||
<a-select
|
||||
v-model:value="formData.rfield"
|
||||
placeholder="请选择"
|
||||
:options="data.columns"
|
||||
:field-names="{ label: 'name', value: 'dbColumnName' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="审核人字段" name="auditorField">
|
||||
<a-select
|
||||
v-model:value="formData.auditorField"
|
||||
placeholder="请选择"
|
||||
:options="data.columns"
|
||||
:field-names="{ label: 'name', value: 'dbColumnName' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal
|
||||
width="60%"
|
||||
wrap-class-name="full-modal-children"
|
||||
v-model:open="data.tableOpen"
|
||||
title="选择数据表"
|
||||
@ok="tableHandleOk"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<SelectTable ref="tableRef" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { SelectTable } from '@/components/SelectTable/index';
|
||||
import { getTableForms } from '@/api/sys/flowPenal';
|
||||
import { getLoadDataBaseLinkTree } from '@/api/demo/system';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const labelCol = { span: 6 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const formRef = ref<any>();
|
||||
const tableRef = ref<any>();
|
||||
const sqlOptions = ref([]);
|
||||
|
||||
const data = reactive({
|
||||
rules: {
|
||||
dbCode: [{ required: true, message: '请选择数据库' }],
|
||||
table: [{ required: true, message: '请选择数据表' }],
|
||||
rfield: [{ required: true, message: '请选择关联字段' }],
|
||||
auditorField: [{ required: true, message: '请选择审核人字段' }],
|
||||
},
|
||||
columns: [],
|
||||
tableOpen: false,
|
||||
});
|
||||
let formData = ref({
|
||||
dbCode: '',
|
||||
table: '',
|
||||
rfield: '',
|
||||
auditorField: '',
|
||||
type: '6',
|
||||
});
|
||||
async function getSQL() {
|
||||
const data = await getLoadDataBaseLinkTree();
|
||||
let res: any = [];
|
||||
data.forEach((element) => {
|
||||
if (element.childNodes == null) {
|
||||
element.childNodes = [];
|
||||
}
|
||||
let point = {};
|
||||
if (element.id == 'hcsystemdb') {
|
||||
point = {
|
||||
id: '1',
|
||||
text: '常用数据库',
|
||||
childNodes: [
|
||||
{
|
||||
id: element.id,
|
||||
text: element.text,
|
||||
},
|
||||
],
|
||||
};
|
||||
res.push(point);
|
||||
} else {
|
||||
res.push(element);
|
||||
}
|
||||
});
|
||||
sqlOptions.value = res;
|
||||
}
|
||||
function handleTableClick() {
|
||||
if (formData.value.dbCode == '') {
|
||||
return createMessage.warning('请先选择数据库');
|
||||
}
|
||||
data.tableOpen = true;
|
||||
}
|
||||
function tableHandleOk() {
|
||||
const obj = tableRef.value.getRow();
|
||||
formData.value.table = obj[0].name;
|
||||
handleTableChange(obj[0].name);
|
||||
data.tableOpen = false;
|
||||
}
|
||||
// 选择数据表,把获取表头
|
||||
async function handleTableChange(table) {
|
||||
var querys = {
|
||||
dbCode: formData.value.dbCode,
|
||||
tableNames: table,
|
||||
};
|
||||
const obj: Recordable = await getTableForms(querys);
|
||||
let columnsData = obj[0].db_codecolumnsList;
|
||||
columnsData.forEach((element) => {
|
||||
element.name = element.dbColumnName + '(' + element.description + ')';
|
||||
});
|
||||
data.columns = columnsData;
|
||||
}
|
||||
// 校验表单
|
||||
function validateForm() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
return true;
|
||||
})
|
||||
.catch(async () => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function getForm() {
|
||||
return formData.value;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validateForm,
|
||||
getForm,
|
||||
});
|
||||
onMounted(() => {
|
||||
getSQL();
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.l-from-body {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.full-modal-children {
|
||||
.ant-modal {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
height: calc(80vh);
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
height: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<div class="l-from-body">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:rules="data.rules"
|
||||
:model="data.formData"
|
||||
labelAlign="left"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="SQL语句" name="Sql">
|
||||
<a-textarea v-model:value="data.formData.Sql" placeholder="SQL语句" :rows="4" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
const formRef = ref<any>();
|
||||
const labelCol = { span: 7 };
|
||||
const wrapperCol = { span: 17 };
|
||||
const data = reactive({
|
||||
formData: {
|
||||
Sql: '',
|
||||
type: '7',
|
||||
},
|
||||
rules: {
|
||||
Sql: [{ required: true, message: '请输入SQL语句' }],
|
||||
},
|
||||
});
|
||||
function validateForm() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
return true;
|
||||
})
|
||||
.catch(async () => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
function getForm() {
|
||||
let rows = data.formData;
|
||||
rows.name = data.formData.Sql;
|
||||
return rows;
|
||||
}
|
||||
defineExpose({
|
||||
getForm,
|
||||
validateForm,
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.l-from-body {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/* 改变主题色变量 */
|
||||
// $--color-primary: #1890ff;
|
||||
// $--color-danger: #ff4d4f;
|
||||
|
||||
/* 改变 icon 字体路径变量,必需 */
|
||||
|
||||
.process-drawer .el-drawer__header {
|
||||
padding: 16px 16px 8px 16px;
|
||||
margin: 0;
|
||||
line-height: 24px;
|
||||
font-size: 18px;
|
||||
color: #303133;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
div[class^="el-drawer"]:focus,
|
||||
span:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.process-drawer .el-drawer__body {
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.process-design {
|
||||
.el-table td,
|
||||
.el-table th {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 16px 16px 8px 16px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 16px;
|
||||
max-height: 80vh;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
.el-dialog__close {
|
||||
font-weight: 600;
|
||||
}
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
.el-divider:not(.el-divider--horizontal) {
|
||||
margin: 0 8px ;
|
||||
}
|
||||
.el-divider.el-divider--horizontal {
|
||||
margin: 16px 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
@import "./flow-element-variables.scss";
|
||||
@import "bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css";
|
||||
// @import "bpmn-js-token-simulation/assets/css/font-awesome.min.css";
|
||||
// @import "bpmn-js-token-simulation/assets/css/normalize.css";
|
||||
@import "bpmn-js/dist/assets/diagram-js.css";
|
||||
@import "bpmn-js/dist/assets/bpmn-font/css/bpmn.css";
|
||||
@import "bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css";
|
||||
@import "./process-designer.scss";
|
||||
@import "./process-panel.scss";
|
||||
|
||||
$success-color: #4eb819;
|
||||
$primary-color: #409EFF;
|
||||
$warning-color: #E6A23C;
|
||||
$danger-color: #F56C6C;
|
||||
$cancel-color: #909399;
|
||||
|
||||
.process-viewer {
|
||||
position: relative;
|
||||
border: 1px solid #EFEFEF;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat!important;
|
||||
|
||||
.success-arrow {
|
||||
fill: $success-color;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.success-conditional {
|
||||
fill: white;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.fail-arrow {
|
||||
fill: $warning-color;
|
||||
stroke: $warning-color;
|
||||
}
|
||||
|
||||
.fail-conditional {
|
||||
fill: white;
|
||||
stroke: $warning-color;
|
||||
}
|
||||
|
||||
.success.djs-connection {
|
||||
.djs-visual path {
|
||||
stroke: $success-color!important;
|
||||
marker-end: url(#sequenceflow-end-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-connection.condition-expression {
|
||||
.djs-visual path {
|
||||
marker-start: url(#conditional-flow-marker-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $success-color!important;
|
||||
}
|
||||
|
||||
.djs-visual path:nth-child(2) {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.primary.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $primary-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.warning.djs-connection {
|
||||
.djs-visual path {
|
||||
stroke: $warning-color!important;
|
||||
marker-end: url(#sequenceflow-end-white-fail)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.warning.djs-connection.condition-expression {
|
||||
.djs-visual path {
|
||||
marker-start: url(#conditional-flow-marker-white-fail)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.warning.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $warning-color!important;
|
||||
fill: $warning-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $warning-color!important;
|
||||
}
|
||||
|
||||
.djs-visual path:nth-child(2) {
|
||||
stroke: $warning-color!important;
|
||||
fill: $warning-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $warning-color!important;
|
||||
fill: $warning-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.danger.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $danger-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $cancel-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.process-viewer .djs-tooltip-container, .process-viewer .djs-overlay-container, .process-viewer .djs-palette {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
// 边框被 token-simulation 样式覆盖了
|
||||
.djs-palette {
|
||||
background: var(--palette-background-color);
|
||||
border: solid 1px var(--palette-border-color) !important;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.my-process-designer {
|
||||
padding: 5px 0 10px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 70%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
.my-process-designer__header {
|
||||
width: 100%;
|
||||
min-height: 36px;
|
||||
.el-button {
|
||||
text-align: center;
|
||||
}
|
||||
.el-button-group {
|
||||
margin: 4px;
|
||||
}
|
||||
.el-tooltip__popper {
|
||||
.el-button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.el-button:hover {
|
||||
background: rgba(64, 158, 255, 0.8);
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.align {
|
||||
position: relative;
|
||||
i {
|
||||
&:after {
|
||||
content: "|";
|
||||
position: absolute;
|
||||
transform: rotate(90deg) translate(200%, 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.align.align-left i {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.align.align-right i {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.align.align-top i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.align.align-bottom i {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.align.align-center i {
|
||||
transform: rotate(90deg);
|
||||
&:after {
|
||||
transform: rotate(90deg) translate(0, 60%);
|
||||
}
|
||||
}
|
||||
.align.align-middle i {
|
||||
transform: rotate(0deg);
|
||||
&:after {
|
||||
transform: rotate(90deg) translate(0, 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.my-process-designer__container {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
.my-process-designer__canvas {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+")
|
||||
repeat !important;
|
||||
div.toggle-mode {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.my-process-designer__property-panel {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
overflow-y: auto;
|
||||
z-index: 10;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//侧边栏配置
|
||||
.djs-palette.open {
|
||||
.djs-palette-entries {
|
||||
div[class^="bpmn-icon-"]:before,
|
||||
div[class*="bpmn-icon-"]:before {
|
||||
line-height: unset;
|
||||
}
|
||||
div.entry {
|
||||
position: relative;
|
||||
}
|
||||
div.entry:hover {
|
||||
&::after {
|
||||
width: max-content;
|
||||
content: attr(title);
|
||||
vertical-align: text-bottom;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
transform: translateX(100%);
|
||||
font-size: 0.5em;
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
background: #fafafa;
|
||||
box-shadow: 0 0 6px #eeeeee;
|
||||
border: 1px solid #cccccc;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
border-radius: 4px;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
max-height: calc(80vh - 32px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.hljs {
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.hljs * {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
.process-design {
|
||||
.process-panel__container {
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px;
|
||||
border-left: 1px solid #eeeeee;
|
||||
box-shadow: 0 0 8px #cccccc;
|
||||
max-height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.panel-tab__title {
|
||||
font-weight: 600;
|
||||
padding: 0 8px;
|
||||
font-size: 1.1em;
|
||||
line-height: 1.2em;
|
||||
i {
|
||||
margin-right: 8px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
.panel-tab__content {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid #eeeeee;
|
||||
padding: 8px 16px;
|
||||
.panel-tab__content--title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 8px;
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
.element-property {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: 8px 0;
|
||||
.element-property__label {
|
||||
display: block;
|
||||
width: 90px;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
padding-right: 12px;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.element-property__value {
|
||||
flex: 1;
|
||||
line-height: 32px;
|
||||
}
|
||||
.el-form-item {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
}
|
||||
.list-property {
|
||||
flex-direction: column;
|
||||
.element-listener-item {
|
||||
width: 100%;
|
||||
display: inline-grid;
|
||||
grid-template-columns: 16px auto 32px 32px;
|
||||
grid-column-gap: 8px;
|
||||
}
|
||||
.element-listener-item + .element-listener-item {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
.listener-filed__title {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 0;
|
||||
span {
|
||||
width: 200px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.element-drawer__button {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.element-drawer__button > .el-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.el-input.is-disabled .el-input__inner {
|
||||
color: #999999;
|
||||
}
|
||||
.el-form-item.el-form-item--mini {
|
||||
margin-bottom: 0;
|
||||
& + .el-form-item {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import CryptoJS from 'crypto-js/crypto-js';
|
||||
// 创建监听器实例
|
||||
export function createListenerObject(options, isTask, prefix) {
|
||||
const listenerObj = Object.create(null);
|
||||
listenerObj.event = options.event;
|
||||
// isTask && (listenerObj.id = options.id); // 任务监听器特有的 id 字段
|
||||
switch (options.listenerType) {
|
||||
case "scriptListener":
|
||||
listenerObj.script = createScriptObject(options, prefix);
|
||||
break;
|
||||
case "expressionListener":
|
||||
listenerObj.expression = options.expression;
|
||||
break;
|
||||
case "delegateExpressionListener":
|
||||
listenerObj.delegateExpression = options.delegateExpression;
|
||||
break;
|
||||
default:
|
||||
listenerObj.class = options.class;
|
||||
}
|
||||
// 注入字段
|
||||
if (options.fields) {
|
||||
listenerObj.fields = options.fields.map(field => {
|
||||
return createFieldObject(field, prefix);
|
||||
});
|
||||
}
|
||||
// 任务监听器的 定时器 设置
|
||||
if (isTask && options.event === "timeout" && !!options.eventDefinitionType) {
|
||||
const timeDefinition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: options.eventTimeDefinitions });
|
||||
const TimerEventDefinition = window.bpmnInstances.moddle.create("bpmn:TimerEventDefinition", {
|
||||
id: `TimerEventDefinition_${uuid(8)}`,
|
||||
[`time${options.eventDefinitionType.replace(/^\S/, s => s.toUpperCase())}`]: timeDefinition
|
||||
});
|
||||
listenerObj.eventDefinitions = [TimerEventDefinition];
|
||||
}
|
||||
return window.bpmnInstances.moddle.create(`${prefix}:${isTask ? "TaskListener" : "ExecutionListener"}`, listenerObj);
|
||||
}
|
||||
|
||||
// 创建 监听器的注入字段 实例
|
||||
export function createFieldObject(option, prefix) {
|
||||
const { name, fieldType, string, expression } = option;
|
||||
const fieldConfig = fieldType === "string" ? { name, string } : { name, expression };
|
||||
return window.bpmnInstances.moddle.create(`${prefix}:Field`, fieldConfig);
|
||||
}
|
||||
|
||||
// 创建脚本实例
|
||||
export function createScriptObject(options, prefix) {
|
||||
const { scriptType, scriptFormat, value, resource } = options;
|
||||
const scriptConfig = scriptType === "inlineScript" ? { scriptFormat, value } : { scriptFormat, resource };
|
||||
return window.bpmnInstances.moddle.create(`${prefix}:Script`, scriptConfig);
|
||||
}
|
||||
|
||||
// 更新元素扩展属性
|
||||
export function updateElementExtensions(element, extensionList) {
|
||||
const extensions = window.bpmnInstances.moddle.create("bpmn:ExtensionElements", {
|
||||
values: extensionList
|
||||
});
|
||||
window.bpmnInstances.modeling.updateProperties(element, {
|
||||
extensionElements: extensions
|
||||
});
|
||||
}
|
||||
|
||||
// 创建一个id
|
||||
export function uuid(length = 8, chars) {
|
||||
let result = "";
|
||||
let charsString = chars || "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (let i = length; i > 0; --i) {
|
||||
result += charsString[Math.floor(Math.random() * charsString.length)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function lr_AESKey(key){
|
||||
const length = key.length
|
||||
if(length <32){
|
||||
for(let i=0,len =32 - length;i<len;i++){
|
||||
key += "0"
|
||||
}
|
||||
return key
|
||||
}
|
||||
else{
|
||||
return key.substring(0, 32)
|
||||
}
|
||||
}
|
||||
// AES加解密算法
|
||||
export function lr_AESEncrypt(source, key) {
|
||||
key = CryptoJS.enc.Utf8.parse(lr_AESKey(key))//32位
|
||||
let iv = CryptoJS.enc.Utf8.parse("1234567890000000")//16位
|
||||
//let srcs = CryptoJS.enc.Utf8.parse(source)
|
||||
let encrypted = CryptoJS.AES.encrypt(source, key, {
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
})
|
||||
return encrypted.toString()
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export { default as ProcessDesigner } from './package/designer/ProcessDesigner.vue';
|
||||
export { default as ProcessDesignerPage } from './index.vue';
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default as ProcessViewer } from './index.vue';
|
||||
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
<template>
|
||||
<div class="my-process-designer">
|
||||
<div class="my-process-designer__header">
|
||||
<slot name="control-header"></slot>
|
||||
<template v-if="!$slots['control-header']">
|
||||
<div slot="content">
|
||||
<a-space>
|
||||
<a-tooltip placement="bottom" class="ml-2" title="缩小视图">
|
||||
<a-button
|
||||
:disabled="data.defaultZoom <= 0.3"
|
||||
:icon="h(ZoomOutOutlined)"
|
||||
@click="processZoomOut()"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-button>{{ Math.floor(data.defaultZoom * 10 * 10) + '%' }}</a-button>
|
||||
<a-tooltip placement="bottom" title="放大视图">
|
||||
<a-button
|
||||
:disabled="data.defaultZoom >= 3.9"
|
||||
:icon="h(ZoomInOutlined)"
|
||||
@click="processZoomIn()"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<div class="ml-2 tag-box">
|
||||
<a-tag color="processing">正在审核</a-tag>
|
||||
<a-tag color="success">已审核</a-tag>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
<div> </div>
|
||||
</div>
|
||||
<div class="my-process-designer__container">
|
||||
<div class="my-process-designer__canvas" ref="bpmn-canvas" id="view"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import '@/components/ProcessDesigner/package/theme/index.scss';
|
||||
import { h, reactive, onMounted, defineProps, defineEmits, watch } from 'vue';
|
||||
import { ZoomOutOutlined, ZoomInOutlined } from '@ant-design/icons-vue';
|
||||
import BpmnViewer from 'bpmn-js/lib/Viewer';
|
||||
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
|
||||
|
||||
const emit = defineEmits(['event', 'element-click']);
|
||||
|
||||
const data = reactive({
|
||||
bpmnModeler: null,
|
||||
defaultZoom: 1,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
xml: String,
|
||||
flowViewer: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
finishedTaskSet: [],
|
||||
finishedSequenceFlowSet: [],
|
||||
unfinishedTaskSet: [],
|
||||
rejectedTaskSet: [],
|
||||
}),
|
||||
},
|
||||
events: {
|
||||
type: Array,
|
||||
default: () => ['element.click'],
|
||||
},
|
||||
});
|
||||
watch(
|
||||
() => props.xml,
|
||||
(newVal) => {
|
||||
createDiagram(newVal);
|
||||
},
|
||||
);
|
||||
onMounted(() => {
|
||||
initBpmnModeler();
|
||||
createDiagram(props.xml);
|
||||
});
|
||||
function initBpmnModeler() {
|
||||
if (data.bpmnModerler) return;
|
||||
const containerEl = document.getElementById('view');
|
||||
data.bpmnModerler && data.bpmnModerler.destroy(); // 避免缓存
|
||||
data.bpmnModerler = new BpmnViewer({
|
||||
container: containerEl,
|
||||
additionalModules: [
|
||||
// 移动整个画布
|
||||
MoveCanvasModule,
|
||||
],
|
||||
});
|
||||
initModelListeners();
|
||||
}
|
||||
function initModelListeners() {
|
||||
const EventBus = data.bpmnModerler.get('eventBus');
|
||||
// 注册需要的监听事件, 将. 替换为 - , 避免解析异常
|
||||
props.events.forEach((event) => {
|
||||
EventBus.on(event, function (eventObj) {
|
||||
let eventName = event.replace('.', '-');
|
||||
let element = eventObj ? eventObj.element : null;
|
||||
emit(eventName, element, eventObj);
|
||||
emit('event', eventName, element, eventObj);
|
||||
});
|
||||
});
|
||||
}
|
||||
async function createDiagram(xml) {
|
||||
const viewer = data.bpmnModerler;
|
||||
if (xml != null && xml !== '') {
|
||||
try {
|
||||
const result = await data.bpmnModerler.importXML(xml);
|
||||
const { warnings } = result;
|
||||
if (warnings && warnings.length) {
|
||||
warnings.forEach((warn) => console.warn(warn));
|
||||
}
|
||||
const canvas = viewer.get('canvas');
|
||||
canvas.zoom('fit-viewport');
|
||||
addCustomDefs();
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setNodeColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 添加自定义箭头
|
||||
function addCustomDefs() {
|
||||
const canvas = data.bpmnModerler.get('canvas');
|
||||
const svg = canvas._svg;
|
||||
const customSuccessDefs = this.$refs.customSuccessDefs;
|
||||
const customFailDefs = this.$refs.customFailDefs;
|
||||
svg.appendChild(customSuccessDefs);
|
||||
svg.appendChild(customFailDefs);
|
||||
}
|
||||
// 设置节点颜色
|
||||
function setNodeColor() {
|
||||
const elementRegistry = data.bpmnModerler.get('elementRegistry');
|
||||
console.log(elementRegistry);
|
||||
let { finishedTaskSet, rejectedTaskSet, unfinishedTaskSet, finishedSequenceFlowSet } =
|
||||
props.flowViewer;
|
||||
if (Array.isArray(unfinishedTaskSet)) {
|
||||
unfinishedTaskSet.forEach((item) => {
|
||||
if (elementRegistry._elements[item]) {
|
||||
const element = elementRegistry._elements[item].gfx;
|
||||
element.classList.add('nodeProcing');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Array.isArray(rejectedTaskSet)) {
|
||||
rejectedTaskSet.forEach((item) => {
|
||||
// if (item != null) {
|
||||
// let element = elementRegistry.get(item);
|
||||
// if (element.type.includes('Task')) {
|
||||
// canvas.addMarker(item, 'danger');
|
||||
// } else {
|
||||
// canvas.addMarker(item, 'warning');
|
||||
// }
|
||||
// }
|
||||
});
|
||||
}
|
||||
if (Array.isArray(finishedSequenceFlowSet)) {
|
||||
finishedSequenceFlowSet.forEach((item) => {
|
||||
if (elementRegistry._elements[item]) {
|
||||
const element = elementRegistry._elements[item].gfx;
|
||||
element.classList.add('nodeSuccess');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Array.isArray(finishedTaskSet)) {
|
||||
finishedTaskSet.forEach((item) => {
|
||||
if (elementRegistry._elements[item]) {
|
||||
const element = elementRegistry._elements[item].gfx;
|
||||
element.classList.add('nodeSuccess');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function processZoomIn(zoomStep = 0.1) {
|
||||
let newZoom = Math.floor(data.defaultZoom * 100 + zoomStep * 100) / 100;
|
||||
if (newZoom > 4) {
|
||||
throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4');
|
||||
}
|
||||
data.defaultZoom = newZoom;
|
||||
data.bpmnModerler.get('canvas').zoom(data.defaultZoom);
|
||||
}
|
||||
function processZoomOut(zoomStep = 0.1) {
|
||||
let newZoom = Math.floor(data.defaultZoom * 100 - zoomStep * 100) / 100;
|
||||
if (newZoom < 0.2) {
|
||||
throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2');
|
||||
}
|
||||
data.defaultZoom = newZoom;
|
||||
data.bpmnModerler.get('canvas').zoom(data.defaultZoom);
|
||||
}
|
||||
|
||||
function getOperationTagType(type) {
|
||||
return 'success';
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.my-process-designer {
|
||||
width: 100%;
|
||||
}
|
||||
.tag-box {
|
||||
float: right;
|
||||
}
|
||||
::v-deep .ant-tag-success {
|
||||
padding: 5px 11px;
|
||||
}
|
||||
::v-deep .ant-tag-processing {
|
||||
padding: 5px 11px;
|
||||
}
|
||||
::v-deep .bjs-container a {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:global(.nodeSuccess .djs-visual > :nth-child(1)) {
|
||||
stroke: #52c41a !important;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
:global(.nodeProcing .djs-visual > :nth-child(1)) {
|
||||
stroke: #1890ff !important;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -2,26 +2,27 @@
|
|||
<!-- <PageWrapper dense contentFullHeight fixedHeight contentClass="flex"> -->
|
||||
<div class="select-account">
|
||||
<DeptTree class="w-1/4 xl:w-1/5" @select="handleSelect" />
|
||||
<BasicTable @register="registerTable" class="w-3/4 xl:w-4/5" :searchInfo="searchInfo"> </BasicTable>
|
||||
<BasicTable @register="registerTable" class="w-3/4 xl:w-4/5" :searchInfo="searchInfo" />
|
||||
</div>
|
||||
<!-- </PageWrapper> -->
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import { reactive, watch, defineProps } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '@/components/Table';
|
||||
import { getAccountList, deleteAccount } from '@/api/demo/system';
|
||||
import { PageWrapper } from '@/components/Page';
|
||||
import DeptTree from './DeptTree.vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
|
||||
import { columns, searchFormSchema } from './account.data';
|
||||
|
||||
defineOptions({ name: 'AccountManagement' });
|
||||
|
||||
const searchInfo = reactive<Recordable>({});
|
||||
const [registerTable, { reload, updateTableDataRecord, getSelectRows, clearSelectedRowKeys }] = useTable({
|
||||
const [
|
||||
registerTable,
|
||||
{ reload, updateTableDataRecord, getSelectRows, clearSelectedRowKeys, setSelectedRowKeys },
|
||||
] = useTable({
|
||||
title: '账号列表',
|
||||
api: getAccountList,
|
||||
rowKey: 'id',
|
||||
|
|
@ -31,7 +32,8 @@
|
|||
schemas: searchFormSchema,
|
||||
autoSubmitOnEnter: true,
|
||||
},
|
||||
rowSelection: {//多选框
|
||||
rowSelection: {
|
||||
//多选框
|
||||
type: 'checkbox',
|
||||
},
|
||||
useSearchForm: true,
|
||||
|
|
@ -44,6 +46,20 @@
|
|||
return info;
|
||||
},
|
||||
});
|
||||
const props = defineProps({
|
||||
selectListValue: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
});
|
||||
watch(
|
||||
() => props.selectListValue,
|
||||
(newVal: any) => {
|
||||
setSelectedRowKeys(newVal);
|
||||
},
|
||||
);
|
||||
function handleSelect(orgId = '') {
|
||||
searchInfo.orgId = orgId;
|
||||
reload();
|
||||
|
|
@ -51,12 +67,11 @@
|
|||
|
||||
function getRow() {
|
||||
let rows = getSelectRows();
|
||||
console.log(rows)
|
||||
return rows
|
||||
return rows;
|
||||
}
|
||||
defineExpose({
|
||||
getRow
|
||||
})
|
||||
getRow,
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.select-account {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@use './common';
|
||||
@use './variable';
|
||||
@use './toolbar';
|
||||
@use './component';
|
||||
@import './common';
|
||||
@import './variable';
|
||||
@import './toolbar';
|
||||
@import './component';
|
||||
// @import 'vxe-table/styles/index';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import lrLayout from './src/lrLayout.vue'
|
||||
|
||||
lrLayout.install = function(Vue) {
|
||||
Vue.component(lrLayout.name, lrLayout)
|
||||
}
|
||||
|
||||
export default lrLayout
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
<template>
|
||||
<div class="l-layout" :style="{'padding-left':leftWidth}">
|
||||
<div class="l-layout--left" :style="{'width':leftWidth}" >
|
||||
<div class="l-layout--wrapper" ><slot name="left"></slot></div>
|
||||
<div v-if="leftMove" class="l-layout--move" @mousedown="onMousedown('left',$event)" ></div>
|
||||
</div>
|
||||
<div class="l-layout--container" :style="{'padding-right':rightWidth}" >
|
||||
<div class="l-layout--right" :style="{ 'width':rightWidth}">
|
||||
<div class="l-layout--wrapper" ><slot name="right"></slot></div>
|
||||
<div v-if="rightMove" class="l-layout--move" @mousedown="onMousedown('right',$event)" ></div>
|
||||
</div>
|
||||
<div class="l-layout--container" :style="{'padding-bottom':bottomHight}" >
|
||||
<div class="l-layout--bottom" :style="{'height':bottomHight}" >
|
||||
<div class="l-layout--wrapper" > <slot name="bottom"></slot></div>
|
||||
<div v-if="bottomMove" class="l-layout--move" @mousedown="onMousedown('bottom',$event)" ></div>
|
||||
</div>
|
||||
<div class="l-layout--container" :style="{'padding-top':topHight}" >
|
||||
<div class="l-layout--top" :style="{'height':topHight}" >
|
||||
<div class="l-layout--wrapper" ><slot name="top"></slot></div>
|
||||
<div v-if="topMove" class="l-layout--move" @mousedown="onMousedown('top',$event)" ></div>
|
||||
</div>
|
||||
<div class="l-layout--wrapper" ref="mid">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name:'l-layout',
|
||||
props: {
|
||||
left: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
leftMove: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
right: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
rightMove:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
top: {
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
topMove:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
bottom:{
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
bottomMove:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
mleft:this.left,
|
||||
mright:this.right,
|
||||
mtop:this.top,
|
||||
mbottom:this.bottom,
|
||||
|
||||
move:{
|
||||
type:'',
|
||||
isMove:false,
|
||||
pageX:0,
|
||||
pageY:0,
|
||||
size:0,
|
||||
h:0,
|
||||
w:0,
|
||||
}
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
watch:{
|
||||
left(val){
|
||||
this.mleft = val;
|
||||
},
|
||||
right(val){
|
||||
this.mright = val;
|
||||
},
|
||||
top(val){
|
||||
this.mtop = val;
|
||||
},
|
||||
bottom(val){
|
||||
this.mbottom = val;
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
leftWidth:function(){
|
||||
if(this.$slots.left){
|
||||
return this.mleft + 'px'
|
||||
}
|
||||
else{
|
||||
return '0'
|
||||
}
|
||||
},
|
||||
rightWidth:function(){
|
||||
if(this.$slots.right){
|
||||
return this.mright + 'px'
|
||||
}
|
||||
else{
|
||||
return '0'
|
||||
}
|
||||
},
|
||||
topHight:function(){
|
||||
if(this.$slots.top){
|
||||
return this.mtop + 'px'
|
||||
}
|
||||
else{
|
||||
return '0'
|
||||
}
|
||||
},
|
||||
bottomHight:function(){
|
||||
if(this.$slots.bottom){
|
||||
return this.mbottom + 'px'
|
||||
}
|
||||
else{
|
||||
return '0'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
onMousedown:function(type,e){
|
||||
this.move.type = type;
|
||||
this.move.isMove = true;
|
||||
this.move.pageX = e.pageX;
|
||||
this.move.pageY = e.pageY;
|
||||
this.move.size = this["m"+type];
|
||||
|
||||
this.move.h = this.$refs.mid.clientHeight;
|
||||
this.move.w = this.$refs.mid.clientWidth;
|
||||
|
||||
document.onmouseup = this.onMouseup;
|
||||
document.onmousemove = this.onMousemove;
|
||||
},
|
||||
onMousemove:function(e){
|
||||
if(this.move.isMove){
|
||||
switch(this.move.type){
|
||||
case 'left':
|
||||
var x1 = e.pageX - this.move.pageX;
|
||||
var left = this.move.size + x1;
|
||||
if(left < 0){
|
||||
left = 4;
|
||||
}
|
||||
else if(left > this.move.size + this.move.w){
|
||||
left = this.move.size + this.move.w
|
||||
}
|
||||
this.mleft = left;
|
||||
break;
|
||||
case 'right':
|
||||
var x2 = e.pageX - this.move.pageX;
|
||||
var right = this.move.size - x2;
|
||||
if(right < 0){
|
||||
right = 4;
|
||||
}
|
||||
else if(right > this.move.size + this.move.w){
|
||||
right = this.move.size + this.move.w
|
||||
}
|
||||
this.mright = right;
|
||||
break;
|
||||
case 'top':
|
||||
var y = e.pageY - this.move.pageY;
|
||||
var top = this.move.size + y;
|
||||
if(top < 0){
|
||||
top = 4;
|
||||
}
|
||||
else if(top > this.move.size + this.move.h){
|
||||
top = this.move.size + this.move.h
|
||||
}
|
||||
this.mtop = top;
|
||||
break;
|
||||
case 'bottom':
|
||||
var y2 = e.pageY - this.move.pageY;
|
||||
var bottom = this.move.size - y2;
|
||||
if(bottom < 0){
|
||||
bottom = 4;
|
||||
}
|
||||
else if(bottom > this.move.size + this.move.h){
|
||||
bottom = this.move.size + this.move.h
|
||||
}
|
||||
this.mbottom = bottom;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
onMouseup:function(){
|
||||
this.move.isMove = false;
|
||||
document.onmousemove = document.onmouseup = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
// @import './index.less';
|
||||
</style>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import lrPanel from './src/lrPanel.vue'
|
||||
|
||||
lrPanel.install = function(Vue) {
|
||||
Vue.component(lrPanel.name, lrPanel)
|
||||
}
|
||||
|
||||
export default lrPanel
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div class="l-panel">
|
||||
<div class="l-panel--warpper" :style="{'padding-top':paddingTop}" >
|
||||
<div v-if="title || $slots.title" class="l-panel--title" >
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</div>
|
||||
<div v-if="$slots.toolLeft || $slots.toolRight" class="l-panel--tool" :style="{'top':toolTop}" >
|
||||
<div class="l-panel--tool-left">
|
||||
<slot name="toolLeft" ></slot>
|
||||
</div>
|
||||
<div class="l-panel--tool-right">
|
||||
<slot name="toolRight" ></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="l-panel--body" >
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name:'l-panel',
|
||||
props: {
|
||||
title:String,
|
||||
loading:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
computed:{
|
||||
paddingTop:function(){
|
||||
var ptop = 0;
|
||||
if(this.title || this.$slots.title){
|
||||
ptop += 40;
|
||||
}
|
||||
if(this.$slots.toolLeft || this.$slots.toolRight){
|
||||
ptop += 40;
|
||||
}
|
||||
return ptop + 'px';
|
||||
},
|
||||
toolTop:function(){
|
||||
if(this.title || this.$slots.title){
|
||||
return '40px'
|
||||
}
|
||||
else{
|
||||
return '0'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
// @import './index.less';
|
||||
.l-panel--warpper {
|
||||
background-color: @component-background;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -41,11 +41,33 @@ export const LoginRoute: AppRouteRecordRaw = {
|
|||
title: t('routes.basic.login'),
|
||||
},
|
||||
};
|
||||
export const FORMCALLPAGE_ROUTE: AppRouteRecordRaw = {
|
||||
path: '/formCallPageParent',
|
||||
component: LAYOUT,
|
||||
name: 'formCallPageParent',
|
||||
meta: {
|
||||
title: '表单',
|
||||
hideBreadcrumb: true,
|
||||
hideMenu: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/formCallPage',
|
||||
name: 'formCallPage',
|
||||
component: () => import('@/views/demo/onlineform/formCall/index.vue'),
|
||||
meta: {
|
||||
title: '表单调用',
|
||||
hideBreadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
// Basic routing without permission
|
||||
// 未经许可的基本路由
|
||||
export const basicRoutes = [
|
||||
LoginRoute,
|
||||
RootRoute,
|
||||
FORMCALLPAGE_ROUTE,
|
||||
...mainOutRoutes,
|
||||
REDIRECT_ROUTE,
|
||||
PAGE_NOT_FOUND_ROUTE,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,278 @@
|
|||
<template>
|
||||
<template v-if="['Grid'].includes(schema.component) && schema.type == 'subTable'">
|
||||
<a-table
|
||||
class="sub-table"
|
||||
:columns="subTableColumns"
|
||||
:data-source="subTableList"
|
||||
:pagination="false"
|
||||
style="width: 100%"
|
||||
v-if="subTableId"
|
||||
:scroll="scrollValue"
|
||||
>
|
||||
<template #headerCell="{ column, record }">
|
||||
<template v-if="column.key === 'setting'">
|
||||
<PlusOutlined class="icon-button" @click="addListItem" />
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'setting'">
|
||||
<DeleteOutlined class="icon-button" @click="delListItem(column, record)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<FormItem :data="column" :record="record" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
<template v-else-if="['Grid'].includes(schema.component)">
|
||||
<Row class="grid-row">
|
||||
<Col
|
||||
class="grid-col"
|
||||
v-for="(colItem, index) in schema.columns"
|
||||
:key="index"
|
||||
:span="colItem.span"
|
||||
>
|
||||
<FormRender
|
||||
v-for="(item, k) in colItem.children"
|
||||
:key="k"
|
||||
:schema="item"
|
||||
:formData="formData"
|
||||
:formConfig="formConfig"
|
||||
:setFormModel="setFormModel"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</template>
|
||||
<template v-else-if="['Card'].includes(schema.component)">
|
||||
<a-row class="grid-row" style="width: 100%; padding: 10px">
|
||||
<a-col class="grid-col" :span="schema.colProps.span">
|
||||
<a-card
|
||||
:title="schema.label"
|
||||
:class="
|
||||
schema.shadow === 'always'
|
||||
? 'card-always'
|
||||
: schema.shadow === 'hover'
|
||||
? 'card-hover'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<a-row class="grid-row">
|
||||
<a-col
|
||||
class="grid-col"
|
||||
v-for="(colItem, index) in schema.columns"
|
||||
:key="index"
|
||||
:span="colItem.span"
|
||||
>
|
||||
<template v-for="(item, k) in colItem.children" :key="k">
|
||||
<FormRender
|
||||
:schema="item"
|
||||
:formData="formData"
|
||||
:formConfig="formConfig"
|
||||
:setFormModel="setFormModel"
|
||||
/>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template v-else-if="['Tabs'].includes(schema.component)">
|
||||
<a-tabs style="width: 100%">
|
||||
<a-tab-pane
|
||||
v-for="(colItem, index) in schema.componentProps.options"
|
||||
:tab="colItem.label"
|
||||
:key="index"
|
||||
>
|
||||
<FormRender
|
||||
v-for="(item, k) in colItem.children"
|
||||
:key="k"
|
||||
:schema="item"
|
||||
:formData="formData"
|
||||
:formConfig="formConfig"
|
||||
:setFormModel="setFormModel"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
<template v-else-if="['CardGroup'].includes(schema.component)">
|
||||
<div style="width: 100%">
|
||||
<div style="display: flex">
|
||||
{{ schema.label }}
|
||||
<div style="margin-left: 10px">
|
||||
<a-radio-group
|
||||
v-model:value="noTitleKey"
|
||||
:options="schema.componentProps.options"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="(item, index) in schema.componentProps.options" :key="index">
|
||||
<a-card style="width: 100%" v-show="noTitleKey === item.value">
|
||||
<FormRender
|
||||
v-for="(childItem, k) in item.children"
|
||||
:key="k"
|
||||
:schema="childItem"
|
||||
:formData="formData"
|
||||
:formConfig="formConfig"
|
||||
:setFormModel="setFormModel"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<VFormItem
|
||||
v-if="(isCreateOrModifyComponent && schema.display) || !isCreateOrModifyComponent"
|
||||
:formConfig="formConfig"
|
||||
:schema="schema"
|
||||
:formData="formData"
|
||||
:setFormModel="setFormModel"
|
||||
@change="emit('change', { schema: schema, value: $event, subTableList: subTableList })"
|
||||
@submit="emit('submit', schema)"
|
||||
@reset="emit('reset')"
|
||||
>
|
||||
<template
|
||||
v-if="schema.componentProps && schema.componentProps.slotName"
|
||||
#[schema.componentProps!.slotName]
|
||||
>
|
||||
<slot :name="schema.componentProps!.slotName"></slot>
|
||||
</template>
|
||||
</VFormItem>
|
||||
</template>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, PropType } from 'vue';
|
||||
import { IVFormComponent, IFormConfig } from '../../../typings/v-form-component';
|
||||
import VFormItem from '../../VFormItem/index.vue';
|
||||
import { Row, Col } from 'ant-design-vue';
|
||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import FormItem from '@/views/demo/onlineform/formCall/ShowFormModal/FormItem/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
formData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
schema: {
|
||||
type: Object as PropType<IVFormComponent>,
|
||||
default: () => ({}),
|
||||
},
|
||||
formConfig: {
|
||||
type: Object as PropType<IFormConfig>,
|
||||
default: () => [] as IFormConfig[],
|
||||
},
|
||||
setFormModel: {
|
||||
type: Function as PropType<(key: string, value: any) => void>,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
console.log('itemitem', props.formConfig)
|
||||
const emit = defineEmits(['change', 'submit', 'reset']);
|
||||
const scrollValue = ref();
|
||||
const isCreateOrModifyComponent = [
|
||||
'createuser',
|
||||
'createtime',
|
||||
'modifyuser',
|
||||
'modifytime',
|
||||
].includes(props.schema.type);
|
||||
let subTableColumns = ref([
|
||||
{
|
||||
dataIndex: 'setting',
|
||||
key: 'setting',
|
||||
fixed: 'left',
|
||||
width: 60,
|
||||
},
|
||||
]);
|
||||
let subTableId = ref(null);
|
||||
let subTableData = ref([]);
|
||||
let subTableList = ref([]);
|
||||
const noTitleKey = ref('0')
|
||||
if(props.schema.component === 'CardGroup'){
|
||||
noTitleKey.value = props.schema.componentProps.options[0].value
|
||||
}
|
||||
|
||||
// if (props.formConfig.schemas) {
|
||||
// props.formConfig.schemas.forEach((item) => {
|
||||
// if (item.type === 'subTable') {
|
||||
// subTableId.value = item.field;
|
||||
// let tableData = [];
|
||||
// item.columns.forEach((itemColumn) => {
|
||||
// itemColumn.children.forEach((itemColumnChild) => {
|
||||
// tableData.push(itemColumnChild);
|
||||
// subTableColumns.value.push({
|
||||
// key: itemColumnChild.field,
|
||||
// title: itemColumnChild.label,
|
||||
// dataIndex: itemColumnChild.field,
|
||||
// width: 120,
|
||||
// ...itemColumnChild,
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// scrollValue.value = { x: (subTableColumns.value.length - 1) * 140, y: 300 };
|
||||
// subTableData.value = tableData;
|
||||
// }else{
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
console.log("props.schema",props.schema)
|
||||
|
||||
if (props.schema) {
|
||||
if(props.schema.type === "subTable"){
|
||||
subTableId.value = props.schema.field
|
||||
let tableData = [];
|
||||
props.schema.columns.forEach((itemColumn) => {
|
||||
itemColumn.children.forEach((itemColumnChild) => {
|
||||
tableData.push(itemColumnChild);
|
||||
subTableColumns.value.push({
|
||||
key: itemColumnChild.field,
|
||||
title: itemColumnChild.label,
|
||||
dataIndex: itemColumnChild.field,
|
||||
width: 120,
|
||||
...itemColumnChild,
|
||||
});
|
||||
});
|
||||
});
|
||||
scrollValue.value = { x: (subTableColumns.value.length - 1) * 140, y: 300 };
|
||||
subTableData.value = tableData;
|
||||
}
|
||||
}
|
||||
|
||||
const addListItem = () => {
|
||||
let keyValue = uuidv4();
|
||||
let emptyItem = { key: keyValue };
|
||||
subTableData.value.map((item) => {
|
||||
if (item.component == 'InputGuid') {
|
||||
emptyItem[item.field] = keyValue;
|
||||
} else {
|
||||
emptyItem[item.field] = '';
|
||||
}
|
||||
});
|
||||
|
||||
subTableList.value.push(emptyItem);
|
||||
};
|
||||
const delListItem = (column, record) => {
|
||||
subTableList.value = subTableList.value.filter((item) => item.key != record.key);
|
||||
};
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.v-form-render-item {
|
||||
overflow: hidden;
|
||||
}
|
||||
/* .card-always{
|
||||
border-color: transparent;
|
||||
box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 5px 12px 4px rgba(0, 0, 0, 0.09);
|
||||
}
|
||||
.card-hover{
|
||||
transition: box-shadow 0.2s, border-color 0.2s;
|
||||
}
|
||||
.card-hover:hover{
|
||||
border-color: transparent;
|
||||
box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 5px 12px 4px rgba(0, 0, 0, 0.09);
|
||||
} */
|
||||
</style>
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
<!--
|
||||
* @Description: 表单渲染器,根据json生成表单
|
||||
-->
|
||||
<template>
|
||||
<div class="v-form-container">
|
||||
<Form class="v-form-model" ref="eFormModel" :model="formModel" v-bind="formModelProps">
|
||||
<Row>
|
||||
<FormRender
|
||||
v-for="(schema, index) of noHiddenList"
|
||||
:key="index"
|
||||
:schema="schema"
|
||||
:formConfig="formConfig"
|
||||
:formData="formModelNew"
|
||||
@change="handleChange"
|
||||
:setFormModel="setFormModel"
|
||||
@submit="handleSubmit"
|
||||
@reset="resetFields"
|
||||
>
|
||||
<template v-if="schema && schema.componentProps" #[`schema.componentProps!.slotName`]>
|
||||
<slot
|
||||
:name="schema.componentProps!.slotName"
|
||||
v-bind="{ formModel: formModel, field: schema.field, schema }"
|
||||
></slot>
|
||||
</template>
|
||||
</FormRender>
|
||||
</Row>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, provide, ref, unref } from 'vue';
|
||||
import FormRender from './components/FormRender.vue';
|
||||
import { IFormConfig, AForm } from '../../typings/v-form-component';
|
||||
import { Form, Row, Col } from 'ant-design-vue';
|
||||
import { useFormInstanceMethods } from '../../hooks/useFormInstanceMethods';
|
||||
import { IProps, IVFormMethods, useVFormMethods } from '../../hooks/useVFormMethods';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'VFormCreate',
|
||||
components: {
|
||||
FormRender,
|
||||
Form,
|
||||
Row,
|
||||
},
|
||||
props: {
|
||||
fApi: {
|
||||
type: Object,
|
||||
},
|
||||
formModel: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
formConfig: {
|
||||
type: Object as PropType<IFormConfig>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['submit', 'change', 'update:fApi', 'update:formModel'],
|
||||
setup(props, context) {
|
||||
const wrapperComp = props.formConfig.layout == 'vertical' ? Col : Row;
|
||||
const { emit } = context;
|
||||
const eFormModel = ref<AForm | null>(null);
|
||||
const formModelNew = computed({
|
||||
get: () => props.formModel,
|
||||
set: (value) => emit('update:formModel', value),
|
||||
});
|
||||
console.log('props.formConfig', props.formConfig)
|
||||
const noHiddenList = computed(() => {
|
||||
return (
|
||||
props.formConfig.schemas &&
|
||||
props.formConfig.schemas.filter((item) => item.hidden !== true)
|
||||
);
|
||||
});
|
||||
console.log('noHiddenList', noHiddenList)
|
||||
const fApi = useVModel(props, 'fApi', emit);
|
||||
|
||||
const { submit, validate, clearValidate, resetFields, validateField } =
|
||||
useFormInstanceMethods<['submit', 'change', 'update:fApi', 'update:formModel']>(
|
||||
props,
|
||||
formModelNew,
|
||||
context,
|
||||
eFormModel,
|
||||
);
|
||||
|
||||
const { linkOn, ...methods } = useVFormMethods<
|
||||
['submit', 'change', 'update:fApi', 'update:formModel']
|
||||
>(
|
||||
{ formConfig: props.formConfig, formData: props.formModel } as unknown as IProps,
|
||||
context,
|
||||
eFormModel,
|
||||
{
|
||||
submit,
|
||||
validate,
|
||||
validateField,
|
||||
resetFields,
|
||||
clearValidate,
|
||||
},
|
||||
);
|
||||
|
||||
fApi.value = methods;
|
||||
|
||||
const handleChange = (_event) => {
|
||||
const { schema, value } = _event;
|
||||
const { field } = unref(schema);
|
||||
|
||||
linkOn[field!]?.forEach((formItem) => {
|
||||
formItem.update?.(value, formItem, fApi.value as IVFormMethods);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取表单属性
|
||||
*/
|
||||
const formModelProps = computed(
|
||||
() => omit(props.formConfig, ['disabled', 'labelWidth', 'schemas']) as Recordable,
|
||||
);
|
||||
|
||||
const handleSubmit = () => {
|
||||
submit();
|
||||
};
|
||||
|
||||
provide('formModel', formModelNew);
|
||||
const setFormModel = (key, value) => {
|
||||
formModelNew.value[key] = value;
|
||||
};
|
||||
|
||||
provide<(key: String, value: any) => void>('setFormModelMethod', setFormModel);
|
||||
|
||||
// 把祖先组件的方法项注入到子组件中,子组件可通过inject获取
|
||||
return {
|
||||
eFormModel,
|
||||
submit,
|
||||
validate,
|
||||
validateField,
|
||||
resetFields,
|
||||
clearValidate,
|
||||
handleChange,
|
||||
formModelProps,
|
||||
handleSubmit,
|
||||
setFormModel,
|
||||
formModelNew,
|
||||
wrapperComp,
|
||||
noHiddenList,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.v-form-model {
|
||||
overflow: hidden;
|
||||
width: 96%;
|
||||
margin-left: 2%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<!--
|
||||
* @Description: 渲染代码
|
||||
-->
|
||||
<template>
|
||||
<Modal
|
||||
title="代码"
|
||||
:footer="null"
|
||||
:open="visible"
|
||||
@cancel="visible = false"
|
||||
wrapClassName="v-code-modal"
|
||||
style="top: 20px"
|
||||
width="850px"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<PreviewCode :editorJson="editorVueJson" fileFormat="vue" />
|
||||
</Modal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs } from 'vue';
|
||||
import { formatRules, removeAttrs } from '../../../utils';
|
||||
import PreviewCode from './PreviewCode.vue';
|
||||
import { IFormConfig } from '../../../typings/v-form-component';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
const codeVueFront = `<template>
|
||||
<div>
|
||||
<v-form-create
|
||||
:formConfig="formConfig"
|
||||
:formData="formData"
|
||||
v-model="fApi"
|
||||
/>
|
||||
<a-button @click="submit">提交</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Demo',
|
||||
data () {
|
||||
return {
|
||||
fApi:{},
|
||||
formData:{},
|
||||
formConfig: `;
|
||||
/* eslint-disable */
|
||||
let codeVueLast = `
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async submit() {
|
||||
const data = await this.fApi.submit()
|
||||
console.log(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
<\/script>`;
|
||||
//
|
||||
export default defineComponent({
|
||||
name: 'CodeModal',
|
||||
components: { PreviewCode, Modal },
|
||||
setup() {
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
jsonData: {} as IFormConfig,
|
||||
});
|
||||
|
||||
const showModal = (formConfig: IFormConfig) => {
|
||||
formConfig.schemas && formatRules(formConfig.schemas);
|
||||
state.visible = true;
|
||||
state.jsonData = formConfig as any;
|
||||
};
|
||||
|
||||
const editorVueJson = computed(() => {
|
||||
return (
|
||||
codeVueFront +
|
||||
JSON.stringify(removeAttrs(state.jsonData as any), null, '\t') +
|
||||
codeVueLast
|
||||
);
|
||||
});
|
||||
|
||||
return { ...toRefs(state), editorVueJson, showModal };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,592 @@
|
|||
<!--
|
||||
* @Description: 组件属性控件
|
||||
-->
|
||||
<template>
|
||||
<div class="properties-content">
|
||||
<div class="properties-body" v-if="formConfig.currentItem">
|
||||
<Empty class="hint-box" v-if="!formConfig.currentItem.key" description="未选择组件" />
|
||||
<Form
|
||||
label-align="left"
|
||||
layout="vertical"
|
||||
v-if="
|
||||
['Grid'].includes(formConfig.currentItem.component) &&
|
||||
formConfig.currentItem.type == 'subTable'
|
||||
"
|
||||
>
|
||||
<FormItem v-for="item in subTableOptions" :key="item.name" :label="item.label">
|
||||
<!-- 处理数组属性,placeholder -->
|
||||
<div v-if="item.children">
|
||||
<template v-for="(child, index) of item.children" :key="index">
|
||||
<component
|
||||
v-if="child.component"
|
||||
v-bind="child.componentProps"
|
||||
v-model:value="formConfig.currentItem.componentProps[item.name][index]"
|
||||
:is="child.component"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 如果不是数组,则正常处理属性值 -->
|
||||
<component
|
||||
v-else-if="item.component"
|
||||
class="component-prop"
|
||||
v-bind="item.componentProps"
|
||||
:is="item.component"
|
||||
@change="handleFieldTableChange"
|
||||
v-model:value="formConfig.currentItem.componentProps[item.name]"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="控制属性">
|
||||
<Col v-for="item in SubControllOptions" :key="item.name">
|
||||
<Checkbox
|
||||
v-if="showControlAttrs(item.includes)"
|
||||
v-bind="item.componentProps"
|
||||
v-model:checked="formConfig.currentItem.componentProps[item.name]"
|
||||
>
|
||||
{{ item.label }}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<Form label-align="left" layout="vertical" v-else>
|
||||
<!-- 循环遍历渲染组件属性 -->
|
||||
|
||||
<div v-if="formConfig.currentItem && formConfig.currentItem.componentProps">
|
||||
<FormItem v-for="item in inputOptions" :key="item.name" :label="item.label">
|
||||
<!-- 处理数组属性,placeholder -->
|
||||
<div v-if="item.children">
|
||||
<template v-for="(child, index) of item.children" :key="index">
|
||||
<component
|
||||
v-if="child.component"
|
||||
v-bind="child.componentProps"
|
||||
v-model:value="formConfig.currentItem.componentProps[item.name][index]"
|
||||
:is="child.component"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 如果不是数组,则正常处理属性值 -->
|
||||
<component
|
||||
v-else-if="item.component"
|
||||
class="component-prop"
|
||||
v-bind="item.componentProps"
|
||||
:is="item.component"
|
||||
@change="handleFieldTableChange"
|
||||
v-model:value="formConfig.currentItem.componentProps[item.name]"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="控制属性">
|
||||
<Col v-for="item in controlOptions" :key="item.name">
|
||||
<Checkbox
|
||||
v-if="showControlAttrs(item.includes)"
|
||||
v-bind="item.componentProps"
|
||||
v-model:checked="formConfig.currentItem.componentProps[item.name]"
|
||||
>
|
||||
{{ item.label }}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</FormItem>
|
||||
<FormItem label="标题集合" v-if="['Transfer'].includes(formConfig.currentItem.component)">
|
||||
<Input v-model:value="formConfig.currentItem.componentProps.titles[0]" />
|
||||
<Input v-model:value="formConfig.currentItem.componentProps.titles[1]" />
|
||||
</FormItem>
|
||||
</div>
|
||||
<FormItem label="关联字段">
|
||||
<Select
|
||||
mode="multiple"
|
||||
v-model:value="formConfig.currentItem['link']"
|
||||
:options="linkOptions"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="图标" v-if="['Button'].includes(formConfig.currentItem.component)">
|
||||
<IconPicker v-model:value="formConfig.currentItem.componentProps.icon" />
|
||||
</FormItem>
|
||||
<FormItem label="脚本" v-if="['Button'].includes(formConfig.currentItem.component)">
|
||||
<a-button size="mini" @click="handleButtonClick"> 点击脚本 </a-button>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
label="选项"
|
||||
v-if="
|
||||
[
|
||||
'CheckboxGroup',
|
||||
'RadioGroup',
|
||||
'TreeSelect',
|
||||
'Cascader',
|
||||
'Transfer',
|
||||
'AutoComplete',
|
||||
'Tabs',
|
||||
'CardGroup',
|
||||
].includes(formConfig.currentItem.component)
|
||||
"
|
||||
>
|
||||
<FormOptions />
|
||||
</FormItem>
|
||||
<FormItem label="选项" v-if="['Select'].includes(formConfig.currentItem.component)">
|
||||
<div class="select-radio">
|
||||
<a-radio-group v-model:value="formConfig.currentItem.dataType" button-style="solid">
|
||||
<a-radio-button value="1">静态数据</a-radio-button>
|
||||
<a-radio-button value="2">数据字典</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<template v-if="formConfig.currentItem.dataType === '1'">
|
||||
<FormOptions />
|
||||
</template>
|
||||
<template v-else-if="formConfig.currentItem.dataType === '2'">
|
||||
<a-select
|
||||
v-model:value="formConfig.currentItem.dataCode"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
placeholder="请选择字典类型"
|
||||
:default-active-first-option="false"
|
||||
:show-arrow="false"
|
||||
:filter-option="false"
|
||||
:not-found-content="null"
|
||||
:options="selectDictionaryData"
|
||||
@search="handleSearch"
|
||||
@change="handleChange"
|
||||
@focus="handleSelectFocus"
|
||||
></a-select>
|
||||
</template>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="栅格"
|
||||
v-if="
|
||||
['Grid'].includes(formConfig.currentItem.component) &&
|
||||
formConfig.currentItem.label == '栅格布局'
|
||||
"
|
||||
>
|
||||
<FormOptions />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
<BasicModal
|
||||
@register="buttonScriptModal"
|
||||
title="按钮点击脚本"
|
||||
:height="500"
|
||||
:width="1000"
|
||||
:useWrapper="false"
|
||||
@ok="handleSubmit"
|
||||
>
|
||||
<div class="alertModal">
|
||||
<div class="alertModal_content">
|
||||
<a-textarea
|
||||
v-model:value="formContent"
|
||||
autosize
|
||||
:auto-size="{ minRows: 22, maxRows: 22 }"
|
||||
/>
|
||||
</div>
|
||||
<div class="alertModal_content">
|
||||
<a-textarea
|
||||
class="alertModal_content-textarea"
|
||||
v-model:value="formItemPropsScript"
|
||||
autosize
|
||||
readOnly
|
||||
:auto-size="{ minRows: 22, maxRows: 22 }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {
|
||||
Empty,
|
||||
Input,
|
||||
Form,
|
||||
FormItem,
|
||||
Switch,
|
||||
Checkbox,
|
||||
Select,
|
||||
InputNumber,
|
||||
RadioGroup,
|
||||
Col,
|
||||
Row,
|
||||
message,
|
||||
} from 'ant-design-vue';
|
||||
import { IconPicker } from '@/components/Icon';
|
||||
import RadioButtonGroup from '@/components/Form/src/components/RadioButtonGroup.vue';
|
||||
import { computed, defineComponent, ref, watch, inject } from 'vue';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import {
|
||||
baseComponentControlAttrs,
|
||||
baseComponentAttrs,
|
||||
baseComponentCommonAttrs,
|
||||
componentPropsFuncs,
|
||||
designSubTableAttrs,
|
||||
designSubControlAttrs,
|
||||
} from '../../VFormDesign/config/componentPropsConfig';
|
||||
import FormOptions from './FormOptions.vue';
|
||||
import { formItemsForEach, remove } from '../../../utils';
|
||||
import { IBaseFormAttrs } from '../config/formItemPropsConfig';
|
||||
import { getOutKeyList } from '@/api/formdesign/index';
|
||||
import { BasicModal, useModal } from '@/components/Modal';
|
||||
import { formItemPropsScript } from '../../VFormDesign/config/formItemPropsScript';
|
||||
import { getDictionaryType, getDictionary } from '@/api/sys/categories.ts';
|
||||
import { useOnlineFormDesignStore } from '@/store/modules/onlineFormDesign';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ComponentProps',
|
||||
components: {
|
||||
FormOptions,
|
||||
Empty,
|
||||
Input,
|
||||
Form,
|
||||
FormItem,
|
||||
Switch,
|
||||
Checkbox,
|
||||
Select,
|
||||
InputNumber,
|
||||
RadioGroup,
|
||||
RadioButtonGroup,
|
||||
Col,
|
||||
Row,
|
||||
message,
|
||||
BasicModal,
|
||||
useModal,
|
||||
IconPicker,
|
||||
},
|
||||
setup() {
|
||||
const onlineFormDesignStore = useOnlineFormDesignStore();
|
||||
let fieldTableValue = ref();
|
||||
let FieldTableOptions = ref();
|
||||
let ChlidTableOptions = ref(); //子表
|
||||
let receivedData = ref();
|
||||
const selectRadio = ref(0);
|
||||
const selectDictionary = ref();
|
||||
const selectDictionaryData = computed(() => onlineFormDesignStore.getSelectDictionaryData());
|
||||
const timeout = ref();
|
||||
const handleNextStepsData = inject('handleNextStepsData');
|
||||
let currentIndex = ref(); //判断是不是子表里的
|
||||
watch(
|
||||
() => handleNextStepsData, // 点击下一步按钮传递的参数
|
||||
(newVal) => {
|
||||
FieldTableOptions.value = [];
|
||||
ChlidTableOptions.value = [];
|
||||
if (newVal && newVal.value && newVal.value.scheme && newVal.value.scheme.scheme) {
|
||||
receivedData.value = JSON.parse(newVal.value.scheme.scheme);
|
||||
receivedData.value.db.forEach((item) => {
|
||||
FieldTableOptions.value.push({
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
});
|
||||
if (item.type == 'chlid') {
|
||||
ChlidTableOptions.value.push({
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
const handleSearch = (val: string) => {
|
||||
onlineFormDesignStore.getSelectData(val);
|
||||
};
|
||||
const handleSelectFocus = () => {
|
||||
onlineFormDesignStore.getSelectData('');
|
||||
};
|
||||
const handleChange = (code: string) => {
|
||||
onlineFormDesignStore.setSelectOptions(formConfig.value.currentItem.field, code);
|
||||
};
|
||||
let FieldNamesOptions = ref<SelectProps['options']>([]);
|
||||
|
||||
const fetch = () => {
|
||||
if (!fieldTableValue.value || typeof fieldTableValue.value !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
getOutKeyList({
|
||||
tableNames: fieldTableValue.value,
|
||||
dbCode: receivedData.value.dbCode,
|
||||
}).then((data: Recordable) => {
|
||||
let arr: any[] = [];
|
||||
if (data && data[0]) {
|
||||
data[0].db_codecolumnsList.forEach((item) => {
|
||||
arr.push({
|
||||
label: item.dbColumnName + '(' + item.description + ')',
|
||||
value: item.dbColumnName,
|
||||
csType: item.csType,
|
||||
});
|
||||
});
|
||||
FieldNamesOptions.value = arr;
|
||||
}
|
||||
});
|
||||
};
|
||||
const handleFieldTableChange = (e) => {
|
||||
fieldTableValue.value = e;
|
||||
fetch();
|
||||
inputOptions.value.forEach((item) => {
|
||||
if (item.name == 'fieldName') {
|
||||
item.componentProps.options = FieldNamesOptions;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 让compuated属性自动更新
|
||||
|
||||
const allOptions = ref([] as Omit<IBaseFormAttrs, 'tag'>[]);
|
||||
|
||||
const showControlAttrs = (includes: string[] | undefined) => {
|
||||
if (!includes) return true;
|
||||
return includes.includes(formConfig.value.currentItem!.component);
|
||||
};
|
||||
|
||||
const { formConfig } = useFormDesignState();
|
||||
if (formConfig.value.currentItem) {
|
||||
formConfig.value.currentItem.componentProps =
|
||||
formConfig.value.currentItem.componentProps || {};
|
||||
}
|
||||
watch(
|
||||
() => formConfig.value.currentItem?.field,
|
||||
(_newValue, oldValue) => {
|
||||
// currentIndex.value = formConfig.value.schemas.findIndex(
|
||||
// (element) => element.field === formConfig.value.currentItem.field,
|
||||
// );
|
||||
formConfig.value.schemas &&
|
||||
formItemsForEach(formConfig.value.schemas, (item) => {
|
||||
if (item.link) {
|
||||
const index = item.link.findIndex((linkItem) => linkItem === oldValue);
|
||||
index !== -1 && remove(item.link, index);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const thisFormType: any = inject('thisFormType');
|
||||
const formType_design = ref();
|
||||
watch(
|
||||
() => thisFormType,
|
||||
() => {
|
||||
formType_design.value = thisFormType.value?.info?.formType | 0;
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => formConfig.value.currentItem && formConfig.value.currentItem.component,
|
||||
() => {
|
||||
// console.log(formConfig.value);
|
||||
// currentIndex.value = formConfig.value.schemas.findIndex(
|
||||
// (element) => element.field === formConfig.value.currentItem.field,
|
||||
// );
|
||||
allOptions.value = [];
|
||||
baseComponentControlAttrs.forEach((item) => {
|
||||
item.category = 'control';
|
||||
if (!item.includes) {
|
||||
// 如果属性没有include,所有的控件都适用
|
||||
|
||||
allOptions.value.push(item);
|
||||
} else if (item.includes.includes(formConfig.value.currentItem!.component)) {
|
||||
// 如果有include,检查是否包含了当前控件类型
|
||||
allOptions.value.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
baseComponentCommonAttrs.forEach((item) => {
|
||||
if (
|
||||
(formType_design.value != 2 &&
|
||||
!['Divider', 'Button'].includes(formConfig.value.currentItem?.component)) ||
|
||||
!['dataTable', 'fieldName'].includes(item.name)
|
||||
) {
|
||||
item.category = 'input';
|
||||
if (item.includes) {
|
||||
if (item.includes.includes(formConfig.value.currentItem!.component)) {
|
||||
allOptions.value.push(item);
|
||||
}
|
||||
} else if (item.exclude) {
|
||||
if (!item.exclude.includes(formConfig.value.currentItem!.component)) {
|
||||
allOptions.value.push(item);
|
||||
}
|
||||
} else {
|
||||
allOptions.value.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
baseComponentAttrs[formConfig.value.currentItem!.component] &&
|
||||
baseComponentAttrs[formConfig.value.currentItem!.component].forEach(async (item) => {
|
||||
if (item.component) {
|
||||
if (['Switch', 'Checkbox', 'Radio'].includes(item.component as string)) {
|
||||
item.category = 'control';
|
||||
allOptions.value.push(item);
|
||||
} else {
|
||||
item.category = 'input';
|
||||
allOptions.value.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
// 控制性的选项
|
||||
const controlOptions = computed(() => {
|
||||
return allOptions.value.filter((item) => {
|
||||
return item.category == 'control';
|
||||
});
|
||||
});
|
||||
|
||||
// 控制性的选项
|
||||
const SubControllOptions = computed(() => {
|
||||
return designSubControlAttrs;
|
||||
});
|
||||
|
||||
// 非控制性选择
|
||||
const inputOptions = computed(() => {
|
||||
fetch();
|
||||
let arr = allOptions.value.filter((item) => {
|
||||
return item.category == 'input';
|
||||
});
|
||||
console.log(arr);
|
||||
console.log(currentIndex.value);
|
||||
arr.forEach((item) => {
|
||||
if (item.name == 'dataTable') {
|
||||
if (currentIndex.value == -1) {
|
||||
item.componentProps.options = ChlidTableOptions;
|
||||
} else {
|
||||
item.componentProps.options = FieldTableOptions;
|
||||
}
|
||||
}
|
||||
if (item.name == 'fieldName') {
|
||||
item.componentProps.options = FieldNamesOptions;
|
||||
}
|
||||
});
|
||||
return arr;
|
||||
});
|
||||
|
||||
const subTableOptions = computed(() => {
|
||||
let arr = designSubTableAttrs;
|
||||
arr.forEach((item) => {
|
||||
if (item.name == 'dataTable') {
|
||||
item.componentProps.options = ChlidTableOptions;
|
||||
}
|
||||
if (item.name == 'fieldName') {
|
||||
item.componentProps.options = FieldNamesOptions;
|
||||
}
|
||||
});
|
||||
return arr;
|
||||
});
|
||||
watch(
|
||||
() => formConfig.value.currentItem!.componentProps,
|
||||
() => {
|
||||
const func = componentPropsFuncs[formConfig.value.currentItem!.component];
|
||||
if (func) {
|
||||
func(formConfig.value.currentItem!.componentProps, allOptions.value);
|
||||
}
|
||||
fieldTableValue.value = formConfig.value?.currentItem?.componentProps?.dataTable;
|
||||
handleFieldTableChange(formConfig.value?.currentItem?.componentProps?.dataTable);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
const linkOptions = computed(() => {
|
||||
return (
|
||||
formConfig.value.schemas &&
|
||||
formConfig.value.schemas
|
||||
.filter((item) => item.key !== formConfig.value.currentItem!.key)
|
||||
.map(({ label, field }) => ({ label: label + '/' + field, value: field }))
|
||||
);
|
||||
});
|
||||
// 脚本
|
||||
const formContent: any = ref('');
|
||||
// 脚本窗口
|
||||
const [buttonScriptModal, { openModal, closeModal }] = useModal();
|
||||
// 点击脚本按钮
|
||||
function handleButtonClick() {
|
||||
formContent.value = formConfig.value.currentItem.componentProps?.clickCode;
|
||||
openModal();
|
||||
}
|
||||
// 脚本窗口提交
|
||||
function handleSubmit() {
|
||||
if (!checkChinese(formContent.value)) {
|
||||
message.warning('脚本的代码部分不能含有中文字符!');
|
||||
return;
|
||||
}
|
||||
formConfig.value.currentItem.componentProps.clickCode = formContent.value;
|
||||
closeModal();
|
||||
}
|
||||
// 检查脚本的运行代码是否含有中文
|
||||
function checkChinese(str) {
|
||||
// 分割字符串为多行
|
||||
const lines = str.split('\n');
|
||||
let flag = true;
|
||||
|
||||
// 遍历每一行
|
||||
for (const line of lines) {
|
||||
// 先检查并移除console.log及其后的内容
|
||||
const consoleIndex = line.indexOf('console.log');
|
||||
let partToCheck = line;
|
||||
if (consoleIndex !== -1) {
|
||||
partToCheck = line.substring(0, consoleIndex).trim(); // 移除console.log及其后的内容,并去掉前导空格
|
||||
}
|
||||
|
||||
// 再检查'//'
|
||||
const commentIndex = partToCheck.indexOf('//');
|
||||
// 如果有'//',检查'//'前的部分并去掉前导空格;如果没有,保留原部分
|
||||
partToCheck =
|
||||
commentIndex !== -1 ? partToCheck.substring(0, commentIndex).trim() : partToCheck;
|
||||
// 代码部分不能含有中文字符
|
||||
console.log(partToCheck);
|
||||
|
||||
if (/[\u4e00-\u9fa5]/.test(partToCheck)) {
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
return {
|
||||
formConfig,
|
||||
showControlAttrs,
|
||||
linkOptions,
|
||||
controlOptions,
|
||||
SubControllOptions,
|
||||
inputOptions,
|
||||
subTableOptions,
|
||||
fieldTableValue,
|
||||
FieldTableOptions,
|
||||
ChlidTableOptions,
|
||||
handleFieldTableChange,
|
||||
fetch,
|
||||
BasicModal,
|
||||
formContent,
|
||||
formItemPropsScript,
|
||||
buttonScriptModal,
|
||||
openModal,
|
||||
closeModal,
|
||||
handleButtonClick,
|
||||
handleSubmit,
|
||||
selectRadio,
|
||||
selectDictionary,
|
||||
selectDictionaryData,
|
||||
handleSearch,
|
||||
handleChange,
|
||||
handleSelectFocus,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.select-radio {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.alertModal {
|
||||
display: flex;
|
||||
background-color: @border-color-base;
|
||||
|
||||
&_content {
|
||||
width: 50%;
|
||||
padding: 1px;
|
||||
|
||||
&-textarea {
|
||||
background-color: @component-onlineform-formdesign-alert-background-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<!--
|
||||
* @Description: 表单项属性
|
||||
-->
|
||||
<template>
|
||||
<div class="properties-content">
|
||||
<div class="properties-body" v-if="formConfig.currentItem">
|
||||
<Empty class="hint-box" v-if="!formConfig.currentItem.key" description="未选择控件" />
|
||||
<Form v-else label-align="left" layout="vertical">
|
||||
<div v-for="item of baseItemColumnProps" :key="item.name">
|
||||
<FormItem :label="item.label" v-if="showProps(item.exclude)">
|
||||
<component
|
||||
v-if="formConfig.currentItem.colProps && item.component"
|
||||
class="component-props"
|
||||
v-bind="item.componentProps"
|
||||
:is="item.component"
|
||||
v-model:value="formConfig.currentItem.colProps[item.name]"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { baseItemColumnProps } from '../config/formItemPropsConfig';
|
||||
|
||||
import { Empty, Input, Form, FormItem, Switch, Checkbox, Select, Slider } from 'ant-design-vue';
|
||||
import RuleProps from './RuleProps.vue';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import { isArray } from 'lodash-es';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FormItemProps',
|
||||
components: {
|
||||
RuleProps,
|
||||
Empty,
|
||||
Input,
|
||||
Form,
|
||||
FormItem,
|
||||
Switch,
|
||||
Checkbox,
|
||||
Select,
|
||||
Slider,
|
||||
},
|
||||
// props: {} as PropsOptions,
|
||||
|
||||
setup() {
|
||||
const { formConfig } = useFormDesignState();
|
||||
const showProps = (exclude: string[] | undefined) => {
|
||||
if (!exclude) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isArray(exclude) ? !exclude.includes(formConfig.value.currentItem!.component) : true;
|
||||
};
|
||||
return {
|
||||
baseItemColumnProps,
|
||||
formConfig,
|
||||
showProps,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
<!--
|
||||
* @Description: 表单项属性,控件属性面板
|
||||
-->
|
||||
<template>
|
||||
<div class="properties-content">
|
||||
<div class="properties-body" v-if="formConfig.currentItem?.itemProps">
|
||||
<Empty class="hint-box" v-if="!formConfig.currentItem.key" description="未选择控件" />
|
||||
<Form
|
||||
v-else-if="formConfig.currentItem.component == 'MapGeom'"
|
||||
label-align="left"
|
||||
layout="vertical"
|
||||
description="图斑控件"
|
||||
>
|
||||
<FormItem label="选择空间数据表">
|
||||
<a-select
|
||||
v-model:value="formConfig.currentItem.mapSetData.chooseLayer"
|
||||
:options="shpLayerSourceOptions"
|
||||
size="middle"
|
||||
placeholder="请选择空间数据表"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="是否允许编辑图斑">
|
||||
<Switch v-model:checked="formConfig.currentItem.mapSetData.isAllowEditPolygon" />
|
||||
</FormItem>
|
||||
<FormItem label="是否开启位置跳转">
|
||||
<Switch v-model:checked="formConfig.currentItem.mapSetData.isEnablePostionJump" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<Form
|
||||
label-align="left"
|
||||
layout="vertical"
|
||||
v-else-if="
|
||||
formConfig.currentItem.component == 'Grid' && formConfig.currentItem.type == 'subTable'
|
||||
"
|
||||
>
|
||||
<FormItem label="标签">
|
||||
<a-input v-model:value="formConfig.currentItem.label" placeholder="请输入" />
|
||||
</FormItem>
|
||||
<FormItem label="字段标识">
|
||||
<a-input v-model:value="formConfig.currentItem.field" placeholder="请输入" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<Form
|
||||
label-align="left"
|
||||
layout="vertical"
|
||||
v-else-if="formConfig.currentItem.component == 'Card'"
|
||||
>
|
||||
<FormItem label="标签">
|
||||
<a-input v-model:value="formConfig.currentItem.label" placeholder="请输入" />
|
||||
</FormItem>
|
||||
<FormItem label="字段标识">
|
||||
<a-input v-model:value="formConfig.currentItem.field" placeholder="请输入" />
|
||||
</FormItem>
|
||||
<FormItem label="显示阴影">
|
||||
<a-select ref="select" v-model:value="formConfig.currentItem.shadow" style="width: 100%">
|
||||
<a-select-option value="always">总是</a-select-option>
|
||||
<a-select-option value="hover">悬浮显示</a-select-option>
|
||||
<a-select-option value="never">不显示</a-select-option>
|
||||
</a-select>
|
||||
</FormItem>
|
||||
<FormItem label="自定义类">
|
||||
<a-input v-model:value="formConfig.currentItem.myclass" placeholder="请输入" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<Form
|
||||
label-align="left"
|
||||
layout="vertical"
|
||||
v-else-if="
|
||||
['CreateUser', 'ModifyUser', 'CreateTime', 'ModifyTime'].includes(
|
||||
formConfig.currentItem.component,
|
||||
)
|
||||
"
|
||||
>
|
||||
<FormItem label="标签">
|
||||
<a-input v-model:value="formConfig.currentItem.label" placeholder="请输入" />
|
||||
</FormItem>
|
||||
<FormItem label="字段标识">
|
||||
<a-input v-model:value="formConfig.currentItem.field" placeholder="请输入" />
|
||||
</FormItem>
|
||||
<FormItem label="是否可见">
|
||||
<a-switch v-model:checked="formConfig.currentItem.display" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<Form v-else label-align="left" layout="vertical">
|
||||
<div v-for="item of baseFormItemProps" :key="item.name">
|
||||
<FormItem :label="item.label" v-if="showProps(item.exclude)">
|
||||
<component
|
||||
v-if="item.component"
|
||||
class="component-props"
|
||||
v-bind="item.componentProps"
|
||||
:is="item.component"
|
||||
v-model:value="formConfig.currentItem[item.name]"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
<div v-for="item of advanceFormItemProps" :key="item.name">
|
||||
<FormItem :label="item.label" v-if="showProps(item.exclude)">
|
||||
<component
|
||||
v-if="item.component"
|
||||
class="component-props"
|
||||
v-bind="item.componentProps"
|
||||
:is="item.component"
|
||||
v-model:value="formConfig.currentItem.itemProps[item.name]"
|
||||
/>
|
||||
</FormItem> </div
|
||||
><div v-for="item of advanceFormItemColProps" :key="item.name">
|
||||
<FormItem :label="item.label" v-if="showProps(item.exclude)">
|
||||
<component
|
||||
v-if="item.component"
|
||||
class="component-props"
|
||||
v-bind="item.componentProps"
|
||||
:is="item.component"
|
||||
v-model:value="formConfig.currentItem.itemProps[item.name]['span']"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
<FormItem label="控制属性" v-if="controlPropsList.length">
|
||||
<Col v-for="item of controlPropsList" :key="item.name">
|
||||
<Checkbox v-model:checked="formConfig.currentItem.itemProps[item.name]">
|
||||
{{ item.label }}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="是否必选"
|
||||
v-if="!['Grid', 'Tabs'].includes(formConfig.currentItem.component)"
|
||||
>
|
||||
<Switch v-model:checked="formConfig.currentItem.itemProps['required']" />
|
||||
<Input
|
||||
v-if="formConfig.currentItem.itemProps['required']"
|
||||
v-model:value="formConfig.currentItem.itemProps['message']"
|
||||
placeholder="请输入必选提示"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="!['Grid', 'Tabs'].includes(formConfig.currentItem.component)"
|
||||
label="校验规则"
|
||||
:class="{ 'form-rule-props': !!formConfig.currentItem.itemProps['rules'] }"
|
||||
>
|
||||
<RuleProps />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="FormItemProps">
|
||||
import { ref, computed, watch, inject } from 'vue';
|
||||
import { Empty, Input, Form, FormItem, Switch, Checkbox, Col } from 'ant-design-vue';
|
||||
import {
|
||||
baseFormItemControlAttrs,
|
||||
baseFormItemProps,
|
||||
advanceFormItemProps,
|
||||
advanceFormItemColProps,
|
||||
} from '../../VFormDesign/config/formItemPropsConfig';
|
||||
|
||||
import { PercentageOutlined } from '@ant-design/icons-vue';
|
||||
import RuleProps from './RuleProps.vue';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import { isArray } from 'lodash-es';
|
||||
// api
|
||||
// import { loadTableRecordInfo } from '@/api/database/index';
|
||||
import { ShpLayerSourceLoadPage } from '@/api/demo/formScheme';
|
||||
|
||||
const { formConfig } = useFormDesignState();
|
||||
|
||||
let shpLayerSourceOptions: any = ref([]);
|
||||
let dbcode = ref();
|
||||
const handleNextStepsData = inject('handleNextStepsData');
|
||||
|
||||
watch(
|
||||
() => handleNextStepsData,
|
||||
(newVal: any) => {
|
||||
dbcode.value = newVal?.value?.info?.DbCode;
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => formConfig.value,
|
||||
() => {
|
||||
// console.log('formConfig', formConfig.value);
|
||||
if (formConfig.value.currentItem) {
|
||||
formConfig.value.currentItem.itemProps = formConfig.value.currentItem.itemProps || {};
|
||||
formConfig.value.currentItem.itemProps.labelCol =
|
||||
formConfig.value.currentItem.itemProps.labelCol || {};
|
||||
formConfig.value.currentItem.itemProps.wrapperCol =
|
||||
formConfig.value.currentItem.itemProps.wrapperCol || {};
|
||||
if (formConfig.value.currentItem.component === 'MapGeom') {
|
||||
// 图斑组件
|
||||
formConfig.value.currentItem.mapSetData = formConfig.value.currentItem.mapSetData
|
||||
? formConfig.value.currentItem.mapSetData
|
||||
: {
|
||||
chooseLayer: '',
|
||||
isAllowEditPolygon: false,
|
||||
isEnablePostionJump: false,
|
||||
};
|
||||
// 选择图层
|
||||
if (shpLayerSourceOptions.value.length == 0) {
|
||||
getShpLayerSourceOptions();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
const showProps = (exclude: string[] | undefined) => {
|
||||
if (!exclude) {
|
||||
return true;
|
||||
}
|
||||
return isArray(exclude) ? !exclude.includes(formConfig.value.currentItem!.component) : true;
|
||||
};
|
||||
|
||||
const controlPropsList = computed(() => {
|
||||
return baseFormItemControlAttrs.filter((item) => {
|
||||
return showProps(item.exclude);
|
||||
});
|
||||
});
|
||||
|
||||
// 选择图层
|
||||
async function getShpLayerSourceOptions() {
|
||||
// let options: any = await loadTableRecordInfo({});
|
||||
let options: any = await ShpLayerSourceLoadPage();
|
||||
shpLayerSourceOptions.value = [];
|
||||
options.items.forEach((e) => {
|
||||
// shpLayerSourceOptions.value.push({ label: e.tableName, value: e.tableName });
|
||||
shpLayerSourceOptions.value.push({ label: e.name, value: e.relationTable });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<!--
|
||||
* @Description: 拖拽节点控件
|
||||
-->
|
||||
<template>
|
||||
<div
|
||||
class="drag-move-box component-border"
|
||||
@click.stop="handleSelectItem"
|
||||
:class="{ active: schema.key === formConfig.currentItem?.key }"
|
||||
>
|
||||
<div class="form-item-box">
|
||||
<VFormItem :formConfig="formConfig" :schema="schema" />
|
||||
</div>
|
||||
<div class="show-key-box">
|
||||
{{ schema.label + (schema.field ? '/' + schema.field : '') }}
|
||||
</div>
|
||||
<FormNodeOperate :schema="schema" :currentItem="formConfig.currentItem" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, PropType } from 'vue';
|
||||
import { IVFormComponent } from '../../../typings/v-form-component';
|
||||
import FormNodeOperate from './FormNodeOperate.vue';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import VFormItem from '../../VFormItem/index.vue';
|
||||
// import VFormItem from '../../VFormItem/vFormItem.vue';
|
||||
export default defineComponent({
|
||||
name: 'FormNode',
|
||||
components: {
|
||||
VFormItem,
|
||||
FormNodeOperate,
|
||||
},
|
||||
props: {
|
||||
schema: {
|
||||
type: Object as PropType<IVFormComponent>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { formConfig, formDesignMethods } = useFormDesignState();
|
||||
const state = reactive({});
|
||||
// 获取 formDesignMethods
|
||||
const handleSelectItem = () => {
|
||||
// 调用 formDesignMethods
|
||||
formDesignMethods.handleSetSelectItem(props.schema);
|
||||
};
|
||||
return {
|
||||
...toRefs(state),
|
||||
handleSelectItem,
|
||||
formConfig,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.component-border{
|
||||
border: 1px solid #d9d9d9
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<!--
|
||||
* @Description: 节点操作复制删除控件
|
||||
-->
|
||||
<template>
|
||||
<div class="copy-delete-box">
|
||||
<a class="copy" :class="activeClass" @click.stop="handleCopy">
|
||||
<Icon icon="ant-design:copy-outlined" />
|
||||
</a>
|
||||
<a class="delete" :class="activeClass" @click.stop="handleDelete">
|
||||
<Icon icon="ant-design:delete-outlined" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { IVFormComponent } from '../../../typings/v-form-component';
|
||||
import { remove } from '../../../utils';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FormNodeOperate',
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
schema: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
currentItem: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { formConfig, formDesignMethods } = useFormDesignState();
|
||||
const activeClass = computed(() => {
|
||||
return props.schema.key === props.currentItem.key ? 'active' : 'unactivated';
|
||||
});
|
||||
/**
|
||||
* 删除当前项
|
||||
*/
|
||||
const handleDelete = () => {
|
||||
const traverse = (schemas: IVFormComponent[]) => {
|
||||
schemas.some((formItem, index) => {
|
||||
const { component, key } = formItem;
|
||||
// 处理栅格和标签页布局
|
||||
if(['Grid', 'Tabs','Card', 'CardGroup'].includes(component)){
|
||||
switch(component){
|
||||
case 'Grid':
|
||||
case 'Card':
|
||||
formItem.columns?.forEach((item) => traverse(item.children))
|
||||
break;
|
||||
case 'Tabs':
|
||||
case 'CardGroup':
|
||||
console.log(formItem,'formItem')
|
||||
formItem?.componentProps?.options?.forEach(optionItem => {
|
||||
traverse(optionItem.children)
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (key === props.currentItem.key) {
|
||||
let params: IVFormComponent =
|
||||
schemas.length === 1
|
||||
? { component: '' }
|
||||
: schemas.length - 1 > index
|
||||
? schemas[index + 1]
|
||||
: schemas[index - 1];
|
||||
formDesignMethods.handleSetSelectItem(params);
|
||||
remove(schemas, index);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
};
|
||||
traverse(formConfig.value!.schemas);
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
formDesignMethods.handleCopy();
|
||||
};
|
||||
return { activeClass, handleDelete, handleCopy };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,466 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="['Grid'].includes(formConfig.currentItem!.component)">
|
||||
<div v-for="(item, index) of formConfig.currentItem!['columns']" :key="index">
|
||||
<div class="options-box">
|
||||
<Input v-model:value="item.span" class="options-value" />
|
||||
<a class="options-delete" @click="deleteGridOptions(index)">
|
||||
<Icon icon="ant-design:delete-outlined" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a @click="addGridOptions">
|
||||
<Icon icon="ant-design:file-add-outlined" />
|
||||
添加栅格
|
||||
</a>
|
||||
</div>
|
||||
<div v-else-if="['TreeSelect', 'Cascader'].includes(formConfig.currentItem!.component)">
|
||||
<BasicTree
|
||||
ref="treeDataRef"
|
||||
:treeData="treeDataAndOptions"
|
||||
:fieldNames="{ key: 'value', title: 'label' }"
|
||||
:clickRowToExpand="false"
|
||||
:defaultExpandAll="true"
|
||||
>
|
||||
<template #title="{ label, value }">
|
||||
<div class="options-box">
|
||||
<Input
|
||||
:value="label"
|
||||
@input="updateLabelOrValue('label', label, value, $event.target.value)"
|
||||
/>
|
||||
<Input
|
||||
:value="value"
|
||||
@blur="updateLabelOrValue('value', label, value, $event.target.value)"
|
||||
class="options-value"
|
||||
/>
|
||||
<Tooltip title="添加子级选项" placement="top">
|
||||
<a class="options-delete" @click="addTreeNode(value)">
|
||||
<Icon icon="ant-design:folder-add-outlined" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<a class="options-delete" @click="deleteTreeNode(value)">
|
||||
<Icon icon="ant-design:delete-outlined" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</BasicTree>
|
||||
<a @click="addTreeNode(null)">
|
||||
<Icon icon="ant-design:file-add-outlined" />
|
||||
添加父级选项
|
||||
</a>
|
||||
<a @click="showModal('import')">
|
||||
<Icon icon="ant-design:import-outlined" />
|
||||
导入数据
|
||||
</a>
|
||||
<!-- <a @click="showModal('export')">
|
||||
<Icon icon="ant-design:export-outlined" />
|
||||
导出数据
|
||||
</a> -->
|
||||
</div>
|
||||
<div v-else-if="['Transfer'].includes(formConfig.currentItem!.component)">
|
||||
<div v-for="(item, index) of formConfig.currentItem!.componentProps![key]" :key="index">
|
||||
<div class="options-box">
|
||||
<Input v-model:value="item.title" />
|
||||
<Input v-model:value="item.description" class="options-value" />
|
||||
<a
|
||||
class="options-delete"
|
||||
@click="updateTransferDisabled(index, false)"
|
||||
v-if="item.disabled"
|
||||
>
|
||||
<Icon icon="bi:unlock" />
|
||||
</a>
|
||||
<a
|
||||
class="options-delete"
|
||||
@click="updateTransferDisabled(index, true)"
|
||||
v-if="!item.disabled"
|
||||
>
|
||||
<Icon icon="bi:lock" />
|
||||
</a>
|
||||
<a class="options-delete" @click="deleteOptions(index)">
|
||||
<Icon icon="ant-design:delete-outlined" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a @click="addOptions">
|
||||
<Icon icon="ant-design:file-add-outlined" />
|
||||
添加标题
|
||||
</a>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="(item, index) of formConfig.currentItem!.componentProps![key]" :key="index">
|
||||
<div class="options-box">
|
||||
<Input v-model:value="item.label" />
|
||||
<Input v-model:value="item.value" class="options-value" />
|
||||
<a class="options-delete" @click="deleteOptions(index)">
|
||||
<Icon icon="ant-design:delete-outlined" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a @click="addOptions">
|
||||
<Icon icon="ant-design:file-add-outlined" />
|
||||
添加选项
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
:title="title"
|
||||
:open="visible"
|
||||
@ok="handleImportJson"
|
||||
@cancel="handleCancel"
|
||||
cancelText="关闭"
|
||||
:destroyOnClose="true"
|
||||
wrapClassName="v-code-modal"
|
||||
style="top: 20px"
|
||||
:width="850"
|
||||
>
|
||||
<p class="hint-box">{{ tip }}</p>
|
||||
<div class="v-json-box">
|
||||
<CodeEditor v-model:value="json" ref="myEditor" :mode="MODE.JSON" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<div v-if="type == 'import'">
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<Upload
|
||||
class="upload-button"
|
||||
:beforeUpload="beforeUpload"
|
||||
:showUploadList="false"
|
||||
accept="application/json"
|
||||
>
|
||||
<a-button type="primary">导入json文件</a-button>
|
||||
</Upload>
|
||||
<a-button type="primary" @click="handleImportJson">确定</a-button>
|
||||
</div>
|
||||
<div v-if="type == 'export'">
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="copy-btn"
|
||||
data-clipboard-action="copy"
|
||||
:data-clipboard-text="json"
|
||||
@click="handleCopyJson"
|
||||
>
|
||||
复制数据
|
||||
</a-button>
|
||||
<a-button @click="handleExportJson" type="primary">导出代码</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, ref, unref, watch } from 'vue';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import { BasicTree, TreeItem, TreeActionType } from '@/components/Tree';
|
||||
import { remove, formItemsForEach, generateKey } from '../../../utils';
|
||||
import { CodeEditor, MODE } from '@/components/CodeEditor';
|
||||
import { Upload, Modal, Input, Tooltip } from 'ant-design-vue';
|
||||
import { copyText } from '@/utils/copyTextToClipboard';
|
||||
import { options_json, treeData_json } from '../../VFormDesign/config/formItemPropsScript';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
import message from '../../../utils/message';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FormOptions',
|
||||
components: { Input, Icon, BasicTree, CodeEditor, Upload, Modal, Tooltip },
|
||||
// props: {},
|
||||
setup() {
|
||||
const { formConfig } = useFormDesignState();
|
||||
|
||||
let key: string = '';
|
||||
|
||||
// 导入窗口
|
||||
const state = reactive({
|
||||
title: '',
|
||||
visible: false,
|
||||
json: {} as any,
|
||||
type: '',
|
||||
tip: '',
|
||||
});
|
||||
// 树的值
|
||||
const treeDataAndOptions = ref<TreeItem[]>([]);
|
||||
|
||||
watch(
|
||||
() => formConfig.value.currentItem.component,
|
||||
() => {
|
||||
if (formConfig.value.currentItem?.component == 'TreeSelect') {
|
||||
// 树形选择
|
||||
treeDataAndOptions.value = formConfig.value.currentItem?.componentProps?.treeData;
|
||||
key = 'treeData';
|
||||
} else if (formConfig.value.currentItem?.component == 'Cascader') {
|
||||
// 级联选择
|
||||
treeDataAndOptions.value = formConfig.value.currentItem?.componentProps?.options;
|
||||
key = 'options';
|
||||
} else if (formConfig.value.currentItem?.component == 'Transfer') {
|
||||
// 穿梭框
|
||||
key = 'dataSource';
|
||||
} else {
|
||||
// 下拉选择、单选框-组、复选框-组、自动完成、选项卡、卡片组
|
||||
key = 'options';
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
// 获取树
|
||||
const treeDataRef = ref<Nullable<TreeActionType>>(null);
|
||||
const getTree = (): any => {
|
||||
return unref(treeDataRef);
|
||||
};
|
||||
// 添加树的父节点、子节点
|
||||
const addTreeNode = (value) => {
|
||||
let length = getLength(1, getTree().getTreeData());
|
||||
getTree().insertNodeByKey({
|
||||
parentKey: value,
|
||||
node: {
|
||||
label: `选项${length}`,
|
||||
value: `${length}`,
|
||||
},
|
||||
// 往后插入
|
||||
push: 'push',
|
||||
});
|
||||
refresh();
|
||||
};
|
||||
// 删除树的节点
|
||||
const deleteTreeNode = (value) => {
|
||||
getTree().deleteNodeByKey(value);
|
||||
refresh();
|
||||
};
|
||||
// 获取树的节点的不重复的默认值
|
||||
const getLength = (length, treeDataOrOptions) => {
|
||||
treeDataOrOptions?.forEach((to) => {
|
||||
if (to.children) {
|
||||
length = getLength(length, to.children);
|
||||
}
|
||||
if (length == to.value) {
|
||||
length++;
|
||||
length = getLength(length, treeDataOrOptions);
|
||||
}
|
||||
});
|
||||
return length;
|
||||
};
|
||||
// 修改树的label或者value
|
||||
const updateLabelOrValue = (type, label, value, newLabelOrValue) => {
|
||||
if (type == 'label') {
|
||||
getTree().updateNodeByKey(value, { label: newLabelOrValue, value: value });
|
||||
}
|
||||
if (type == 'value') {
|
||||
if (checkRepeat(true, getTree().getTreeData(), newLabelOrValue)) {
|
||||
getTree().updateNodeByKey(value, { label: label, value: newLabelOrValue });
|
||||
} else {
|
||||
message.warning('不能赋给重复的值');
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
};
|
||||
// 修改树的label或者value-检查是否有重复值
|
||||
const checkRepeat = (flag, treeData, newValue) => {
|
||||
treeData.forEach((tree) => {
|
||||
if (tree.value == newValue) {
|
||||
flag = false;
|
||||
} else if (tree.children) {
|
||||
return checkRepeat(flag, tree.children, newValue);
|
||||
}
|
||||
});
|
||||
return flag;
|
||||
};
|
||||
// 刷新树的值
|
||||
const refresh = () => {
|
||||
getTree().expandAll(true);
|
||||
treeDataAndOptions.value = getTree().getTreeData();
|
||||
formConfig.value.currentItem.componentProps[key] = getTree().getTreeData();
|
||||
};
|
||||
|
||||
// 树的导入窗口关闭
|
||||
const handleCancel = () => {
|
||||
state.visible = false;
|
||||
};
|
||||
// 树的导入窗口开启
|
||||
const showModal = (type) => {
|
||||
state.visible = true;
|
||||
state.type = type;
|
||||
if (type == 'import') {
|
||||
state.title = '树状结构数据导入';
|
||||
state.tip = '导入格式如下:';
|
||||
if (key == 'treeData') {
|
||||
state.json = treeData_json;
|
||||
} else {
|
||||
state.json = options_json;
|
||||
}
|
||||
} else if (type == 'export') {
|
||||
state.title = '树状结构数据导出';
|
||||
state.tip = '导出代码如下:';
|
||||
state.json = getTree().getTreeData();
|
||||
if (key == 'treeData') {
|
||||
state.json = { treeData: getTree().getTreeData() };
|
||||
} else {
|
||||
state.json = { options: getTree().getTreeData() };
|
||||
}
|
||||
}
|
||||
};
|
||||
// 树的导入JSON
|
||||
const handleImportJson = () => {
|
||||
try {
|
||||
const editorJsonData = JSON.parse(state.json);
|
||||
editorJsonData[key] &&
|
||||
formItemsForEach(editorJsonData[key], (formItem) => {
|
||||
generateKey(formItem);
|
||||
});
|
||||
// 删除旧的树
|
||||
cloneDeep(getTree().getTreeData())?.forEach((item) => {
|
||||
deleteTreeNode(item.value);
|
||||
});
|
||||
// 导入新数据
|
||||
editorJsonData[key]?.forEach((item) => {
|
||||
getTree().insertNodeByKey({
|
||||
parentKey: null,
|
||||
node: item,
|
||||
push: 'push',
|
||||
});
|
||||
});
|
||||
refresh();
|
||||
handleCancel();
|
||||
message.success('导入成功');
|
||||
} catch {
|
||||
message.error('导入失败,数据格式不对');
|
||||
}
|
||||
};
|
||||
// 通过json文件导入
|
||||
const beforeUpload = (e: File) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(e);
|
||||
reader.onload = function () {
|
||||
state.json = this.result as string;
|
||||
handleImportJson();
|
||||
};
|
||||
return false;
|
||||
};
|
||||
// 导出json文件
|
||||
const handleExportJson = () => {
|
||||
let content = 'data:text/csv;charset=utf-8,';
|
||||
content += `${JSON.stringify(state.json, null, 2)}`;
|
||||
const encodedUri = encodeURI(content);
|
||||
const actions = document.createElement('a');
|
||||
actions.setAttribute('href', encodedUri);
|
||||
actions.setAttribute('download', 'file.json');
|
||||
actions.click();
|
||||
};
|
||||
// 复制数据
|
||||
const handleCopyJson = () => {
|
||||
const value = `${JSON.stringify(state.json, null, 2)}`;
|
||||
if (!value) {
|
||||
message.warning('代码为空');
|
||||
return;
|
||||
}
|
||||
copyText(value);
|
||||
};
|
||||
|
||||
const addOptions = () => {
|
||||
if (!formConfig.value.currentItem?.componentProps?.[key])
|
||||
formConfig.value.currentItem!.componentProps![key] = [];
|
||||
const len = formConfig.value.currentItem?.componentProps?.[key].length + 1;
|
||||
|
||||
if (['Tabs'].includes(formConfig.value.currentItem!.component)) {
|
||||
formConfig.value.currentItem!.componentProps![key].push({
|
||||
label: `选项卡${len}`,
|
||||
value: '' + len,
|
||||
children: [],
|
||||
});
|
||||
} else if (['CardGroup'].includes(formConfig.value.currentItem!.component)) {
|
||||
formConfig.value.currentItem!.componentProps![key].push({
|
||||
label: `卡片${len}`,
|
||||
value: '' + len,
|
||||
field: `use_card_${uuidv4()}`,
|
||||
children: [],
|
||||
});
|
||||
} else if (['Transfer'].includes(formConfig.value.currentItem!.component)) {
|
||||
formConfig.value.currentItem!.componentProps![key].push({
|
||||
key: `key-${len}`,
|
||||
title: `标题${len}`,
|
||||
description: `描述${len}`,
|
||||
disabled: false,
|
||||
});
|
||||
} else {
|
||||
formConfig.value.currentItem!.componentProps![key].push({
|
||||
label: `选项${len}`,
|
||||
value: '' + len,
|
||||
});
|
||||
}
|
||||
};
|
||||
const deleteOptions = (index: number) => {
|
||||
remove(formConfig.value.currentItem?.componentProps?.[key], index);
|
||||
};
|
||||
const addGridOptions = () => {
|
||||
formConfig.value.currentItem?.['columns']?.push({
|
||||
span: 12,
|
||||
children: [],
|
||||
});
|
||||
};
|
||||
const deleteGridOptions = (index: number) => {
|
||||
if (index === 0) return message.warning('请至少保留一个栅格');
|
||||
remove(formConfig.value.currentItem!['columns']!, index);
|
||||
};
|
||||
const updateTransferDisabled = (index: number, flag: boolean) => {
|
||||
formConfig.value.currentItem.componentProps[key][index].disabled = flag;
|
||||
};
|
||||
return {
|
||||
...toRefs(state),
|
||||
formConfig,
|
||||
addOptions,
|
||||
deleteOptions,
|
||||
key,
|
||||
deleteGridOptions,
|
||||
addGridOptions,
|
||||
treeDataRef,
|
||||
treeDataAndOptions,
|
||||
addTreeNode,
|
||||
deleteTreeNode,
|
||||
updateLabelOrValue,
|
||||
updateTransferDisabled,
|
||||
handleImportJson,
|
||||
handleExportJson,
|
||||
handleCopyJson,
|
||||
beforeUpload,
|
||||
handleCancel,
|
||||
showModal,
|
||||
MODE,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.options-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.options-value {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.options-delete {
|
||||
flex-shrink: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background: #ff4d4f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
margin: 0 5px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
<!--
|
||||
* @Description: 右侧属性面板控件 表单属性面板
|
||||
-->
|
||||
<template>
|
||||
<div class="properties-content">
|
||||
<Form class="properties-body" label-align="left" layout="vertical">
|
||||
<!-- <e-upload v-model="fileList"></e-upload>-->
|
||||
|
||||
<FormItem label="表单布局">
|
||||
<RadioGroup button-style="solid" v-model:value="formConfig.layout">
|
||||
<RadioButton value="horizontal">水平</RadioButton>
|
||||
<RadioButton value="vertical" :disabled="formConfig.labelLayout === 'Grid'">
|
||||
垂直
|
||||
</RadioButton>
|
||||
<RadioButton value="inline" :disabled="formConfig.labelLayout === 'Grid'">
|
||||
行内
|
||||
</RadioButton>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
|
||||
<!-- <Row> -->
|
||||
<FormItem label="标签布局">
|
||||
<RadioGroup
|
||||
buttonStyle="solid"
|
||||
v-model:value="formConfig.labelLayout"
|
||||
@change="lableLayoutChange"
|
||||
>
|
||||
<RadioButton value="flex">固定</RadioButton>
|
||||
<RadioButton value="Grid" :disabled="formConfig.layout !== 'horizontal'">
|
||||
栅格
|
||||
</RadioButton>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<!-- </Row> -->
|
||||
<FormItem label="标签宽度(px)" v-show="formConfig.labelLayout === 'flex'">
|
||||
<InputNumber
|
||||
:style="{ width: '100%' }"
|
||||
v-model:value="formConfig.labelWidth"
|
||||
:min="0"
|
||||
:step="1"
|
||||
/>
|
||||
</FormItem>
|
||||
<div v-if="formConfig.labelLayout === 'Grid'">
|
||||
<FormItem label="labelCol">
|
||||
<Slider v-model:value="sliderSpan" :max="24" />
|
||||
</FormItem>
|
||||
<FormItem label="wrapperCol">
|
||||
<Slider v-model:value="sliderSpan" :max="24" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="标签对齐">
|
||||
<RadioGroup button-style="solid" v-model:value="formConfig.labelAlign">
|
||||
<RadioButton value="left">靠左</RadioButton>
|
||||
<RadioButton value="right">靠右</RadioButton>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="控件大小">
|
||||
<RadioGroup button-style="solid" v-model:value="formConfig.size">
|
||||
<RadioButton value="default">默认</RadioButton>
|
||||
<RadioButton value="small">小</RadioButton>
|
||||
<RadioButton value="large">大</RadioButton>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</div>
|
||||
<FormItem label="表单属性">
|
||||
<Col
|
||||
><Checkbox v-model:checked="formConfig.colon" v-if="formConfig.layout == 'horizontal'"
|
||||
>label后面显示冒号</Checkbox
|
||||
></Col
|
||||
>
|
||||
<Col><Checkbox v-model:checked="formConfig.disabled">禁用</Checkbox></Col>
|
||||
<Col><Checkbox v-model:checked="formConfig.hideRequiredMark">隐藏必选标记</Checkbox></Col>
|
||||
</FormItem>
|
||||
<FormItem label="脚本">
|
||||
<a-space direction="vertical">
|
||||
<a-button
|
||||
v-model:value="formConfig.beforeSetData"
|
||||
size="mini"
|
||||
@click="handleBtnClick('beforeSetData')"
|
||||
>
|
||||
添加赋值前脚本
|
||||
</a-button>
|
||||
<a-button
|
||||
v-model:value="formConfig.afterValidateForm"
|
||||
size="mini"
|
||||
@click="handleBtnClick('afterValidateForm')"
|
||||
>
|
||||
添加校验后脚本
|
||||
</a-button>
|
||||
<a-button
|
||||
v-model:value="formConfig.afterSaveEvent"
|
||||
size="mini"
|
||||
@click="handleBtnClick('afterSaveEvent')"
|
||||
>
|
||||
添加保存后脚本
|
||||
</a-button>
|
||||
<a-button
|
||||
v-model:value="formConfig.changeDataEvent"
|
||||
size="mini"
|
||||
@click="handleBtnClick('changeDataEvent')"
|
||||
>
|
||||
添加数据改变脚本
|
||||
</a-button>
|
||||
</a-space>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
<BasicModal
|
||||
@register="propsScriptModal"
|
||||
:title="formTitle"
|
||||
:height="500"
|
||||
:width="1000"
|
||||
@ok="handleSubmit"
|
||||
>
|
||||
<div class="alertModal">
|
||||
<div class="alertModal_content">
|
||||
<a-textarea
|
||||
v-model:value="formContent"
|
||||
autosize
|
||||
:auto-size="{ minRows: 22, maxRows: 22 }"
|
||||
/>
|
||||
</div>
|
||||
<div class="alertModal_content">
|
||||
<a-textarea
|
||||
class="alertModal_content-textarea"
|
||||
v-model:value="formItemPropsScript"
|
||||
autosize
|
||||
readOnly
|
||||
:auto-size="{ minRows: 22, maxRows: 22 }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup name="FormProps">
|
||||
import { ref, computed } from 'vue';
|
||||
import { BasicModal, useModal } from '@/components/Modal';
|
||||
import { formItemPropsScript } from '../../VFormDesign/config/formItemPropsScript';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import {
|
||||
InputNumber,
|
||||
Slider,
|
||||
Checkbox,
|
||||
Col,
|
||||
RadioChangeEvent,
|
||||
Form,
|
||||
FormItem,
|
||||
RadioButton,
|
||||
RadioGroup,
|
||||
message,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
const { formConfig } = useFormDesignState();
|
||||
|
||||
formConfig.value = formConfig.value || {
|
||||
labelCol: { span: 24 },
|
||||
wrapperCol: { span: 24 },
|
||||
};
|
||||
|
||||
const lableLayoutChange = (e: RadioChangeEvent) => {
|
||||
if (e.target.value === 'Grid') {
|
||||
formConfig.value.layout = 'horizontal';
|
||||
}
|
||||
};
|
||||
|
||||
const sliderSpan = computed(() => {
|
||||
if (formConfig.value.labelLayout) {
|
||||
return Number(formConfig.value.labelCol!.span);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
// 当前打开脚本
|
||||
let formTitle: any = ref('');
|
||||
let btnClickEvent_now = '';
|
||||
let formContent: any = ref('');
|
||||
// 脚本窗口
|
||||
const [propsScriptModal, { openModal, closeModal }] = useModal();
|
||||
// 点击脚本按钮
|
||||
function handleBtnClick(btnClickEvent: string) {
|
||||
switch (btnClickEvent) {
|
||||
case 'beforeSetData':
|
||||
formTitle.value = '添加赋值前脚本';
|
||||
btnClickEvent_now = 'beforeSetData';
|
||||
formContent.value = formConfig.value?.beforeSetData;
|
||||
break;
|
||||
case 'afterValidateForm':
|
||||
formTitle.value = '添加校验后脚本';
|
||||
btnClickEvent_now = 'afterValidateForm';
|
||||
formContent.value = formConfig.value?.afterValidateForm;
|
||||
break;
|
||||
case 'afterSaveEvent':
|
||||
formTitle.value = '添加保存后脚本';
|
||||
btnClickEvent_now = 'afterSaveEvent';
|
||||
formContent.value = formConfig.value?.afterSaveEvent;
|
||||
break;
|
||||
case 'changeDataEvent':
|
||||
formTitle.value = '添加数据改变脚本';
|
||||
btnClickEvent_now = 'changeDataEvent';
|
||||
formContent.value = formConfig.value?.changeDataEvent;
|
||||
break;
|
||||
default:
|
||||
formTitle.value = '';
|
||||
break;
|
||||
}
|
||||
// console.log(formConfig.value);
|
||||
// console.log(description);
|
||||
openModal();
|
||||
}
|
||||
// 脚本窗口提交
|
||||
function handleSubmit() {
|
||||
if (!checkChinese(formContent.value)) {
|
||||
message.warning('脚本的代码部分不能含有中文字符!');
|
||||
return;
|
||||
}
|
||||
if (btnClickEvent_now) {
|
||||
if (btnClickEvent_now == 'beforeSetData') {
|
||||
formConfig.value.beforeSetData = formContent.value;
|
||||
} else if (btnClickEvent_now == 'afterValidateForm') {
|
||||
formConfig.value.afterValidateForm = formContent.value;
|
||||
} else if (btnClickEvent_now == 'afterSaveEvent') {
|
||||
formConfig.value.afterSaveEvent = formContent.value;
|
||||
} else if (btnClickEvent_now == 'changeDataEvent') {
|
||||
formConfig.value.changeDataEvent = formContent.value;
|
||||
}
|
||||
}
|
||||
closeModal();
|
||||
}
|
||||
// 检查脚本的运行代码是否含有中文
|
||||
function checkChinese(str) {
|
||||
// 分割字符串为多行
|
||||
const lines = str.split('\n');
|
||||
let flag = true;
|
||||
|
||||
// 遍历每一行
|
||||
for (const line of lines) {
|
||||
// 先检查并移除console.log及其后的内容
|
||||
const consoleIndex = line.indexOf('console.log');
|
||||
let partToCheck = line;
|
||||
if (consoleIndex !== -1) {
|
||||
partToCheck = line.substring(0, consoleIndex).trim(); // 移除console.log及其后的内容,并去掉前导空格
|
||||
}
|
||||
|
||||
// 再检查'//'
|
||||
const commentIndex = partToCheck.indexOf('//');
|
||||
// 如果有'//',检查'//'前的部分并去掉前导空格;如果没有,保留原部分
|
||||
partToCheck =
|
||||
commentIndex !== -1 ? partToCheck.substring(0, commentIndex).trim() : partToCheck;
|
||||
// 代码部分不能含有中文字符
|
||||
if (/[\u4e00-\u9fa5]/.test(partToCheck)) {
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.alertModal {
|
||||
display: flex;
|
||||
background-color: @border-color-base;
|
||||
|
||||
&_content {
|
||||
width: 50%;
|
||||
padding: 1px;
|
||||
|
||||
&-textarea {
|
||||
background-color: @component-onlineform-formdesign-alert-background-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<!--
|
||||
* @Description: 导入JSON模板
|
||||
-->
|
||||
<template>
|
||||
<Modal
|
||||
title="JSON数据"
|
||||
:open="visible"
|
||||
@ok="handleImportJson"
|
||||
@cancel="handleCancel"
|
||||
cancelText="关闭"
|
||||
:destroyOnClose="true"
|
||||
wrapClassName="v-code-modal"
|
||||
style="top: 20px"
|
||||
:width="850"
|
||||
>
|
||||
<p class="hint-box">导入格式如下:</p>
|
||||
<div class="v-json-box">
|
||||
<CodeEditor v-model:value="json" ref="myEditor" :mode="MODE.JSON" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<Upload
|
||||
class="upload-button"
|
||||
:beforeUpload="beforeUpload"
|
||||
:showUploadList="false"
|
||||
accept="application/json"
|
||||
>
|
||||
<a-button type="primary">导入json文件</a-button>
|
||||
</Upload>
|
||||
<a-button type="primary" @click="handleImportJson">确定</a-button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs } from 'vue';
|
||||
// import message from '../../../utils/message';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
// import { codemirror } from 'vue-codemirror-lite';
|
||||
import { IFormConfig } from '../../../typings/v-form-component';
|
||||
import { formItemsForEach, generateKey } from '../../../utils';
|
||||
import { CodeEditor, MODE } from '@/components/CodeEditor';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
import { Upload, Modal } from 'ant-design-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ImportJsonModal',
|
||||
components: {
|
||||
CodeEditor,
|
||||
Upload,
|
||||
Modal,
|
||||
},
|
||||
setup() {
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
json: `{
|
||||
"schemas": [
|
||||
{
|
||||
"component": "input",
|
||||
"label": "输入框",
|
||||
"field": "input_2",
|
||||
"span": 24,
|
||||
"props": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"layout": "horizontal",
|
||||
"labelLayout": "flex",
|
||||
"labelWidth": 100,
|
||||
"labelCol": {},
|
||||
"wrapperCol": {}
|
||||
}`,
|
||||
jsonData: {
|
||||
schemas: {},
|
||||
config: {},
|
||||
},
|
||||
handleSetSelectItem: null,
|
||||
});
|
||||
const { formDesignMethods } = useFormDesignState();
|
||||
const handleCancel = () => {
|
||||
state.visible = false;
|
||||
};
|
||||
const showModal = () => {
|
||||
state.visible = true;
|
||||
};
|
||||
const handleImportJson = () => {
|
||||
// 导入JSON
|
||||
try {
|
||||
const editorJsonData = JSON.parse(state.json) as IFormConfig;
|
||||
console.log('editorJsonData', editorJsonData);
|
||||
editorJsonData.schemas &&
|
||||
formItemsForEach(editorJsonData.schemas, (formItem) => {
|
||||
generateKey(formItem);
|
||||
});
|
||||
formDesignMethods.setFormConfig({
|
||||
...editorJsonData,
|
||||
activeKey: 1,
|
||||
currentItem: { component: '' },
|
||||
});
|
||||
handleCancel();
|
||||
createMessage.success('导入成功');
|
||||
} catch {
|
||||
createMessage.error('导入失败,数据格式不对');
|
||||
}
|
||||
};
|
||||
const beforeUpload = (e: File) => {
|
||||
// 通过json文件导入
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(e);
|
||||
reader.onload = function () {
|
||||
state.json = this.result as string;
|
||||
handleImportJson();
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
return {
|
||||
handleImportJson,
|
||||
beforeUpload,
|
||||
handleCancel,
|
||||
showModal,
|
||||
...toRefs(state),
|
||||
MODE,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.upload-button {
|
||||
margin: 0 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<!--
|
||||
* @Description: 渲染JSON数据
|
||||
-->
|
||||
<template>
|
||||
<Modal
|
||||
title="JSON数据"
|
||||
:footer="null"
|
||||
:open="visible"
|
||||
@cancel="handleCancel"
|
||||
:destroyOnClose="true"
|
||||
wrapClassName="v-code-modal"
|
||||
style="top: 20px"
|
||||
width="850px"
|
||||
>
|
||||
<PreviewCode :editorJson="editorJson" />
|
||||
</Modal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs } from 'vue';
|
||||
import PreviewCode from './PreviewCode.vue';
|
||||
import { IFormConfig } from '../../../typings/v-form-component';
|
||||
import { formatRules, removeAttrs } from '../../../utils';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JsonModal',
|
||||
components: {
|
||||
PreviewCode,
|
||||
Modal,
|
||||
},
|
||||
emits: ['cancel'],
|
||||
setup(_props, { emit }) {
|
||||
const state = reactive<{
|
||||
visible: boolean;
|
||||
jsonData: IFormConfig;
|
||||
}>({
|
||||
visible: false, // 控制json数据弹框显示
|
||||
jsonData: {} as IFormConfig, // json数据
|
||||
});
|
||||
/**
|
||||
* 显示Json数据弹框
|
||||
* @param jsonData
|
||||
*/
|
||||
const showModal = (jsonData: IFormConfig) => {
|
||||
formatRules(jsonData.schemas);
|
||||
state.jsonData = jsonData as any;
|
||||
state.visible = true;
|
||||
};
|
||||
|
||||
// 计算json数据
|
||||
const editorJson = computed(() => {
|
||||
// @ts-ignore
|
||||
return JSON.stringify(removeAttrs(state.jsonData), null, '\t');
|
||||
});
|
||||
|
||||
// 关闭弹框
|
||||
const handleCancel = () => {
|
||||
state.visible = false;
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
return { ...toRefs(state), editorJson, handleCancel, showModal };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
<!--
|
||||
* @Description: 表单项布局控件
|
||||
* 千万不要在template下面的第一行加注释,因为这里拖动的第一个元素
|
||||
-->
|
||||
|
||||
<template>
|
||||
<Col v-bind="colPropsComputed">
|
||||
<template v-if="['Grid','Card'].includes(schema.component)">
|
||||
<div
|
||||
class="grid-box component-border"
|
||||
:class="{ active: schema.key === currentItem.key }"
|
||||
@click.stop="handleSetSelectItem(schema)"
|
||||
>
|
||||
{{ schema.label }}
|
||||
<Row class="grid-row" v-bind="schema.componentProps">
|
||||
<Col
|
||||
class="grid-col"
|
||||
v-for="(colItem, index) in schema.columns"
|
||||
:key="index"
|
||||
:span="colItem.span"
|
||||
>
|
||||
<draggable
|
||||
class="list-main draggable-box"
|
||||
:component-data="{ name: 'list', tag: 'div', type: 'transition-group' }"
|
||||
v-bind="{
|
||||
group: 'form-draggable',
|
||||
ghostClass: 'moving',
|
||||
animation: 180,
|
||||
handle: '.drag-move',
|
||||
}"
|
||||
item-key="key"
|
||||
v-model="colItem.children"
|
||||
@start="$emit('dragStart', $event, colItem.children)"
|
||||
@add="$emit('handleColAdd', $event, colItem.children)"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<LayoutItem
|
||||
class="drag-move"
|
||||
:schema="element"
|
||||
:current-item="currentItem"
|
||||
@handle-copy="$emit('handle-copy')"
|
||||
@handle-delete="$emit('handle-delete')"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</Col>
|
||||
</Row>
|
||||
<FormNodeOperate :schema="schema" :currentItem="currentItem" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="['Tabs'].includes(schema.component)">
|
||||
<div
|
||||
class="grid-box component-border"
|
||||
:class="{ active: schema.key === currentItem.key }"
|
||||
@click.stop="handleSetSelectItem(schema)"
|
||||
>
|
||||
<a-tabs>
|
||||
<a-tab-pane v-for="(colItem, index) in schema.componentProps.options" :tab="colItem.label" :key="index">
|
||||
<draggable
|
||||
class="list-main draggable-box"
|
||||
:component-data="{ name: 'list', tag: 'div', type: 'transition-group' }"
|
||||
v-bind="{
|
||||
group: 'form-draggable',
|
||||
ghostClass: 'moving',
|
||||
animation: 180,
|
||||
handle: '.drag-move',
|
||||
}"
|
||||
item-key="key"
|
||||
v-model="colItem.children"
|
||||
@start="$emit('dragStart', $event, colItem.children)"
|
||||
@add="$emit('handleColAdd', $event, colItem.children)"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<LayoutItem
|
||||
class="drag-move"
|
||||
:schema="element"
|
||||
:current-item="currentItem"
|
||||
@handle-copy="$emit('handle-copy')"
|
||||
@handle-delete="$emit('handle-delete')"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<FormNodeOperate :schema="schema" :currentItem="currentItem" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="['CardGroup'].includes(schema.component)">
|
||||
<div
|
||||
class="grid-box component-border"
|
||||
:class="{ active: schema.key === currentItem.key }"
|
||||
@click.stop="handleSetSelectItem(schema)"
|
||||
>
|
||||
{{ schema.label }}
|
||||
<a-radio-group v-model:value="cardGroupRadio" :options="schema.componentProps.options" />
|
||||
<div class="list-main draggable-box" v-for="(colItem, index) in schema.componentProps.options" :tab="colItem.label" :key="index">
|
||||
<draggable
|
||||
v-show="cardGroupRadio == colItem.value"
|
||||
class="list-main draggable-box"
|
||||
style="min-height: 50px"
|
||||
:component-data="{ name: 'list', tag: 'div', type: 'transition-group' }"
|
||||
v-bind="{
|
||||
group: 'form-draggable',
|
||||
ghostClass: 'moving',
|
||||
animation: 180,
|
||||
handle: '.drag-move',
|
||||
}"
|
||||
item-key="key"
|
||||
v-model="colItem.children"
|
||||
@start="$emit('dragStart', $event, colItem.children)"
|
||||
@add="$emit('handleColAdd', $event, colItem.children)"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<LayoutItem
|
||||
class="drag-move"
|
||||
:schema="element"
|
||||
:current-item="currentItem"
|
||||
@handle-copy="$emit('handle-copy')"
|
||||
@handle-delete="$emit('handle-delete')"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<FormNodeOperate :schema="schema" :currentItem="currentItem" />
|
||||
</div>
|
||||
</template>
|
||||
<FormNode
|
||||
v-else
|
||||
:key="schema.key"
|
||||
:schema="schema"
|
||||
:current-item="currentItem"
|
||||
@handle-copy="$emit('handle-copy')"
|
||||
@handle-delete="$emit('handle-delete')"
|
||||
/>
|
||||
</Col>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, reactive, toRefs, ref } from 'vue';
|
||||
import draggable from 'vuedraggable';
|
||||
import FormNode from './FormNode.vue';
|
||||
import FormNodeOperate from './FormNodeOperate.vue';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import { IVFormComponent } from '../../../typings/v-form-component';
|
||||
import { Row, Col } from 'ant-design-vue';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LayoutItem',
|
||||
components: {
|
||||
FormNode,
|
||||
FormNodeOperate,
|
||||
draggable,
|
||||
Row,
|
||||
Col,
|
||||
},
|
||||
props: {
|
||||
schema: {
|
||||
type: Object as PropType<IVFormComponent>,
|
||||
required: true,
|
||||
},
|
||||
currentItem: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['dragStart', 'handleColAdd', 'handle-copy', 'handle-delete'],
|
||||
setup(props) {
|
||||
const {
|
||||
formDesignMethods: { handleSetSelectItem },
|
||||
formConfig,
|
||||
} = useFormDesignState();
|
||||
const state = reactive({});
|
||||
const colPropsComputed = computed(() => {
|
||||
const { colProps = {} } = props.schema;
|
||||
return colProps;
|
||||
});
|
||||
|
||||
const list1 = computed(() => props.schema.columns);
|
||||
|
||||
// 计算布局元素,水平模式下为ACol,非水平模式下为div
|
||||
const layoutTag = computed(() => {
|
||||
return formConfig.value.layout === 'horizontal' ? 'Col' : 'div';
|
||||
});
|
||||
// 控制卡片组
|
||||
const cardGroupRadio = ref()
|
||||
if(props.schema.component === 'CardGroup'){
|
||||
// 为卡片组的孩子卡片分配field
|
||||
props.schema.componentProps.options = props.schema.componentProps.options?.map(item => {
|
||||
return {...item,field:`use_card_${uuidv4()}`}
|
||||
})
|
||||
if(props.schema.componentProps.options.length > 0){
|
||||
cardGroupRadio.value = props.schema.componentProps.options[0].value
|
||||
}
|
||||
}
|
||||
return {
|
||||
...toRefs(state),
|
||||
colPropsComputed,
|
||||
handleSetSelectItem,
|
||||
layoutTag,
|
||||
list1,
|
||||
cardGroupRadio,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import url('../styles/variable.less');
|
||||
|
||||
.layout-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hidden-item {
|
||||
background-color: rgb(240 191 195);
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.component-border{
|
||||
border: 1px solid #d9d9d9
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="v-json-box">
|
||||
<CodeEditor :value="editorJson" ref="myEditor" :mode="MODE.JSON" />
|
||||
</div>
|
||||
<div class="copy-btn-box">
|
||||
<a-button
|
||||
@click="handleCopyJson"
|
||||
type="primary"
|
||||
class="copy-btn"
|
||||
data-clipboard-action="copy"
|
||||
:data-clipboard-text="editorJson"
|
||||
>
|
||||
复制数据
|
||||
</a-button>
|
||||
<a-button @click="handleExportJson" type="primary">导出代码</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs } from 'vue';
|
||||
import { CodeEditor, MODE } from '@/components/CodeEditor';
|
||||
|
||||
import { copyText } from '@/utils/copyTextToClipboard';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PreviewCode',
|
||||
components: {
|
||||
CodeEditor,
|
||||
},
|
||||
props: {
|
||||
fileFormat: {
|
||||
type: String,
|
||||
default: 'json',
|
||||
},
|
||||
editorJson: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
});
|
||||
|
||||
const exportData = (data: string, fileName = `file.${props.fileFormat}`) => {
|
||||
let content = 'data:text/csv;charset=utf-8,';
|
||||
content += data;
|
||||
const encodedUri = encodeURI(content);
|
||||
const actions = document.createElement('a');
|
||||
actions.setAttribute('href', encodedUri);
|
||||
actions.setAttribute('download', fileName);
|
||||
actions.click();
|
||||
};
|
||||
|
||||
const handleExportJson = () => {
|
||||
exportData(props.editorJson);
|
||||
};
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const handleCopyJson = () => {
|
||||
// 复制数据
|
||||
const value = props.editorJson;
|
||||
if (!value) {
|
||||
createMessage.warning('代码为空!');
|
||||
return;
|
||||
}
|
||||
copyText(value);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
exportData,
|
||||
handleCopyJson,
|
||||
handleExportJson,
|
||||
MODE,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// modal复制按钮样式
|
||||
.copy-btn-box {
|
||||
padding-top: 8px;
|
||||
text-align: center;
|
||||
|
||||
.copy-btn {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
<!--
|
||||
* @Description: 正则校验选项组件
|
||||
-->
|
||||
<template>
|
||||
<div class="rule-props-content">
|
||||
<Form v-if="formConfig.currentItem && formConfig.currentItem['rules']">
|
||||
<div
|
||||
v-for="(item, index) of formConfig.currentItem['rules']"
|
||||
:key="index"
|
||||
class="rule-props-item"
|
||||
>
|
||||
<Icon
|
||||
icon="ant-design:close-circle-filled"
|
||||
class="rule-props-item-close"
|
||||
@click="removeRule(index)"
|
||||
/>
|
||||
<FormItem label="正则" :labelCol="{ span: 6 }" :wrapperCol="{ span: 16 }">
|
||||
<AutoComplete
|
||||
v-model:value="item.pattern"
|
||||
placeholder="请输入正则表达式"
|
||||
:dataSource="patternDataSource"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="文案" :labelCol="{ span: 6 }" :wrapperCol="{ span: 16 }">
|
||||
<Input v-model:value="item.message" placeholder="请输入提示文案" />
|
||||
</FormItem>
|
||||
</div>
|
||||
</Form>
|
||||
<a @click="addRules">
|
||||
<Icon icon="ant-design:file-add-outlined" />
|
||||
添加正则
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, defineComponent } from 'vue';
|
||||
import { remove } from '../../../utils';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import { isArray } from 'lodash-es';
|
||||
import { Form, FormItem, AutoComplete, Input } from 'ant-design-vue';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RuleProps',
|
||||
components: {
|
||||
Form,
|
||||
FormItem,
|
||||
AutoComplete,
|
||||
Input,
|
||||
Icon,
|
||||
},
|
||||
setup() {
|
||||
// 获取祖先组件的状态
|
||||
const { formConfig } = useFormDesignState();
|
||||
// 抽离 currentItem
|
||||
/**
|
||||
* 添加正则校验,判断当前组件的rules是不是数组,如果不是数组,使用set方法重置成数组,然后添加正则校验
|
||||
*/
|
||||
const addRules = () => {
|
||||
if (!isArray(formConfig.value.currentItem!.rules))
|
||||
formConfig.value.currentItem!['rules'] = [];
|
||||
formConfig.value.currentItem!.rules?.push({ pattern: '', message: '' });
|
||||
};
|
||||
/**
|
||||
* 删除正则校验,当正则规则为0时,删除rules属性
|
||||
* @param index {number} 需要删除的规则下标
|
||||
*/
|
||||
const removeRule = (index: number) => {
|
||||
remove(formConfig.value.currentItem!.rules as Array<any>, index);
|
||||
if (formConfig.value.currentItem!.rules?.length === 0)
|
||||
delete formConfig.value.currentItem!['rules'];
|
||||
};
|
||||
|
||||
const patternDataSource = ref([
|
||||
{
|
||||
value: '/^(?:(?:\\+|00)86)?1[3-9]\\d{9}$/',
|
||||
text: '手机号码',
|
||||
},
|
||||
{
|
||||
value: '/^((ht|f)tps?:\\/\\/)?[\\w-]+(\\.[\\w-]+)+:\\d{1,5}\\/?$/',
|
||||
text: '网址带端口号',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/^(((ht|f)tps?):\\/\\/)?[\\w-]+(\\.[\\w-]+)+([\\w.,@?^=%&:/~+#-\\(\\)]*[\\w@?^=%&/~+#-\\(\\)])?$/',
|
||||
text: '网址带参数',
|
||||
},
|
||||
{
|
||||
value: '/^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/',
|
||||
text: '统一社会信用代码',
|
||||
},
|
||||
{
|
||||
value: '/^(s[hz]|S[HZ])(000[\\d]{3}|002[\\d]{3}|300[\\d]{3}|600[\\d]{3}|60[\\d]{4})$/',
|
||||
text: '股票代码',
|
||||
},
|
||||
{
|
||||
value: '/^([a-f\\d]{32}|[A-F\\d]{32})$/',
|
||||
text: 'md5格式(32位)',
|
||||
},
|
||||
{
|
||||
value: '/^[a-f\\d]{4}(?:[a-f\\d]{4}-){4}[a-f\\d]{12}$/i',
|
||||
text: 'GUID/UUID',
|
||||
},
|
||||
{
|
||||
value: '/^\\d+(?:\\.\\d+){2}$/',
|
||||
text: '版本号(x.y.z)格式',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/^https?:\\/\\/(.+\\/)+.+(\\.(swf|avi|flv|mpg|rm|mov|wav|asf|3gp|mkv|rmvb|mp4))$/i',
|
||||
text: '视频链接地址',
|
||||
},
|
||||
{
|
||||
value: '/^https?:\\/\\/(.+\\/)+.+(\\.(gif|png|jpg|jpeg|webp|svg|psd|bmp|tif))$/i',
|
||||
text: '图片链接地址',
|
||||
},
|
||||
{
|
||||
value: '/^-?\\d+(,\\d{3})*(\\.\\d{1,2})?$/',
|
||||
text: '数字/货币金额(支持负数、千分位分隔符)',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/(?:^[1-9]([0-9]+)?(?:\\.[0-9]{1,2})?$)|(?:^(?:0)$)|(?:^[0-9]\\.[0-9](?:[0-9])?$)/',
|
||||
text: '数字/货币金额',
|
||||
},
|
||||
{
|
||||
value: '/^[1-9]\\d{9,29}$/',
|
||||
text: '银行卡号',
|
||||
},
|
||||
{
|
||||
value: '/^(?:[\u4e00-\u9fa5·]{2,16})$/',
|
||||
text: '中文姓名',
|
||||
},
|
||||
{
|
||||
value: '/(^[a-zA-Z][a-zA-Z\\s]{0,20}[a-zA-Z]$)/',
|
||||
text: '英文姓名',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z](?:((\\d{5}[A-HJK])|([A-HJK][A-HJ-NP-Z0-9][0-9]{4}))|[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳])$/',
|
||||
text: '车牌号(新能源)',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]$/',
|
||||
text: '车牌号(非新能源)',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/',
|
||||
text: '车牌号(新能源+非新能源)',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/^(([^<>()[\\]\\\\.,;:\\s@"]+(\\.[^<>()[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/',
|
||||
text: 'email(邮箱)',
|
||||
},
|
||||
{
|
||||
value: '/^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$/',
|
||||
text: '座机',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/^[1-9]\\d{5}(?:18|19|20)\\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\\d|30|31)\\d{3}[\\dXx]$/',
|
||||
text: '身份证号',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/(^[EeKkGgDdSsPpHh]\\d{8}$)|(^(([Ee][a-fA-F])|([DdSsPp][Ee])|([Kk][Jj])|([Mm][Aa])|(1[45]))\\d{7}$)/',
|
||||
text: '护照',
|
||||
},
|
||||
{
|
||||
value:
|
||||
'/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/',
|
||||
text: '中文汉字',
|
||||
},
|
||||
{
|
||||
value: '/^\\d+\\.\\d+$/',
|
||||
text: '小数',
|
||||
},
|
||||
{
|
||||
value: '/^\\d{1,}$/',
|
||||
text: '数字',
|
||||
},
|
||||
{
|
||||
value: '/^[1-9][0-9]{4,10}$/',
|
||||
text: 'qq号',
|
||||
},
|
||||
{
|
||||
value: '/^[A-Za-z0-9]+$/',
|
||||
text: '数字字母组合',
|
||||
},
|
||||
{
|
||||
value: '/^[a-zA-Z]+$/',
|
||||
text: '英文字母',
|
||||
},
|
||||
{
|
||||
value: '/^[a-z]+$/',
|
||||
text: '小写英文字母',
|
||||
},
|
||||
{
|
||||
value: '/^[A-Z]+$/',
|
||||
text: '大写英文字母',
|
||||
},
|
||||
{
|
||||
value: '/^[a-zA-Z0-9_-]{4,16}$/',
|
||||
text: '用户名校验,4到16位(字母,数字,下划线,减号)',
|
||||
},
|
||||
{
|
||||
value: '/^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/',
|
||||
text: '16进制颜色',
|
||||
},
|
||||
{
|
||||
value: '/^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/',
|
||||
text: '微信号',
|
||||
},
|
||||
{
|
||||
value: '/^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\\d{4}$/',
|
||||
text: '邮政编码(中国)',
|
||||
},
|
||||
{
|
||||
value: '/^[^A-Za-z]*$/',
|
||||
text: '不能包含字母',
|
||||
},
|
||||
{
|
||||
value: '/^\\+?[1-9]\\d*$/',
|
||||
text: '正整数,不包含0',
|
||||
},
|
||||
{
|
||||
value: '/^-[1-9]\\d*$/',
|
||||
text: '负整数,不包含0',
|
||||
},
|
||||
{
|
||||
value: '/^-?[0-9]\\d*$/',
|
||||
text: '整数',
|
||||
},
|
||||
{
|
||||
value: '/^(-?\\d+)(\\.\\d+)?$/',
|
||||
text: '浮点数',
|
||||
},
|
||||
{
|
||||
value: '/^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$/',
|
||||
text: 'email(支持中文邮箱)',
|
||||
},
|
||||
]);
|
||||
|
||||
return { addRules, removeRule, formConfig, patternDataSource };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.icon) {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
overflow: hidden;
|
||||
fill: currentcolor;
|
||||
vertical-align: -0.15em;
|
||||
}
|
||||
|
||||
.rule-props-content {
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rule-props-item {
|
||||
position: relative;
|
||||
margin-bottom: 5px;
|
||||
padding: 3px 2px;
|
||||
border-radius: 5px;
|
||||
background-color: #f0eded;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
&-close {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
border-radius: 7px;
|
||||
background-color: #a3a0a0;
|
||||
color: #ccc;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #00c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,365 @@
|
|||
import { IAnyObject } from '../../../typings/base-type';
|
||||
import { baseComponents, commonComponents } from '../../../core/formItemConfig';
|
||||
import { Input, Select, RadioGroup, Slider } from 'ant-design-vue';
|
||||
import { Component } from 'vue';
|
||||
|
||||
export const globalConfigState: { span: number } = {
|
||||
span: 24,
|
||||
};
|
||||
export interface IBaseFormAttrs {
|
||||
name: string; // 字段名
|
||||
label: string; // 字段标签
|
||||
component?: string | Component; // 属性控件
|
||||
componentProps?: IAnyObject; // 传递给控件的属性
|
||||
exclude?: string[]; // 需要排除的控件
|
||||
includes?: string[]; // 符合条件的组件
|
||||
on?: IAnyObject;
|
||||
children?: IBaseFormAttrs[];
|
||||
category?: 'control' | 'input';
|
||||
}
|
||||
|
||||
export interface IBaseFormItemControlAttrs extends IBaseFormAttrs {
|
||||
target?: 'props' | 'options'; // 绑定到对象下的某个目标key中
|
||||
}
|
||||
|
||||
export const baseItemColumnProps: IBaseFormAttrs[] = [
|
||||
{
|
||||
name: 'span',
|
||||
label: '栅格数',
|
||||
component: 'Slider',
|
||||
on: {
|
||||
change(value: number) {
|
||||
globalConfigState.span = value;
|
||||
},
|
||||
},
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'offset',
|
||||
label: '栅格左侧的间隔格数',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
label: '栅格顺序,flex 布局模式下有效',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'pull',
|
||||
label: '栅格向左移动格数',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'push',
|
||||
label: '栅格向右移动格数',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'xs',
|
||||
label: '<576px 响应式栅格',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sm',
|
||||
label: '≥576px 响应式栅格',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'md',
|
||||
label: '≥768p 响应式栅格',
|
||||
component: 'Slider',
|
||||
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lg',
|
||||
label: '≥992px 响应式栅格',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'xl',
|
||||
label: '≥1200px 响应式栅格',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'xxl',
|
||||
label: '≥1600px 响应式栅格',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '≥2000px',
|
||||
label: '≥1600px 响应式栅格',
|
||||
component: 'Slider',
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 控件属性面板的配置项
|
||||
export const advanceFormItemColProps: IBaseFormAttrs[] = [
|
||||
{
|
||||
name: 'labelCol',
|
||||
label: '标签col',
|
||||
component: Slider,
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
{
|
||||
name: 'wrapperCol',
|
||||
label: '控件-span',
|
||||
component: Slider,
|
||||
componentProps: {
|
||||
max: 24,
|
||||
min: 0,
|
||||
marks: { 12: '' },
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
];
|
||||
// 控件属性面板的配置项
|
||||
export const baseFormItemProps: IBaseFormAttrs[] = [
|
||||
{
|
||||
// 动态的切换控件的类型
|
||||
name: 'component',
|
||||
label: '控件-FormItem',
|
||||
component: Select,
|
||||
componentProps: {
|
||||
options: commonComponents.concat(baseComponents).map((item) => ({
|
||||
value: item.component,
|
||||
label: item.label,
|
||||
key: item.component + '===' + item.label,
|
||||
})),
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
label: '标签',
|
||||
component: Input,
|
||||
componentProps: {
|
||||
type: 'Input',
|
||||
placeholder: '请输入标签',
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
{
|
||||
name: 'field',
|
||||
label: '字段标识',
|
||||
component: Input,
|
||||
componentProps: {
|
||||
type: 'InputTextArea',
|
||||
placeholder: '请输入字段标识',
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
{
|
||||
name: 'defaultValue',
|
||||
label: '默认值',
|
||||
component: Input,
|
||||
componentProps: {
|
||||
placeholder: '请输入默认值',
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
{
|
||||
name: 'helpMessage',
|
||||
label: '提示信息',
|
||||
component: Input,
|
||||
componentProps: {
|
||||
placeholder: '请输入提示信息',
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
];
|
||||
|
||||
// 控件属性面板的配置项
|
||||
export const advanceFormItemProps: IBaseFormAttrs[] = [
|
||||
{
|
||||
name: 'labelAlign',
|
||||
label: '标签对齐',
|
||||
component: RadioGroup,
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '靠左',
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: '靠右',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'help',
|
||||
label: '提示',
|
||||
component: Input,
|
||||
componentProps: {
|
||||
placeholder: '请输入提示信息',
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
{
|
||||
name: 'extra',
|
||||
label: '额外消息',
|
||||
component: Input,
|
||||
componentProps: {
|
||||
type: 'InputTextArea',
|
||||
placeholder: '请输入额外消息',
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
{
|
||||
name: 'validateTrigger',
|
||||
label: '检验触发',
|
||||
component: Input,
|
||||
componentProps: {
|
||||
type: 'InputTextArea',
|
||||
placeholder: '请输入',
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
{
|
||||
name: 'validateStatus',
|
||||
label: '校验状态',
|
||||
component: RadioGroup,
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '默认',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: '成功',
|
||||
value: 'success',
|
||||
},
|
||||
{
|
||||
label: '警告',
|
||||
value: 'warning',
|
||||
},
|
||||
{
|
||||
label: '错误',
|
||||
value: 'error',
|
||||
},
|
||||
{
|
||||
label: '校验中',
|
||||
value: 'validating',
|
||||
},
|
||||
],
|
||||
},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
];
|
||||
|
||||
export const baseFormItemControlAttrs: IBaseFormItemControlAttrs[] = [
|
||||
{
|
||||
name: 'required',
|
||||
label: '必填项',
|
||||
component: 'Checkbox',
|
||||
exclude: ['alert'],
|
||||
},
|
||||
{
|
||||
name: 'hidden',
|
||||
label: '隐藏',
|
||||
component: 'Checkbox',
|
||||
exclude: ['alert'],
|
||||
},
|
||||
{
|
||||
name: 'hiddenLabel',
|
||||
component: 'Checkbox',
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
label: '隐藏标签',
|
||||
},
|
||||
{
|
||||
name: 'colon',
|
||||
label: 'label后面显示冒号',
|
||||
component: 'Checkbox',
|
||||
componentProps: {},
|
||||
exclude: ['Grid', 'Tabs'],
|
||||
},
|
||||
{
|
||||
name: 'hasFeedback',
|
||||
label: '输入反馈',
|
||||
component: 'Checkbox',
|
||||
componentProps: {},
|
||||
includes: ['Input'],
|
||||
},
|
||||
{
|
||||
name: 'autoLink',
|
||||
label: '自动关联',
|
||||
component: 'Checkbox',
|
||||
componentProps: {},
|
||||
includes: ['Input'],
|
||||
},
|
||||
{
|
||||
name: 'validateFirst',
|
||||
label: '检验证错误停止',
|
||||
component: 'Checkbox',
|
||||
componentProps: {},
|
||||
includes: ['Input'],
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
export const formItemPropsScript = `脚本参数说明
|
||||
// 获取表单是新增还是编辑
|
||||
let update = utils.isUpdate();
|
||||
// 组件变更数据
|
||||
let data = utils.data();
|
||||
|
||||
// 数据设置
|
||||
// 获取主表数据
|
||||
let mainValue = utils.getValue('组件的字段标识');
|
||||
// 设置主表数据
|
||||
setFieldsValue(utils.setValue('组件的字段标识', '设置的值'));
|
||||
|
||||
// 获取子表数据
|
||||
let childValue = getChildValue('组件的字段标识');
|
||||
// 添加子表数据
|
||||
subTableList.value = addChildValue(data);
|
||||
data格式为[{组件的字段标识1: "value1", {组件的字段标识2: "value2"}]或者{组件的字段标识1: "value1"}
|
||||
// 修改子表数据
|
||||
subTableList.value = addChildValue('组件的字段标识', '旧value', '新value');
|
||||
// 删除子表数据
|
||||
subTableList.value = deleteChildValue('组件的字段标识', 'value');
|
||||
|
||||
// 组件设置
|
||||
// 设置组件为隐藏
|
||||
formColumns.value = utils.setHide('组件的字段标识', false);
|
||||
// 取消组件隐藏
|
||||
formColumns.value = utils.setHide('组件的字段标识', true);
|
||||
// 设置组件为禁用
|
||||
formColumns.value = utils.setDisabled('组件的字段标识', true);
|
||||
// 取消组件禁用
|
||||
formColumns.value = utils.setDisabled('组件的字段标识', false);
|
||||
// 设置组件为必填
|
||||
formColumns.value = utils.setRequired('组件的字段标识', true);
|
||||
// 设置组件不为必填
|
||||
formColumns.value = utils.setRequired('组件的字段标识', false);
|
||||
|
||||
// 功能设置
|
||||
// 提示消息
|
||||
utils.message('提示信息', '提示类型');
|
||||
提示类型:success(成功)、error(错误)、warn(警告)、info(默认)
|
||||
// 获取登录者信息
|
||||
var loginUser = utils.loginUser();
|
||||
loginUser返回值为{ account: 账号, name: 姓名 }
|
||||
|
||||
// 回调方法
|
||||
// get请求
|
||||
// 需要用到回调方法
|
||||
// 例子:let resGet = await utils.httpGet('/api/FormScheme/LoadFormPage', { page: 1, limit: 10 });
|
||||
let resGet = await utils.httpGet(url, params);
|
||||
url:api地址, params:参数
|
||||
|
||||
// post请求
|
||||
let resPost = await utils.httpPost(url, params);
|
||||
url:api地址, params:参数
|
||||
|
||||
// put请求
|
||||
// 需要用到回调方法
|
||||
let resPut = await utils.httpPut(url, params);
|
||||
url:api地址, params:参数
|
||||
`;
|
||||
|
||||
|
||||
export const options_json = `{
|
||||
"options": [
|
||||
{
|
||||
"label": "选项1",
|
||||
"value": "1",
|
||||
"children": [
|
||||
{
|
||||
"label": "选项3",
|
||||
"value": "3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "选项2",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
}`;
|
||||
|
||||
export const treeData_json = `{
|
||||
"treeData": [
|
||||
{
|
||||
"label": "选项1",
|
||||
"value": "1",
|
||||
"children": [
|
||||
{
|
||||
"label": "选项3",
|
||||
"value": "3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "选项2",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
}`;
|
||||
|
|
@ -0,0 +1,476 @@
|
|||
<template>
|
||||
<Layout>
|
||||
<LayoutSider
|
||||
:class="`left ${prefixCls}-sider`"
|
||||
collapsible
|
||||
collapsedWidth="0"
|
||||
width="345"
|
||||
:zeroWidthTriggerStyle="{
|
||||
'margin-top': '-70px',
|
||||
'background-color': 'white',
|
||||
width: '35px',
|
||||
height: '35px',
|
||||
'background-image': `url(/iocn/text-indent-right.svg)`,
|
||||
'background-position': 'center',
|
||||
'background-repeat': 'no-repeat',
|
||||
'border-radius:': '15px',
|
||||
}"
|
||||
breakpoint="md"
|
||||
>
|
||||
<div class="collapseItem-box">
|
||||
<CollapseContainer title="常用控件" isTitleBold="true">
|
||||
<CollapseItem
|
||||
:list="commonComponents"
|
||||
:handleListPush="handleListPushDrag"
|
||||
@add-attrs="handleAddAttrs"
|
||||
@handle-list-push="handleListPush"
|
||||
/>
|
||||
</CollapseContainer>
|
||||
<CollapseContainer title="基础控件" isTitleBold="true">
|
||||
<CollapseItem
|
||||
:list="baseComponents"
|
||||
:handleListPush="handleListPushDrag"
|
||||
@add-attrs="handleAddAttrs"
|
||||
@handle-list-push="handleListPush"
|
||||
/>
|
||||
</CollapseContainer>
|
||||
<CollapseContainer title="自定义控件" isTitleBold="true">
|
||||
<CollapseItem
|
||||
:list="customComponents"
|
||||
@add-attrs="handleAddAttrs"
|
||||
:handleListPush="handleListPushDrag"
|
||||
@handle-list-push="handleListPush"
|
||||
/>
|
||||
</CollapseContainer>
|
||||
<CollapseContainer title="布局控件" isTitleBold="true">
|
||||
<CollapseItem
|
||||
:list="layoutComponents"
|
||||
:handleListPush="handleListPushDrag"
|
||||
@add-attrs="handleAddAttrs"
|
||||
@handle-list-push="handleListPush"
|
||||
/>
|
||||
</CollapseContainer>
|
||||
</div>
|
||||
</LayoutSider>
|
||||
<LayoutContent>
|
||||
<Toolbar
|
||||
@handle-open-json-modal="handleOpenModal(jsonModal!)"
|
||||
@handle-open-import-json-modal="handleOpenModal(importJsonModal!)"
|
||||
@handle-preview="handleOpenModal(eFormPreview!)"
|
||||
@handle-open-code-modal="handleOpenModal(codeModal!)"
|
||||
@handle-clear-form-items="handleClearFormItems"
|
||||
/>
|
||||
<FormComponentPanel
|
||||
:current-item="formConfig.currentItem"
|
||||
:data="formConfig"
|
||||
@handle-set-select-item="handleSetSelectItem"
|
||||
/>
|
||||
</LayoutContent>
|
||||
<LayoutSider
|
||||
:class="`right ${prefixCls}-sider`"
|
||||
collapsible
|
||||
:reverseArrow="true"
|
||||
collapsedWidth="0"
|
||||
width="320"
|
||||
:zeroWidthTriggerStyle="{
|
||||
'margin-top': '-70px',
|
||||
'background-color': 'white',
|
||||
width: '35px',
|
||||
height: '35px',
|
||||
'background-image': `url(/iocn/text-indent-left.svg)`,
|
||||
'background-position': 'center',
|
||||
'background-repeat': 'no-repeat',
|
||||
'border-radius:': '15px',
|
||||
}"
|
||||
breakpoint="lg"
|
||||
>
|
||||
<PropsPanel ref="propsPanel" :activeKey="formConfig.activeKey">
|
||||
<template v-for="item of formConfig.schemas" #[`${item.component}Props`]="data">
|
||||
<slot
|
||||
:name="`${item.component}Props`"
|
||||
v-bind="{ formItem: data, props: data.componentProps }"
|
||||
></slot>
|
||||
</template>
|
||||
</PropsPanel>
|
||||
</LayoutSider>
|
||||
</Layout>
|
||||
|
||||
<JsonModal ref="jsonModal" />
|
||||
<CodeModal ref="codeModal" />
|
||||
<ImportJsonModal ref="importJsonModal" />
|
||||
<VFormPreview ref="eFormPreview" :formConfig="formConfig" />
|
||||
<!-- <VFormPreview2 ref="eFormPreview2" :formConfig="formConfig" /> -->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import CollapseItem from './modules/CollapseItem.vue';
|
||||
import FormComponentPanel from './modules/FormComponentPanel.vue';
|
||||
import JsonModal from './components/JsonModal.vue';
|
||||
import VFormPreview from '../VFormPreview/index.vue';
|
||||
// import VFormPreview2 from '../VFormPreview/useForm.vue';
|
||||
|
||||
import Toolbar from './modules/Toolbar.vue';
|
||||
import PropsPanel from './modules/PropsPanel.vue';
|
||||
import ImportJsonModal from './components/ImportJsonModal.vue';
|
||||
import CodeModal from './components/CodeModal.vue';
|
||||
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
|
||||
import { ref, provide, Ref, inject, watch, computed, defineProps } from 'vue';
|
||||
import { Layout, LayoutContent, LayoutSider } from 'ant-design-vue';
|
||||
|
||||
import { IVFormComponent, IFormConfig, PropsTabKey } from '../../typings/v-form-component';
|
||||
import { formItemsForEach, generateKey, removeAttrs } from '../../utils';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import {
|
||||
commonComponents,
|
||||
baseComponents,
|
||||
customComponents,
|
||||
layoutComponents,
|
||||
} from '../../core/formItemConfig';
|
||||
import { useRefHistory, UseRefHistoryReturn } from '@vueuse/core';
|
||||
import { globalConfigState } from './config/formItemPropsConfig';
|
||||
import { IFormDesignMethods, IPropsPanel, IToolbarMethods } from '../../typings/form-type';
|
||||
import { useDesign } from '@/hooks/web/useDesign';
|
||||
|
||||
import { CollapseContainer } from '@/components/Container';
|
||||
import { useOnlineFormDesignStore } from '@/store/modules/onlineFormDesign'
|
||||
|
||||
// defineProps({
|
||||
// title: {
|
||||
// type: String,
|
||||
// default: 'v-form-antd表单设计器',
|
||||
// }
|
||||
// });
|
||||
const onlineFormDesignStore = useOnlineFormDesignStore()
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: 'v-form-antd表单设计器',
|
||||
},
|
||||
saveClick: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
});
|
||||
|
||||
const { prefixCls } = useDesign('form-design');
|
||||
// 子组件实例
|
||||
const propsPanel = ref<null | IPropsPanel>(null);
|
||||
const jsonModal = ref<null | IToolbarMethods>(null);
|
||||
const importJsonModal = ref<null | IToolbarMethods>(null);
|
||||
const eFormPreview = ref<null | IToolbarMethods>(null);
|
||||
// const eFormPreview2 = ref<null | IToolbarMethods>(null);
|
||||
|
||||
const codeModal = ref<null | IToolbarMethods>(null);
|
||||
|
||||
const formModel = ref({});
|
||||
// endregion
|
||||
const formConfig = ref<IFormConfig>({
|
||||
// 表单配置
|
||||
schemas: [],
|
||||
layout: 'horizontal',
|
||||
labelLayout: 'flex',
|
||||
labelWidth: 100,
|
||||
labelCol: {},
|
||||
wrapperCol: {},
|
||||
currentItem: {
|
||||
component: '',
|
||||
componentProps: {},
|
||||
},
|
||||
activeKey: 1,
|
||||
});
|
||||
const designSendGrandson = inject('designSendGrandson');
|
||||
watch(
|
||||
() => props.saveClick,
|
||||
(newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
const config = cloneDeep(formConfig.value);
|
||||
let formJson = JSON.stringify(removeAttrs(config), null, '\t');
|
||||
designSendGrandson(formJson);
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
let receivedData = ref();
|
||||
const handleNextStepsData = inject('handleNextStepsData');
|
||||
|
||||
watch(
|
||||
() => handleNextStepsData, // 点击下一步按钮传递的参数
|
||||
(newVal) => {
|
||||
if (newVal.value && newVal.value.scheme && newVal.value.scheme.scheme) {
|
||||
receivedData.value = JSON.parse(newVal.value.scheme.scheme);
|
||||
if (receivedData.value.formInfo.tabList && receivedData.value.formInfo.tabList.length > 1) {
|
||||
const arr: any = [];
|
||||
receivedData.value.formInfo.tabList.forEach((item, index) => {
|
||||
arr.push({
|
||||
label: item.text,
|
||||
value: index + 1,
|
||||
children: item.schemas,
|
||||
});
|
||||
});
|
||||
receivedData.value.formInfo.schemas = [
|
||||
{
|
||||
component: 'Tabs',
|
||||
label: '选项卡',
|
||||
colProps: {
|
||||
span: 24,
|
||||
},
|
||||
field: 'Tabs',
|
||||
componentProps: {
|
||||
options: arr,
|
||||
},
|
||||
itemProps: {
|
||||
labelCol: {},
|
||||
wrapperCol: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
if (
|
||||
receivedData.value.formInfo.tabList &&
|
||||
receivedData.value.formInfo.tabList.length == 1
|
||||
) {
|
||||
receivedData.value.formInfo.schemas = receivedData.value.formInfo.tabList[0].schemas;
|
||||
}
|
||||
delete receivedData.value.formInfo.tabList;
|
||||
//渲染表单
|
||||
const editorJsonData = receivedData.value.formInfo as IFormConfig;
|
||||
editorJsonData.schemas &&
|
||||
formItemsForEach(editorJsonData.schemas, (formItem) => {
|
||||
generateKey(formItem);
|
||||
});
|
||||
setFormConfig({
|
||||
...editorJsonData,
|
||||
activeKey: 1,
|
||||
currentItem: { component: '' },
|
||||
});
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
const setFormConfig = (config: IFormConfig) => {
|
||||
//外部导入时,可能会缺少必要的信息。
|
||||
config.schemas = config.schemas || [];
|
||||
config.schemas.forEach((item) => {
|
||||
item.colProps = item.colProps || { span: 24 };
|
||||
item.componentProps = item.componentProps || {};
|
||||
item.itemProps = item.itemProps || {};
|
||||
});
|
||||
formConfig.value = config as any;
|
||||
};
|
||||
// 获取历史记录,用于撤销和重构
|
||||
const historyReturn = useRefHistory(formConfig as any, {
|
||||
deep: true,
|
||||
capacity: 20,
|
||||
parse: (val: IFormConfig) => {
|
||||
// 使用lodash.cloneDeep重新拷贝数据,把currentItem指向选中项
|
||||
const formConfig = cloneDeep(val);
|
||||
const { currentItem, schemas } = formConfig;
|
||||
// 从formItems中查找选中项
|
||||
|
||||
const item = schemas && schemas.find((item) => item.key === currentItem?.key);
|
||||
// 如果有,则赋值给当前项,如果没有,则切换属性面板
|
||||
if (item) {
|
||||
formConfig.currentItem = item;
|
||||
}
|
||||
return formConfig;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 选中表单项
|
||||
* @param schema 当前选中的表单项
|
||||
*/
|
||||
const handleSetSelectItem = (schema: IVFormComponent) => {
|
||||
formConfig.value.currentItem = schema as any;
|
||||
handleChangePropsTabs(
|
||||
schema.key ? (formConfig.value.activeKey! === 1 ? 2 : formConfig.value.activeKey!) : 1,
|
||||
);
|
||||
if(schema.component === 'Select' && schema.dataType === '2' && onlineFormDesignStore.getSelectDictionaryData().length === 0){
|
||||
onlineFormDesignStore.getSelectData("")
|
||||
onlineFormDesignStore.setSelectOptions(schema.field,schema.dataCode)
|
||||
}
|
||||
};
|
||||
|
||||
const setGlobalConfigState = (formItem: IVFormComponent) => {
|
||||
formItem.colProps = formItem.colProps || {};
|
||||
formItem.colProps.span = globalConfigState.span;
|
||||
// console.log('setGlobalConfigState', formItem);
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加属性
|
||||
* @param schemas
|
||||
* @param index
|
||||
*/
|
||||
const handleAddAttrs = (_formItems: IVFormComponent[], _index: number) => {};
|
||||
|
||||
const handleListPushDrag = (item: IVFormComponent) => {
|
||||
const formItem = cloneDeep(item);
|
||||
setGlobalConfigState(formItem);
|
||||
generateKey(formItem);
|
||||
|
||||
return formItem;
|
||||
};
|
||||
/**
|
||||
* 单击控件时添加到面板中
|
||||
* @param item {IVFormComponent} 当前点击的组件
|
||||
*/
|
||||
const handleListPush = (item: IVFormComponent) => {
|
||||
// console.log('handleListPush', item);
|
||||
const formItem = cloneDeep(item);
|
||||
setGlobalConfigState(formItem);
|
||||
generateKey(formItem);
|
||||
if (!formConfig.value.currentItem?.key) {
|
||||
handleSetSelectItem(formItem);
|
||||
formConfig.value.schemas && formConfig.value.schemas.push(formItem as any);
|
||||
|
||||
return;
|
||||
}
|
||||
handleCopy(formItem, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* 复制表单项,如果表单项为栅格布局,则遍历所有自表单项重新生成key
|
||||
* @param {IVFormComponent} formItem
|
||||
* @return {IVFormComponent}
|
||||
*/
|
||||
const copyFormItem = (formItem: IVFormComponent) => {
|
||||
const newFormItem = cloneDeep(formItem);
|
||||
if (newFormItem.component === 'Grid') {
|
||||
formItemsForEach([formItem], (item) => {
|
||||
generateKey(item);
|
||||
});
|
||||
}
|
||||
return newFormItem;
|
||||
};
|
||||
/**
|
||||
* 复制或者添加表单,isCopy为true时则复制表单
|
||||
* @param item {IVFormComponent} 当前点击的组件
|
||||
* @param isCopy {boolean} 是否复制
|
||||
*/
|
||||
const handleCopy = (
|
||||
item: IVFormComponent = formConfig.value.currentItem as IVFormComponent,
|
||||
isCopy = true,
|
||||
) => {
|
||||
const key = formConfig.value.currentItem?.key;
|
||||
/**
|
||||
* 遍历当表单项配置,如果是复制,则复制一份表单项,如果不是复制,则直接添加到表单项中
|
||||
* @param schemas
|
||||
*/
|
||||
const traverse = (schemas: IVFormComponent[]) => {
|
||||
// 使用some遍历,找到目标后停止遍历
|
||||
schemas.some((formItem: IVFormComponent, index: number) => {
|
||||
if (formItem.key === key) {
|
||||
// 判断是不是复制
|
||||
isCopy
|
||||
? schemas.splice(index, 0, copyFormItem(formItem))
|
||||
: schemas.splice(index + 1, 0, item);
|
||||
const event = {
|
||||
newIndex: index + 1,
|
||||
};
|
||||
// 添加到表单项中
|
||||
handleBeforeColAdd(event, schemas, isCopy);
|
||||
return true;
|
||||
}
|
||||
if (['Grid', 'Tabs'].includes(formItem.component)) {
|
||||
// 栅格布局
|
||||
formItem.columns?.forEach((item) => {
|
||||
traverse(item.children);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
if (formConfig.value.schemas) {
|
||||
traverse(formConfig.value.schemas as any);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加到表单中
|
||||
* @param newIndex {object} 事件对象
|
||||
* @param schemas {IVFormComponent[]} 表单项列表
|
||||
* @param isCopy {boolean} 是否复制
|
||||
*/
|
||||
const handleBeforeColAdd = ({ newIndex }: any, schemas: IVFormComponent[], isCopy = false) => {
|
||||
const item = schemas[newIndex];
|
||||
isCopy && generateKey(item);
|
||||
handleSetSelectItem(item);
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开模态框
|
||||
* @param Modal {IToolbarMethods}
|
||||
*/
|
||||
const handleOpenModal = (Modal: IToolbarMethods) => {
|
||||
const config = cloneDeep(formConfig.value);
|
||||
Modal?.showModal(config);
|
||||
};
|
||||
/**
|
||||
* 切换属性面板
|
||||
* @param key
|
||||
*/
|
||||
const handleChangePropsTabs = (key: PropsTabKey) => {
|
||||
formConfig.value.activeKey = key;
|
||||
};
|
||||
/**
|
||||
* 清空表单项列表
|
||||
*/
|
||||
const handleClearFormItems = () => {
|
||||
formConfig.value.schemas = [];
|
||||
handleSetSelectItem({ component: '' });
|
||||
};
|
||||
|
||||
const setFormModel = (key, value) => (formModel.value[key] = value);
|
||||
provide('formModel', formModel);
|
||||
// 把祖先组件的方法项注入到子组件中,子组件可通过inject获取
|
||||
provide<(key: String, value: any) => void>('setFormModelMethod', setFormModel);
|
||||
// region 注入给子组件的属性
|
||||
// provide('currentItem', formConfig.value.currentItem)
|
||||
|
||||
// 把表单配置项注入到子组件中,子组件可通过inject获取,获取到的数据为响应式
|
||||
provide<Ref<IFormConfig>>('formConfig', formConfig as any);
|
||||
|
||||
// 注入历史记录
|
||||
provide<UseRefHistoryReturn<any, any>>('historyReturn', historyReturn);
|
||||
|
||||
// 把祖先组件的方法项注入到子组件中,子组件可通过inject获取
|
||||
provide<IFormDesignMethods>('formDesignMethods', {
|
||||
handleBeforeColAdd,
|
||||
handleCopy,
|
||||
handleListPush,
|
||||
handleSetSelectItem,
|
||||
handleAddAttrs,
|
||||
setFormConfig,
|
||||
});
|
||||
|
||||
// endregion
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-form-design';
|
||||
|
||||
[data-theme='dark'] {
|
||||
.@{prefix-cls}-sider {
|
||||
background-color: #1f1f1f;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='light'] {
|
||||
.@{prefix-cls}-sider {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.collapseItem-box {
|
||||
height: calc(100vh - 55px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.vben-basic-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<div :class="prefixCls">
|
||||
<draggable
|
||||
tag="ul"
|
||||
:model-value="list"
|
||||
v-bind="{
|
||||
group: { name: 'form-draggable', pull: 'clone', put: false },
|
||||
sort: false,
|
||||
clone: cloneItem,
|
||||
animation: 180,
|
||||
ghostClass: 'moving',
|
||||
}"
|
||||
item-key="type"
|
||||
@start="handleStart($event, list)"
|
||||
@add="handleAdd"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<li
|
||||
class="bs-box text-ellipsis"
|
||||
@dragstart="$emit('add-attrs', list, index)"
|
||||
@click="$emit('handle-list-push', element)"
|
||||
>
|
||||
<!-- <svg v-if="element.icon.indexOf('icon-') > -1" class="icon" aria-hidden="true">
|
||||
<use :xlink:href="`#${element.icon}`" />
|
||||
</svg> -->
|
||||
<Icon :icon="element.icon" />
|
||||
{{ element.label }}</li
|
||||
></template
|
||||
>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, PropType } from 'vue';
|
||||
import { IVFormComponent } from '../../../typings/v-form-component';
|
||||
import draggable from 'vuedraggable';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
import { useDesign } from '@/hooks/web/useDesign';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CollapseItem',
|
||||
components: { draggable, Icon },
|
||||
props: {
|
||||
list: {
|
||||
type: [Array] as PropType<IVFormComponent[]>,
|
||||
default: () => [],
|
||||
},
|
||||
handleListPush: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { prefixCls } = useDesign('form-design-collapse-item');
|
||||
|
||||
const state = reactive({});
|
||||
const handleStart = (e: any, list1: IVFormComponent[]) => {
|
||||
emit('start', list1[e.oldIndex].component);
|
||||
};
|
||||
const handleAdd = (e: any) => {
|
||||
console.log(e);
|
||||
};
|
||||
// https://github.com/SortableJS/vue.draggable.next
|
||||
// https://github.com/SortableJS/vue.draggable.next/blob/master/example/components/custom-clone.vue
|
||||
const cloneItem = (one) => {
|
||||
return props.handleListPush(one);
|
||||
};
|
||||
return { prefixCls, state, handleStart, handleAdd, cloneItem };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-form-design-collapse-item';
|
||||
|
||||
@import url('../styles/variable.less');
|
||||
|
||||
.@{prefix-cls} {
|
||||
ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0;
|
||||
padding: 5px;
|
||||
list-style: none;
|
||||
// background: #efefef;
|
||||
|
||||
li {
|
||||
width: calc(50% - 6px);
|
||||
height: 36px;
|
||||
margin: 2.7px;
|
||||
padding: 8px 12px;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid @border-color;
|
||||
border-radius: 3px;
|
||||
line-height: 20px;
|
||||
cursor: move;
|
||||
|
||||
&:hover {
|
||||
position: relative;
|
||||
border: 1px solid @primary-color;
|
||||
// z-index: 1;
|
||||
box-shadow: 0 2px 6px @primary-color;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
display: inline !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
<!--
|
||||
* @Description: 中间表单布局面板
|
||||
* https://github.com/SortableJS/vue.draggable.next/issues/138
|
||||
-->
|
||||
<template>
|
||||
<div class="form-panel v-form-container">
|
||||
<ImagePreview v-if="isShowImagePreview" :globalImagePreviewUrl="globalImagePreviewUrl" @closeImagePreview="closeImagePreview"></ImagePreview>
|
||||
<Empty
|
||||
class="empty-text"
|
||||
v-show="formConfig.schemas.length === 0"
|
||||
description="从左侧选择控件添加"
|
||||
/>
|
||||
<Form v-bind="formConfig">
|
||||
<div class="image-preview" v-if="false">
|
||||
<ImagePreview ></ImagePreview>
|
||||
</div>
|
||||
|
||||
<div class="draggable-box">
|
||||
<draggable
|
||||
class="list-main ant-row"
|
||||
group="form-draggable"
|
||||
:component-data="{ name: 'list', tag: 'div', type: 'transition-group' }"
|
||||
ghostClass="moving"
|
||||
:animation="180"
|
||||
handle=".drag-move"
|
||||
v-model="formConfig.schemas"
|
||||
item-key="key"
|
||||
@add="addItem"
|
||||
@start="handleDragStart"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<LayoutItem
|
||||
class="drag-move"
|
||||
:schema="element"
|
||||
:data="formConfig"
|
||||
:current-item="formConfig.currentItem || {}"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import draggable from 'vuedraggable';
|
||||
import LayoutItem from '../components/LayoutItem.vue';
|
||||
import { defineComponent, computed,ref,inject,watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import { Form, Empty } from 'ant-design-vue';
|
||||
import ImagePreview from "@/components/Upload/src/components/image_preview.vue";
|
||||
import { userFormFileStore } from '@/store/modules/formFileUrl';
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FormComponentPanel',
|
||||
components: {
|
||||
LayoutItem,
|
||||
draggable,
|
||||
Form,
|
||||
Empty,
|
||||
ImagePreview
|
||||
},
|
||||
emits: ['handleSetSelectItem'],
|
||||
setup(_, { emit }) {
|
||||
const { formConfig } = useFormDesignState();
|
||||
|
||||
/**
|
||||
* 拖拽完成事件
|
||||
* @param newIndex
|
||||
*/
|
||||
const addItem = ({ newIndex }: any) => {
|
||||
formConfig.value.schemas = formConfig.value.schemas || [];
|
||||
|
||||
const schemas = formConfig.value.schemas;
|
||||
schemas[newIndex] = cloneDeep(schemas[newIndex]);
|
||||
emit('handleSetSelectItem', schemas[newIndex]);
|
||||
};
|
||||
|
||||
/**
|
||||
* 拖拽开始事件
|
||||
* @param e {Object} 事件对象
|
||||
*/
|
||||
const handleDragStart = (e: any) => {
|
||||
emit('handleSetSelectItem', formConfig.value.schemas[e.oldIndex]);
|
||||
};
|
||||
|
||||
// 获取祖先组件传递的currentItem
|
||||
|
||||
// 计算布局元素,水平模式下为ACol,非水平模式下为div
|
||||
const layoutTag = computed(() => {
|
||||
return formConfig.value.layout === 'horizontal' ? 'Col' : 'div';
|
||||
});
|
||||
|
||||
// 图片预览
|
||||
const isShowImagePreview = ref<Boolean>(false);
|
||||
|
||||
const closeImagePreview = ()=>{
|
||||
isShowImagePreview.value = false;
|
||||
}
|
||||
const globalImagePreviewUrl=ref<String>();
|
||||
const formFileStore = userFormFileStore();
|
||||
const formFileState = storeToRefs(formFileStore);
|
||||
watch(formFileState.url, (newValue, oldValue) => {
|
||||
// isShowImagePreview.value = true;
|
||||
// globalImagePreviewUrl.value = newValue;
|
||||
});
|
||||
|
||||
return {
|
||||
addItem,
|
||||
handleDragStart,
|
||||
formConfig,
|
||||
layoutTag,
|
||||
closeImagePreview,
|
||||
isShowImagePreview,
|
||||
globalImagePreviewUrl
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('../styles/variable.less');
|
||||
@import url('../styles/drag.less');
|
||||
|
||||
.v-form-container {
|
||||
// 内联布局样式
|
||||
.ant-form-inline {
|
||||
.list-main {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
place-content: flex-start flex-start;
|
||||
|
||||
.layout-width {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-item-control-wrapper {
|
||||
min-width: 175px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-panel {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.empty-text {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
inset: -10% 0 0;
|
||||
height: 150px;
|
||||
margin: auto;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.draggable-box {
|
||||
height: calc(100vh - 120px);
|
||||
// width: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.drag-move {
|
||||
min-height: 62px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.list-main {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
// 列表动画
|
||||
.list-enter-active {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.list-leave-active {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.list-enter,
|
||||
.list-leave-to {
|
||||
transform: translateX(-100px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.list-enter {
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<!--
|
||||
* @Description: 右侧属性配置面板
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<Tabs v-model:activeKey="formConfig.activeKey" :tabBarStyle="{ margin: 0 }">
|
||||
<TabPane :key="1" tab="表单">
|
||||
<FormProps />
|
||||
</TabPane>
|
||||
<TabPane :key="2" tab="控件">
|
||||
<FormItemProps />
|
||||
</TabPane>
|
||||
<TabPane :key="3" tab="栅格">
|
||||
<ComponentColumnProps />
|
||||
</TabPane>
|
||||
<TabPane :key="4" tab="组件">
|
||||
<slot v-if="slotProps" :name="slotProps.component + 'Props'"></slot>
|
||||
<ComponentProps v-else />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watch, inject } from 'vue';
|
||||
import FormProps from '../components/FormProps.vue';
|
||||
import FormItemProps from '../components/FormItemProps.vue';
|
||||
import ComponentProps from '../components/ComponentProps.vue';
|
||||
import ComponentColumnProps from '../components/FormItemColumnProps.vue';
|
||||
import { useFormDesignState } from '../../../hooks/useFormDesignState';
|
||||
import { customComponents } from '../../../core/formItemConfig';
|
||||
import { TabPane, Tabs } from 'ant-design-vue';
|
||||
|
||||
type ChangeTabKey = 1 | 2;
|
||||
export interface IPropsPanel {
|
||||
changeTab: (key: ChangeTabKey) => void;
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'PropsPanel',
|
||||
components: {
|
||||
FormProps,
|
||||
FormItemProps,
|
||||
ComponentProps,
|
||||
ComponentColumnProps,
|
||||
Tabs,
|
||||
TabPane,
|
||||
},
|
||||
setup() {
|
||||
const { formConfig } = useFormDesignState();
|
||||
const slotProps = computed(() => {
|
||||
// return customComponents.find(
|
||||
// (item) => item.component === formConfig.value.currentItem?.component,
|
||||
// );
|
||||
});
|
||||
// return { formConfig, customComponents, slotProps };
|
||||
return { formConfig, slotProps };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('../styles/variable.less');
|
||||
|
||||
:deep(.ant-tabs) {
|
||||
box-sizing: border-box;
|
||||
|
||||
form {
|
||||
width: 100%;
|
||||
height: 85vh;
|
||||
margin-right: 10px;
|
||||
overflow: hidden auto;
|
||||
}
|
||||
|
||||
.hint-box {
|
||||
margin-top: 200px;
|
||||
}
|
||||
|
||||
.ant-form-item,
|
||||
.ant-slider-with-marks {
|
||||
margin-right: 20px;
|
||||
margin-bottom: 0;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
// width: 100%;
|
||||
margin-bottom: 0;
|
||||
|
||||
.ant-form-item-label {
|
||||
line-height: 2;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<!--
|
||||
* @Description: 工具栏
|
||||
-->
|
||||
<template>
|
||||
<div class="operating-area">
|
||||
<!-- 头部操作按钮区域 start -->
|
||||
<!-- 操作左侧区域 start -->
|
||||
<div class="left-btn-box" style="text-indent: 20px">
|
||||
<Tooltip v-for="item in toolbarsConfigs" :title="item.title" :key="item.icon">
|
||||
<a-button @click="$emit(item.event)" id="button">
|
||||
<template #icon>
|
||||
<Icon :icon="item.icon" :style="{ fontSize: '16px' }" />
|
||||
</template>
|
||||
</a-button>
|
||||
</Tooltip>
|
||||
<Tooltip title="撤销">
|
||||
<a-button :class="{ disabled: !canUndo }" :disabled="!canUndo" @click="undo" id="button">
|
||||
<template #icon>
|
||||
<Icon icon="ant-design:undo-outlined" :style="{ fontSize: '16px' }" />
|
||||
</template>
|
||||
</a-button>
|
||||
</Tooltip>
|
||||
<Tooltip title="重做">
|
||||
<a-button :class="{ disabled: !canRedo }" @click="redo" id="button">
|
||||
<template #icon>
|
||||
<Icon icon="ant-design:redo-outlined" :style="{ fontSize: '16px' }" />
|
||||
</template>
|
||||
</a-button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 操作区域 start -->
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, reactive, toRefs } from 'vue';
|
||||
import { UseRefHistoryReturn } from '@vueuse/core';
|
||||
import { IFormConfig } from '../../../typings/v-form-component';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
|
||||
interface IToolbarsConfig {
|
||||
type: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
event: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'OperatingArea',
|
||||
components: {
|
||||
Tooltip,
|
||||
Icon,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive<{
|
||||
toolbarsConfigs: IToolbarsConfig[];
|
||||
}>({
|
||||
toolbarsConfigs: [
|
||||
{
|
||||
title: '预览-支持布局',
|
||||
type: 'preview',
|
||||
event: 'handlePreview',
|
||||
icon: 'bi:life-preserver',
|
||||
},
|
||||
// {
|
||||
// title: '预览-不支持布局',
|
||||
// type: 'preview',
|
||||
// event: 'handlePreview2',
|
||||
// icon: 'ant-design:chrome-filled',
|
||||
// },
|
||||
{
|
||||
title: '导入JSON',
|
||||
type: 'importJson',
|
||||
event: 'handleOpenImportJsonModal',
|
||||
icon: 'ant-design:import-outlined',
|
||||
},
|
||||
{
|
||||
title: '生成JSON',
|
||||
type: 'exportJson',
|
||||
event: 'handleOpenJsonModal',
|
||||
icon: 'ant-design:export-outlined',
|
||||
},
|
||||
{
|
||||
title: '生成代码',
|
||||
type: 'exportCode',
|
||||
event: 'handleOpenCodeModal',
|
||||
icon: 'bi:code-slash',
|
||||
},
|
||||
{
|
||||
title: '清空',
|
||||
type: 'reset',
|
||||
event: 'handleClearFormItems',
|
||||
icon: 'ant-design:clear-outlined',
|
||||
},
|
||||
],
|
||||
});
|
||||
const historyRef = inject('historyReturn') as UseRefHistoryReturn<IFormConfig, IFormConfig>;
|
||||
|
||||
const { undo, redo, canUndo, canRedo } = historyRef;
|
||||
return { ...toRefs(state), undo, redo, canUndo, canRedo };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
//noinspection CssUnknownTarget
|
||||
@import url('../styles/variable.less');
|
||||
|
||||
.operating-area {
|
||||
display: flex;
|
||||
place-content: center space-between;
|
||||
height: @operating-area-height;
|
||||
padding: 0 12px;
|
||||
padding-left: 30px;
|
||||
border-bottom: 2px solid @border-color;
|
||||
font-size: 16px;
|
||||
line-height: @operating-area-height;
|
||||
text-align: left;
|
||||
|
||||
#button {
|
||||
margin: 0 5px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
&.disabled,
|
||||
&.disabled:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
> span {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
.draggable-box {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: @component-onlineform-formdesign-background-color;
|
||||
|
||||
|
||||
:deep(.list-main) {
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
margin: 3px;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
|
||||
.moving {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
// 拖放移动中;
|
||||
min-height: 35px;
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-move-box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
min-height: 60px;
|
||||
padding: 8px;
|
||||
margin: 3px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background-color: @primary-hover-bg-color;
|
||||
}
|
||||
|
||||
// 选择时 start
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -100%;
|
||||
width: 0%;
|
||||
height: 3px;
|
||||
transition: all 0.3s;
|
||||
outline: 3px solid @primary-color;
|
||||
background-color: @primary-color;
|
||||
}
|
||||
|
||||
&.active {
|
||||
outline: 3px solid @primary-color;
|
||||
// outline-offset: 0;
|
||||
background-color: @primary-hover-bg-color;
|
||||
|
||||
&::before {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 选择时 end
|
||||
.form-item-box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
word-wrap: break-word;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
// 修改ant form-item的margin为padding
|
||||
margin: 0;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.show-key-box {
|
||||
// 显示key
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
bottom: 2px;
|
||||
// z-index: 999;
|
||||
color: @primary-color;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.copy,
|
||||
.delete {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
// z-index: 989;
|
||||
transition: all 0.3s;
|
||||
color: #fff;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
|
||||
&.unactivated {
|
||||
opacity: 0 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.copy {
|
||||
right: 30px;
|
||||
border-radius: 0 0 0 8px;
|
||||
background-color: @primary-color;
|
||||
}
|
||||
|
||||
.delete {
|
||||
right: 0;
|
||||
background-color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
margin: 3px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
background-color: @layout-background-color;
|
||||
border-radius: 3px;
|
||||
|
||||
// 鼠标划过
|
||||
&:hover {
|
||||
background-color: @layout-hover-bg-color;
|
||||
}
|
||||
|
||||
.form-item-box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
.ant-form-item {
|
||||
// 修改ant form-item的margin为padding
|
||||
margin: 0;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-row {
|
||||
background-color: @layout-background-color;
|
||||
|
||||
.grid-col {
|
||||
.draggable-box {
|
||||
min-width: 50px;
|
||||
min-height: 80px;
|
||||
border: 1px #ccc dashed;
|
||||
// background: #fff;
|
||||
|
||||
.list-main {
|
||||
position: relative;
|
||||
min-height: 83px;
|
||||
border: 1px #ccc dashed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择时 start
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -100%;
|
||||
width: 0%;
|
||||
height: 3px;
|
||||
transition: all 0.3s;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&.active {
|
||||
outline: 3px solid @layout-color;
|
||||
// outline-offset: 0;
|
||||
background-color: @layout-hover-bg-color;
|
||||
|
||||
&::before {
|
||||
right: 0;
|
||||
background-color: @layout-color;
|
||||
}
|
||||
}
|
||||
// 选择时 end
|
||||
> .copy-delete-box {
|
||||
> .copy,
|
||||
> .delete {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
// z-index: 989;
|
||||
transition: all 0.3s;
|
||||
color: #fff;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
|
||||
&.unactivated {
|
||||
opacity: 0 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
> .copy {
|
||||
right: 30px;
|
||||
border-radius: 0 0 0 8px;
|
||||
background-color: @layout-color;
|
||||
}
|
||||
|
||||
> .delete {
|
||||
right: 0;
|
||||
background-color: @layout-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue