userName 2025-04-08 17:01:49 +08:00
commit 625be5bde2
6 changed files with 1015 additions and 3 deletions

View File

@ -0,0 +1,121 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicForm, useForm } from '@/components/Form';
import { formSchema } from './data';
import { getMenuList, addMenu, editMenu, addButton, editButton } from '@/api/demo/system';
import { useMessage } from '@/hooks/web/useMessage';
import { cloneDeep } from 'lodash-es';
import { BasicModal, useModalInner } from '@/components/Modal';
const { createMessage } = useMessage();
defineOptions({ name: 'MenuDrawer' });
const emit = defineEmits(['success', 'register']);
const props = defineProps(['treeData']);
const isUpdate = ref(true);
const [registerForm, { resetFields, setFieldsValue, updateSchema, validate }] = useForm({
labelWidth: 100,
schemas: formSchema,
showActionButtonGroup: false,
// baseColProps: { lg: 12, md: 24 },
baseColProps: { span: 24 },
});
const rowId = ref('');
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields();
setModalProps({ confirmLoading: false });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
const { record } = data;
if (record.type == '2') {
record.status = record.status ? 1 : 0;
}
setFieldsValue({
...record,
});
}
// const treeData = await getMenuList();
const treeData = cloneDeep(props.treeData);
treeData.unshift({
id: '0',
key: '0',
name: '根节点',
});
updateSchema({
field: 'parentId',
componentProps: { treeData },
});
updateSchema({
field: 'moduleId',
componentProps: { treeData },
});
});
const getTitle = computed(() => (!unref(isUpdate) ? '新增图层' : '编辑图层'));
async function handleSubmit() {
try {
const values = await validate();
values.sortNo = Number(values.sortNo);
setDrawerProps({ confirmLoading: true });
// TODO custom api
if (values.type == '1') {
delete values.type;
//
if (!unref(isUpdate)) {
const data = await addMenu(values);
if (data) {
closeDrawer();
emit('success');
return createMessage.success('新增成功');
} else {
return createMessage.error('新增失败');
}
} else {
const data = await editMenu(values);
if (data) {
closeDrawer();
emit('success');
return createMessage.success('编辑成功');
} else {
return createMessage.error('编辑失败');
}
}
} else {
//
delete values.type;
values.status = values.status == 1 ? true : false;
if (!unref(isUpdate)) {
const data = await addButton(values);
if (data) {
closeDrawer();
emit('success');
return createMessage.info('创建成功');
} else {
return createMessage.error('创建失败');
}
} else {
const data = await editButton(values);
if (data) {
closeDrawer();
emit('success');
return createMessage.success('编辑成功');
} else {
return createMessage.error('编辑失败');
}
}
}
} finally {
setDrawerProps({ confirmLoading: false });
}
}
</script>

View File

