diff --git a/.env b/.env index ed9611f..268d7cb 100644 --- a/.env +++ b/.env @@ -8,6 +8,8 @@ VITE_APP_BASE_URL=/ # 服务器基础地址 VITE_HTTP_SERVER_BASE_URL=/api VITE_HTTP_PROXY_TARGET=http://127.0.0.1:10086 +VITE_HTTP_MAP_SERVER_BASE_URL=/api/map +VITE_HTTP_MAP_SERVER_PROXY_TARGET=https://apis.map.qq.com VITE_WS_SERVER_BASE_URL=/api/fdx VITE_WS_PROXY_TARGET=ws://127.0.0.1:10086 diff --git a/package-lock.json b/package-lock.json index 60dbc6b..a0e4bb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "axios": "^1.11.0", "decimal.js": "^10.6.0", "echarts": "^5.4.1", - "element-plus": "^2.11.8", + "element-plus": "^2.13.2", "luxon": "^3.7.1", "mitt": "^3.0.1", "nanoid": "^5.1.5", @@ -2971,9 +2971,9 @@ "license": "ISC" }, "node_modules/element-plus": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.1.tgz", - "integrity": "sha512-eG4BDBGdAsUGN6URH1PixzZb0ngdapLivIk1meghS1uEueLvQ3aljSKrCt5x6sYb6mUk8eGtzTQFgsPmLavQcA==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.2.tgz", + "integrity": "sha512-Zjzm1NnFXGhV4LYZ6Ze9skPlYi2B4KAmN18FL63A3PZcjhDfroHwhtM6RE8BonlOPHXUnPQynH0BgaoEfvhrGw==", "license": "MIT", "dependencies": { "@ctrl/tinycolor": "^3.4.1", @@ -2985,8 +2985,8 @@ "@vueuse/core": "^10.11.0", "async-validator": "^4.2.5", "dayjs": "^1.11.19", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", + "lodash": "^4.17.23", + "lodash-es": "^4.17.23", "lodash-unified": "^1.0.3", "memoize-one": "^6.0.0", "normalize-wheel-es": "^1.2.0" diff --git a/package.json b/package.json index 97f9e15..32e57c1 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "axios": "^1.11.0", "decimal.js": "^10.6.0", "echarts": "^5.4.1", - "element-plus": "^2.11.8", + "element-plus": "^2.13.2", "luxon": "^3.7.1", "mitt": "^3.0.1", "nanoid": "^5.1.5", diff --git a/src/assets/images/mark_point.png b/src/assets/images/mark_point.png new file mode 100644 index 0000000..f33e0c6 Binary files /dev/null and b/src/assets/images/mark_point.png differ diff --git a/src/assets/stylus/dialog.styl b/src/assets/stylus/dialog.styl index b959c85..3356f30 100644 --- a/src/assets/stylus/dialog.styl +++ b/src/assets/stylus/dialog.styl @@ -51,7 +51,7 @@ box-sizing border-box !important & > .el-scrollbar > .el-scrollbar__wrap { - max-height calc(100vh - 400px) + max-height 850px height unset } } diff --git a/src/common/app/app-setting-store.ts b/src/common/app/app-setting-store.ts index af2dd37..0e61d70 100644 --- a/src/common/app/app-setting-store.ts +++ b/src/common/app/app-setting-store.ts @@ -7,6 +7,14 @@ export const useAppSettingStore = defineStore('AppSetting', () => { const language = ref<'zh' | 'en'>('zh') const logo = ref(null) const collectedMenus = ref([]) + const defaultAddress = reactive({ + lat: 31.96873, + lng: 118.798171, + province: '320000', + city: '320100', + provinceName: '江苏省', + cityName: '南京市', + }) const menus = ref([]) const menuTree = ref([]) @@ -29,6 +37,7 @@ export const useAppSettingStore = defineStore('AppSetting', () => { logo, language, stationId, + defaultAddress, $reset, } }, { diff --git a/src/common/index.ts b/src/common/index.ts index aacaee8..94ed4a2 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -13,6 +13,7 @@ export const appTitle = import.meta.env.VITE_APP_TITLE * 服务器基础地址 */ export const serverBaseUrl = import.meta.env.VITE_HTTP_SERVER_BASE_URL ?? '' +export const mapServerBaseUrl = import.meta.env.VITE_HTTP_MAP_SERVER_BASE_URL ?? '' export const wsServerBaseUrl = import.meta.env.VITE_WS_SERVER_BASE_URL ?? '/' diff --git a/src/components/a-form-panel/AFormPanel.tsx b/src/components/a-form-panel/AFormPanel.tsx index 89eb71c..1eac990 100644 --- a/src/components/a-form-panel/AFormPanel.tsx +++ b/src/components/a-form-panel/AFormPanel.tsx @@ -21,6 +21,7 @@ type RuleType = Partial { title: string + defaultFormData: T detailsLoader: (id?: string) => Promise doSubmit: (data: T) => Promise rules: RuleType | ((formData: T) => RuleType) @@ -40,7 +41,7 @@ const component = defineComponent( const showDialog = ref(false) const loading = ref(false) - const formData = Utils.resetAble(reactive({} as T)) + const formData = Utils.resetAble(reactive(props.defaultFormData)) function dialogCloseHandler() { formData.$reset() @@ -134,7 +135,7 @@ const component = defineComponent( }, { name: 'AFormPanel', - props: [ 'title', 'detailsLoader', 'doSubmit', 'rules', 'watchForm', 'labelWidth', 'width', 'modal', 'appendToBody' ], + props: [ 'title', 'defaultFormData', 'detailsLoader', 'doSubmit', 'rules', 'watchForm', 'labelWidth', 'width', 'modal', 'appendToBody' ], }) export interface AFormPanelInstance extends InstanceType { @@ -167,6 +168,10 @@ export function buildFormPanelProps(props: Partial(props: Partial) } diff --git a/src/components/a-map/AMap.tsx b/src/components/a-map/AMap.tsx deleted file mode 100644 index 7dcc518..0000000 --- a/src/components/a-map/AMap.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { defineComponent } from 'vue' -import Types from '@/common/utils/types.ts' - -const key = '3TGBZ-ZK7K5-CMNIK-ICIGZ-K6TYQ-HTBTZ' - -// 32.087182, 118.797109 -interface InitOption { - /** - * 容器 - */ - el: string | HTMLDivElement - /** - * 中心点 - */ - center?: { - /** - * 经度 - */ - lat: number - /** - * 纬度 - */ - lng: number - } - /** - * 缩放级别 - */ - zoom?: number - /** - * 俯仰角 - */ - pitch?: number - /** - * 旋转角度 - */ - rotation?: number -} - -function initMap({ - el, - center, - zoom = 15, - pitch = 0, - rotation = 0, - }: InitOption) { - let centerLatLng: TMap.LatLng | undefined = undefined - if (center != null) { - centerLatLng = new TMap.LatLng(center.lat, center.lng) - } - if (Types.isString(el)) { - const htmlNode = document.getElementById(el as string) - if (htmlNode == null) { - throw new Error('地图容器不存在') - } - el = htmlNode as HTMLDivElement - } - - return new TMap.Map(el, { - center: centerLatLng, - zoom, - pitch, - rotation, - }) -} - -export interface AMapType { - init: () => void -} - -const component = defineComponent( - (_, ctx) => { - const el = ref(undefined) - const init = () => { - initMap({ - el: el.value as HTMLDivElement, - center: { - lat: 32.087182, - lng: 118.797109, - }, - }) - } - ctx.expose({init}) - return () => { - return ( -
-
) - } - }, -) - - -export default component diff --git a/src/components/a-map/AMap.vue b/src/components/a-map/AMap.vue new file mode 100644 index 0000000..39e954c --- /dev/null +++ b/src/components/a-map/AMap.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/src/components/a-map/amap-api.ts b/src/components/a-map/amap-api.ts index 0babac3..81d50ac 100644 --- a/src/components/a-map/amap-api.ts +++ b/src/components/a-map/amap-api.ts @@ -1 +1,113 @@ // https://apis.map.qq.com/ws/place/v1/search?boundary=nearby(40.040589,116.273543,1000)&keyword=公园&page_size=10&page_index=1&key=OB4BZ-D4W3U-***** +import axios from 'axios' +import { mapServerBaseUrl } from '@/common' +import mime from '@/common/utils/mime.ts' +import qs from 'qs' + +const httpUtil = axios.create({ + timeout: 10000, + baseURL: mapServerBaseUrl, + headers: { + Accept: mime.JSON, + }, +}) + +interface SearchPlaceParam { + /** + * 开发密钥,必填 + */ + key: string; + /** + * 搜索关键字(UTF-8编码,最大96字节),必填 + */ + keyword: string; + /** + * 格式:nearby(纬度,经度,半径[,是否自动扩大]),必填 + */ + boundary: { + lat: number + lng: number + radius?: number + auto_extend?: 0 | 1 + }; + /** + * 是否返回子地点,如大厦停车场、出入口等取值: + * 0 [默认]不返回、1 返回 + */ + get_subpois?: 0 | 1; + filter?: string; // 筛选条件(分类筛选/排除分类/筛选有电话),可选 + added_fields?: string; // 附加字段,仅支持category_code,可选 + orderby?: '_distance'; // 排序方式,仅支持按距离排序,可选 + page_size?: number; // 每页条目数(1-20),默认10,可选 + page_index?: number; // 页码,默认1,可选 + output?: 'json' | 'jsonp'; // 返回格式,默认json,可选 + callback?: string; // JSONP回调函数名,可选 +} + +// 坐标类型 +interface Location { + lat: number; // 纬度 + lng: number; // 经度 +} + +// 行政区划信息类型 +interface AdInfo { + adcode: number; // 行政区划代码 + province: string; // 省份 + city: string; // 城市(省直辖县级区划返回空) + district: string; // 区/县 +} + +export interface Poi { + id: string; // POI唯一标识 + title: string; // POI名称 + address: string; // 地址 + tel: string; // 电话 + category: string; // POI分类 + category_code?: number; // 分类编码(added_fields=category_code时返回) + type: 0 | 1 | 2 | 3 | 4; // POI类型 + location: Location; // 坐标 + _distance: number; // 直线距离(米) + ad_info: AdInfo; // 行政区划信息 +} + +// 子地点类型 +interface SubPoi extends Exclude { + parent_id: string; // 主地点ID +} + +// 周边搜索响应类型 +export interface SearchPlaceResult { + status: number; // 状态码(0为正常) + message: string; // 状态说明 + count: number; // 搜索结果总数(最多返回200条) + request_id: string; // 请求唯一标识 + data: Poi[]; + sub_pois?: SubPoi[]; // 子地点列表(get_subpois=1时返回) +} + +const paramsSerializer = (params: any) => { + return qs.stringify(params, {indices: false, allowDots: true}) +} + +function searchPlace(params: SearchPlaceParam) { + return httpUtil.get('/ws/place/v1/search', { + params: { + ...params, + boundary: `nearby(${params.boundary.lat},${params.boundary.lng},${params.boundary.radius == null || params.boundary.radius < 10 ? 1000 : params.boundary.radius},${params.boundary.auto_extend ?? 1})`, + }, + paramsSerializer, + responseType: 'json', + }) + .then(res => { + if (res.data.status !== 0) return Promise.reject({message: res.data.message}) + return res.data + }) + .catch(err => { + ElMessage.error(err.message) + }) +} + +export default { + searchPlace, +} diff --git a/src/components/a-map/tmap.d.ts b/src/dts/tmap.d.ts similarity index 100% rename from src/components/a-map/tmap.d.ts rename to src/dts/tmap.d.ts diff --git a/src/dts/vite-env.d.ts b/src/dts/vite-env.d.ts index 2623bff..4e5facb 100644 --- a/src/dts/vite-env.d.ts +++ b/src/dts/vite-env.d.ts @@ -8,6 +8,8 @@ interface ImportMetaEnv { readonly VITE_APP_TITLE: string readonly VITE_APP_BASE_URL: string readonly VITE_HTTP_SERVER_BASE_URL: string + readonly VITE_HTTP_MAP_SERVER_BASE_URL: string + readonly VITE_HTTP_MAP_SERVER_PROXY_TARGET: string readonly VITE_HTTP_PROXY_TARGET: string readonly VITE_WS_SERVER_BASE_URL: string readonly VITE_WS_PROXY_TARGET: string diff --git a/src/pages/cst/station/Station.vue b/src/pages/cst/station/Station.vue index 740856b..59b2749 100644 --- a/src/pages/cst/station/Station.vue +++ b/src/pages/cst/station/Station.vue @@ -4,10 +4,8 @@ v-bind="tablePageProps"> @@ -40,7 +49,10 @@ import AFormPanel, { buildFormPanelProps, } from '@/components/a-form-panel/AFormPanel.tsx' import UserDropTable from '@/pages/sys/user/UserDropTable.vue' -import AMap, { type AMapType } from '@/components/a-map/AMap.tsx' +import AMap from '@/components/a-map/AMap.vue' +import { useAppSettingStore } from '@/common/app/app-setting-store.ts' + +const appSettingStore = useAppSettingStore() const props = withDefaults(defineProps<{ research?: () => void @@ -49,32 +61,116 @@ const props = withDefaults(defineProps<{ }, }) -const amapIns = useTemplateRef('amap') +interface StationFormType { + id?: string + // 站点名称 + stationName?: string + // 省;代码 + province?: string + // 市;代码 + city?: string + // 区县;代码 + area?: string + // 乡镇街道;代码 + town?: string + // 省;名称 + provinceName?: string + // 市;名称 + cityName?: string + // 区县;名称 + areaName?: string + // 乡镇街道;名称 + townName?: string + // 详细地址 + address?: string + // 经度 + lng?: number + // 纬度 + lat?: number + // 账号 + userId?: string + username?: string + customerName?: string + phone?: string + // 统一社会信用代码 + uscc?: string + // 企业名称 + orgName?: string +} + const formPanelIns = useTemplateRef('formPanel') const status = ref<'add' | 'modify'>('add') -type T = StationTypes.SearchStationResult & StationTypes.AddStationParam -const formPanelProps = buildFormPanelProps({ +const formPanelProps = buildFormPanelProps({ title: status.value === 'add' ? '新建站点' : '修改站点', labelWidth: '100px', detailsLoader(id?: string) { if (Strings.isBlank(id)) { status.value = 'add' - return Promise.resolve({ - orgInfo: {}, - } as T) + return Promise.resolve() } else { status.value = 'modify' return StationApi .detail(id!) - .then(res => res.data) + .then(res => { + return { + id: res.data.id, + stationName: res.data.stationName, + province: res.data.province, + city: res.data.city, + area: res.data.area, + town: res.data.town, + provinceName: res.data.provinceName, + cityName: res.data.cityName, + areaName: res.data.areaName, + townName: res.data.townName, + address: res.data.address, + lng: res.data.lng, + lat: res.data.lat, + userId: res.data.customer.userId, + username: res.data.customer.username, + customerName: res.data.customer.customerName, + phone: res.data.customer.phone, + uscc: res.data.customer.uscc, + orgName: res.data.customer.orgName, + } + }) } }, doSubmit(data) { if (status.value === 'add') { - return StationApi.add(data) + return StationApi.add({ + stationName: data.stationName, + area: data.area, + areaName: data.areaName, + address: data.address, + lng: data.lng, + lat: data.lat, + userId: data.userId, + customerName: data.customerName, + phone: data.phone, + orgInfo: { + uscc: data.uscc, + orgName: data.orgName, + }, + }) .then(props.research) } else { - return StationApi.modify(data) + return StationApi.modify({ + id: data.id, + stationName: data.stationName, + area: data.area, + areaName: data.areaName, + address: data.address, + lng: data.lng, + lat: data.lat, + userId: data.userId, + customerName: data.customerName, + phone: data.phone, + orgInfo: { + uscc: data.uscc, + orgName: data.orgName, + }, + }) .then(props.research) } }, @@ -93,33 +189,25 @@ const formPanelProps = buildFormPanelProps({ defineExpose({ open(data?: ProjectTypes.SearchProjectResult) { formPanelIns.value?.open(data?.id) - nextTick(() => { - amapIns.value?.init() - }) }, }) diff --git a/src/pages/cst/station/station.d.ts b/src/pages/cst/station/station.d.ts index f284b93..af729f6 100644 --- a/src/pages/cst/station/station.d.ts +++ b/src/pages/cst/station/station.d.ts @@ -9,7 +9,9 @@ declare global { interface CustomerInfo { id?: string + userId?: string username?: string + uscc?: string // 客户姓名 customerName?: string // 客户联系电话 @@ -134,6 +136,11 @@ declare global { lng?: number // 纬度 lat?: number + // 账号 + userId?: string + customerName?: string + phone?: string + orgInfo?: OrgInfo } } } diff --git a/src/pages/gds/goods-category/GoodsCategoryForm.vue b/src/pages/gds/goods-category/GoodsCategoryForm.vue index aa2dc89..e36a8eb 100644 --- a/src/pages/gds/goods-category/GoodsCategoryForm.vue +++ b/src/pages/gds/goods-category/GoodsCategoryForm.vue @@ -50,15 +50,17 @@ const formPanelIns = useTemplateRef('formPanel') const uploaderIns = useTemplateRef>('uploader') const status = ref<'add' | 'modify'>('add') const formPanelProps = buildFormPanelProps({ - title: status.value === 'add' ? '新建产品分类' : '修改产品分类', + title: '新建产品分类', detailsLoader(id?: string) { if (Strings.isBlank(id)) { status.value = 'add' + formPanelProps.title = '新建产品分类' return Promise.resolve({ bizType: props.defaultBizType, sort: 0, } as GoodsCategoryTypes.SearchGoodsCategoryResult) } else { + formPanelProps.title = '修改产品分类' status.value = 'modify' return GoodsCategoryApi.detail(id!) .then((res) => { @@ -72,6 +74,7 @@ const formPanelProps = buildFormPanelProps再生品、HuiShouPin-->回收品、QiTa-->其他 bizType?: string + sn?: string // 分类名称 categoryName?: string // 图片 diff --git a/src/pages/sys/user/UserDropTable.vue b/src/pages/sys/user/UserDropTable.vue index 6b8aadb..d02823b 100644 --- a/src/pages/sys/user/UserDropTable.vue +++ b/src/pages/sys/user/UserDropTable.vue @@ -2,6 +2,9 @@ import ADropTable from '@/components/a-drop-table/ADropTable.vue' import UserApi from '@/pages/sys/user/user-api.ts' +defineProps<{ + placeholder?: string +}>() const model = defineModel() const dropTableColumns = [ { @@ -23,5 +26,6 @@ const dropTableLoader = (param: UserTypes.SearchUserParam) => { diff --git a/vite.config.ts b/vite.config.ts index 23c0aea..7fc5085 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -64,6 +64,13 @@ export default defineConfig((configEnv) => { host: '0.0.0.0', port: 5173, proxy: { + [env.VITE_HTTP_MAP_SERVER_BASE_URL]: { + proxyTimeout: 10000, + target: env.VITE_HTTP_MAP_SERVER_PROXY_TARGET, + secure: false, + changeOrigin: true, + rewrite: (path) => (env.VITE_HTTP_MAP_SERVER_BASE_URL == null || env.VITE_HTTP_MAP_SERVER_BASE_URL == '/' ? path : path.replace(new RegExp(env.VITE_HTTP_MAP_SERVER_BASE_URL), '')), + } as ProxyOptions, [env.VITE_HTTP_SERVER_BASE_URL]: { proxyTimeout: 10000, target: env.VITE_HTTP_PROXY_TARGET,