Browse Source

修改 bug

master
王泽彦 8 months ago
parent
commit
4c5699cb12
  1. 499
      admin/src/components/TencentMapPicker.vue
  2. 4
      niucloud/app/model/sys/SysMenu.php
  3. 40
      niucloud/app/service/admin/sys/MenuService.php
  4. 46
      niucloud/app/service/admin/sys/SysMenuService.php

499
admin/src/components/TencentMapPicker.vue

@ -53,11 +53,16 @@
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
<el-input v-model="detailAddress" placeholder="输入地区" /> <el-input
v-model="detailAddress"
placeholder="输入详细地址"
@keyup.enter="handleAddressSearch"
@blur="handleAddressBlur"
/>
<el-button <el-button
type="primary" type="primary"
@click="handleAddressSearch" @click="handleAddressSearch"
:disabled="!province || !city || !district" :disabled="!province || !city"
>{{ t('search') }}</el-button >{{ t('search') }}</el-button
> >
</div> </div>
@ -127,12 +132,140 @@ const provinceList = ref<any[]>([])
const cityList = ref<any[]>([]) const cityList = ref<any[]>([])
const districtList = ref<any[]>([]) const districtList = ref<any[]>([])
// ====================== ======================
const cleanupMap = () => {
if (mapScript) {
try {
document.body.removeChild(mapScript)
} catch (e) {
console.warn('移除地图脚本失败:', e)
}
mapScript = null
}
if (map) {
try {
map.destroy()
} catch (e) {
console.warn('销毁地图实例失败:', e)
}
map = null
}
marker = null
if (resizeTimer) {
clearTimeout(resizeTimer)
resizeTimer = null
}
window.removeEventListener('resize', handleResize)
}
const loadMapSDK = (): Promise<void> => {
return new Promise((resolve, reject) => {
//
if ((window as any).TMap) {
resolve()
return
}
mapKey.value = import.meta.env.VITE_MAP_KEY
mapScript = document.createElement('script')
mapScript.type = 'text/javascript'
mapScript.src = `https://map.qq.com/api/gljs?libraries=tools,service&v=1.exp&key=${mapKey.value}`
mapScript.onload = () => {
if ((window as any).TMap) {
resolve()
} else {
reject(new Error('TMap未定义'))
}
}
mapScript.onerror = (error) => {
reject(error)
}
document.body.appendChild(mapScript)
})
}
const initMap = () => {
const container = document.getElementById('container')
if (!(window as any).TMap || !container) {
throw new Error('地图SDK未加载或容器不存在')
}
const TMap = (window as any).TMap
let center
// 使
if (props.modelValue.lat && props.modelValue.lng) {
center = new TMap.LatLng(props.modelValue.lat, props.modelValue.lng)
} else {
// 使
center = new TMap.LatLng(39.90403, 116.407526)
}
//
map = new TMap.Map('container', {
center,
zoom: 12,
})
//
marker = createMarker(map)
//
if (props.modelValue.lat && props.modelValue.lng) {
marker.updateGeometries({
id: 'center',
position: new TMap.LatLng(props.modelValue.lat, props.modelValue.lng),
})
}
//
map.on('click', (evt: any) => {
map.setCenter(evt.latLng)
marker.updateGeometries({
id: 'center',
position: evt.latLng,
})
emit('update:modelValue', {
lat: evt.latLng.lat,
lng: evt.latLng.lng,
address: detailAddress.value,
})
})
}
const bindResizeListener = () => {
window.addEventListener('resize', handleResize)
}
const handleResize = () => {
if (resizeTimer) clearTimeout(resizeTimer)
resizeTimer = setTimeout(() => {
if (map && typeof map.setSize === 'function') {
map.setSize()
} else if (map && typeof map.getSize === 'function') {
// 使
map.invalidateSize && map.invalidateSize()
}
}, 300)
}
// ====================== ======================
const handleError = (error: any) => {
console.error('地图组件错误:', error)
ElMessage.error('地图加载失败,请重试')
}
// ====================== ====================== // ====================== ======================
onMounted(() => { onMounted(() => {
// SDK // SDK
const preloadScript = document.createElement('script') mapKey.value = import.meta.env.VITE_MAP_KEY || 'YOUR_API_KEY'
preloadScript.src = `https://map.qq.com/api/gljs?key= ${mapKey.value}`
document.head.appendChild(preloadScript)
}) })
// ====================== ====================== // ====================== ======================
@ -211,190 +344,137 @@ const handleConfirm = () => {
dialogVisible.value = false dialogVisible.value = false
} }
// //
const parseAndSetAddress = async (address: string) => { const parseAndSetAddress = async (address: string) => {
// if (!address) return
const { provinceName, cityName, districtName, detail } = parseAddress(address)
detailAddress.value = detail try {
//
// 1. const { provinceName, cityName, districtName, detail } = parseAddress(address)
if (provinceName) { detailAddress.value = detail
const provinceItem = provinceList.value.find((p) =>
p.name.includes(provinceName) // 1.
) if (provinceName) {
if (provinceItem) { const provinceItem = provinceList.value.find((p) => {
province.value = provinceItem.id return p.name.includes(provinceName.replace(/[省市区]/g, '')) ||
p.name === provinceName
// 2. })
const cityRes = await getAreaListByPid(province.value)
cityList.value = cityRes.data if (provinceItem) {
province.value = provinceItem.id
if (cityName) {
const cityItem = cityList.value.find((c) => c.name.includes(cityName)) // 2.
if (cityItem) { const cityRes = await getAreaListByPid(province.value)
city.value = cityItem.id cityList.value = cityRes.data
// 3. if (cityName) {
const districtRes = await getAreaListByPid(city.value) const cityItem = cityList.value.find((c) => {
districtList.value = districtRes.data return c.name.includes(cityName.replace(/[市盟州]/g, '')) ||
c.name === cityName
if (districtName) { })
const districtItem = districtList.value.find((d) =>
d.name.includes(districtName) if (cityItem) {
) city.value = cityItem.id
if (districtItem) {
district.value = districtItem.id // 3.
const districtRes = await getAreaListByPid(city.value)
districtList.value = districtRes.data
if (districtName) {
const districtItem = districtList.value.find((d) => {
return d.name.includes(districtName.replace(/[区县旗市]/g, '')) ||
d.name === districtName
})
if (districtItem) {
district.value = districtItem.id
}
}
}
} else if (provinceName.includes('市')) {
// ""
const directCity = cityList.value.find((c) => c.name === provinceName)
if (directCity) {
city.value = directCity.id
const districtRes = await getAreaListByPid(city.value)
districtList.value = districtRes.data
if (districtName) {
const districtItem = districtList.value.find((d) => {
return d.name.includes(districtName.replace(/[区县旗市]/g, '')) ||
d.name === districtName
})
if (districtItem) {
district.value = districtItem.id
}
} }
} }
} }
} }
} }
//
await nextTick()
if (province.value && city.value) {
handleAddressSearch()
}
} catch (error) {
console.warn('地址解析失败:', error)
} }
//
handleAddressSearch()
} }
// //
const parseAddress = (address: string) => { const parseAddress = (address: string) => {
if (!address) return { provinceName: '', cityName: '', districtName: '', detail: '' }
let provinceName = '' let provinceName = ''
let cityName = '' let cityName = ''
let districtName = '' let districtName = ''
let detail = address let detail = address.trim()
// // // ///
const provinceMatch = address.match(/^([^省市自治区]+[省市区]|[^市]+市)/) const provincePatterns = [
if (provinceMatch) { /^([^省市自治区特别行政人民共和]+[省自治区])/, // XX/XX
provinceName = provinceMatch[0] /^(北京市|上海市|天津市|重庆市)/, //
detail = address.slice(provinceName.length) /^([^市]+特别行政区)/, //
]
for (const pattern of provincePatterns) {
const match = detail.match(pattern)
if (match) {
provinceName = match[1]
detail = detail.slice(provinceName.length)
break
}
} }
// //
const cityMatch = detail.match(/^([^市区]+市)/) if (!provinceName.includes('市')) {
if (cityMatch) { const cityMatch = detail.match(/^([^市区县盟州]+[市盟州])/)
cityName = cityMatch[0] if (cityMatch) {
detail = detail.slice(cityName.length) cityName = cityMatch[1]
detail = detail.slice(cityName.length)
}
} }
// / //
const districtMatch = detail.match(/^([^区县]+[区县])/) const districtMatch = detail.match(/^([^区县旗市]+[区县旗市])/)
if (districtMatch) { if (districtMatch) {
districtName = districtMatch[0] districtName = districtMatch[1]
detail = detail.slice(districtName.length) detail = detail.slice(districtName.length)
} }
return { return {
provinceName, provinceName: provinceName.trim(),
cityName, cityName: cityName.trim(),
districtName, districtName: districtName.trim(),
detail: detail.trim() || '', detail: detail.trim(),
} }
} }
// ====================== ======================
const cleanupMap = () => {
if (mapScript) {
document.body.removeChild(mapScript)
mapScript = null
}
if (map) {
map.destroy()
map = null
}
marker = null
if (resizeTimer) {
clearTimeout(resizeTimer)
resizeTimer = null
}
window.removeEventListener('resize', handleResize)
}
const loadMapSDK = (): Promise<void> => {
return new Promise((resolve, reject) => {
mapKey.value = import.meta.env.VITE_MAP_KEY
mapScript = document.createElement('script')
mapScript.type = 'text/javascript'
mapScript.src = `https://map.qq.com/api/gljs?libraries=tools ,service&v=1.exp&key=${mapKey.value}`
mapScript.onload = () => {
if ((window as any).TMap) {
resolve()
} else {
reject(new Error('TMap未定义'))
}
}
mapScript.onerror = (error) => {
reject(error)
}
document.body.appendChild(mapScript)
})
}
const initMap = () => {
const container = document.getElementById('container')
if (!(window as any).TMap || !container) {
throw new Error('地图SDK未加载或容器不存在')
}
const TMap = (window as any).TMap
let center
// 使
if (props.modelValue.lat && props.modelValue.lng) {
center = new TMap.LatLng(props.modelValue.lat, props.modelValue.lng)
} else {
// 使
center = new TMap.LatLng(39.90403, 116.407526)
}
//
map = new TMap.Map('container', {
center,
zoom: 12,
})
//
marker = createMarker(map)
//
if (props.modelValue.lat && props.modelValue.lng) {
marker.updateGeometries({
id: 'center',
position: new TMap.LatLng(props.modelValue.lat, props.modelValue.lng),
})
}
//
map.on('click', (evt: any) => {
map.setCenter(evt.latLng)
marker.updateGeometries({
id: 'center',
position: evt.latLng,
})
emit('update:modelValue', {
lat: evt.latLng.lat,
lng: evt.latLng.lng,
address: detailAddress.value,
})
})
}
const bindResizeListener = () => {
window.addEventListener('resize', handleResize)
}
const handleResize = () => {
if (resizeTimer) clearTimeout(resizeTimer)
resizeTimer = setTimeout(() => {
map?.setSize()
}, 300)
}
// ====================== ====================== // ====================== ======================
const handleProvinceChange = async (val: string) => { const handleProvinceChange = async (val: string) => {
@ -419,32 +499,54 @@ const handleCityChange = async (val: string) => {
} }
const handleDistrictChange = (val: string) => { const handleDistrictChange = (val: string) => {
// TODO: //
if (val && detailAddress.value.trim()) {
handleAddressSearch()
}
}
//
const handleAddressBlur = () => {
//
if (province.value && city.value && detailAddress.value.trim()) {
handleAddressSearch()
}
} }
// //
const handleAddressSearch = async () => { const handleAddressSearch = async () => {
try { try {
//
if (!province.value || !city.value) {
ElMessage.warning('请选择省市')
return
}
const address = [ const address = [
province.value provinceList.value.find((p) => p.id === province.value)?.name || '',
? provinceList.value.find((p) => p.id === province.value)?.name cityList.value.find((c) => c.id === city.value)?.name || '',
: '',
city.value ? cityList.value.find((c) => c.id === city.value)?.name : '',
district.value district.value
? districtList.value.find((d) => d.id === district.value)?.name ? districtList.value.find((d) => d.id === district.value)?.name || ''
: '', : '',
detailAddress.value, detailAddress.value.trim(),
].join('') ].filter(Boolean).join('')
if (!address.trim()) {
ElMessage.warning('请输入地址')
return
}
const { message, result } = await addressToLatLng({ const result = await addressToLatLng({
mapKey: mapKey.value, mapKey: mapKey.value,
address, address,
}) })
if (message == 'Success' || message == 'query ok') { //
if (result.status === 0 && result.result) {
//
const latLng = new (window as any).TMap.LatLng( const latLng = new (window as any).TMap.LatLng(
result.location.lat, result.result.location.lat,
result.location.lng result.result.location.lng
) )
map.setCenter(latLng) map.setCenter(latLng)
marker.updateGeometries({ marker.updateGeometries({
@ -452,22 +554,47 @@ const handleAddressSearch = async () => {
position: latLng, position: latLng,
}) })
emit('update:modelValue', { emit('update:modelValue', {
lat: result.location.lat, lat: result.result.location.lat,
lng: result.location.lng, lng: result.result.location.lng,
address: detailAddress.value, address: address, // 使
}) })
ElMessage.success('地址搜索成功')
} else if (result.status === 112) {
// IP
ElMessage({
type: 'warning',
message: '地址搜索暂不可用(API授权限制),请直接在地图上点击选择位置',
duration: 5000
})
console.warn('腾讯地图API IP授权限制:', result.message)
//
emit('update:modelValue', {
lat: props.modelValue.lat || null,
lng: props.modelValue.lng || null,
address: address, //
})
} else {
//
ElMessage.warning(`搜索失败:${result.message || '未找到对应地址,请检查输入'}`)
}
} catch (error: any) {
console.error('地址搜索异常:', error)
// IP
if (error.status === 112 || (error.message && error.message.includes('IP未被授权'))) {
ElMessage({
type: 'info',
message: '地址搜索暂不可用,请直接在地图上点击选择位置',
duration: 5000
})
} else {
ElMessage.error('搜索失败,请重试')
} }
} catch (error) {
handleError(error)
} }
} }
// ====================== ======================
const handleError = (error: any) => {
console.error('地图组件错误:', error)
ElMessage.error(t('mapLoadFailed'))
dialogVisible.value = false
}
// //
watch( watch(

4
niucloud/app/model/sys/SysMenu.php

@ -35,13 +35,13 @@ class SysMenu extends BaseModel
* 模型名称 * 模型名称
* @var string * @var string
*/ */
protected $name = 'sys_menus'; protected $name = 'sys_menu';
/** /**
* 表名(不使用前缀) * 表名(不使用前缀)
* @var string * @var string
*/ */
protected $table = 'sys_menus'; protected $table = 'school_sys_menu';
/** /**
* 追加字段 * 追加字段
* @var array * @var array

40
niucloud/app/service/admin/sys/MenuService.php

@ -98,21 +98,47 @@ class MenuService extends BaseAdminService
} }
/** /**
* 菜单删除 * 菜单删除(递归删除子菜单)
* @param string $menu_key * @param string $menu_key
* @return bool * @return bool
* @throws DbException * @throws DbException
*/ */
public function del(string $menu_key) public function del(string $menu_key)
{ {
//查询是否有下级菜单或按钮 // 直接查询菜单是否存在,不依赖缓存
$menu = $this->find($menu_key); $menu = (new SysMenu())->where('menu_key', $menu_key)->find();
if (( new SysMenu() )->where([ [ 'parent_key', '=', $menu_key ] ])->count() > 0) if (!$menu) {
throw new AdminException('MENU_NOT_ALLOW_DELETE'); throw new AdminException('菜单不存在');
}
$res = $menu->delete(); // 递归删除菜单及其子菜单
$this->deleteMenuRecursive($menu_key);
// 清除缓存
Cache::tag(self::$cache_tag_name)->clear(); Cache::tag(self::$cache_tag_name)->clear();
return $res; return true;
}
/**
* 递归删除菜单及其子菜单
* @param string $menu_key
* @return void
*/
private function deleteMenuRecursive(string $menu_key)
{
// 查找所有子菜单
$childMenus = (new SysMenu())->where('parent_key', $menu_key)->select();
// 递归删除所有子菜单
foreach ($childMenus as $childMenu) {
$this->deleteMenuRecursive($childMenu->menu_key);
}
// 删除当前菜单
$currentMenu = (new SysMenu())->where('menu_key', $menu_key)->find();
if ($currentMenu) {
$currentMenu->delete();
}
} }
/** /**

46
niucloud/app/service/admin/sys/SysMenuService.php

@ -110,7 +110,7 @@ class SysMenuService extends BaseAdminService
} }
/** /**
* 删除菜单 * 删除菜单(递归删除子菜单)
* @param int $id * @param int $id
* @return bool * @return bool
*/ */
@ -121,15 +121,45 @@ class SysMenuService extends BaseAdminService
throw new AdminException('菜单不存在'); throw new AdminException('菜单不存在');
} }
// 检查是否有角色在使用此菜单 Db::startTrans();
$result = Db::query("SELECT COUNT(*) as count FROM role_menu_permissions WHERE menu_id = ?", [$id]); try {
$roleMenuCount = $result[0]['count'] ?? 0; // 递归删除所有子菜单
if ($roleMenuCount > 0) { $this->deleteMenuRecursive($model->menu_key);
throw new AdminException('该菜单已被角色使用,无法删除');
Db::commit();
return true;
} catch (\Exception $e) {
Db::rollback();
throw new AdminException('删除菜单失败:' . $e->getMessage());
}
}
/**
* 递归删除菜单及其子菜单
* @param string $menuKey
* @return void
*/
private function deleteMenuRecursive(string $menuKey)
{
// 获取当前菜单信息
$currentMenu = $this->model->where('menu_key', $menuKey)->find();
if (!$currentMenu) {
return;
}
// 查找所有子菜单
$childMenus = $this->model->where('parent_key', $menuKey)->select();
// 递归删除所有子菜单
foreach ($childMenus as $childMenu) {
$this->deleteMenuRecursive($childMenu->menu_key);
} }
$res = $model->delete(); // 删除角色菜单权限关联
return $res !== false; Db::execute("DELETE FROM role_menu_permissions WHERE menu_id = ?", [$currentMenu->id]);
// 删除当前菜单
$currentMenu->delete();
} }
/** /**

Loading…
Cancel
Save