@ -0,0 +1,167 @@
<template>
<div class="m-4 mr-0 overflow-hidden bg-white">
<BasicTree
ref="asyncExpandTreeRef"
title="图层列表"
toolbar
search
treeWrapperClassName="h-[calc(100%-35px)] overflow-auto"
:actionList="actionList"
:renderIcon="createIcon"
:clickRowToExpand="false"
:treeData="treeData"
:fieldNames="{ key: 'id', title: 'name' }"
:defaultExpandAll="true"
@select="handleSelect"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, h, nextTick, unref } from 'vue';
import { BasicTree, TreeItem, TreeActionItem, TreeActionType } from '@/components/Tree';
import { getMenuList, deleteMenu, getAllModuleDetail } from '@/api/demo/system';
import { FormOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { router } from '@/router';
import { useMessage } from '@/hooks/web/useMessage';
const { createMessage, createConfirm } = useMessage();
const emit = defineEmits(['select', 'edit', 'add']);
defineOptions({ name: 'DeptTree' });
const treeData = ref<TreeItem[]>([]);
let selectItemId = ref('');
const asyncExpandTreeRef = ref<Nullable<TreeActionType>>(null);
async function fetch() {
treeData.value = (await getMenuList()) as unknown as TreeItem[];
setTimeout(() => {
treeData.value.forEach((item, index) => {
let moduleDetail = allModuleDetail.value?.find((detail) => {
return detail.id == item.id;
});
});
}, 500);
//
nextTick(() => {
unref(asyncExpandTreeRef)?.expandAll(true);
});
}
function handleSelect(keys) {
emit('select', keys[0]);
}
const btnList = router.currentRoute.value.meta.elements;
const actionList: TreeActionItem[] = [
{
render: (node) => {
return h(PlusOutlined, {
class: 'ml-2',
onClick: () => {
emit('add', node);
},
});
},
},
{
render: (node) => {
return h(FormOutlined, {
class: 'ml-2',
onClick: () => {
emit('edit', node);
},
});
},
},
{
render: (node) => {
return h(DeleteOutlined, {
class: 'ml-2',
onClick: () => {
selectItemId.value = node.id;
createConfirm({
iconType: 'warning',
title: '删除',
content: '确认要删除菜单吗?',
onOk: async () => {
var query = [selectItemId.value];
const data = await deleteMenu(query);
if (data) {
fetch();
createMessage.success('删除成功');
} else {
createMessage.info('删除失败');
}
},
});
},
});
},
},
];
// btnList.forEach((element) => {
// if (element.domId == 'btnEdit') {
// actionList.push({
// render: (node) => {
// return h(FormOutlined, {
// class: 'ml-2',
// onClick: () => {
// emit('edit', node);
// },
// });
// },
// });
// } else if (element.domId == 'btnDelete') {
// actionList.push({
// render: (node) => {
// return h(DeleteOutlined, {
// class: 'ml-2',
// onClick: () => {
// selectItemId.value = node.id;
// createConfirm({
// iconType: 'warning',
// title: '',
// content: '?',
// onOk: async () => {
// var query = [selectItemId.value];
// const data = await deleteMenu(query);
// if (data) {
// fetch();
// createMessage.success('');
// } else {
// createMessage.info('');
// }
// },
// });
// },
// });
// },
// });
// }
// });
function createIcon({ level }) {
if (level === 1) {
return 'ion:git-compare-outline';
}
if (level === 2) {
return 'ion:home';
}
if (level === 3) {
return 'ion:airplane';
}
return '';
}
const allModuleDetail = ref();
async function funGetAllModuleDetail() {
allModuleDetail.value = await getAllModuleDetail();
}
onMounted(() => {
funGetAllModuleDetail();
fetch();
});
defineExpose({
fetch,
treeData,
});
</script>

View File

@ -0,0 +1,256 @@
<template>
<div
style="position: relative; height: 100%"
ref="vChartRef"
:id="'mars3d-container-' + name"
></div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import * as mars3d from 'mars3d';
let map: mars3d.Map; //
const vChartRef = ref<HTMLElement>();
const props = defineProps({
name: {
type: String,
default: '',
},
});
onMounted(() => {
let options = {
scene: {
center: {
lat: 35.362625,
lng: 118.033886,
alt: 8306.3,
heading: 360,
pitch: -45,
},
scene3DOnly: false,
shadows: false,
removeDblClick: true,
sceneMode: 3,
showSun: true,
showMoon: true,
showSkyBox: true,
showSkyAtmosphere: true,
fog: true,
fxaa: true,
requestRenderMode: true,
contextOptions: {
requestWebgl1: false,
webgl: {
preserveDrawingBuffer: true,
alpha: false,
stencil: true,
powerPreference: 'high-performance',
},
},
globe: {
depthTestAgainstTerrain: false,
baseColor: '#546a53',
showGroundAtmosphere: true,
enableLighting: false,
},
cameraController: {
zoomFactor: 3,
minimumZoomDistance: 1,
maximumZoomDistance: 50000000,
enableRotate: true,
enableTranslate: true,
enableTilt: true,
enableZoom: true,
enableCollisionDetection: true,
minimumCollisionTerrainHeight: 15000,
},
},
control: {
homeButton: false,
baseLayerPicker: false,
sceneModePicker: false,
vrButton: false,
fullscreenButton: false,
navigationHelpButton: false,
animation: false,
timeline: false,
infoBox: false,
geocoder: false,
selectionIndicator: false,
showRenderLoopErrors: true,
contextmenu: {
hasDefault: true,
},
mouseDownView: true,
zoom: {
insertIndex: 1,
},
},
method: {
templateValues: {
dataServer: '//data.mars3d.cn',
gltfServerUrl: '//data.mars3d.cn/gltf',
},
},
terrain: {
url: '//data.mars3d.cn/terrain',
show: true,
clip: true,
},
basemaps: [
{
id: 10,
name: '地图底图',
type: 'group',
opacity: 1,
},
{
id: 2021,
pid: 10,
name: '天地图影像',
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
type: 'group',
layers: [
{
name: '底图',
type: 'tdt',
layer: 'img_d',
eventParent: {
id: 2021,
pid: 10,
name: '天地图影像',
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
type: 'group',
layers: [
{
name: '底图',
type: 'tdt',
layer: 'img_d',
show: true,
},
{
name: '注记',
type: 'tdt',
layer: 'img_z',
show: true,
},
],
show: true,
},
private: false,
id: 'm-a57ecb7d-ba05-47a3-b1be-2e28411a5954',
opacity: 1,
pid: 2021,
parent: {
id: 2021,
pid: 10,
name: '天地图影像',
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
type: 'group',
layers: [
{
name: '底图',
type: 'tdt',
layer: 'img_d',
show: true,
},
{
name: '注记',
type: 'tdt',
layer: 'img_z',
show: true,
},
],
show: true,
},
zIndex: 1,
},
{
name: '注记',
type: 'tdt',
layer: 'img_z',
eventParent: {
id: 2021,
pid: 10,
name: '天地图影像',
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
type: 'group',
layers: [
{
name: '底图',
type: 'tdt',
layer: 'img_d',
show: true,
},
{
name: '注记',
type: 'tdt',
layer: 'img_z',
show: true,
},
],
show: true,
},
private: false,
id: 'm-671f9d42-dda7-45ec-9d0f-4259c915b2cb',
opacity: 1,
pid: 2021,
parent: {
id: 2021,
pid: 10,
name: '天地图影像',
icon: 'https://data.mars3d.cn/img/thumbnail/basemap/tdt_img.png',
type: 'group',
layers: [
{
name: '底图',
type: 'tdt',
layer: 'img_d',
show: true,
},
{
name: '注记',
type: 'tdt',
layer: 'img_z',
show: true,
},
],
show: true,
},
zIndex: 2,
},
],
show: true,
opacity: 1,
},
],
layers: [],
};
initMap(options);
});
const isFirstLoad = ref(true);
const initMap = (newData: any) => {
//
if (isFirstLoad.value) {
map = new mars3d.Map(vChartRef.value, newData);
} else {
//
// map.setOptions(newData);
map.setSceneOptions(newData.scene);
}
isFirstLoad.value = false;
//
handlerInitEntityLayer();
};
let graphicLayer = null;
//
const handlerInitEntityLayer = () => {
if (graphicLayer == null) {
graphicLayer = new mars3d.layer.GraphicLayer({ id: 999 });
}
};
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,80 @@
import { FormSchema } from '@/components/Table';
import { loadTableRecordInfo } from '@/api/database/index';
export const columns: BasicColumn[] = [
{
title: '编号',
dataIndex: 'name',
},
{
title: '名称',
dataIndex: 'name',
},
];
export const searchFormSchema: FormSchema[] = [
{
field: 'key',
label: '关键字',
component: 'Input',
colProps: { span: 8 },
},
];
const isLayer = (type: string) => type === '2';
export const formSchema: FormSchema[] = [
{
field: 'type',
label: '类型',
component: 'RadioButtonGroup',
defaultValue: '1',
componentProps: {
options: [
{ label: '分组', value: '1' },
{ label: '图层', value: '2' },
],
},
colProps: { lg: 24, md: 24 },
},
{
field: 'id',
label: '名称',
component: 'Input',
ifShow: false,
},
{
field: 'name',
label: '名称',
component: 'Input',
required: true,
},
{
field: 'parentId',
label: '上级',
component: 'TreeSelect',
required: true,
componentProps: {
fieldNames: {
label: 'name',
key: 'id',
value: 'id',
},
getPopupContainer: () => document.body,
},
},
{
field: 'tableId',
component: 'ApiSelect',
label: '空间数据表',
required: true,
componentProps: ({ formActionType, formModel }) => {
return {
api: loadTableRecordInfo, // 接口
// 接口参数
resultField: 'items',
labelField: 'tableName',
valueField: 'tableName',
};
},
ifShow: ({ values }) => isLayer(values.type),
},
];

View File

@ -1,5 +1,390 @@
<template>
<div> 4555 </div>
<PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
<LayerTree
ref="treeRef"
class="w-1/4 xl:w-1/5"
@select="handleSelect"
@edit="editLayer"
@add="addLayer"
/>
<div class="map-container w-3/4 xl:w-4/5">
<Map />
<!-- 数据列表 -->
<div class="table-constainer" v-if="tableVisible">
<div class="drawer-title-box">
<a-input
v-model:value="keyWord"
placeholder="关键字"
style="width: 200px; margin-right: 10px"
/>
<a-button type="primary" @click="handleCreate"> </a-button>
<a-button type="primary" @click="handleItem"> </a-button>
<a-button type="primary" @click="handleItem"> </a-button>
<a-button type="primary" @click="styleHandle"> </a-button>
</div>
<div class="close-icon" @click="tableVisible = false">
<CloseOutlined />
</div>
<BasicTable @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
label: '定位',
onClick: posData.bind(null, record),
},
{
label: '编辑',
onClick: editData.bind(null, record),
},
{
label: '查看',
onClick: viewData.bind(null, record),
},
{
label: '删除',
color: 'error',
onClick: delData.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
</div>
<!-- 详情 -->
<div class="data-detail" v-if="showTable == 'detail'">
<a-descriptions title="详情" :column="columnVal">
<a-descriptions-item label="编号">Zhou Maomao</a-descriptions-item>
<a-descriptions-item label="名称">1810000000</a-descriptions-item>
</a-descriptions>
<div class="detail-button">
<a-button type="primary" @click="showTable = 'edit'"> 编辑 </a-button>
<a-button type="primary" @click="showTable = ''" danger> 关闭 </a-button>
</div>
</div>
<!-- 编辑 -->
<div class="data-edit" v-if="showTable == 'edit'">
<a-form
ref="formRef"
:model="formState"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item ref="name" label="Activity name" name="name">
<a-input v-model:value="formState.name" />
</a-form-item>
<a-form-item label="Activity zone" name="region">
<a-select v-model:value="formState.region" placeholder="please select your zone">
<a-select-option value="shanghai">Zone one</a-select-option>
<a-select-option value="beijing">Zone two</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Activity time" required name="date1">
<a-date-picker
v-model:value="formState.date1"
show-time
type="date"
placeholder="Pick a date"
style="width: 100%"
/>
</a-form-item>
<a-form-item label="Instant delivery" name="delivery">
<a-switch v-model:checked="formState.delivery" />
</a-form-item>
<a-form-item label="Activity type" name="type">
<a-checkbox-group v-model:value="formState.type">
<a-checkbox value="1" name="type">Online</a-checkbox>
<a-checkbox value="2" name="type">Promotion</a-checkbox>
<a-checkbox value="3" name="type">Offline</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item label="Resources" name="resource">
<a-radio-group v-model:value="formState.resource">
<a-radio value="1">Sponsor</a-radio>
<a-radio value="2">Venue</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="Activity form" name="desc">
<a-textarea v-model:value="formState.desc" />
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="onSubmit"></a-button>
<a-button style="margin-left: 10px" @click="resetForm"></a-button>
</a-form-item>
</a-form>
</div>
<!-- 样式配置 -->
<div class="data-style" v-if="false">
<div class="img-box">
<a-upload-dragger v-model:fileList="fileList" name="file" @change="handleChange">
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text">上传SLD文件</p>
</a-upload-dragger>
</div>
<!-- <a-upload
v-model:file-list="fileList"
list-type="picture-card"
class="upload-list-inline"
:customRequest="handleCustomRequest"
>
:accept="'.jpg,.jpeg,.png'"
<InboxOutlined />
<div class="ant-upload-text">上传</div>
</a-upload> -->
</div>
</div>
<AddModel @register="registerAddModal" :treeData="treeData" @success="handleSuccess" />
</PageWrapper>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
<script setup lang="ts">
import { LayerTree, AddModel, Map } from './page';
import { ref, UnwrapRef, reactive } from 'vue';
import { PageWrapper } from '@/components/Page';
import { useModal } from '@/components/Modal';
import { CloseOutlined, InboxOutlined } from '@ant-design/icons-vue';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { columns } from './data';
import { getRoleListByPage } from '@/api/demo/system';
import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
import { useMessage } from '@/hooks/web/useMessage';
const { createConfirm, createMessage } = useMessage();
const showTable = ref('');
const tableVisible = ref(false);
const styleVisible = ref(false);
const selectVal = ref();
const columnVal = ref(1);
const [registerAddModal, { openModal: openAddModal }] = useModal();
const keyWord = ref('');
const [registerTable, { reload, getSelectRows, clearSelectedRowKeys }] = useTable({
//
title: '数据列表',
//
api: getRoleListByPage,
// BasicColumn[]
columns,
rowKey: 'id',
// formConfig: {
// labelWidth: 120,
// schemas: searchFormSchema,
// },
// 使
useSearchForm: false,
//
showTableSetting: true,
//
bordered: true,
//
showIndexColumn: false,
//
handleSearchInfoFn(info) {
return info;
},
actionColumn: {
width: 200,
title: '操作',
dataIndex: 'action',
// slots: { customRender: 'action' },
fixed: undefined,
},
});
const treeRef = ref();
const treeData = ref();
const formRef = ref();
interface FormState {
name: string;
region: string | undefined;
delivery: boolean;
type: string[];
resource: string;
desc: string;
}
const formState: UnwrapRef<FormState> = reactive({
name: '',
region: undefined,
date1: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
});
const rules = {
name: [
{ required: true, message: 'Please input Activity name', trigger: 'blur' },
{ min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
],
region: [{ required: true, message: 'Please select Activity zone', trigger: 'change' }],
date1: [{ required: true, message: 'Please pick a date', trigger: 'change', type: 'object' }],
type: [
{
type: 'array',
required: true,
message: 'Please select at least one activity type',
trigger: 'change',
},
],
resource: [{ required: true, message: 'Please select activity resource', trigger: 'change' }],
desc: [{ required: true, message: 'Please input activity form', trigger: 'blur' }],
};
const onSubmit = () => {
formRef.value
.validate()
.then(() => {
console.log('values', formState, toRaw(formState));
})
.catch((error: ValidateErrorEntity<FormState>) => {
console.log('error', error);
});
};
const resetForm = () => {
showTable.value = '';
};
const handleSelect = (record) => {
console.log(record);
tableVisible.value = true;
};
const editLayer = (node) => {
console.log(node);
treeData.value = treeRef.value.treeData;
openAddModal(true, {
node,
isUpdate: true,
});
};
const addLayer = () => {
treeData.value = treeRef.value.treeData;
openAddModal(true, {
isUpdate: false,
});
};
const handleSuccess = () => {};
const editData = (record) => {
console.log(record);
selectVal.value = record;
showTable.value = 'edit';
};
const viewData = (record) => {
console.log(record);
selectVal.value = record;
showTable.value = 'detail';
};
const delData = (record) => {
createConfirm({
iconType: 'info',
title: '删除',
content: '确定要删除当前数据吗?',
onOk: async () => {
// let result = await functionDeleteForm(query);
// if (result) {
// createMessage.success('!');
// } else {
// createMessage.error('!');
// }
reload();
},
});
};
const posData = (record) => {};
const styleHandle = () => {
styleVisible.value = true;
};
const handleChange = (info) => {
console.log(info);
};
//
const handleCustomRequest = (file) => {
console.log(file);
// fileList.value = [];
// const formData = new FormData();
// formData.append('files', file.file);
// uploadFile(formData)
// .then((res: any) => {
// modalData.value.imgUrl = res[0].filePath;
// imageUrl.value = VITE_GLOB_API_URL_VAR.value + res[0].filePath;
// })
// .catch((err) => {
// file.onError(err);
// });
};
</script>
<style lang="less" scoped>
.map-container {
position: relative;
}
.table-constainer {
padding: 20px;
width: 99%;
height: 40vh;
position: absolute;
bottom: 0;
left: 5px;
z-index: 2;
background-color: @component-background;
border-radius: 4px;
}
.drawer-title-box {
span {
margin-right: 100px;
}
button {
margin-right: 10px;
}
}
.close-icon {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
.data-detail {
position: absolute;
top: 0;
right: 0;
background-color: @component-background;
z-index: 3;
width: 30%;
height: 100%;
padding: 20px;
box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2);
.detail-button {
position: absolute;
bottom: 30px;
right: 40px;
button {
margin-left: 10px;
}
}
}
.data-edit {
position: absolute;
top: 0;
right: 0;
background-color: @component-background;
z-index: 3;
width: 30%;
height: 100%;
padding: 20px;
box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2);
}
.data-style {
position: absolute;
top: 0;
right: 0;
background-color: @component-background;
z-index: 3;
width: 30%;
height: 100%;
padding: 20px;
box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2);
.img-box {
height: 200px;
}
}
</style>

View File

@ -0,0 +1,3 @@
export { default as LayerTree } from './LayerTree.vue';
export { default as AddModel } from './AddModel.vue';
export { default as Map } from './Map.vue';