10 changed files with 228 additions and 472 deletions
@ -0,0 +1,187 @@ |
|||
<template> |
|||
<el-row style="flex-wrap: nowrap"> |
|||
<el-input v-model="value" :placeholder="placeholder" /> |
|||
<el-button @click="visibleClick">选择</el-button> |
|||
</el-row> |
|||
<el-dialog v-model="state.visible" title="详细地址" width="800px"> |
|||
<div class="map-container"> |
|||
<el-input id="tipInput" v-model="state.tipInput" /> |
|||
<div id="container" style="width: 100%; height: 400px"></div> |
|||
<div id="panel"></div> |
|||
</div> |
|||
<template #footer> |
|||
<el-button @click="selectClick" type="primary">确定</el-button> |
|||
</template> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { reactive, watch, ref, onMounted, nextTick } from 'vue' |
|||
import { loadScript } from '@/utils/DesignForm' |
|||
|
|||
const props = withDefaults( |
|||
defineProps<{ |
|||
modelValue?: string |
|||
disabled?: boolean |
|||
placeholder?: string |
|||
}>(), |
|||
{} |
|||
) |
|||
|
|||
const emits = defineEmits<{ |
|||
(e: 'update:modelValue', value: string): void |
|||
}>() |
|||
const value = ref(props.modelValue) |
|||
watch( |
|||
() => props.modelValue, |
|||
() => { |
|||
value.value = props.modelValue |
|||
} |
|||
) |
|||
const state = reactive({ |
|||
visible: false, |
|||
tipInput: '' |
|||
}) |
|||
const selectClick = () => { |
|||
state.visible = false |
|||
value.value = state.tipInput |
|||
emits('update:modelValue', value.value) |
|||
} |
|||
const visibleClick = () => { |
|||
state.visible = true |
|||
// 初始地址 |
|||
nextTick(() => { |
|||
const map = initMap() |
|||
mapSearch(map) |
|||
}) |
|||
} |
|||
const center = ref([113.264499, 23.130061]) |
|||
// 地图选择 |
|||
const initMap = () => { |
|||
const map = new AMap.Map('container', { |
|||
resizeEnable: true, |
|||
center: center.value, |
|||
zoom: 11, |
|||
viewMode: '3D' // 使用3D视图 |
|||
}) |
|||
//实时路况图层 |
|||
const marker = new AMap.Marker({ |
|||
position: map.getCenter(), |
|||
//icon: '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png', |
|||
// 设置是否可以拖拽 |
|||
draggable: true, |
|||
cursor: 'move' |
|||
// content: 'markerContent', |
|||
}) |
|||
const { lat, lng } = map.getCenter() |
|||
getDetailAddress(lng, lat, marker) |
|||
map.add(marker) //添加到地图 |
|||
map.on('click', (e: any) => { |
|||
const lnglat = e.lnglat |
|||
const lat = lnglat.lat |
|||
const lng = lnglat.lng |
|||
map.setCenter([lng, lat]) //设置地图中心点 |
|||
marker.setPosition([lng, lat]) //更新点标记位置 |
|||
getDetailAddress(lng, lat, marker) |
|||
}) |
|||
return map |
|||
} |
|||
const mapSearch = (map: any) => { |
|||
AMap.plugin(['AMap.PlaceSearch', 'AMap.AutoComplete'], function () { |
|||
const auto = new AMap.AutoComplete({ |
|||
input: 'tipInput' |
|||
}) |
|||
const placeSearch = new AMap.PlaceSearch({ |
|||
pageSize: 5, // 单页显示结果条数 |
|||
pageIndex: 1, // 页码 |
|||
city: '', // 兴趣点城市 |
|||
citylimit: false, //是否强制限制在设置的城市内搜索 |
|||
map: map, // 展现结果的地图实例 |
|||
panel: 'panel', // 结果列表将在此容器中进行展示。 |
|||
autoFitView: true // 是否自动调整地图视野使绘制的 Marker点都处于视口的可见范围 |
|||
}) |
|||
auto.on('select', (evt: any) => { |
|||
placeSearch.setCity(evt.poi.adcode) |
|||
placeSearch.search(evt.poi.name) //关键字查询查询 |
|||
/*placeSearch.search(evt.poi.name, function (status, result) { |
|||
//关键字查询查询 |
|||
// 查询成功时,result即对应匹配的POI信息 |
|||
console.log("搜索结果", result); |
|||
});*/ |
|||
state.tipInput = evt.poi.name // 更新输入框的值 |
|||
}) //注册监听,当选中某条记录时会触发 |
|||
/* |
|||
// 搜索结果地图上的icon层 |
|||
placeSearch.on('markerClick',()=>{ |
|||
console.log('placeSearch.onmarkerClick') |
|||
})*/ |
|||
// 搜索列表点击事件 |
|||
placeSearch.on('listElementClick', (evt: any) => { |
|||
state.tipInput = evt.data.name |
|||
}) |
|||
}) |
|||
} |
|||
const getDetailAddress = (lng: number, lat: number, marker: any) => { |
|||
// 根据经纬度获取详细地址 |
|||
AMap.plugin('AMap.Geocoder', () => { |
|||
const geocoder = new AMap.Geocoder() |
|||
geocoder.getAddress([lng, lat], (status: string, result: any) => { |
|||
if (status === 'complete' && result.info === 'OK') { |
|||
const detailAddress = result.regeocode.formattedAddress |
|||
state.tipInput = detailAddress |
|||
marker.setLabel({ |
|||
direction: 'center', |
|||
offset: new AMap.Pixel(0, -25), //设置文本标注偏移量 |
|||
content: `<div class='info'>${detailAddress}</div>` //设置文本标注内容 |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
onMounted(() => { |
|||
console.log('onMountedonMounted') |
|||
loadScript( |
|||
'https://webapi.amap.com/maps?v=2.0&key=ljiKlTAsS7SNVqDM16IUwRVFFhrvbxiF&plugin=AMap.PlaceSearch' |
|||
).then(() => {}) |
|||
}) |
|||
</script> |
|||
<style lang="scss"> |
|||
.map-container { |
|||
position: relative; |
|||
} |
|||
.amap-sug-result { |
|||
z-index: 2500; |
|||
} |
|||
#panel { |
|||
z-index: 2500; |
|||
position: absolute; |
|||
background-color: white; |
|||
max-height: 90%; |
|||
overflow-y: auto; |
|||
top: 10px; |
|||
right: 10px; |
|||
width: 280px; |
|||
.amap_lib_placeSearch { |
|||
border: 1px solid #ebedf0; |
|||
border-radius: 2px; |
|||
padding: 5px 10px 2px; |
|||
.poibox { |
|||
border-bottom: 1px solid #e8e8e8; |
|||
cursor: pointer; |
|||
padding: 10px 5px; |
|||
position: relative; |
|||
min-height: 35px; |
|||
} |
|||
.amap_lib_placeSearch_pic { |
|||
width: 46px; |
|||
height: 46px; |
|||
float: left; |
|||
margin: 4px 10px 0 0; |
|||
img { |
|||
width: 46px; |
|||
height: 46px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,463 +0,0 @@ |
|||
<script setup lang="ts"> |
|||
defineOptions({ |
|||
name: "cmenu", |
|||
inheritAttrs: false, |
|||
}); |
|||
|
|||
import { MenuQuery, MenuForm, MenuVO } from "@/api/menu/types"; |
|||
import { |
|||
listMenus, |
|||
getMenuForm, |
|||
listMenuOptions, |
|||
addMenu, |
|||
deleteMenu, |
|||
updateMenu, |
|||
} from "@/api/menu"; |
|||
|
|||
import { MenuTypeEnum } from "@/enums/MenuTypeEnum"; |
|||
|
|||
import SvgIcon from "@/components/SvgIcon/index.vue"; |
|||
import IconSelect from "@/components/IconSelect/index.vue"; |
|||
|
|||
const queryFormRef = ref(ElForm); |
|||
const menuFormRef = ref(ElForm); |
|||
|
|||
const loading = ref(false); |
|||
const dialog = reactive<DialogOption>({ |
|||
visible: false, |
|||
}); |
|||
|
|||
const queryParams = reactive<MenuQuery>({}); |
|||
const menuList = ref<MenuVO[]>([]); |
|||
|
|||
const menuOptions = ref<OptionType[]>([]); |
|||
|
|||
const formData = reactive<MenuForm>({ |
|||
parentId: 0, |
|||
visible: 1, |
|||
sort: 1, |
|||
type: MenuTypeEnum.MENU, |
|||
outside:1 |
|||
}); |
|||
|
|||
const rules = reactive({ |
|||
parentId: [{ required: true, message: "请选择顶级菜单", trigger: "blur" }], |
|||
name: [{ required: true, message: "请输入菜单名称", trigger: "blur" }], |
|||
type: [{ required: true, message: "请选择菜单类型", trigger: "blur" }], |
|||
path: [{ required: true, message: "请输入路由路径", trigger: "blur" }], |
|||
component: [ |
|||
{ required: true, message: "请输入组件完整路径", trigger: "blur" }, |
|||
], |
|||
}); |
|||
|
|||
// 选择表格的行菜单ID |
|||
const selectedRowMenuId = ref<number | undefined>(); |
|||
|
|||
const menuCacheData = reactive({ |
|||
type: "", |
|||
path: "", |
|||
}); |
|||
|
|||
/** |
|||
* 查询 |
|||
*/ |
|||
function handleQuery() { |
|||
// 重置父组件 |
|||
loading.value = true; |
|||
listMenus(queryParams) |
|||
.then(({ data }) => { |
|||
menuList.value = data; |
|||
}) |
|||
.then(() => { |
|||
loading.value = false; |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 查询重置 |
|||
*/ |
|||
function resetQuery() { |
|||
queryFormRef.value.resetFields(); |
|||
handleQuery(); |
|||
} |
|||
|
|||
/** |
|||
* 行点击事件 |
|||
* |
|||
* @param row |
|||
*/ |
|||
function onRowClick(row: MenuVO) { |
|||
selectedRowMenuId.value = row.id; |
|||
} |
|||
|
|||
/** |
|||
* 打开表单弹窗 |
|||
* |
|||
* @param parentId 父菜单ID |
|||
* @param menuId 菜单ID |
|||
*/ |
|||
function openDialog(parentId?: number, menuId?: number) { |
|||
listMenuOptions() |
|||
.then(({ data }) => { |
|||
menuOptions.value = [{ value: 0, label: "顶级菜单", children: data }]; |
|||
}) |
|||
.then(() => { |
|||
dialog.visible = true; |
|||
if (menuId) { |
|||
dialog.title = "编辑菜单"; |
|||
getMenuForm({id:menuId.toString()}).then(({ data }) => { |
|||
Object.assign(formData, data); |
|||
menuCacheData.type = data.type; |
|||
menuCacheData.path = data.path ?? ""; |
|||
}); |
|||
} else { |
|||
dialog.title = "新增菜单"; |
|||
formData.parentId = parentId; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 菜单类型 change |
|||
*/ |
|||
function onMenuTypeChange() { |
|||
// 如果菜单类型改变,清空路由路径;未改变在切换后还原路由路径 |
|||
if (formData.type !== menuCacheData.type) { |
|||
formData.path = ""; |
|||
} else { |
|||
formData.path = menuCacheData.path; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 菜单提交 |
|||
*/ |
|||
function submitForm() { |
|||
menuFormRef.value.validate((isValid: boolean) => { |
|||
if (isValid) { |
|||
const menuId = formData.id; |
|||
if (menuId) { |
|||
updateMenu(formData).then(() => { |
|||
ElMessage.success("修改成功"); |
|||
closeDialog(); |
|||
handleQuery(); |
|||
}); |
|||
} else { |
|||
addMenu(formData).then(() => { |
|||
ElMessage.success("新增成功"); |
|||
closeDialog(); |
|||
handleQuery(); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 删除菜单 |
|||
*/ |
|||
function handleDelete(menuId: number) { |
|||
if (!menuId) { |
|||
ElMessage.warning("请勾选删除项"); |
|||
return false; |
|||
} |
|||
|
|||
ElMessageBox.confirm("确认删除已选中的数据项?", "警告", { |
|||
confirmButtonText: "确定", |
|||
cancelButtonText: "取消", |
|||
type: "warning", |
|||
}) |
|||
.then(() => { |
|||
deleteMenu({id:menuId.toString()}).then(() => { |
|||
ElMessage.success("删除成功"); |
|||
handleQuery(); |
|||
}); |
|||
}) |
|||
.catch(() => ElMessage.info("已取消删除")); |
|||
} |
|||
|
|||
/** |
|||
* 关闭弹窗 |
|||
*/ |
|||
function closeDialog() { |
|||
dialog.visible = false; |
|||
resetForm(); |
|||
} |
|||
|
|||
/** |
|||
* 重置表单 |
|||
*/ |
|||
function resetForm() { |
|||
menuFormRef.value.resetFields(); |
|||
menuFormRef.value.clearValidate(); |
|||
|
|||
formData.id = undefined; |
|||
formData.parentId = 0; |
|||
formData.visible = 1; |
|||
formData.sort = 1; |
|||
} |
|||
|
|||
onMounted(() => { |
|||
handleQuery(); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="app-container"> |
|||
<div class="search"> |
|||
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> |
|||
<el-form-item label="关键字" prop="keywords"> |
|||
<el-input |
|||
v-model="queryParams.keywords" |
|||
placeholder="菜单名称" |
|||
clearable |
|||
@keyup.enter="handleQuery" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleQuery" |
|||
><template #icon><i-ep-search /></template>搜索</el-button |
|||
> |
|||
<el-button @click="resetQuery"> |
|||
<template #icon><i-ep-refresh /></template> |
|||
重置</el-button |
|||
> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<el-card shadow="never"> |
|||
<template #header> |
|||
<el-button type="success" @click="openDialog(0)" v-hasPerm="['121646328009732096']"> |
|||
<template #icon><i-ep-plus /></template> |
|||
新增</el-button |
|||
> |
|||
</template> |
|||
|
|||
<el-table |
|||
v-loading="loading" |
|||
:data="menuList" |
|||
highlight-current-row |
|||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" |
|||
row-key="id" |
|||
default-expand-all |
|||
border |
|||
@row-click="onRowClick" |
|||
> |
|||
<el-table-column label="菜单名称" min-width="200"> |
|||
<template #default="scope"> |
|||
<svg-icon |
|||
:icon-class=" |
|||
scope.row.type === MenuTypeEnum.BUTTON |
|||
? 'button' |
|||
: scope.row.icon |
|||
" |
|||
/> |
|||
{{ scope.row.name }} |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="菜单类型" align="center" width="100"> |
|||
<template #default="scope"> |
|||
<el-tag |
|||
v-if="scope.row.class === MenuTypeEnum.CATALOG" |
|||
type="warning" |
|||
>目录</el-tag |
|||
> |
|||
<el-tag v-if="scope.row.class === MenuTypeEnum.MENU" type="success" |
|||
>菜单</el-tag |
|||
> |
|||
<el-tag v-if="scope.row.class === MenuTypeEnum.BUTTON" type="danger" |
|||
>按钮</el-tag |
|||
> |
|||
<el-tag v-if="scope.row.class === MenuTypeEnum.EXTLINK" type="info" |
|||
>外链</el-tag |
|||
> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
label="权限标识" |
|||
align="center" |
|||
width="200" |
|||
prop="permcode" |
|||
/> |
|||
|
|||
<el-table-column label="状态" align="center" width="100"> |
|||
<template #default="scope"> |
|||
<el-tag v-if="scope.row.visible === 1" type="success">显示</el-tag> |
|||
<el-tag v-else type="info">隐藏</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="排序" align="center" width="50" prop="sort" /> |
|||
|
|||
<el-table-column fixed="right" align="center" label="操作" width="220"> |
|||
<template #default="scope"> |
|||
<el-button |
|||
v-if="scope.row.class == 'CATALOG' || scope.row.class == 'MENU'" |
|||
v-hasPerm="['121646328009732096']" |
|||
type="primary" |
|||
link |
|||
size="small" |
|||
@click.stop="openDialog(scope.row.id)" |
|||
> |
|||
<i-ep-plus />新增 |
|||
</el-button> |
|||
|
|||
<el-button |
|||
v-hasPerm="['122274485305880576']" |
|||
type="primary" |
|||
link |
|||
size="small" |
|||
@click.stop="openDialog(undefined, scope.row.id)" |
|||
> |
|||
<i-ep-edit />编辑 |
|||
</el-button> |
|||
<el-button |
|||
v-hasPerm="['122274565337395200']" |
|||
type="primary" |
|||
link |
|||
size="small" |
|||
@click.stop="handleDelete(scope.row.id)" |
|||
><i-ep-delete /> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</el-card> |
|||
|
|||
<el-dialog |
|||
v-model="dialog.visible" |
|||
:title="dialog.title" |
|||
destroy-on-close |
|||
append-to-body |
|||
width="750px" |
|||
@close="closeDialog" |
|||
> |
|||
<el-form |
|||
ref="menuFormRef" |
|||
:model="formData" |
|||
:rules="rules" |
|||
label-width="100px" |
|||
> |
|||
<el-form-item label="父级菜单" prop="parentId"> |
|||
<el-tree-select |
|||
v-model="formData.parentId" |
|||
placeholder="选择上级菜单" |
|||
:data="menuOptions" |
|||
filterable |
|||
check-strictly |
|||
:render-after-expand="false" |
|||
/> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="菜单名称" prop="name"> |
|||
<el-input v-model="formData.name" placeholder="请输入菜单名称" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="菜单类型" prop="type"> |
|||
<el-radio-group v-model="formData.type" @change="onMenuTypeChange"> |
|||
<el-radio label="CATALOG">目录</el-radio> |
|||
<el-radio label="MENU">菜单</el-radio> |
|||
<el-radio label="BUTTON">按钮</el-radio> |
|||
<el-radio label="EXTLINK">外链</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
|
|||
<el-form-item |
|||
v-if="formData.type == 'EXTLINK'" |
|||
label="外链地址" |
|||
prop="path" |
|||
> |
|||
<el-input v-model="formData.path" placeholder="请输入外链完整路径" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item |
|||
v-if="formData.type == 'CATALOG' || formData.type == 'MENU'" |
|||
label="路由路径" |
|||
prop="path" |
|||
> |
|||
<el-input |
|||
v-if="formData.type == 'CATALOG'" |
|||
v-model="formData.path" |
|||
placeholder="/system (目录以/开头)" |
|||
/> |
|||
<el-input v-else v-model="formData.path" placeholder="user" /> |
|||
</el-form-item> |
|||
|
|||
<!-- 组件页面完整路径 --> |
|||
<el-form-item |
|||
v-if="formData.type == MenuTypeEnum.MENU" |
|||
label="页面路径" |
|||
prop="component" |
|||
> |
|||
<el-input |
|||
v-model="formData.component" |
|||
placeholder="system/user/index" |
|||
style="width: 95%" |
|||
> |
|||
<template v-if="formData.parentId != 0" #prepend |
|||
>src/views/</template |
|||
> |
|||
<template v-if="formData.parentId != 0" #append>.vue</template> |
|||
</el-input> |
|||
</el-form-item> |
|||
|
|||
<!-- 权限标识 --> |
|||
<!-- <el-form-item |
|||
v-if="formData.type == 'BUTTON'" |
|||
label="权限标识" |
|||
prop="perm" |
|||
> |
|||
<el-input v-model="formData.perm" placeholder="sys:user:add" /> |
|||
</el-form-item> --> |
|||
|
|||
<el-form-item |
|||
v-if="formData.type !== 'BUTTON'" |
|||
label="图标" |
|||
prop="icon" |
|||
> |
|||
<!-- 图标选择器 --> |
|||
<icon-select v-model="formData.icon" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item |
|||
v-if="formData.type == MenuTypeEnum.CATALOG" |
|||
label="跳转路由" |
|||
> |
|||
<el-input v-model="formData.redirect" placeholder="跳转路由" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item v-if="formData.type !== 'BUTTON'" label="状态"> |
|||
<el-radio-group v-model="formData.visible"> |
|||
<el-radio :label="1">显示</el-radio> |
|||
<el-radio :label="0">隐藏</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
<el-form-item label="可见范围"> |
|||
<el-radio-group v-model="formData.outside"> |
|||
<el-radio :label="1">内部使用</el-radio> |
|||
<el-radio :label="2">外部使用</el-radio> |
|||
<el-radio :label="3">内外使用</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
<el-form-item label="排序" prop="sort"> |
|||
<el-input-number |
|||
v-model="formData.sort" |
|||
style="width: 100px" |
|||
controls-position="right" |
|||
:min="0" |
|||
/> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<template #footer> |
|||
<div class="dialog-footer"> |
|||
<el-button type="primary" @click="submitForm">确 定</el-button> |
|||
<el-button @click="closeDialog">取 消</el-button> |
|||
</div> |
|||
</template> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
Loading…
Reference in new issue