Compare commits

...

2 Commits

Author SHA1 Message Date
air
3f93bcdd90 【类 型】:factor
【原  因】:完善地图添加 编辑功能
【过  程】:
【影  响】:
2025-09-23 13:34:38 +08:00
air
6c30667309 【类 型】:feat
【原  因】:地图设置 的排序功能完善
【过  程】:
【影  响】:
2025-09-22 19:29:44 +08:00
8 changed files with 426 additions and 6 deletions

View File

@ -123,7 +123,9 @@ export default {
}, },
async mounted () { async mounted () {
// //
await this.$store.dispatch('fetchMapStyleList', this.$store.state.settings.host) if (this.$store.state.mapStyleList.length === 0) {
await this.$store.dispatch('fetchMapStyleList', this.$store.state.settings.host)
}
// //
await this.init() await this.init()

View File

@ -16,6 +16,51 @@ const routes = [
hidden: true hidden: true
} }
}, },
{
path: '/mapstyle',
component: Layout,
redirect: '/mapstyle/index',
meta: {
title: '地图类型',
icon: 'el-icon-map-location',
roles: ['master'],
hidden: true
},
children: [
{
path: '/mapstyle/index',
component: () => import('@/views/layout/components/main/mapstyle/index'),
meta: {
title: '地图列表',
icon: 'el-icon-tickets',
roles: ['master'],
hidden: true
}
},
{
path: '/mapstyle/add',
component: () => import('@/views/layout/components/main/mapstyle/add'),
meta: {
title: '地图添加',
icon: 'el-icon-plus',
roles: ['master'],
tapName: 'plane',
hidden: true
}
},
{
path: '/mapstyle/edit/:id',
component: () => import('@/views/layout/components/main/mapstyle/add'),
meta: {
title: '地图更新',
icon: 'el-icon-edit',
roles: ['master'],
tapName: 'plane',
hidden: true
}
}
]
},
{ {
path: '/home', path: '/home',
component: Layout, component: Layout,

View File

@ -1214,19 +1214,24 @@ const store = new Vuex.Store({
const host = rootState.settings.host const host = rootState.settings.host
const list = res.data.mapStyleList.map(style => { const list = res.data.mapStyleList.map(style => {
const tiles = Array.isArray(style.tiles) ? style.tiles : JSON.parse(style.tiles) const tiles = Array.isArray(style.tiles) ? style.tiles : JSON.parse(style.tiles)
const sourceKey = style.source_key || 'default' const sourceKey = 'default'
const spriteUrl = style.sprite && String(style.sprite).trim() ? style.sprite : `${host}/Public/map/sprite`
return { return {
// 附带原始字段,便于管理端展示和操作
id: style.id,
is_active: Number(style.is_active ?? 1),
sort_order: Number(style.sort_order ?? 0),
name: style.name, name: style.name,
sprite: `${host}/Public/map/sprite`, sprite: spriteUrl,
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf', glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8, version: 8,
sources: { sources: {
[sourceKey]: { [sourceKey]: {
type: 'raster', type: 'raster',
tileSize: Number(style.tileSize || 256), tileSize: 256,
tiles, tiles,
attribution: style.attribution || '' attribution: ''
} }
}, },
layers: [{ layers: [{
@ -1241,6 +1246,23 @@ const store = new Vuex.Store({
commit('setMapStyleList', []) commit('setMapStyleList', [])
Message.error(res.data.msg || '地图样式获取失败') Message.error(res.data.msg || '地图样式获取失败')
} }
},
/**
* @description: 地图样式排序
* @param {*} form { id, sort_order }
*/
async fetchOrderMapStyle ({ dispatch }, form) {
const params = new URLSearchParams()
params.append('id', form.id)
params.append('sort_order', form.sort_order)
const res = await api.post('orderMapStyle', params, 'Plane')
if (res.data.status === 1) {
await dispatch('fetchMapStyleList') // 刷新样式列表
Message.success(res.data.msg || '排序已更新')
} else {
Message.error(res.data.msg || '排序更新失败')
}
return res
} }
}, },
modules: { modules: {

View File

@ -137,6 +137,10 @@ label {
color: #606266 !important; color: #606266 !important;
} }
.el-form-item__content {
padding-left: 25px !important;
}
/* 当屏幕宽度小于等于480px时 */ /* 当屏幕宽度小于等于480px时 */
@media (max-width: 480px) { @media (max-width: 480px) {
.el-dialog { .el-dialog {

View File

@ -40,6 +40,16 @@
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
<!-- 地图组件设置 -->
<div class="p-t-10 p-b-5">
<el-divider content-position="left">
<font class="fb f-s-18 normalFontColor">地图组件设置</font>
</el-divider>
</div>
<el-form-item label="地图样式" v-if="this.$store.state.user.power === 'master'">
<el-button type="text" @click="goMapStyle">前往地图样式</el-button>
</el-form-item>
</el-form> </el-form>
</el-main> </el-main>
</el-container> </el-container>
@ -102,7 +112,12 @@ export default {
this.$store.commit('settings/setLanguage', lang) this.$store.commit('settings/setLanguage', lang)
this.$message.success(`语言已切换为:${lang === 'zh-CN' ? '简体中文' : lang}`) this.$message.success(`语言已切换为:${lang === 'zh-CN' ? '简体中文' : lang}`)
// vue-i18n i18n.global.locale = lang // vue-i18n i18n.global.locale = lang
},
goMapStyle () {
this.$router.push('/mapstyle/index')
} }
} }
} }
</script> </script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,217 @@
<template>
<div class="app-container">
<el-row class="m-t-0">
<el-col :span="24">
<el-container>
<el-header height="42px" class="l-h-42 p-l-10 p-r-10 border border-b-n">
<div class="l">
<i :class="pageState === 'add' ? 'el-icon-plus f-s-20' : 'el-icon-edit f-s-20'"></i>
<font class="m-l-10 f-s-18 fb">{{ $route.meta.title }}</font>
</div>
</el-header>
<el-main class="border p-20 m-b-20">
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" :label-position="$store.state.app.isWideScreen ? 'top' : 'right'" class="w-80">
<!-- 基本信息 -->
<div class="p-b-5">
<el-divider content-position="left">
<font class="fb f-s-18 normalFontColor">基本信息</font>
</el-divider>
</div>
<el-form-item label="样式名称" prop="name">
<el-input v-model="form.name" placeholder="例如:谷歌卫星瓦片" />
</el-form-item>
<!-- 版本号固定为8不显示 -->
<el-form-item v-show="false" label="版本" prop="version">
<el-input-number v-model="form.version" :min="1" :max="8" :step="1" />
</el-form-item>
<el-form-item label="是否启用" prop="is_active">
<el-switch v-model="form.is_active" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="排序" prop="sort_order">
<el-input-number v-model="form.sort_order" :min="0" :max="9999" :step="1" />
</el-form-item>
<!-- 资源信息 -->
<div class="p-t-10 p-b-5">
<el-divider content-position="left">
<font class="fb f-s-18 normalFontColor">资源与源配置</font>
</el-divider>
</div>
<el-form-item label="sprite 路径" prop="sprite">
<el-input v-model="form.sprite" :placeholder="form.sprite && form.sprite.includes('/Public/map/sprite') ? '使用默认路径 /Public/map/sprite' : '可选sprite 路径(留空则使用默认路径)'" />
</el-form-item>
<el-form-item label="glyphs 路径" prop="glyphs">
<el-input v-model="form.glyphs" placeholder="可选glyphs 路径(如有)" />
</el-form-item>
<!-- 固定使用默认值不显示 -->
<el-form-item label="瓦片URL列表" prop="tiles">
<el-input
type="textarea"
:rows="4"
v-model="tilesInput"
placeholder="每行一个URL或粘贴JSON数组例如\nhttps://server.com/tiles/{z}/{x}/{y}.png"
/>
</el-form-item>
<!-- 固定使用默认值不显示 -->
<!-- 提交按钮 -->
<el-form-item>
<el-button type="primary" :icon="pageState === 'add' ? 'el-icon-plus' : 'el-icon-edit'" @click="handleSubmit">
{{ pageState === 'add' ? '创建' : '更新' }}
</el-button>
<el-button v-if="pageState === 'add'" class="iconfont icon-qingchu" @click="resetForm">
<font class="m-l-5">重填</font>
</el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'MapStyleAdd',
data () {
return {
form: {
id: null,
name: '',
sprite: '',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
tiles: [], // tilesInput
is_active: 1,
sort_order: 0
},
tilesInput: '', // /JSON
pageState: 'add',
mapStyleId: this.$route.params.id,
rules: {
name: [{ required: true, message: '请输入样式名称', trigger: 'blur' }]
}
}
},
async created () {
//
if (this.mapStyleId != null) {
this.pageState = 'edit'
this.form.id = this.mapStyleId
let item = (this.$store.state.mapStyleList || []).find(i => String(i.id) === String(this.mapStyleId))
if (!item) {
// store
try {
await this.$store.dispatch('fetchMapStyleList', this.$store.state.settings.host)
item = (this.$store.state.mapStyleList || []).find(i => String(i.id) === String(this.mapStyleId))
} catch (e) {}
}
if (item) {
this.fillFormFromItem(item)
} else {
this.$message.warning('未在列表中找到该样式,表单为空')
}
}
},
methods: {
// tiles JSON
parseTilesInput (val) {
const text = (val || '').trim()
if (!text) return []
try {
const asJson = JSON.parse(text)
if (Array.isArray(asJson)) return asJson
} catch (e) {
// JSON
}
return text.split(/\r?\n/).map(s => s.trim()).filter(Boolean)
},
fillFormFromItem (item) {
this.form.name = item.name || ''
//
this.form.sprite = (item.sprite && item.sprite.includes('/Public/map/sprite')) ? '' : (item.sprite || '')
this.form.glyphs = item.glyphs || 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf'
this.form.version = 8
// tiles JSON
let tiles = []
if (Array.isArray(item.tiles)) {
tiles = item.tiles
} else if (typeof item.tiles === 'string') {
try {
tiles = JSON.parse(item.tiles)
} catch (e) {
tiles = item.tiles.split('\n').filter(Boolean)
}
}
this.form.tiles = tiles
this.tilesInput = Array.isArray(tiles) ? tiles.join('\n') : tiles
this.form.is_active = Number(item.is_active ?? 1)
this.form.sort_order = Number(item.sort_order ?? 0)
},
resetForm () {
this.$refs.formRef && this.$refs.formRef.resetFields()
this.tilesInput = ''
this.form.version = 8
this.form.is_active = 1
this.form.sort_order = 0
},
handleSubmit () {
this.$refs.formRef.validate(valid => {
if (valid) {
// tiles
this.form.tiles = this.parseTilesInput(this.tilesInput)
if (this.form.tiles.length === 0) {
this.$message.warning('请至少输入一个瓦片URL')
return
}
//
const submitData = {
name: this.form.name,
// sprite使
sprite: this.form.sprite || '/Public/map/sprite',
glyphs: this.form.glyphs,
tiles: this.form.tiles,
is_active: this.form.is_active,
sort_order: this.form.sort_order
}
// ID
if (this.pageState === 'edit' && this.form.id) {
submitData.id = this.form.id
}
const loading = this.$loading({ lock: true })
const action = this.pageState === 'add'
? this.$store.dispatch('addMapStyle', submitData)
: this.$store.dispatch('saveMapStyle', submitData)
action.then(res => {
if (res.data.status === 1) {
this.$message.success(res.data.msg)
this.$router.push('/layout/mapstyle')
} else {
this.$message.error(res.data.msg || '操作失败')
}
}).catch(e => {
this.$message.error(e.message || '请求出错')
}).finally(() => {
loading.close()
})
} else {
this.$message.warning('请正确填写表单')
return false
}
})
}
}
}
</script>
<style lang="scss" scoped>
@import "@/styles/theme.scss";
.w-80 { max-width: 880px; }
</style>

View File

@ -0,0 +1,114 @@
<template>
<div class="app-container">
<!-- 操作按钮 -->
<el-button-group>
<el-button type="primary" icon="el-icon-plus" @click="$router.replace('/mapstyle/add')">添加</el-button>
<el-button type="warning" icon="el-icon-edit" @click="toEditPage()">编辑</el-button>
</el-button-group>
<!-- 样式表格 -->
<el-table class="m-t-20 w-100" ref="myTable" :data="styleListPaged" border tooltip-effect="dark">
<el-table-column align="center" type="selection" width="40" />
<el-table-column align="center" label="排序" width="120">
<template slot-scope="scope">
<el-input
v-model="scope.row.sort_order"
size="mini"
style="width: 80px;"
placeholder="排序"
@focus="tempSort = scope.row.sort_order"
@blur="blurOrder(scope.row)"
/>
</template>
</el-table-column>
<el-table-column align="center" label="ID" width="60">
<template slot-scope="scope">{{ (currentPage - 1) * pageSize + scope.$index + 1 }}</template>
</el-table-column>
<el-table-column prop="name" label="样式名称" width="160" min-width="160">
<template slot-scope="scope">{{ displayName(scope.row, scope.$index) }}</template>
</el-table-column>
<el-table-column prop="style" label="样式/URL" min-width="260" show-overflow-tooltip>
<template slot-scope="scope">{{ displayStyle(scope.row) }}</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination class="m-t-20" layout="prev, pager, next" :current-page.sync="currentPage" :page-size="pageSize"
:total="styleList.length" />
</div>
</template>
<script>
export default {
name: 'MapStyleIndex',
data () {
return {
pageSize: 8,
currentPage: 1,
tempSort: ''
}
},
computed: {
// store MapBox 使
styleList () {
//
return Array.isArray(this.$store.state.mapStyleList) ? this.$store.state.mapStyleList : []
},
styleListPaged () {
const start = (this.currentPage - 1) * this.pageSize
return this.styleList.slice(start, start + this.pageSize)
}
},
created () {
setTimeout(() => {
console.log(this.styleList)
}, 1000)
},
methods: {
displayName (row, idx) {
// name/title
if (typeof row === 'string') return `样式 ${idx + 1}`
return row.name || row.title || `样式 ${idx + 1}`
},
displayStyle (row) {
if (typeof row === 'string') return row
// style/url/id
return row.style || row.url || JSON.stringify(row)
},
// input
async blurOrder (row) {
if (this.tempSort !== row.sort_order) {
if (!row || row.id == null) {
this.$message.warning('该样式缺少ID无法排序')
return
}
await this.$store.dispatch('fetchOrderMapStyle', {
id: row.id,
sort_order: row.sort_order
})
}
},
toEditPage () {
const sel = this.$refs.myTable.selection || []
if (!sel.length) {
this.$message.error('请选择一条需要编辑的记录')
return
}
if (sel.length > 1) {
this.$message.error('只能选择一条记录')
return
}
const row = sel[0]
if (!row || row.id == null) {
this.$message.error('此样式缺少后端ID无法编辑')
return
}
this.$router.push('/mapstyle/edit/' + row.id)
}
}
}
</script>
<style lang="scss" scoped>
@import "@/styles/theme.scss";
</style>

View File

@ -122,7 +122,8 @@ export default {
this.$store.dispatch('fetchSpuList'), this.$store.dispatch('fetchSpuList'),
this.$store.dispatch('fetchSkuList'), this.$store.dispatch('fetchSkuList'),
this.$store.dispatch('fetchPaidOrderList'), this.$store.dispatch('fetchPaidOrderList'),
this.$store.dispatch('fetchMessageList') this.$store.dispatch('fetchMessageList'),
this.$store.dispatch('fetchMapStyleList', this.$store.state.settings.host)
]).then(() => { ]).then(() => {
return this.$store.dispatch('fetchNoflyData', this.shop_id) return this.$store.dispatch('fetchNoflyData', this.shop_id)
}) })