mirror of
https://gitee.com/niucloud-team/niucloud.git
synced 2026-06-25 11:21:59 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67e7669694 | ||
|
|
9f683839e6 | ||
|
|
44805a4a1f | ||
|
|
0864a7a502 | ||
|
|
396eb8406d | ||
|
|
20db81d87e | ||
|
|
4309d7c458 | ||
|
|
9ed68ca110 | ||
|
|
b4ebe684b2 | ||
|
|
c721282ffa | ||
|
|
da84a1087c | ||
|
|
65d9a38693 | ||
|
|
330ffa3efc |
@ -125,5 +125,5 @@ niucloud-admin-saas是一款快速开发通用管理后台框架,整体功能
|
|||||||
All rights reserved。
|
All rights reserved。
|
||||||
|
|
||||||
杭州数字云动科技有限公司
|
杭州数字云动科技有限公司
|
||||||
杭州牛之云科技有限公司
|
杭州牛之云科技有限公司
|
||||||
提供技术支持
|
提供技术支持
|
||||||
@ -8,4 +8,4 @@ VITE_IMG_DOMAIN=''
|
|||||||
VITE_REQUEST_HEADER_TOKEN_KEY='token'
|
VITE_REQUEST_HEADER_TOKEN_KEY='token'
|
||||||
|
|
||||||
# 请求时header中站点的参数名
|
# 请求时header中站点的参数名
|
||||||
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
|
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
|
||||||
@ -1,143 +0,0 @@
|
|||||||
# NiuCloud Admin 开发规范
|
|
||||||
|
|
||||||
## 技术栈区分
|
|
||||||
|
|
||||||
本项目采用前后端分离架构,包含两个主要前端部分:
|
|
||||||
|
|
||||||
1. **PC端后台管理系统**
|
|
||||||
- 框架: Vue 3 + TypeScript + Vite
|
|
||||||
- UI组件库: Element Plus
|
|
||||||
- 状态管理: Pinia
|
|
||||||
- 样式处理: SCSS + Tailwind CSS
|
|
||||||
|
|
||||||
2. **移动端应用**
|
|
||||||
- 框架: uni-app + Vue 3 + TypeScript
|
|
||||||
- UI组件库: uview-plus
|
|
||||||
- 状态管理: Pinia
|
|
||||||
- 样式处理: SCSS + Windi CSS
|
|
||||||
|
|
||||||
## 关键组件使用规范
|
|
||||||
|
|
||||||
### 消息提示组件
|
|
||||||
|
|
||||||
**重要注意事项:请根据开发平台选择正确的消息提示组件!**
|
|
||||||
|
|
||||||
#### PC端 (admin目录)
|
|
||||||
- **必须使用Element Plus的消息提示组件**,而不是uni-app的方法
|
|
||||||
- 主要组件包括:`ElMessage`、`ElMessageBox`、`ElNotification`等
|
|
||||||
- 导入方式:`import { ElMessage, ElMessageBox } from 'element-plus'`
|
|
||||||
- 使用示例:
|
|
||||||
```typescript
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
|
|
||||||
// 成功消息
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
|
|
||||||
// 错误消息
|
|
||||||
ElMessage.error('操作失败')
|
|
||||||
|
|
||||||
// 确认对话框
|
|
||||||
ElMessageBox.confirm('确定要执行此操作吗?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
// 用户点击确认后的逻辑
|
|
||||||
}).catch(() => {
|
|
||||||
// 用户点击取消后的逻辑
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 移动端 (uni-app目录)
|
|
||||||
- **使用uni-app提供的API**进行消息提示
|
|
||||||
- 主要方法包括:`uni.showToast`、`uni.showModal`、`uni.showLoading`等
|
|
||||||
- 使用示例:
|
|
||||||
```typescript
|
|
||||||
// 成功提示
|
|
||||||
uni.showToast({
|
|
||||||
title: '操作成功',
|
|
||||||
icon: 'success',
|
|
||||||
duration: 2000
|
|
||||||
})
|
|
||||||
|
|
||||||
// 模态对话框
|
|
||||||
uni.showModal({
|
|
||||||
title: '提示',
|
|
||||||
content: '确定要执行此操作吗?',
|
|
||||||
success: (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
// 用户点击确认后的逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## API请求规范
|
|
||||||
|
|
||||||
### PC端API请求
|
|
||||||
- 使用`@/utils/request.ts`封装的请求工具
|
|
||||||
- 支持`showSuccessMessage`和`showErrorMessage`选项控制消息显示
|
|
||||||
- 示例:
|
|
||||||
```typescript
|
|
||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
// GET请求
|
|
||||||
export function getOrderList(params: Record<string, any>) {
|
|
||||||
return request.get('order/list', params)
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST请求(带成功消息)
|
|
||||||
export function createOrder(params: Record<string, any>) {
|
|
||||||
return request.post('order/create', params, { showSuccessMessage: true })
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 移动端API请求
|
|
||||||
- 使用uni-app的`uni.request`或封装的请求工具
|
|
||||||
- 示例:
|
|
||||||
```typescript
|
|
||||||
// 发送请求
|
|
||||||
uni.request({
|
|
||||||
url: 'https://example.com/api/order/list',
|
|
||||||
method: 'GET',
|
|
||||||
data: {
|
|
||||||
page: 1,
|
|
||||||
limit: 10
|
|
||||||
},
|
|
||||||
success: (res) => {
|
|
||||||
// 处理成功响应
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
// 处理请求失败
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## 代码风格规范
|
|
||||||
|
|
||||||
1. **文件命名**
|
|
||||||
- 组件文件:PascalCase,如 `OrderList.vue`
|
|
||||||
- 普通文件:kebab-case 或 camelCase,如 `api-service.ts` 或 `commonUtils.ts`
|
|
||||||
|
|
||||||
2. **TypeScript规范**
|
|
||||||
- 为函数参数、返回值和重要变量添加明确的类型注解
|
|
||||||
- 使用接口 (interface) 定义复杂数据结构
|
|
||||||
- 避免 `any` 类型的滥用
|
|
||||||
|
|
||||||
3. **Vue组件规范**
|
|
||||||
- 使用 Vue 3 Composition API 和 `<script setup lang="ts">` 语法
|
|
||||||
- 组件样式建议使用 scoped 属性或 CSS Modules
|
|
||||||
|
|
||||||
## 国际化规范
|
|
||||||
|
|
||||||
- PC端使用Vue I18n进行国际化,语言文件位于`src/lang`目录
|
|
||||||
- 移动端同样使用Vue I18n,语言文件位于`src/app/locale`目录
|
|
||||||
- 使用`t('key')`函数获取翻译文本
|
|
||||||
|
|
||||||
## 其他重要规范
|
|
||||||
|
|
||||||
- 严格遵循RESTful API设计规范
|
|
||||||
- 统一处理API响应数据和错误情况
|
|
||||||
- 代码提交前确保通过TypeScript类型检查
|
|
||||||
- 组件开发遵循高内聚低耦合原则
|
|
||||||
- 优先复用现有组件和工具函数
|
|
||||||
1
admin/auto-imports.d.ts
vendored
1
admin/auto-imports.d.ts
vendored
@ -1,7 +1,6 @@
|
|||||||
// Generated by 'unplugin-auto-import'
|
// Generated by 'unplugin-auto-import'
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
|
||||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||||
const ElNotification: typeof import('element-plus/es')['ElNotification']
|
const ElNotification: typeof import('element-plus/es')['ElNotification']
|
||||||
}
|
}
|
||||||
|
|||||||
18
admin/components.d.ts
vendored
18
admin/components.d.ts
vendored
@ -21,22 +21,16 @@ declare module '@vue/runtime-core' {
|
|||||||
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||||
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
|
||||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
|
||||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
|
||||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||||
@ -44,27 +38,19 @@ declare module '@vue/runtime-core' {
|
|||||||
ElImage: typeof import('element-plus/es')['ElImage']
|
ElImage: typeof import('element-plus/es')['ElImage']
|
||||||
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
|
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
|
||||||
ElLink: typeof import('element-plus/es')['ElLink']
|
|
||||||
ElMain: typeof import('element-plus/es')['ElMain']
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
ElOptionGroup: typeof import('element-plus/es')['ElOptionGroup']
|
|
||||||
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
|
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
|
||||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
|
||||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
|
||||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
|
||||||
ElResult: typeof import('element-plus/es')['ElResult']
|
ElResult: typeof import('element-plus/es')['ElResult']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||||
ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
|
ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
|
||||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
|
||||||
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||||
ElStep: typeof import('element-plus/es')['ElStep']
|
ElStep: typeof import('element-plus/es')['ElStep']
|
||||||
ElSteps: typeof import('element-plus/es')['ElSteps']
|
ElSteps: typeof import('element-plus/es')['ElSteps']
|
||||||
@ -77,14 +63,12 @@ declare module '@vue/runtime-core' {
|
|||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||||
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
|
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
ElTree: typeof import('element-plus/es')['ElTree']
|
|
||||||
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
|
||||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||||
ExportSure: typeof import('./src/components/export-sure/index.vue')['default']
|
ExportSure: typeof import('./src/components/export-sure/index.vue')['default']
|
||||||
HeatMap: typeof import('./src/components/heat-map/index.vue')['default']
|
HeatMap: typeof import('./src/components/heat-map/index.vue')['default']
|
||||||
Icon: typeof import('./src/components/icon/index.vue')['default']
|
Icon: typeof import('./src/components/icon/index.vue')['default']
|
||||||
|
MapSelector: typeof import('./src/components/map-selector/index.vue')['default']
|
||||||
Markdown: typeof import('./src/components/markdown/index.vue')['default']
|
Markdown: typeof import('./src/components/markdown/index.vue')['default']
|
||||||
PopoverInput: typeof import('./src/components/popover-input/index.vue')['default']
|
PopoverInput: typeof import('./src/components/popover-input/index.vue')['default']
|
||||||
RangeInput: typeof import('./src/components/range-input/index.vue')['default']
|
RangeInput: typeof import('./src/components/range-input/index.vue')['default']
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 && vite build && node publish.cjs",
|
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build && node publish.cjs",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export function installAddon(params: Record<string, any>) {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function cloudInstallAddon(params: Record<string, any>) {
|
export function cloudInstallAddon(params: Record<string, any>) {
|
||||||
return request.post(`addon/cloudinstall/${ params.addon }`, params)
|
return request.post(`addon/cloudinstall/${ params.addon }`, params, { showErrorMessage: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,36 +1,90 @@
|
|||||||
|
import axios from 'axios'
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
import storage from '@/utils/storage'
|
||||||
|
|
||||||
/**
|
export const CLOUD_COMPILE_BASE_URL = 'http://go.site.niucloud.com'
|
||||||
* 云编译
|
|
||||||
*/
|
function createCloudCompileRequest() {
|
||||||
export function cloudBuild() {
|
const instance = axios.create({
|
||||||
return request.post('niucloud/build', {})
|
baseURL: CLOUD_COMPILE_BASE_URL,
|
||||||
|
timeout: 0,
|
||||||
|
headers: {
|
||||||
|
'lang': storage.get('lang') ?? 'zh-cn'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
instance.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const token = storage.get('token')
|
||||||
|
if (token) {
|
||||||
|
config.headers['token'] = token
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudCompileRequest = createCloudCompileRequest()
|
||||||
|
|
||||||
|
export function cloudBuild() {
|
||||||
|
return request.post('niucloud/build')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取云编译任务
|
|
||||||
*/
|
|
||||||
export function getCloudBuildTask() {
|
export function getCloudBuildTask() {
|
||||||
return request.get('niucloud/build')
|
return request.get('niucloud/build')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 云编译前检测
|
|
||||||
*/
|
|
||||||
export function getCloudBuildLog() {
|
export function getCloudBuildLog() {
|
||||||
return request.get('niucloud/build/log')
|
return request.get('niucloud/build/log')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除
|
|
||||||
*/
|
|
||||||
export function clearCloudBuildTask() {
|
export function clearCloudBuildTask() {
|
||||||
return request.post('niucloud/build/clear')
|
return request.post('niucloud/build/clear', {}, { showErrorMessage: false, showSuccessMessage: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 云编译前检测
|
|
||||||
*/
|
|
||||||
export function preBuildCheck() {
|
export function preBuildCheck() {
|
||||||
return request.get('niucloud/build/check')
|
return request.get('niucloud/build/check')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCloudBuildQueuePosition(taskId: string) {
|
||||||
|
return cloudCompileRequest.get('/cloud/queue_position', { params: { task_id: taskId } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCloudBuildSseUrl(taskId: string): string {
|
||||||
|
return `${CLOUD_COMPILE_BASE_URL}/cloud/sse?task_id=${taskId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCloudCompileLocalUrl() {
|
||||||
|
return request.get('niucloud/build/get_local_url')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCloudCompileLocalUrl(url: string) {
|
||||||
|
return request.post('niucloud/build/set_local_url', { url })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startServerDownload(taskId: string, downloadUrl: string, authorizeCode: string, timestamp: string) {
|
||||||
|
return request.post('niucloud/build/start_server_download', {
|
||||||
|
task_id: taskId,
|
||||||
|
download_url: downloadUrl,
|
||||||
|
authorize_code: authorizeCode,
|
||||||
|
timestamp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSseBuildLog(taskId: string) {
|
||||||
|
return request.get('niucloud/build/get_sse_build_log', { params: { task_id: taskId } })
|
||||||
|
}
|
||||||
|
|||||||
@ -629,8 +629,8 @@ export function setMap(params: Record<string, any>) {
|
|||||||
/**
|
/**
|
||||||
* 获取地图配置
|
* 获取地图配置
|
||||||
*/
|
*/
|
||||||
export function getMap() {
|
export function getMap(params: Record<string, any>) {
|
||||||
return request.get(`sys/config/map`)
|
return request.get(`sys/config/map`, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -77,6 +77,15 @@ export function getWeappUploadLog(key: string) {
|
|||||||
return request.get(`weapp/upload/${ key }`)
|
return request.get(`weapp/upload/${ key }`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接获取小程序上传日志(不更新状态)
|
||||||
|
* @param key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function fetchWeappUploadLog(key: string) {
|
||||||
|
return request.get(`weapp/upload_log/${ key }`)
|
||||||
|
}
|
||||||
|
|
||||||
/***************************************************** 管理端 ****************************************************/
|
/***************************************************** 管理端 ****************************************************/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -70,7 +70,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end mt-[20px]" v-show="cloudBuildTask">
|
<div class="flex justify-end mt-[20px]" v-show="cloudBuildTask">
|
||||||
<el-button @click="dialogCancel()" class="!w-[90px]">取消</el-button>
|
<el-button @click="dialogCancel()" class="!w-[90px]">取消</el-button>
|
||||||
<el-button type="primary" :loading="timeloading" class="!w-[140px]">已用时 {{ formattedDuration }}</el-button>
|
<el-button type="primary" :loading="timeloading" class="!w-[140px]" v-if="!errorInfo">已用时 {{ formattedDuration }}</el-button>
|
||||||
|
<el-button type="primary" @click="active = 'error'" v-if="errorInfo">下一步</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="active == 'error'">
|
<div v-show="active == 'error'">
|
||||||
@ -81,10 +82,18 @@
|
|||||||
<img src="@/app/assets/images/error_icon.png" alt="">
|
<img src="@/app/assets/images/error_icon.png" alt="">
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<el-scrollbar class="max-h-[150px] !overflow-auto text-[15px] text-[#4F516D] mb-[15px] mt-[-15px]">
|
<el-scrollbar class="max-h-[150px] !overflow-auto text-[15px] text-[#4F516D] mb-[15px] mt-[-15px]" v-if="errorInfo">
|
||||||
{{errorInfo}}
|
{{errorInfo}}
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
|
<el-alert :closable="false" class="!mb-[15px] !w-full" v-if="errorAnalysis.analysis" type="warning">
|
||||||
|
<template #default>
|
||||||
|
<div class="text-left">
|
||||||
|
错误分析:{{ errorAnalysis.analysis }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
<el-button @click="handleReturn" class="!w-[90px]">错误信息</el-button>
|
<el-button @click="handleReturn" class="!w-[90px]">错误信息</el-button>
|
||||||
|
<el-button @click="againBuild" type="primary" plain class="!w-[90px]" v-if="errorAnalysis.error_addon">重新编译</el-button>
|
||||||
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-result>
|
</el-result>
|
||||||
@ -106,29 +115,358 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-show="active == 'again_build'">
|
||||||
|
<div class="h-[50vh] flex flex-col">
|
||||||
|
<div class="flex-1 h-0 flex items-center flex-col">
|
||||||
|
<el-table
|
||||||
|
ref="tableRef"
|
||||||
|
:data="installedAddonList"
|
||||||
|
row-key="key"
|
||||||
|
size="large"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column label="应用信息" align="left" width="300">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex items-center cursor-pointer relative left-[-10px]">
|
||||||
|
<el-image class="w-[54px] h-[54px] rounded-[5px]" :src="row.icon" fit="contain">
|
||||||
|
<template #error>
|
||||||
|
<div class="flex items-center w-full h-full rounded-[5px]">
|
||||||
|
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon-one.png" alt="" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
<div class="flex-1 w-0 flex flex-col justify-center pl-[20px] font-500 text-[13px]">
|
||||||
|
<div class="w-[236px] truncate leading-[18px]">{{ row.title }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="编译结果" align="left">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex items-center" v-if="errorAnalysis.error_addon && errorAnalysis.error_addon != row.key">
|
||||||
|
<el-icon class="text-success mr-1"><SuccessFilled /></el-icon> 编译成功
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex items-center">
|
||||||
|
<el-icon class="text-error mr-1"><WarningFilled /></el-icon> 编译失败,请排除该插件后重新进行编译
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end mt-[20px]">
|
||||||
|
<el-button @click="dialogCancel()" class="!w-[90px]">取消</el-button>
|
||||||
|
<el-button @click="active = 'error'" plain type="primary" class="!w-[90px]">上一步</el-button>
|
||||||
|
<el-button type="primary" @click="againBuild" :loading="loading" class="!w-[100px]">开始编译</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, h, watch, computed } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import { t } from '@/lang'
|
import { t } from '@/lang'
|
||||||
import { getCloudBuildLog, getCloudBuildTask, cloudBuild, clearCloudBuildTask, preBuildCheck } from '@/app/api/cloud'
|
import { getCloudBuildLog, getCloudBuildTask, cloudBuild, clearCloudBuildTask, preBuildCheck, getCloudBuildQueuePosition, getCloudBuildSseUrl, CLOUD_COMPILE_BASE_URL, startServerDownload, getSseBuildLog } from '@/app/api/cloud'
|
||||||
|
import { getInstalledAddonList } from '@/app/api/addon'
|
||||||
import { Terminal, TerminalFlash } from 'vue-web-terminal'
|
import { Terminal, TerminalFlash } from 'vue-web-terminal'
|
||||||
import 'vue-web-terminal/lib/theme/dark.css'
|
import 'vue-web-terminal/lib/theme/dark.css'
|
||||||
import { AnyObject } from '@/types/global'
|
import { AnyObject } from '@/types/global'
|
||||||
import { ElNotification, ElMessageBox } from 'element-plus'
|
import {ElNotification, ElMessageBox, ElMessage} from 'element-plus'
|
||||||
|
|
||||||
const showDialog = ref<boolean>(false)
|
const showDialog = ref<boolean>(false)
|
||||||
const terminalId = ref(Date.now());
|
const terminalId = ref(Date.now())
|
||||||
const cloudBuildTask = ref<null | AnyObject>(null)
|
const cloudBuildTask = ref<null | AnyObject>(null)
|
||||||
const active = ref('build')
|
const active = ref('build')
|
||||||
const cloudBuildCheck = ref<null | AnyObject>(null)
|
const cloudBuildCheck = ref<null | AnyObject>(null)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const terminalRef = ref(null)
|
const terminalRef = ref(null)
|
||||||
|
const selectAddon = ref([])
|
||||||
|
const installedAddonList = ref([])
|
||||||
|
const tableRef = ref(null)
|
||||||
|
|
||||||
|
getInstalledAddonList().then(({ data }) => {
|
||||||
|
installedAddonList.value = Object.values(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSelectionChange = (rows) => {
|
||||||
|
selectAddon.value = rows.map(row => {
|
||||||
|
return row.key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let cloudBuildLog = []
|
let cloudBuildLog = []
|
||||||
|
let eventSource: EventSource | null = null
|
||||||
|
|
||||||
|
interface SSEMessage {
|
||||||
|
type: string
|
||||||
|
task_id?: string
|
||||||
|
percent?: number
|
||||||
|
action?: string
|
||||||
|
msg?: string
|
||||||
|
code?: string
|
||||||
|
time?: string
|
||||||
|
download_url?: string
|
||||||
|
download_percent?: number
|
||||||
|
downloaded_bytes?: number
|
||||||
|
total_bytes?: number
|
||||||
|
authorize_code?: string
|
||||||
|
timestamp?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectSSE = (taskId: string) => {
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = getCloudBuildSseUrl(taskId)
|
||||||
|
eventSource = new EventSource(url)
|
||||||
|
|
||||||
|
eventSource.onopen = () => {
|
||||||
|
console.log('SSE connected')
|
||||||
|
}
|
||||||
|
|
||||||
|
eventSource.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse(event.data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('SSE parse error:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventSource.addEventListener('progress', (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse((event as MessageEvent).data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('SSE progress parse error:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.addEventListener('complete', (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse((event as MessageEvent).data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('SSE complete parse error:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.addEventListener('failed', (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse((event as MessageEvent).data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('SSE failed parse error:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.addEventListener('heartbeat', () => {
|
||||||
|
console.log('SSE heartbeat')
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.addEventListener('download_start', (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse((event as MessageEvent).data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('SSE download_start parse error:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.addEventListener('download_progress', (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse((event as MessageEvent).data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('SSE download_progress parse error:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.addEventListener('download_complete', (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse((event as MessageEvent).data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('SSE download_complete parse error:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.onerror = (error) => {
|
||||||
|
console.error('SSE error:', error)
|
||||||
|
setTimeout(() => {
|
||||||
|
if (showDialog.value && cloudBuildTask.value) {
|
||||||
|
getCloudBuildLogFn()
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSSEMessage = (data: SSEMessage) => {
|
||||||
|
if (!showDialog.value) return
|
||||||
|
|
||||||
|
if (data.type === 'progress' && data.action) {
|
||||||
|
if (!cloudBuildLog.includes(data.action)) {
|
||||||
|
if (cloudBuildLog.length === 0) {
|
||||||
|
const storedTime = localStorage.getItem('cloud_build_start_time')
|
||||||
|
if (storedTime) {
|
||||||
|
buildStartTime.value = Number(storedTime)
|
||||||
|
} else {
|
||||||
|
const now = Date.now()
|
||||||
|
buildStartTime.value = now
|
||||||
|
localStorage.setItem('cloud_build_start_time', String(now))
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||||
|
buildTimer && clearInterval(buildTimer)
|
||||||
|
buildTimer = setInterval(() => {
|
||||||
|
if (buildStartTime.value) {
|
||||||
|
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
terminalRef.value.execute('开始编译')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.action.indexOf('云编译任务正在排队') != -1) {
|
||||||
|
cloudQueue = data.action
|
||||||
|
cloudBuildLog.push(data.action)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cloudQueue = null
|
||||||
|
}
|
||||||
|
|
||||||
|
terminalRef.value.pushMessage({ content: `${data.action}` })
|
||||||
|
cloudBuildLog.push(data.action)
|
||||||
|
|
||||||
|
if (data.code === '0' && data.msg) {
|
||||||
|
errorAnalysis.value = {}
|
||||||
|
errorInfo.value = data.msg
|
||||||
|
terminalRef.value.pushMessage({ content: data.msg, class: 'error' })
|
||||||
|
timeloading.value = false
|
||||||
|
active.value = 'error'
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
clearCloudBuildTask()
|
||||||
|
buildTimer && clearInterval(buildTimer)
|
||||||
|
buildTimer = null
|
||||||
|
localStorage.removeItem('cloud_build_start_time')
|
||||||
|
localStorage.removeItem('cloud_build_task')
|
||||||
|
closeSSE()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (data.type === 'complete') {
|
||||||
|
terminalRef.value.pushMessage({ content: '编译完成' })
|
||||||
|
} else if (data.type === 'failed') {
|
||||||
|
errorInfo.value = data.msg || '编译失败'
|
||||||
|
active.value = 'error'
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
clearCloudBuildTask()
|
||||||
|
buildTimer && clearInterval(buildTimer)
|
||||||
|
buildTimer = null
|
||||||
|
localStorage.removeItem('cloud_build_start_time')
|
||||||
|
localStorage.removeItem('cloud_build_task')
|
||||||
|
closeSSE()
|
||||||
|
} else if (data.type === 'download_start') {
|
||||||
|
if (data.action && !cloudBuildLog.includes(data.action)) {
|
||||||
|
terminalRef.value.pushMessage({ content: data.action })
|
||||||
|
cloudBuildLog.push(data.action)
|
||||||
|
}
|
||||||
|
} else if (data.type === 'download_progress') {
|
||||||
|
const percent = data.download_percent || 0
|
||||||
|
const downloaded = data.downloaded_bytes || 0
|
||||||
|
const total = data.total_bytes || 0
|
||||||
|
const downloadedMB = (downloaded / 1024 / 1024).toFixed(2)
|
||||||
|
const totalMB = (total / 1024 / 1024).toFixed(2)
|
||||||
|
terminalRef.value.pushMessage({ content: `下载进度: ${percent}% (${downloadedMB}MB / ${totalMB}MB)` })
|
||||||
|
} else if (data.type === 'download_complete') {
|
||||||
|
if (data.action && !cloudBuildLog.includes(data.action)) {
|
||||||
|
terminalRef.value.pushMessage({ content: data.action })
|
||||||
|
cloudBuildLog.push(data.action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.download_url && data.task_id) {
|
||||||
|
startServerDownloadFn(data.task_id, data.download_url, data.authorize_code, data.timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let downloadPollingTimer: number | null = null
|
||||||
|
|
||||||
|
const startServerDownloadFn = async (taskId: string, downloadUrl: string, authorizeCode: string, timestamp: string) => {
|
||||||
|
try {
|
||||||
|
terminalRef.value.pushMessage({ content: '正在启动后台下载...' })
|
||||||
|
|
||||||
|
const parseUrl = new URL(downloadUrl, CLOUD_COMPILE_BASE_URL)
|
||||||
|
const authorize_code = parseUrl.searchParams.get('authorize_code') || authorizeCode || ''
|
||||||
|
const ts = parseUrl.searchParams.get('timestamp') || timestamp || ''
|
||||||
|
|
||||||
|
await startServerDownload(taskId, downloadUrl, authorize_code, ts)
|
||||||
|
|
||||||
|
downloadPollingTimer = setTimeout(async () => {
|
||||||
|
if (!downloadPollingTimer) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getSseBuildLog(taskId)
|
||||||
|
if (res.code === 1) {
|
||||||
|
const { status, percent, msg } = res.data
|
||||||
|
|
||||||
|
if (status === 'downloading') {
|
||||||
|
terminalRef.value.pushMessage({ content: `下载进度: ${percent}% - ${msg}` })
|
||||||
|
} else if (status === 'unzipping') {
|
||||||
|
terminalRef.value.pushMessage({ content: msg })
|
||||||
|
} else if (status === 'completed') {
|
||||||
|
terminalRef.value.pushMessage({ content: '部署完成!' })
|
||||||
|
downloadPollingTimer && clearInterval(downloadPollingTimer)
|
||||||
|
downloadPollingTimer = null
|
||||||
|
active.value = 'complete'
|
||||||
|
timeloading.value = false
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
clearCloudBuildTask()
|
||||||
|
buildTimer && clearInterval(buildTimer)
|
||||||
|
buildTimer = null
|
||||||
|
localStorage.removeItem('cloud_build_start_time')
|
||||||
|
localStorage.removeItem('cloud_build_task')
|
||||||
|
closeSSE()
|
||||||
|
ElMessage.success('编译部署完成')
|
||||||
|
} else if (status === 'error') {
|
||||||
|
terminalRef.value.pushMessage({ content: `错误: ${msg}`, class: 'error' })
|
||||||
|
downloadPollingTimer && clearInterval(downloadPollingTimer)
|
||||||
|
downloadPollingTimer = null
|
||||||
|
errorInfo.value = msg
|
||||||
|
active.value = 'error'
|
||||||
|
timeloading.value = false
|
||||||
|
clearCloudBuildTask()
|
||||||
|
closeSSE()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
terminalRef.value.pushMessage({ content: '获取下载进度失败', class: 'error' })
|
||||||
|
downloadPollingTimer && clearInterval(downloadPollingTimer)
|
||||||
|
downloadPollingTimer = null
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Polling error:', e)
|
||||||
|
}
|
||||||
|
}, 2000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启动后台下载失败:', error)
|
||||||
|
terminalRef.value.pushMessage({ content: '启动后台下载失败', class: 'error' })
|
||||||
|
errorInfo.value = '启动后台下载失败'
|
||||||
|
active.value = 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSSE = () => {
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close()
|
||||||
|
eventSource = null
|
||||||
|
}
|
||||||
|
if (downloadPollingTimer) {
|
||||||
|
clearInterval(downloadPollingTimer)
|
||||||
|
downloadPollingTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 计时器相关
|
|
||||||
const buildStartTime = ref<number | null>(null)
|
const buildStartTime = ref<number | null>(null)
|
||||||
const buildDuration = ref<number>(0)
|
const buildDuration = ref<number>(0)
|
||||||
let buildTimer: number | null = null
|
let buildTimer: number | null = null
|
||||||
@ -157,6 +495,7 @@ const getCloudBuildTaskFn = () => {
|
|||||||
getCloudBuildTaskFn()
|
getCloudBuildTaskFn()
|
||||||
const errorInfo = ref('')
|
const errorInfo = ref('')
|
||||||
const timeloading = ref(false)
|
const timeloading = ref(false)
|
||||||
|
const errorAnalysis = ref({})
|
||||||
const getCloudBuildLogFn = () => {
|
const getCloudBuildLogFn = () => {
|
||||||
timeloading.value = true
|
timeloading.value = true
|
||||||
getCloudBuildLog().then(res => {
|
getCloudBuildLog().then(res => {
|
||||||
@ -202,10 +541,18 @@ const getCloudBuildLogFn = () => {
|
|||||||
|
|
||||||
data[0].forEach(item => {
|
data[0].forEach(item => {
|
||||||
if (!cloudBuildLog.includes(item.action)) {
|
if (!cloudBuildLog.includes(item.action)) {
|
||||||
|
if (item.action.indexOf('云编译任务正在排队') != -1) {
|
||||||
|
cloudQueue = item.action
|
||||||
|
cloudBuildLog.push(item.action)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cloudQueue = null
|
||||||
|
}
|
||||||
terminalRef.value.pushMessage({ content: `${item.action}` })
|
terminalRef.value.pushMessage({ content: `${item.action}` })
|
||||||
cloudBuildLog.push(item.action)
|
cloudBuildLog.push(item.action)
|
||||||
|
|
||||||
if (item.code == 0) {
|
if (item.code === '0') {
|
||||||
|
errorAnalysis.value = res.data.error_analysis || {}
|
||||||
error = item.msg
|
error = item.msg
|
||||||
terminalRef.value.pushMessage({ content: item.msg, class: 'error' })
|
terminalRef.value.pushMessage({ content: item.msg, class: 'error' })
|
||||||
timeloading.value = false
|
timeloading.value = false
|
||||||
@ -234,7 +581,7 @@ const getCloudBuildLogFn = () => {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getCloudBuildLogFn()
|
getCloudBuildLogFn()
|
||||||
}, 2000)
|
}, 5000)
|
||||||
}).catch()
|
}).catch()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,10 +618,17 @@ const open = async () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
active.value = 'build'
|
active.value = 'build'
|
||||||
closeType.value = 'normal'
|
closeType.value = 'normal'
|
||||||
|
cloudBuildLog = []
|
||||||
|
|
||||||
if (cloudBuildTask.value) {
|
if (cloudBuildTask.value) {
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
loading.value = false
|
loading.value = false
|
||||||
getCloudBuildLogFn()
|
const taskId = (cloudBuildTask.value as any).task_id
|
||||||
|
if (taskId) {
|
||||||
|
connectSSE(taskId)
|
||||||
|
} else {
|
||||||
|
getCloudBuildLogFn()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,7 +638,12 @@ const open = async () => {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
cloudBuildTask.value = data
|
cloudBuildTask.value = data
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
getCloudBuildLogFn()
|
const taskId = data.task_id
|
||||||
|
if (taskId) {
|
||||||
|
connectSSE(taskId)
|
||||||
|
} else {
|
||||||
|
getCloudBuildLogFn()
|
||||||
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
showDialog.value = false
|
showDialog.value = false
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@ -294,22 +653,80 @@ const open = async () => {
|
|||||||
cloudBuildCheck.value = data
|
cloudBuildCheck.value = data
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
}
|
}
|
||||||
|
}).catch((e) => {
|
||||||
|
loading.value = false
|
||||||
|
showDialog.value = false
|
||||||
|
if (e.code && e.code == 601) {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
'云编译服务未启动,必须在启动后进行云编译!',
|
||||||
|
'提示',
|
||||||
|
{
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: '重新检测',
|
||||||
|
cancelButtonText: '查看操作手册',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
|
open()
|
||||||
|
}).catch((action) => {
|
||||||
|
action == 'cancel' && window.open('https://doc.press.niucloud.com/php/saas-framework/use/other/third-party-cloud-compilation.html', '_blank')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ElMessage({ message: e.msg, type: 'error' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const againBuild = () => {
|
||||||
|
if (active.value != 'again_build') {
|
||||||
|
active.value = 'again_build'
|
||||||
|
selectAddon.value = installedAddonList.value.map((item) => {
|
||||||
|
tableRef.value.toggleRowSelection(item, true)
|
||||||
|
return item.key
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
cloudBuildLog = []
|
||||||
|
cloudBuild().then(({ data }) => {
|
||||||
|
active.value = 'build'
|
||||||
|
loading.value = false
|
||||||
|
cloudBuildTask.value = data
|
||||||
|
showDialog.value = true
|
||||||
|
localStorage.removeItem('cloud_build_start_time')
|
||||||
|
const taskId = data.task_id
|
||||||
|
if (taskId) {
|
||||||
|
connectSSE(taskId)
|
||||||
|
} else {
|
||||||
|
getCloudBuildLogFn()
|
||||||
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
showDialog.value = false
|
showDialog.value = false
|
||||||
|
loading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectable = (row: any) => {
|
||||||
|
return selectAddon.value.includes(row.key)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 升级进度动画
|
* 升级进度动画
|
||||||
*/
|
*/
|
||||||
let flashInterval: null | number = null
|
let flashInterval: null | number = null
|
||||||
const terminalFlash = new TerminalFlash()
|
let terminalFlash = null
|
||||||
|
let cloudQueue = null
|
||||||
const onExecCmd = (key, command, success, failed, name) => {
|
const onExecCmd = (key, command, success, failed, name) => {
|
||||||
if (command == '开始编译') {
|
if (command == '开始编译') {
|
||||||
|
terminalFlash = new TerminalFlash()
|
||||||
success(terminalFlash)
|
success(terminalFlash)
|
||||||
const frames = makeIterator(['/', '——', '\\', '|'])
|
const frames = makeIterator(['/', '——', '\\', '|'])
|
||||||
flashInterval = setInterval(() => {
|
flashInterval = setInterval(() => {
|
||||||
terminalFlash.flush('> ' + frames.next().value)
|
if (cloudQueue) {
|
||||||
|
terminalFlash.flush(cloudQueue + '<br>> ' + frames.next().value)
|
||||||
|
} else {
|
||||||
|
terminalFlash.flush('> ' + frames.next().value)
|
||||||
|
}
|
||||||
}, 150)
|
}, 150)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,6 +744,7 @@ const makeIterator = (array: string[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dialogClose = (done: () => {}) => {
|
const dialogClose = (done: () => {}) => {
|
||||||
|
closeSSE()
|
||||||
if (active.value == 'build' && cloudBuildTask.value && closeType.value == 'normal') {
|
if (active.value == 'build' && cloudBuildTask.value && closeType.value == 'normal') {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
t('cloudbuild.showDialogCloseTips'),
|
t('cloudbuild.showDialogCloseTips'),
|
||||||
@ -352,6 +770,7 @@ const dialogClose = (done: () => {}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dialogCancel = () => {
|
const dialogCancel = () => {
|
||||||
|
closeSSE()
|
||||||
if (active.value == 'build' && cloudBuildTask.value && closeType.value == 'normal') {
|
if (active.value == 'build' && cloudBuildTask.value && closeType.value == 'normal') {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
t('cloudbuild.showDialogCloseTips'),
|
t('cloudbuild.showDialogCloseTips'),
|
||||||
@ -382,10 +801,12 @@ const cloudBuildCheckDirFn = () => {
|
|||||||
|
|
||||||
watch(() => showDialog.value, () => {
|
watch(() => showDialog.value, () => {
|
||||||
if (!showDialog.value) {
|
if (!showDialog.value) {
|
||||||
|
closeSSE()
|
||||||
cloudBuildTask.value = null
|
cloudBuildTask.value = null
|
||||||
active.value = 'build'
|
active.value = 'build'
|
||||||
cloudBuildLog = []
|
cloudBuildLog = []
|
||||||
flashInterval && clearInterval(flashInterval)
|
flashInterval && clearInterval(flashInterval)
|
||||||
|
terminalFlash && terminalFlash.finish()
|
||||||
buildTimer && clearInterval(buildTimer)
|
buildTimer && clearInterval(buildTimer)
|
||||||
buildStartTime.value = null
|
buildStartTime.value = null
|
||||||
buildDuration.value = 0
|
buildDuration.value = 0
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
441
admin/src/app/components/weappupload/index.vue
Normal file
441
admin/src/app/components/weappupload/index.vue
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="showDialog" title="小程序上传" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="dialogClose">
|
||||||
|
<div v-show="active == 'upload'" class="h-[50vh]" v-loading="loading">
|
||||||
|
<div class="h-[45vh]" v-show="cloudBuildTask">
|
||||||
|
<terminal ref="terminalRef" :name="`weappupload-${terminalId}`" context="" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end mt-[20px]" v-show="cloudBuildTask">
|
||||||
|
<el-button @click="dialogCancel()">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="timeloading" class="!w-[140px]" v-if="!errorInfo">已用时 {{ formattedDuration }}</el-button>
|
||||||
|
<el-button type="primary" @click="active = 'error'" v-if="errorInfo">下一步</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="active == 'complete'">
|
||||||
|
<div class="h-[50vh] flex flex-col">
|
||||||
|
<div class="flex-1 h-0 flex justify-center items-center flex-col">
|
||||||
|
<el-result icon="success" title="上传成功">
|
||||||
|
<template #sub-title>
|
||||||
|
<p class="text-[16px]">小程序上传成功,耗时 {{ formattedDuration }}</p>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<el-button type="primary" @click="handleComplete">完成</el-button>
|
||||||
|
</template>
|
||||||
|
</el-result>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="active == 'error'">
|
||||||
|
<div class="h-[50vh] flex flex-col">
|
||||||
|
<div class="flex-1 h-0 flex justify-center items-center flex-col">
|
||||||
|
<el-result icon="error" title="上传失败">
|
||||||
|
<template #extra>
|
||||||
|
<p class="text-[14px] text-red-500 mb-[10px]" v-if="errorInfo">错误信息: {{ errorInfo }}</p>
|
||||||
|
<el-button type="primary" @click="handleErrorNextStep">下一步</el-button>
|
||||||
|
</template>
|
||||||
|
</el-result>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, nextTick, onUnmounted, computed } from 'vue'
|
||||||
|
import { t } from '@/lang'
|
||||||
|
import { getWeappUploadLog, fetchWeappUploadLog } from '@/app/api/weapp'
|
||||||
|
import { CLOUD_COMPILE_BASE_URL } from '@/app/api/cloud'
|
||||||
|
import { Terminal } from 'vue-web-terminal'
|
||||||
|
import 'vue-web-terminal/lib/theme/dark.css'
|
||||||
|
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
const showDialog = ref(false)
|
||||||
|
const terminalId = ref(Date.now())
|
||||||
|
const cloudBuildTask = ref<any>(null)
|
||||||
|
const active = ref('upload')
|
||||||
|
const loading = ref(false)
|
||||||
|
const terminalRef = ref(null)
|
||||||
|
const errorInfo = ref('')
|
||||||
|
const timeloading = ref(false)
|
||||||
|
const errorAnalysis = ref({})
|
||||||
|
|
||||||
|
let uploadLog: any[] = []
|
||||||
|
let eventSource: EventSource | null = null
|
||||||
|
const uploadStartTime = ref<number | null>(null)
|
||||||
|
const uploadDuration = ref(0)
|
||||||
|
let durationTimer: number | null = null
|
||||||
|
|
||||||
|
interface TerminalMessage {
|
||||||
|
content: string
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let messageQueue: TerminalMessage[] = []
|
||||||
|
let messageQueueTimer: number | null = null
|
||||||
|
const MESSAGE_FLUSH_INTERVAL = 300
|
||||||
|
|
||||||
|
const flushMessageQueue = () => {
|
||||||
|
if (!terminalRef.value || messageQueue.length === 0) return
|
||||||
|
while (messageQueue.length > 0) {
|
||||||
|
const msg = messageQueue.shift()
|
||||||
|
if (msg) {
|
||||||
|
terminalRef.value.pushMessage(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const queueMessage = (content: string, className?: string) => {
|
||||||
|
messageQueue.push({ content, class: className })
|
||||||
|
if (messageQueueTimer === null) {
|
||||||
|
messageQueueTimer = window.setTimeout(() => {
|
||||||
|
flushMessageQueue()
|
||||||
|
messageQueueTimer = null
|
||||||
|
}, MESSAGE_FLUSH_INTERVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearMessageQueue = () => {
|
||||||
|
messageQueue = []
|
||||||
|
if (messageQueueTimer !== null) {
|
||||||
|
clearTimeout(messageQueueTimer)
|
||||||
|
messageQueueTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedDuration = computed(() => {
|
||||||
|
const seconds = uploadDuration.value
|
||||||
|
const mins = Math.floor(seconds / 60)
|
||||||
|
const secs = seconds % 60
|
||||||
|
return mins > 0 ? `${mins}分${secs}秒` : `${secs}秒`
|
||||||
|
})
|
||||||
|
|
||||||
|
const onExecCmd = (cmd: string) => {
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWeappSseUrl = (taskId: string): string => {
|
||||||
|
return `${CLOUD_COMPILE_BASE_URL}/cloud/weapp_sse?task_id=${taskId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SSEMessage {
|
||||||
|
type: string
|
||||||
|
task_id?: string
|
||||||
|
percent?: number
|
||||||
|
action?: string
|
||||||
|
msg?: string
|
||||||
|
code?: string
|
||||||
|
time?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectSSE = (taskId: string) => {
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = getWeappSseUrl(taskId)
|
||||||
|
eventSource = new EventSource(url)
|
||||||
|
|
||||||
|
eventSource.onopen = () => {
|
||||||
|
console.log('Weapp SSE connected')
|
||||||
|
}
|
||||||
|
|
||||||
|
eventSource.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse(event.data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Weapp SSE parse error:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventSource.addEventListener('progress', (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse((event as MessageEvent).data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Weapp SSE progress parse error:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.addEventListener('complete', (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse((event as MessageEvent).data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Weapp SSE complete parse error:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.addEventListener('failed', (event) => {
|
||||||
|
try {
|
||||||
|
const data: SSEMessage = JSON.parse((event as MessageEvent).data)
|
||||||
|
handleSSEMessage(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Weapp SSE failed parse error:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.onerror = (error) => {
|
||||||
|
console.error('Weapp SSE error:', error)
|
||||||
|
closeSSE()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSSE = () => {
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close()
|
||||||
|
eventSource = null
|
||||||
|
}
|
||||||
|
clearMessageQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
const earlyLogCheck = (taskKey: string): Promise<boolean> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetchWeappUploadLog(taskKey).then(res => {
|
||||||
|
const data = (res.data && res.data.data) ? res.data.data : []
|
||||||
|
if (data[0] && data[0].length) {
|
||||||
|
const last = data[0][data[0].length - 1]
|
||||||
|
if (last.code == 1 && last.percent == 100) {
|
||||||
|
resolve(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (last.code == 0) {
|
||||||
|
resolve(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(false)
|
||||||
|
}).catch(() => {
|
||||||
|
resolve(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSSEMessage = (data: SSEMessage) => {
|
||||||
|
if (!showDialog.value) return
|
||||||
|
|
||||||
|
if (data.type === 'progress' && data.action) {
|
||||||
|
if (!uploadLog.includes(data.action)) {
|
||||||
|
if (uploadLog.length === 0) {
|
||||||
|
const now = Date.now()
|
||||||
|
uploadStartTime.value = now
|
||||||
|
uploadDuration.value = 0
|
||||||
|
durationTimer && clearInterval(durationTimer)
|
||||||
|
durationTimer = setInterval(() => {
|
||||||
|
if (uploadStartTime.value) {
|
||||||
|
uploadDuration.value = Math.floor((Date.now() - uploadStartTime.value) / 1000)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (terminalRef.value) {
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
terminalRef.value.execute('开始上传')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
queueMessage(`${data.action}`)
|
||||||
|
uploadLog.push(data.action)
|
||||||
|
|
||||||
|
if (data.code === '0') {
|
||||||
|
errorInfo.value = data.msg || '上传失败'
|
||||||
|
timeloading.value = false
|
||||||
|
active.value = 'error'
|
||||||
|
nextTick(() => {
|
||||||
|
if (terminalRef.value) {
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
closeSSE()
|
||||||
|
uploadDurationTimerClear()
|
||||||
|
if (props.onError) {
|
||||||
|
props.onError()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.code === '1' && data.percent === 100) {
|
||||||
|
nextTick(() => {
|
||||||
|
if (terminalRef.value) {
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
queueMessage('上传完成')
|
||||||
|
timeloading.value = false
|
||||||
|
active.value = 'complete'
|
||||||
|
closeSSE()
|
||||||
|
uploadDurationTimerClear()
|
||||||
|
ElMessage.success('上传成功')
|
||||||
|
if (props.onSuccess) {
|
||||||
|
props.onSuccess()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (data.type === 'complete') {
|
||||||
|
getWeappUploadLog(data.task_id)
|
||||||
|
queueMessage('上传完成')
|
||||||
|
timeloading.value = false
|
||||||
|
active.value = 'complete'
|
||||||
|
closeSSE()
|
||||||
|
uploadDurationTimerClear()
|
||||||
|
ElMessage.success('上传成功')
|
||||||
|
if (props.onSuccess) {
|
||||||
|
props.onSuccess()
|
||||||
|
}
|
||||||
|
} else if (data.type === 'failed') {
|
||||||
|
errorInfo.value = data.msg || '上传失败'
|
||||||
|
timeloading.value = false
|
||||||
|
active.value = 'error'
|
||||||
|
nextTick(() => {
|
||||||
|
if (terminalRef.value) {
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
closeSSE()
|
||||||
|
uploadDurationTimerClear()
|
||||||
|
if (props.onError) {
|
||||||
|
props.onError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadDurationTimerClear = () => {
|
||||||
|
if (durationTimer) {
|
||||||
|
clearInterval(durationTimer)
|
||||||
|
durationTimer = null
|
||||||
|
}
|
||||||
|
uploadStartTime.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogClose = (done: () => void) => {
|
||||||
|
closeSSE()
|
||||||
|
uploadDurationTimerClear()
|
||||||
|
if (active.value == 'upload' && cloudBuildTask.value) {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
'确定要关闭上传窗口吗?关闭后将停止当前上传任务',
|
||||||
|
'提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
|
done()
|
||||||
|
}).catch(() => { })
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogCancel = () => {
|
||||||
|
showDialog.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleComplete = () => {
|
||||||
|
showDialog.value = false
|
||||||
|
if (props.onSuccess) {
|
||||||
|
props.onSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleErrorNextStep = () => {
|
||||||
|
showDialog.value = false
|
||||||
|
if (props.onError) {
|
||||||
|
props.onError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
onSuccess?: () => void
|
||||||
|
onError?: () => void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const open = async (taskKey: string, task?: any) => {
|
||||||
|
loading.value = true
|
||||||
|
active.value = 'upload'
|
||||||
|
uploadLog = []
|
||||||
|
clearMessageQueue()
|
||||||
|
errorInfo.value = ''
|
||||||
|
errorAnalysis.value = {}
|
||||||
|
timeloading.value = true
|
||||||
|
terminalId.value = Date.now()
|
||||||
|
|
||||||
|
const taskData = task || cloudBuildTask.value
|
||||||
|
|
||||||
|
if (taskData) {
|
||||||
|
cloudBuildTask.value = taskData
|
||||||
|
if (taskData.status == 0) {
|
||||||
|
loading.value = false
|
||||||
|
const isCompleted = await earlyLogCheck(taskData.task_key)
|
||||||
|
if (isCompleted) {
|
||||||
|
closeSSE()
|
||||||
|
getWeappUploadLog(taskData.task_key)
|
||||||
|
showDialog.value = true
|
||||||
|
active.value = 'complete'
|
||||||
|
loading.value = false
|
||||||
|
uploadDuration.value = taskData.update_time - taskData.create_time
|
||||||
|
nextTick(() => {
|
||||||
|
if (terminalRef.value) {
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
terminalRef.value.execute('上传完成')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showDialog.value = true
|
||||||
|
connectSSE(taskData.task_key)
|
||||||
|
} else if (taskData.status == 1) {
|
||||||
|
active.value = 'complete'
|
||||||
|
loading.value = false
|
||||||
|
uploadDuration.value = taskData.update_time - taskData.create_time
|
||||||
|
showDialog.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
if (terminalRef.value) {
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
terminalRef.value.execute('上传完成')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (props.onSuccess) {
|
||||||
|
props.onSuccess()
|
||||||
|
}
|
||||||
|
} else if (taskData.status == -1 || taskData.status == -2) {
|
||||||
|
active.value = 'error'
|
||||||
|
loading.value = false
|
||||||
|
errorInfo.value = taskData.fail_reason || '上传失败'
|
||||||
|
showDialog.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
if (terminalRef.value) {
|
||||||
|
terminalRef.value.execute('clear')
|
||||||
|
terminalRef.value.execute('上传失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (props.onError) {
|
||||||
|
props.onError()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loading.value = false
|
||||||
|
showDialog.value = true
|
||||||
|
connectSSE(taskData.task_key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTask = (task: any) => {
|
||||||
|
cloudBuildTask.value = task
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
closeSSE()
|
||||||
|
uploadDurationTimerClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
setTask,
|
||||||
|
cloudBuildTask,
|
||||||
|
loading,
|
||||||
|
showDialog
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@ -46,5 +46,28 @@
|
|||||||
"uploadWeapp": "上传小程序",
|
"uploadWeapp": "上传小程序",
|
||||||
"undoAudit" : "撤回审核",
|
"undoAudit" : "撤回审核",
|
||||||
"undoAuditTips" : "撤回代码审核,单个账号每天审核撤回次数最多不超过 5 次(每天的额度从0点开始生效),一个月不超过 10 次。是否要继续撤回?",
|
"undoAuditTips" : "撤回代码审核,单个账号每天审核撤回次数最多不超过 5 次(每天的额度从0点开始生效),一个月不超过 10 次。是否要继续撤回?",
|
||||||
"helpInfo": "查看帮助"
|
"helpInfo": "查看帮助",
|
||||||
|
"weappUpload": {
|
||||||
|
"title": "小程序上传",
|
||||||
|
"noTask": "暂无上传任务",
|
||||||
|
"uploadFailed": "上传失败",
|
||||||
|
"uploadSuccess": "上传成功",
|
||||||
|
"uploadSuccessDesc": "小程序上传成功,耗时",
|
||||||
|
"usedTime": "已用时",
|
||||||
|
"startUpload": "开始上传",
|
||||||
|
"uploadComplete": "上传完成",
|
||||||
|
"dialogCloseTips": "确定要关闭上传窗口吗?关闭后将停止当前上传任务",
|
||||||
|
"errorInfo": "错误信息",
|
||||||
|
"errorAnalysis": "错误分析",
|
||||||
|
"complete": "完成",
|
||||||
|
"return": "返回",
|
||||||
|
"nextStep": "下一步",
|
||||||
|
"waitingUpload": "等待上传任务...",
|
||||||
|
"minute": "分",
|
||||||
|
"second": "秒",
|
||||||
|
"dot": ".",
|
||||||
|
"defaultDesc": "默认为列表版本号递增,自定义则为手动输入版本号进行上传,首位必须大于1"
|
||||||
|
},
|
||||||
|
"minute": "分",
|
||||||
|
"second": "秒"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,11 +110,11 @@
|
|||||||
<el-form-item prop="code1">
|
<el-form-item prop="code1">
|
||||||
<el-input v-model.number="form.code1" class="!w-[70px]" :placeholder="t('codePlaceholder')" />
|
<el-input v-model.number="form.code1" class="!w-[70px]" :placeholder="t('codePlaceholder')" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<span class="mx-[10px]">.</span>
|
<span class="mx-[10px]">{{ t('weappUpload.dot') }}</span>
|
||||||
<el-form-item prop="code2">
|
<el-form-item prop="code2">
|
||||||
<el-input v-model.number="form.code2" class="!w-[70px]" :placeholder="t('codePlaceholder')" />
|
<el-input v-model.number="form.code2" class="!w-[70px]" :placeholder="t('codePlaceholder')" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<span class="mx-[10px]">.</span>
|
<span class="mx-[10px]">{{ t('weappUpload.dot') }}</span>
|
||||||
<el-form-item prop="code3">
|
<el-form-item prop="code3">
|
||||||
<el-input v-model.number="form.code3" class="!w-[70px]" :placeholder="t('codePlaceholder')" />
|
<el-input v-model.number="form.code3" class="!w-[70px]" :placeholder="t('codePlaceholder')" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -155,6 +155,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<component :is="WeappUpload" ref="weappUploadRef" @success="getWeappVersionListFn" @error="getWeappVersionListFn" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -168,12 +170,17 @@ import { ElMessageBox } from 'element-plus'
|
|||||||
import { AnyObject } from '@/types/global'
|
import { AnyObject } from '@/types/global'
|
||||||
import Storage from '@/utils/storage'
|
import Storage from '@/utils/storage'
|
||||||
import { siteWeappCommit, undoAudit } from "@/app/api/wxoplatform";
|
import { siteWeappCommit, undoAudit } from "@/app/api/wxoplatform";
|
||||||
|
import WeappUpload from '@/app/components/weappupload/index.vue'
|
||||||
|
// 使用导入的组件,避免未读取警告
|
||||||
|
const WeappUploadComponent = WeappUpload
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pageName = route.meta.title
|
const pageName = route.meta.title
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
const weappUploadRef = ref<any>(null)
|
||||||
|
|
||||||
const weappTableData:{
|
const weappTableData:{
|
||||||
page: number,
|
page: number,
|
||||||
limit: number,
|
limit: number,
|
||||||
@ -286,19 +293,24 @@ const formRules = reactive({
|
|||||||
/**
|
/**
|
||||||
* 获取版本列表
|
* 获取版本列表
|
||||||
*/
|
*/
|
||||||
const getWeappVersionListFn = (page: number = 1) => {
|
const getWeappVersionListFn = (page?: number) => {
|
||||||
weappTableData.loading = true
|
weappTableData.loading = true
|
||||||
weappTableData.page = page
|
if (page) weappTableData.page = page
|
||||||
|
|
||||||
getWeappVersionList({
|
return getWeappVersionList({
|
||||||
page: weappTableData.page,
|
page: weappTableData.page,
|
||||||
limit: weappTableData.limit
|
limit: weappTableData.limit
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
weappTableData.loading = false
|
weappTableData.loading = false
|
||||||
weappTableData.data = res.data.data
|
weappTableData.data = res.data.data
|
||||||
weappTableData.total = res.data.total
|
weappTableData.total = res.data.total
|
||||||
if (page == 1 && weappTableData.data.length && weappTableData.data[0].status == 0) getWeappUploadLogFn(weappTableData.data[0].task_key)
|
|
||||||
weappTableData.version_info = res.data.version_info
|
weappTableData.version_info = res.data.version_info
|
||||||
|
|
||||||
|
const uploadingTask = res.data.data.find((d: any) => d.status == 0)
|
||||||
|
if (uploadingTask && weappUploadRef.value) {
|
||||||
|
weappUploadRef.value.setTask(uploadingTask)
|
||||||
|
weappUploadRef.value.open(uploadingTask.task_key, uploadingTask)
|
||||||
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
weappTableData.loading = false
|
weappTableData.loading = false
|
||||||
})
|
})
|
||||||
@ -367,15 +379,35 @@ const insert = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uploading.value) return
|
if (uploading.value) {
|
||||||
|
const ref = weappUploadRef.value
|
||||||
|
if (ref && ref.cloudBuildTask && ref.cloudBuildTask.task_key) {
|
||||||
|
ref.open(ref.cloudBuildTask.task_key, ref.cloudBuildTask)
|
||||||
|
} else if (ref) {
|
||||||
|
ref.showDialog = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
uploading.value = true
|
uploading.value = true
|
||||||
|
|
||||||
previewContent.value = ''
|
previewContent.value = ''
|
||||||
|
|
||||||
setWeappVersion(form.value).then(res => {
|
setWeappVersion(form.value).then(res => {
|
||||||
getWeappVersionListFn()
|
const versionId = res.data
|
||||||
getWeappPreviewImage()
|
|
||||||
uploading.value = false
|
getWeappVersionListFn().then(() => {
|
||||||
|
const item = weappTableData.data.find((d: any) => d.id === versionId)
|
||||||
|
if (item && item.task_key) {
|
||||||
|
const ref = weappUploadRef.value
|
||||||
|
if (ref) {
|
||||||
|
ref.open(item.task_key, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getWeappPreviewImage()
|
||||||
|
uploading.value = false
|
||||||
|
}).catch(() => {
|
||||||
|
uploading.value = false
|
||||||
|
})
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
uploading.value = false
|
uploading.value = false
|
||||||
})
|
})
|
||||||
@ -388,6 +420,7 @@ const localInsert = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const previewContent = ref('')
|
const previewContent = ref('')
|
||||||
|
|
||||||
const getWeappPreviewImage = () => {
|
const getWeappPreviewImage = () => {
|
||||||
if (!authCode.value) return
|
if (!authCode.value) return
|
||||||
getWeappPreview().then(res => {
|
getWeappPreview().then(res => {
|
||||||
@ -395,21 +428,55 @@ const getWeappPreviewImage = () => {
|
|||||||
}).catch()
|
}).catch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentUploadKey = ref<string | null>(null)
|
||||||
|
const uploadPollingCount = ref(0)
|
||||||
|
const maxPollingCount = 150
|
||||||
|
|
||||||
const getWeappUploadLogFn = (key: string) => {
|
const getWeappUploadLogFn = (key: string) => {
|
||||||
|
if (!key) return
|
||||||
|
|
||||||
|
if (currentUploadKey.value !== key) {
|
||||||
|
currentUploadKey.value = key
|
||||||
|
uploadPollingCount.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadPollingCount.value++
|
||||||
|
|
||||||
|
if (uploadPollingCount.value > maxPollingCount) {
|
||||||
|
currentUploadKey.value = null
|
||||||
|
uploadPollingCount.value = 0
|
||||||
|
uploading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
getWeappUploadLog(key).then(res => {
|
getWeappUploadLog(key).then(res => {
|
||||||
const data = res.data.data ?? []
|
if (currentUploadKey.value !== key) return
|
||||||
|
|
||||||
|
const data = (res.data && res.data.data) ? res.data.data : []
|
||||||
if (data[0] && data[0].length) {
|
if (data[0] && data[0].length) {
|
||||||
const last = data[0][data[0].length - 1]
|
const last = data[0][data[0].length - 1]
|
||||||
if (last.code == 0) {
|
if (last.code == 0) {
|
||||||
|
currentUploadKey.value = null
|
||||||
|
uploadPollingCount.value = 0
|
||||||
getWeappVersionListFn()
|
getWeappVersionListFn()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (last.code == 1 && last.percent == 100) {
|
if (last.code == 1 && last.percent == 100) {
|
||||||
|
currentUploadKey.value = null
|
||||||
|
uploadPollingCount.value = 0
|
||||||
getWeappVersionListFn()
|
getWeappVersionListFn()
|
||||||
getWeappPreviewImage()
|
getWeappPreviewImage()
|
||||||
!Storage.get('weappUploadTipsLock') && (uploadSuccessShowDialog.value = true)
|
!Storage.get('weappUploadTipsLock') && (uploadSuccessShowDialog.value = true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (currentUploadKey.value === key) {
|
||||||
|
setTimeout(() => {
|
||||||
|
getWeappUploadLogFn(key)
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
if (currentUploadKey.value === key) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getWeappUploadLogFn(key)
|
getWeappUploadLogFn(key)
|
||||||
}, 2000)
|
}, 2000)
|
||||||
|
|||||||
@ -469,18 +469,21 @@ initPage({
|
|||||||
if (import.meta.env.MODE == 'development') {
|
if (import.meta.env.MODE == 'development') {
|
||||||
|
|
||||||
// env文件配置过wap域名
|
// env文件配置过wap域名
|
||||||
if (wapDomain.value) {
|
// if (wapDomain.value) {
|
||||||
wapUrl.value = wapDomain.value + '/wap'
|
// wapUrl.value = wapDomain.value + '/wap'
|
||||||
repeat = false
|
// repeat = false
|
||||||
setDomain()
|
// setDomain()
|
||||||
}
|
// }
|
||||||
|
|
||||||
let wap_domain_storage = storage.get('wap_domain')
|
// let wap_domain_storage = storage.get('wap_domain')
|
||||||
if (wap_domain_storage) {
|
// if (wap_domain_storage) {
|
||||||
wapUrl.value = wap_domain_storage
|
// wapUrl.value = wap_domain_storage
|
||||||
repeat = false
|
// repeat = false
|
||||||
setDomain()
|
// setDomain()
|
||||||
}
|
// }
|
||||||
|
wapUrl.value = 'http://localhost:5173/wap'
|
||||||
|
repeat = false
|
||||||
|
setDomain()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repeat) {
|
if (repeat) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -7,15 +7,50 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-form class="page-form mt-[20px]" :model="formData" :rules="formRules" label-width="150px" ref="formRef" v-loading="loading">
|
<el-form class="page-form mt-[20px]" :model="formData" :rules="formRules" label-width="150px" ref="formRef" v-loading="loading">
|
||||||
<el-form-item :label="t('mapKey')" prop="key">
|
<el-form-item label="地图类型" prop="map_type">
|
||||||
<el-input v-model.trim="formData.key" class="input-width" clearable />
|
<div>
|
||||||
<span class="ml-2 cursor-pointer tutorial-btn" @click="tutorialFn">{{ t('clickTutorial') }}</span>
|
<el-radio-group v-model="formData.map_type">
|
||||||
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://lbs.qq.com/dev/console/key/manage')">{{ t('clickSecretKey') }}</span>
|
<el-radio label="tianditu">天地图</el-radio>
|
||||||
</el-form-item>
|
<el-radio label="tencent">腾讯地图</el-radio>
|
||||||
<el-form-item :label="t('aMapKey')" prop="key">
|
</el-radio-group>
|
||||||
<el-input v-model.trim="formData.amap_key" class="input-width" clearable />
|
<div class="text-sm text-gray-400 mt-[10px] leading-none">选择地图服务提供商</div>
|
||||||
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://lbs.amap.com/')">{{ t('clickSecretKey') }}</span>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<template v-if="formData.map_type === 'tencent'">
|
||||||
|
<el-form-item :label="t('mapKey')" prop="key">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<el-input v-model.trim="formData.key" class="input-width" clearable />
|
||||||
|
<span class="ml-2 cursor-pointer tutorial-btn" @click="tutorialFn">{{ t('clickTutorial') }}</span>
|
||||||
|
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://lbs.qq.com/dev/console/key/manage')">{{ t('clickSecretKey') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="formData.map_type === 'tianditu'">
|
||||||
|
<el-form-item label="天地图服务端KEY" prop="tianditu_map_key">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<el-input v-model.trim="formData.tianditu_map_key" class="input-width" clearable />
|
||||||
|
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://cloudcenter.tianditu.gov.cn/center/development/myApp')">获取密钥</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-400 mt-[10px] leading-none">天地图服务器端API密钥,用于后端地理编码等服务,请求从业务服务器发起,不支持前端地图瓦片加载与 JS API 渲染。</div>
|
||||||
|
<div class="text-sm text-gray-400 mt-[10px] leading-none">使用场景:如地理编码、逆地理编码、坐标转换、批量 POI 查询等。</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="天地图浏览器端KEY" prop="tianditu_map_web_key">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<el-input v-model.trim="formData.tianditu_map_web_key" class="input-width" clearable />
|
||||||
|
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://cloudcenter.tianditu.gov.cn/center/development/myApp')">获取密钥</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-400 mt-[10px] leading-none">天地图浏览器端API密钥,用于前端加载地图,不可用于后端接口鉴权。</div>
|
||||||
|
<div class="text-sm text-gray-400 mt-[10px] leading-none">使用场景:展示地图瓦片、实现地图交互(缩放、拖拽、图层切换、标注、可视化展示、地图选位置等)。</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
<el-form-item :label="t('isOpen')" prop="is_open">
|
<el-form-item :label="t('isOpen')" prop="is_open">
|
||||||
<el-switch v-model="formData.is_open" :active-value="1" :inactive-value="0" />
|
<el-switch v-model="formData.is_open" :active-value="1" :inactive-value="0" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -45,8 +80,10 @@ import { FormInstance } from 'element-plus'
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
|
map_type: 'tencent',
|
||||||
key: '',
|
key: '',
|
||||||
amap_key: '',
|
tianditu_map_key: '',
|
||||||
|
tianditu_map_web_key: '',
|
||||||
is_open: 0,
|
is_open: 0,
|
||||||
valid_time: 0
|
valid_time: 0
|
||||||
})
|
})
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,79 +1,79 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<el-form class="page-form loading-box" :model="formData" label-width="150px" ref="formRef" :rules="formRules" v-loading="loading">
|
<el-form class="page-form loading-box" :model="formData" label-width="150px" ref="formRef" :rules="formRules" v-loading="loading">
|
||||||
<el-card class="box-card !border-none" shadow="never">
|
<el-card class="box-card !border-none" shadow="never">
|
||||||
<h3 class="text-[16px] text-[#1D1F3A] font-bold mb-4">{{ pageName }}</h3>
|
<h3 class="text-[16px] text-[#1D1F3A] font-bold mb-4">{{ pageName }}</h3>
|
||||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('websiteInfo') }}</h3>
|
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('websiteInfo') }}</h3>
|
||||||
|
|
||||||
<el-form-item :label="t('siteName')" prop="site_name">
|
<el-form-item :label="t('siteName')" prop="site_name">
|
||||||
<el-input v-model.trim="formData.site_name" :placeholder="t('siteNamePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
<el-input v-model.trim="formData.site_name" :placeholder="t('siteNamePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('logo')" prop="logo">
|
<el-form-item :label="t('logo')" prop="logo">
|
||||||
<div>
|
<div>
|
||||||
<upload-image v-model="formData.logo" />
|
<upload-image v-model="formData.logo" />
|
||||||
<p class="text-[12px] text-[#a9a9a9]">{{ t('logoPlaceholder') }}</p>
|
<p class="text-[12px] text-[#a9a9a9]">{{ t('logoPlaceholder') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('icon')" prop="icon">
|
<el-form-item :label="t('icon')" prop="icon">
|
||||||
<div>
|
<div>
|
||||||
<upload-image v-model="formData.icon" />
|
<upload-image v-model="formData.icon" />
|
||||||
<p class="text-[12px] text-[#a9a9a9]">{{ t('iconPlaceholder') }}</p>
|
<p class="text-[12px] text-[#a9a9a9]">{{ t('iconPlaceholder') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('keywords')">
|
<el-form-item :label="t('keywords')">
|
||||||
<el-input v-model.trim="formData.keywords" :placeholder="t('keywordsPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
<el-input v-model.trim="formData.keywords" :placeholder="t('keywordsPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('desc')">
|
<el-form-item :label="t('desc')">
|
||||||
<el-input v-model.trim="formData.desc" type="textarea" :rows="4" clearable :placeholder="t('descPlaceholder')" class="input-width" maxlength="100" show-word-limit />
|
<el-input v-model.trim="formData.desc" type="textarea" :rows="4" clearable :placeholder="t('descPlaceholder')" class="input-width" maxlength="100" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<div class="mt-[20px]" v-show="appType == 'site'">
|
<div class="mt-[20px]" v-show="appType == 'site'">
|
||||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('frontEndInfo') }}</h3>
|
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('frontEndInfo') }}</h3>
|
||||||
<el-form-item :label="t('frontEndName')">
|
<el-form-item :label="t('frontEndName')">
|
||||||
<el-input v-model.trim="formData.front_end_name" :placeholder="t('frontEndNamePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
<el-input v-model.trim="formData.front_end_name" :placeholder="t('frontEndNamePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('phone')">
|
<el-form-item :label="t('phone')">
|
||||||
<el-input v-model.trim="formData.phone" :placeholder="t('phonePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
<el-input v-model.trim="formData.phone" :placeholder="t('phonePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('logo')">
|
<el-form-item :label="t('logo')">
|
||||||
<upload-image v-model="formData.front_end_logo" />
|
<upload-image v-model="formData.front_end_logo" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('icon')">
|
<el-form-item :label="t('icon')">
|
||||||
<upload-image v-model="formData.front_end_icon" />
|
<upload-image v-model="formData.front_end_icon" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="t('metaTitle')">
|
<el-form-item :label="t('metaTitle')">
|
||||||
<el-input v-model.trim="formData.meta_title" :placeholder="t('MetaPlaceholder')" class="input-width" clearable maxlength="40" show-word-limit />
|
<el-input v-model.trim="formData.meta_title" :placeholder="t('MetaPlaceholder')" class="input-width" clearable maxlength="40" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('metaDescription')">
|
<el-form-item :label="t('metaDescription')">
|
||||||
<el-input v-model.trim="formData.meta_desc" :placeholder="t('metaDescriptionPlaceholder')" class="input-width" clearable maxlength="200" show-word-limit />
|
<el-input v-model.trim="formData.meta_desc" :placeholder="t('metaDescriptionPlaceholder')" class="input-width" clearable maxlength="200" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('metaKeywords')">
|
<el-form-item :label="t('metaKeywords')">
|
||||||
<el-input v-model.trim="formData.meta_keyword" :placeholder="t('metaKeywordsPlaceholder')" class="input-width" clearable maxlength="200" show-word-limit />
|
<el-input v-model.trim="formData.meta_keyword" :placeholder="t('metaKeywordsPlaceholder')" class="input-width" clearable maxlength="200" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-[20px]" v-if="appType == 'admin'">
|
||||||
|
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('serviceInformation') }}</h3>
|
||||||
|
<el-form-item :label="t('contactsTel')">
|
||||||
|
<el-input v-model.trim="formData.tel" :placeholder="t('contactsTelPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('wechatCode')">
|
||||||
|
<upload-image v-model="formData.wechat_code" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('customerServiceCode')">
|
||||||
|
<upload-image v-model="formData.enterprise_wechat" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<div class="fixed-footer-wrap">
|
||||||
|
<div class="fixed-footer">
|
||||||
|
<el-button type="primary" :loading="loading" @click="save(formRef)">{{ t('save') }}</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-[20px]" v-if="appType == 'admin'">
|
|
||||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('serviceInformation') }}</h3>
|
|
||||||
<el-form-item :label="t('contactsTel')">
|
|
||||||
<el-input v-model.trim="formData.tel" :placeholder="t('contactsTelPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="t('wechatCode')">
|
|
||||||
<upload-image v-model="formData.wechat_code" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="t('customerServiceCode')">
|
|
||||||
<upload-image v-model="formData.enterprise_wechat" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<div class="fixed-footer-wrap">
|
|
||||||
<div class="fixed-footer">
|
|
||||||
<el-button type="primary" :loading="loading" @click="save(formRef)">{{ t('save') }}</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -91,46 +91,46 @@ const pageName = route.meta.title
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const appType = ref(getAppType())
|
const appType = ref(getAppType())
|
||||||
const formData: any = reactive<Record<string, string>>({
|
const formData: any = reactive<Record<string, string>>({
|
||||||
site_name: '',
|
site_name: '',
|
||||||
logo: '',
|
logo: '',
|
||||||
desc: '',
|
desc: '',
|
||||||
latitude: '',
|
latitude: '',
|
||||||
keywords: '',
|
keywords: '',
|
||||||
longitude: '',
|
longitude: '',
|
||||||
province_id: '',
|
province_id: '',
|
||||||
city_id: '',
|
city_id: '',
|
||||||
district_id: '',
|
district_id: '',
|
||||||
address: '',
|
address: '',
|
||||||
full_address: '',
|
full_address: '',
|
||||||
business_hours: '',
|
business_hours: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
front_end_name: '',
|
front_end_name: '',
|
||||||
front_end_logo: '',
|
front_end_logo: '',
|
||||||
front_end_icon: '',
|
front_end_icon: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
wechat_code: '',
|
wechat_code: '',
|
||||||
enterprise_wechat: '',
|
enterprise_wechat: '',
|
||||||
tel: '',
|
tel: '',
|
||||||
site_login_logo: '',
|
site_login_logo: '',
|
||||||
site_login_bg_img: '',
|
site_login_bg_img: '',
|
||||||
meta_title: '',
|
meta_title: '',
|
||||||
meta_desc: '',
|
meta_desc: '',
|
||||||
meta_keyword: ''
|
meta_keyword: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const setFormData = async () => {
|
const setFormData = async () => {
|
||||||
const data = await (await getWebsite()).data
|
const data = await (await getWebsite()).data
|
||||||
Object.keys(formData).forEach((key: string) => {
|
Object.keys(formData).forEach((key: string) => {
|
||||||
if (data[key] != undefined) formData[key] = data[key]
|
if (data[key] != undefined) formData[key] = data[key]
|
||||||
})
|
})
|
||||||
|
|
||||||
const service_data: any = await (await getService()).data
|
const service_data: any = await (await getService()).data
|
||||||
formData.wechat_code = service_data.wechat_code
|
formData.wechat_code = service_data.wechat_code
|
||||||
formData.enterprise_wechat = service_data.enterprise_wechat
|
formData.enterprise_wechat = service_data.enterprise_wechat
|
||||||
formData.tel = service_data.tel
|
formData.tel = service_data.tel
|
||||||
formData.site_login_logo = service_data.site_login_logo
|
formData.site_login_logo = service_data.site_login_logo
|
||||||
formData.site_login_bg_img = service_data.site_login_bg_img
|
formData.site_login_bg_img = service_data.site_login_bg_img
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
setFormData()
|
setFormData()
|
||||||
|
|
||||||
@ -138,42 +138,42 @@ const formRef = ref<FormInstance>()
|
|||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const formRules = reactive<FormRules>({
|
const formRules = reactive<FormRules>({
|
||||||
site_name: [
|
site_name: [
|
||||||
{ required: true, message: t('siteNamePlaceholder'), trigger: 'blur' }
|
{ required: true, message: t('siteNamePlaceholder'), trigger: 'blur' }
|
||||||
],
|
],
|
||||||
logo: [
|
logo: [
|
||||||
{ required: true, message: t('请选择长方形Logo'), trigger: 'blur' }
|
{ required: true, message: t('请选择长方形Logo'), trigger: 'blur' }
|
||||||
],
|
],
|
||||||
icon: [
|
icon: [
|
||||||
{ required: true, message: t('请选择正方形Logo'), trigger: 'blur' }
|
{ required: true, message: t('请选择正方形Logo'), trigger: 'blur' }
|
||||||
],
|
],
|
||||||
front_end_name: [
|
front_end_name: [
|
||||||
{ required: true, message: t('frontEndNamePlaceholder'), trigger: 'blur' }
|
{ required: true, message: t('frontEndNamePlaceholder'), trigger: 'blur' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存
|
* 保存
|
||||||
*/
|
*/
|
||||||
const save = async (formEl: FormInstance | undefined) => {
|
const save = async (formEl: FormInstance | undefined) => {
|
||||||
if (loading.value || !formEl) return
|
if (loading.value || !formEl) return
|
||||||
|
|
||||||
await formEl.validate(async (valid) => {
|
await formEl.validate(async (valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
setWebsite(formData).then(() => {
|
setWebsite(formData).then(() => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
appType.value == 'admin' ? useSystemStore().getWebsiteInfo() : useUserStore().getSiteInfo()
|
appType.value == 'admin' ? useSystemStore().getWebsiteInfo() : useUserStore().getSiteInfo()
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.loading-box .el-loading-spinner){
|
:deep(.loading-box .el-loading-spinner){
|
||||||
top: 33%;
|
top: 33%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,149 +1,200 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<el-card class="box-card mt-[15px] !border-none" shadow="never" v-for="(item, key) in screne" :key="key">
|
<el-card class="box-card mt-[15px] !border-none" shadow="never" v-for="(item, key) in screne" :key="key">
|
||||||
<div class="flex items-center mb-[20px]">
|
<div class="flex items-center mb-[20px]">
|
||||||
<h3 class="text-[14px] mr-[20px]">{{ item.name }}</h3>
|
<h3 class="text-[14px] mr-[20px]">{{ item.name }}</h3>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="text-[14px] mr-[10px]">{{ t('transferSceneId') }}:</span>
|
<span class="text-[14px] mr-[10px]">{{ t('transferSceneId') }}:</span>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<el-input v-model.trim="item.scene_id" maxlength="5" class="!w-[60px]" :disabled="item.disabled" @blur="handleInput($event,key,item)" :ref="(el: any) =>{ if(el) inputRefs[key] = el }" v-show="!item.disabled"/>
|
<el-input
|
||||||
<div v-show="item.disabled">{{item.scene_id ? item.scene_id : '--'}}</div>
|
v-model.trim="item.scene_id"
|
||||||
<div @click="handleDisabled(item, key)" class="w-[40xp] flex items-center ml-[8px]"><el-icon size="20" color="var(--el-color-primary)"><Edit /></el-icon></div>
|
maxlength="5"
|
||||||
</div>
|
minlength="4"
|
||||||
</div>
|
class="!w-[60px]"
|
||||||
</div>
|
:class="item.error ? '!border-red-500' : ''"
|
||||||
<div>
|
:disabled="item.disabled"
|
||||||
<div class="flex items-center justify-between p-[10px] table-item-border bg">
|
@blur="handleInput($event,key,item)"
|
||||||
<span class="text-base w-[230px]">{{ t('transferType') }}</span>
|
:ref="(el: any) =>{ if(el) inputRefs[key] = el }"
|
||||||
<span class="text-base w-[230px]">{{ t('recvPerception') }}</span>
|
v-show="!item.disabled"
|
||||||
<span class="text-base w-[230px]">{{ t('reportInfos') }}</span>
|
/>
|
||||||
<span class="text-base w-[80px] text-center">{{ t('operation') }}</span>
|
<div v-show="item.disabled">{{ item.scene_id ? item.scene_id : '--' }}</div>
|
||||||
</div>
|
<div @click="handleDisabled(item, key)" class="w-[40px] flex items-center ml-[8px]">
|
||||||
<div v-if="Object.values(item.trade_scene_data).length">
|
<el-icon size="20" color="var(--el-color-primary)">
|
||||||
<div class="flex items-center justify-between p-[10px] table-item-border" v-for="(subItem, subKey) in item.trade_scene_data" :key="subKey">
|
<Edit/>
|
||||||
<div class="flex w-[230px] flex-shrink-0 text-base">{{ subItem.name }}</div>
|
</el-icon>
|
||||||
<div class="flex w-[230px] flex-shrink-0 text-base">{{ subItem.perception }}</div>
|
</div>
|
||||||
<div class="w-[230px] flex-shrink-0 text-base">
|
<span v-if="item.error" class="text-red-500 text-xs ml-2">场景值最少为4位</span>
|
||||||
<div v-for="(childItem,childKey) in subItem.infos" :key="childKey">{{ childKey }}:{{ childItem }}</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center w-[80px] select-none">
|
</div>
|
||||||
<button class="text-base text-primary" @click="configFn(item,subItem,subKey)">{{ t('deploy') }}</button>
|
<div>
|
||||||
</div>
|
<div class="flex items-center justify-between p-[10px] table-item-border bg">
|
||||||
</div>
|
<span class="text-base w-[230px]">{{ t('transferType') }}</span>
|
||||||
</div>
|
<span class="text-base w-[230px]">{{ t('recvPerception') }}</span>
|
||||||
<div v-else class="min-h-[80px] flex items-center justify-center text-base">
|
<span class="text-base w-[230px]">{{ t('reportInfos') }}</span>
|
||||||
{{ t('noData') }}
|
<span class="text-base w-[80px] text-center">{{ t('operation') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-if="Object.values(item.trade_scene_data).length">
|
||||||
</el-card>
|
<div class="flex items-center justify-between p-[10px] table-item-border"
|
||||||
<el-dialog v-model="showDialog" :title="curData.name" width="550px" :destroy-on-close="true">
|
v-for="(subItem, subKey) in item.trade_scene_data" :key="subKey">
|
||||||
<el-form :model="formData" label-width="110px" ref="formRef" class="page-form">
|
<div class="flex w-[230px] flex-shrink-0 text-base">{{ subItem.name }}</div>
|
||||||
<el-form-item :label="t('recvPerception')" prop="perception" :rules="[{ required: true, message: t('recvPerceptionTips'), trigger: 'blur' }]">
|
<div class="flex w-[230px] flex-shrink-0 text-base">{{ subItem.perception }}</div>
|
||||||
<el-select v-model="formData.perception" :placeholder="t('recvPerceptionTips')" clearable class="!w-[300px]">
|
<div class="w-[230px] flex-shrink-0 text-base">
|
||||||
<el-option v-for="(item,index) in curData.user_recv_perception" :key="index" :label="item" :value="item" />
|
<div v-for="(childItem,childKey) in subItem.infos" :key="childKey">{{ childKey }}:{{ childItem }}</div>
|
||||||
</el-select>
|
</div>
|
||||||
</el-form-item>
|
<div class="flex items-center justify-center w-[80px] select-none">
|
||||||
<template v-for="(item, index) in curData.transfer_scene_report_infos" :key="index">
|
<button class="text-base text-primary" @click="configFn(item,subItem,subKey)">{{ t('deploy') }}</button>
|
||||||
<el-form-item :label="item" :prop="`infos[${item}]`" :rules="[{ required: true, message: `请输入${item}`, trigger: 'blur' }]">
|
</div>
|
||||||
<el-input v-model.trim="formData.infos[item]" maxlength="40" class="!w-[300px]"/>
|
</div>
|
||||||
</el-form-item>
|
</div>
|
||||||
</template>
|
<div v-else class="min-h-[80px] flex items-center justify-center text-base">
|
||||||
</el-form>
|
{{ t('noData') }}
|
||||||
<template #footer>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<el-dialog v-model="showDialog" :title="curData.name" width="550px" :destroy-on-close="true">
|
||||||
|
<el-form :model="formData" label-width="110px" ref="formRef" class="page-form">
|
||||||
|
<el-form-item :label="t('recvPerception')" prop="perception"
|
||||||
|
:rules="[{ required: true, message: t('recvPerceptionTips'), trigger: 'blur' }]">
|
||||||
|
<el-select v-model="formData.perception" :placeholder="t('recvPerceptionTips')" clearable class="!w-[300px]">
|
||||||
|
<el-option v-for="(item,index) in curData.user_recv_perception" :key="index" :label="item" :value="item"/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-for="(item, index) in curData.transfer_scene_report_infos" :key="index">
|
||||||
|
<el-form-item :label="item" :prop="`infos[${item}]`"
|
||||||
|
:rules="[{ required: true, message: `请输入${item}`, trigger: 'blur' }]">
|
||||||
|
<el-input v-model.trim="formData.infos[item]" maxlength="40" class="!w-[300px]"/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="cancel">{{ t('cancel') }}</el-button>
|
<el-button @click="cancel">{{ t('cancel') }}</el-button>
|
||||||
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ t('confirm') }}</el-button>
|
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{
|
||||||
|
t('confirm')
|
||||||
|
}}</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { nextTick, ref } from 'vue'
|
import {nextTick, ref} from 'vue'
|
||||||
import { t } from '@/lang'
|
import {t} from '@/lang'
|
||||||
import { getTransferScene, setSceneId, setTradeScene } from '@/app/api/pay'
|
import {getTransferScene, setSceneId, setTradeScene} from '@/app/api/pay'
|
||||||
import { cloneDeep } from 'lodash-es'
|
import {cloneDeep} from 'lodash-es'
|
||||||
import { FormInstance } from 'element-plus'
|
import {FormInstance} from 'element-plus'
|
||||||
|
|
||||||
const screne = ref<any>({})
|
const screne = ref<any>({})
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const getTransferSceneFn = () => {
|
const getTransferSceneFn = () => {
|
||||||
getTransferScene().then(res => {
|
getTransferScene().then(res => {
|
||||||
screne.value = res.data
|
screne.value = res.data
|
||||||
for (const key in screne.value) {
|
for (const key in screne.value) {
|
||||||
screne.value[key].disabled = true
|
screne.value[key].disabled = true
|
||||||
}
|
screne.value[key].error = false
|
||||||
})
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
getTransferSceneFn()
|
getTransferSceneFn()
|
||||||
|
|
||||||
// 更改场景值
|
// 更改场景值(最终修复版)
|
||||||
const handleInput = (e: any, key: any, data: any) => {
|
const handleInput = (e: any, key: any, data: any) => {
|
||||||
if (e.target.value) {
|
const val = e.target.value?.trim() || ''
|
||||||
|
const originalVal = originalValues.value[key] || ''
|
||||||
|
|
||||||
|
// 有值 → 清空:必须提交接口置空
|
||||||
|
if (val === '' && originalVal !== '') {
|
||||||
|
data.error = false
|
||||||
setSceneId({
|
setSceneId({
|
||||||
scene: key,
|
scene: key,
|
||||||
scene_id: e.target.value
|
scene_id: ''
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
data.disabled = true
|
data.disabled = true
|
||||||
getTransferSceneFn()
|
getTransferSceneFn()
|
||||||
})
|
})
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本来就空:不提交
|
||||||
|
if (val === '' && originalVal === '') {
|
||||||
|
data.error = false
|
||||||
data.disabled = true
|
data.disabled = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有值但长度不够:提示,不提交
|
||||||
|
if (val.length < 4) {
|
||||||
|
data.error = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合法 4-5 位:提交
|
||||||
|
if (val.length >= 4 && val.length <= 5) {
|
||||||
|
data.error = false
|
||||||
|
setSceneId({
|
||||||
|
scene: key,
|
||||||
|
scene_id: val
|
||||||
|
}).then(() => {
|
||||||
|
data.disabled = true
|
||||||
|
getTransferSceneFn()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputRefs = ref<any>({})
|
const inputRefs = ref<any>({})
|
||||||
|
const originalValues = ref<any>({})
|
||||||
const handleDisabled = (data: any, key: any) => {
|
const handleDisabled = (data: any, key: any) => {
|
||||||
data.disabled = false
|
originalValues.value[key] = data.scene_id
|
||||||
nextTick(() => {
|
data.disabled = false
|
||||||
inputRefs.value[key].focus()
|
data.error = false
|
||||||
})
|
nextTick(() => {
|
||||||
|
inputRefs.value[key]?.focus()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
const curData = ref<any>({})
|
const curData = ref<any>({})
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
type: '',
|
type: '',
|
||||||
scene: '',
|
scene: '',
|
||||||
perception: '',
|
perception: '',
|
||||||
infos: {}
|
infos: {}
|
||||||
})
|
})
|
||||||
const configFn = (data: any, subData: any, type: any) => {
|
const configFn = (data: any, subData: any, type: any) => {
|
||||||
curData.value = cloneDeep(data)
|
curData.value = cloneDeep(data)
|
||||||
formData.value.type = type
|
formData.value.type = type
|
||||||
formData.value.scene = subData.scene
|
formData.value.scene = subData.scene
|
||||||
formData.value.perception = subData.perception
|
formData.value.perception = subData.perception
|
||||||
formData.value.infos = cloneDeep(subData.infos)
|
formData.value.infos = cloneDeep(subData.infos)
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
}
|
}
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
/**
|
|
||||||
* 确认
|
|
||||||
* @param formEl
|
|
||||||
*/
|
|
||||||
const confirm = async (formEl: FormInstance | undefined) => {
|
const confirm = async (formEl: FormInstance | undefined) => {
|
||||||
if (loading.value || !formEl) return
|
if (loading.value || !formEl) return
|
||||||
await formEl.validate(async (valid) => {
|
await formEl.validate(async (valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
setTradeScene(formData.value).then(() => {
|
setTradeScene(formData.value).then(() => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
showDialog.value = false
|
showDialog.value = false
|
||||||
getTransferSceneFn()
|
getTransferSceneFn()
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
showDialog.value = false
|
showDialog.value = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.table-item-border {
|
.table-item-border {
|
||||||
@apply border-b border-[var(--el-border-color)];
|
@apply border-b border-[var(--el-border-color)];
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -87,7 +87,7 @@
|
|||||||
<el-table-column :label="t('siteInfo')" width="300" align="left">
|
<el-table-column :label="t('siteInfo')" width="300" align="left">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<img class="w-[54px] h-[54px] mr-[10px] rounded-[4px]" v-if="row.logo" :src="img(row.logo)" alt="">
|
<img class="w-[54px] h-[54px] mr-[10px] rounded-[4px]" v-if="row.icon" :src="img(row.icon)" alt="">
|
||||||
<img class="w-[54px] h-[54px] mr-[10px] rounded-[4px]" v-else src="@/app/assets/images/site_default.png" alt="">
|
<img class="w-[54px] h-[54px] mr-[10px] rounded-[4px]" v-else src="@/app/assets/images/site_default.png" alt="">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span>{{ row.site_name || '' }}</span>
|
<span>{{ row.site_name || '' }}</span>
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main-container attachment-container">
|
<div class="main-container attachment-container">
|
||||||
<el-card class="box-card !border-none full-container" shadow="never">
|
<el-card class="box-card !border-none full-container" shadow="never">
|
||||||
|
|
||||||
<div class="flex justify-between items-center mb-[20px]">
|
<div class="flex justify-between items-center mb-[20px]">
|
||||||
<span class="text-page-title">{{ pageName }}</span>
|
<span class="text-page-title">{{ pageName }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-tabs v-model="type" tab-position="top">
|
<el-tabs v-model="type" tab-position="top">
|
||||||
<el-tab-pane :label="t(tab)" v-for="(tab, index) in attachmentType" :name="tab" :key="index">
|
<el-tab-pane :label="t(tab)" v-for="(tab, index) in attachmentType" :name="tab" :key="index">
|
||||||
<attachment scene="attachment" :type="tab" />
|
<attachment scene="attachment" :type="tab" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -30,46 +30,46 @@ const type = ref(attachmentType[0])
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.attachment-container {
|
.attachment-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: calc(100vh - 94px);
|
min-height: calc(100vh - 94px);
|
||||||
background-color: var(--el-bg-color-overlay);
|
background-color: var(--el-bg-color-overlay);
|
||||||
|
|
||||||
.full-container {
|
.full-container {
|
||||||
height: calc(100vh - 100px);
|
height: calc(100vh - 100px);
|
||||||
}
|
|
||||||
|
|
||||||
.el-card__body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-tabs {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: calc(100% - 40px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-tabs__content {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.el-tab-pane {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-tabs__nav-wrap::after {
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-wrap {
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
.group-wrap {
|
|
||||||
padding: 0 15px 0 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-list-wrap {
|
.el-card__body {
|
||||||
padding: 0 0 0 15px;
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__content {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.el-tab-pane {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__nav-wrap::after {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-wrap {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.group-wrap {
|
||||||
|
padding: 0 15px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-list-wrap {
|
||||||
|
padding: 0 0 0 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
512
admin/src/components/map-selector/index.vue
Normal file
512
admin/src/components/map-selector/index.vue
Normal file
@ -0,0 +1,512 @@
|
|||||||
|
<template>
|
||||||
|
<div :id="containerId" :class="containerClass" :style="containerStyle" v-loading="loading"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, watch, nextTick, onBeforeUnmount } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { t } from '@/lang'
|
||||||
|
import { getMap } from '@/app/api/sys'
|
||||||
|
|
||||||
|
import {
|
||||||
|
createMarker,
|
||||||
|
createCircle,
|
||||||
|
createPolygon,
|
||||||
|
deleteGeometry,
|
||||||
|
selectGeometry,
|
||||||
|
latLngToAddress
|
||||||
|
} from '@/utils/qqmap'
|
||||||
|
|
||||||
|
// 天地图工具
|
||||||
|
import {
|
||||||
|
createCircle as tdCreateCircle,
|
||||||
|
createPolygon as tdCreatePolygon,
|
||||||
|
deleteGeometry as tdDeleteGeometry,
|
||||||
|
selectGeometry as tdSelectGeometry,
|
||||||
|
clearAllGeometry,
|
||||||
|
latLngToAddress as tiandituLatLngToAddress
|
||||||
|
} from '@/utils/tianditu'
|
||||||
|
|
||||||
|
const MAP_INIT_DELAY = 500
|
||||||
|
const MAP_SEARCH_TIMEOUT = 5000
|
||||||
|
const MAP_SEARCH_INTERVAL = 100
|
||||||
|
const MAP_SEARCH_MAX_ATTEMPTS = 50
|
||||||
|
|
||||||
|
interface MapInstance {
|
||||||
|
destroy?: () => void
|
||||||
|
clearOverLays?: () => void
|
||||||
|
on?: (event: string, callback: Function) => void
|
||||||
|
addControl?: (control: any) => void
|
||||||
|
addOverLay?: (overlay: any) => void
|
||||||
|
removeOverLay?: (overlay: any) => void
|
||||||
|
centerAndZoom?: (center: any, zoom: number) => void
|
||||||
|
setZoom?: (zoom: number) => void
|
||||||
|
setCenter?: (center: any) => void
|
||||||
|
addEventListener?: (event: string, callback: Function) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
containerId?: string
|
||||||
|
containerClass?: string
|
||||||
|
containerStyle?: string
|
||||||
|
longitude?: string | number
|
||||||
|
latitude?: string | number
|
||||||
|
zoom?: number
|
||||||
|
selectedKey?: string
|
||||||
|
disabledClickMarker?: any // 是否禁止点击事件添加标注
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:longitude', value: string): void
|
||||||
|
(e: 'update:latitude', value: string): void
|
||||||
|
(e: 'locationChange', data: { lat: number, lng: number, address: any }): void
|
||||||
|
(e: 'areaChange', data: { key: string, type: 'circle' | 'polygon', path: any }): void
|
||||||
|
(e: 'selectChange', key: string): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
containerId: 'map-container',
|
||||||
|
containerClass: 'w-[800px] h-[500px] relative border border-gray-300',
|
||||||
|
containerStyle: '',
|
||||||
|
longitude: 116.397463,
|
||||||
|
latitude: 39.909187,
|
||||||
|
zoom: 14,
|
||||||
|
selectedKey: '',
|
||||||
|
disabledClickMarker: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const loading = ref(true)
|
||||||
|
const mapKey = ref('')
|
||||||
|
const mapType = ref<'tencent' | 'tianditu'>('tianditu')
|
||||||
|
let map: MapInstance | null = null
|
||||||
|
let marker: any = null
|
||||||
|
const overlays = ref<any[]>([])
|
||||||
|
|
||||||
|
// 获取地图配置
|
||||||
|
const getMapConfigData = () => {
|
||||||
|
return getMap({
|
||||||
|
need_encrypt: false
|
||||||
|
}).then((res: any) => {
|
||||||
|
mapType.value = res.data.map_type || 'tianditu'
|
||||||
|
mapKey.value = mapType.value === 'tianditu' ? res.data.tianditu_map_web_key : res.data.key
|
||||||
|
}).catch((error) => {
|
||||||
|
loading.value = false
|
||||||
|
ElMessage.error(t('获取地图配置失败'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化腾讯地图
|
||||||
|
const initTencentMap = () => {
|
||||||
|
const mapScript = document.createElement('script')
|
||||||
|
mapScript.src = 'https://map.qq.com/api/gljs?libraries=tools,service&v=1.exp&key=' + mapKey.value
|
||||||
|
document.body.appendChild(mapScript)
|
||||||
|
|
||||||
|
mapScript.onload = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const TMap = (window as any).TMap
|
||||||
|
if (TMap) {
|
||||||
|
const lng = props.longitude ? Number(props.longitude) : 116.397463
|
||||||
|
const lat = props.latitude ? Number(props.latitude) : 39.909187
|
||||||
|
const center = new TMap.LatLng(lat, lng)
|
||||||
|
map = new TMap.Map(props.containerId, { center, zoom: props.zoom })
|
||||||
|
map.on('tilesloaded', () => loading.value = false)
|
||||||
|
|
||||||
|
marker = createMarker(map)
|
||||||
|
if(!props.disabledClickMarker) {
|
||||||
|
map.on('click', (evt: any) => {
|
||||||
|
const ll = evt?.latLng
|
||||||
|
if (!ll) return
|
||||||
|
const lat = typeof ll.getLat === 'function' ? ll.getLat() : ll.lat
|
||||||
|
const lng = typeof ll.getLng === 'function' ? ll.getLng() : ll.lng
|
||||||
|
if (lat == null || lng == null || Number.isNaN(lat) || Number.isNaN(lng)) return
|
||||||
|
|
||||||
|
const newCenter = new TMap.LatLng(lat, lng)
|
||||||
|
map.setZoom(16)
|
||||||
|
map.setCenter(newCenter)
|
||||||
|
marker.updateGeometries({ id: 'center', position: newCenter })
|
||||||
|
nextTick(() => {
|
||||||
|
handleLocationChange(lat, lng)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.latitude && props.longitude) {
|
||||||
|
handleLocationChange(lat, lng)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loading.value = false
|
||||||
|
ElMessage.error(t('腾讯地图加载失败'))
|
||||||
|
}
|
||||||
|
}, MAP_INIT_DELAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapScript.onerror = () => {
|
||||||
|
loading.value = false
|
||||||
|
ElMessage.error(t('腾讯地图脚本加载失败'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化天地图
|
||||||
|
const initTiandituMap = () => {
|
||||||
|
const mapScript = document.createElement('script')
|
||||||
|
mapScript.src = 'https://api.tianditu.gov.cn/api?v=4.0&tk=' + mapKey.value
|
||||||
|
document.body.appendChild(mapScript)
|
||||||
|
|
||||||
|
mapScript.onload = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (T) {
|
||||||
|
const lng = props.longitude ? Number(props.longitude) : 116.397463
|
||||||
|
const lat = props.latitude ? Number(props.latitude) : 39.909187
|
||||||
|
map = new T.Map(props.containerId)
|
||||||
|
map.centerAndZoom(new T.LngLat(lng, lat), props.zoom)
|
||||||
|
|
||||||
|
const ctrl = new T.Control.Zoom()
|
||||||
|
map.addControl(ctrl)
|
||||||
|
|
||||||
|
marker = new T.Marker(new T.LngLat(lng, lat))
|
||||||
|
map.addOverLay(marker)
|
||||||
|
|
||||||
|
loading.value = false
|
||||||
|
|
||||||
|
if(!props.disabledClickMarker) {
|
||||||
|
map.addEventListener('click', (evt: any) => {
|
||||||
|
const lngLat = evt.lnglat
|
||||||
|
if (!lngLat) return
|
||||||
|
const lat = lngLat.lat
|
||||||
|
const lng = lngLat.lng
|
||||||
|
if (lat == null || lng == null || Number.isNaN(lat) || Number.isNaN(lng)) return
|
||||||
|
|
||||||
|
map.removeOverLay(marker)
|
||||||
|
|
||||||
|
marker = new T.Marker(new T.LngLat(lng, lat))
|
||||||
|
map.addOverLay(marker)
|
||||||
|
|
||||||
|
map.centerAndZoom(new T.LngLat(lng, lat), 16)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
handleLocationChange(lat, lng)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.latitude && props.longitude) {
|
||||||
|
handleLocationChange(lat, lng)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loading.value = false
|
||||||
|
ElMessage.error(t('天地图加载失败'))
|
||||||
|
}
|
||||||
|
}, MAP_INIT_DELAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapScript.onerror = () => {
|
||||||
|
loading.value = false
|
||||||
|
ElMessage.error(t('天地图加载失败,请检查密钥配置'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化地图
|
||||||
|
const initMap = () => {
|
||||||
|
if (!mapKey.value) {
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防止重复初始化
|
||||||
|
if (map) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapType.value === 'tencent') {
|
||||||
|
initTencentMap()
|
||||||
|
} else if (mapType.value === 'tianditu') {
|
||||||
|
initTiandituMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理位置变化
|
||||||
|
const handleLocationChange = (lat: number, lng: number) => {
|
||||||
|
emit('update:longitude', String(lng))
|
||||||
|
emit('update:latitude', String(lat))
|
||||||
|
|
||||||
|
if (mapType.value === 'tencent') {
|
||||||
|
latLngToAddress({ mapKey: mapKey.value, lat, lng }).then(({ message, result }) => {
|
||||||
|
if (message == 'query ok' || message == 'Success') {
|
||||||
|
emit('locationChange', { lat, lng, address: result })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (mapType.value === 'tianditu') {
|
||||||
|
tiandituLatLngToAddress({ mapKey: mapKey.value, lat, lng }).then((addressData: any) => {
|
||||||
|
emit('locationChange', { lat, lng, address: addressData })
|
||||||
|
}).catch((error: any) => {
|
||||||
|
ElMessage.error(error.message || t('地址解析失败'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新地图位置(供外部调用)
|
||||||
|
const updateLocation = (lat: number, lng: number) => {
|
||||||
|
if (!map || !marker) return
|
||||||
|
|
||||||
|
if (mapType.value === 'tencent') {
|
||||||
|
const TMap = (window as any).TMap
|
||||||
|
if (!TMap) return
|
||||||
|
const newCenter = new TMap.LatLng(lat, lng)
|
||||||
|
map.setZoom(16)
|
||||||
|
map.setCenter(newCenter)
|
||||||
|
marker.updateGeometries({ id: 'center', position: newCenter })
|
||||||
|
} else if (mapType.value === 'tianditu') {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (!T) return
|
||||||
|
const newCenter = new T.LngLat(lng, lat)
|
||||||
|
|
||||||
|
// 移除旧标记
|
||||||
|
map.removeOverLay(marker)
|
||||||
|
|
||||||
|
// 创建新标记
|
||||||
|
marker = new T.Marker(newCenter)
|
||||||
|
map.addOverLay(marker)
|
||||||
|
|
||||||
|
map.centerAndZoom(newCenter, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地址搜索(供外部调用)
|
||||||
|
const searchAddress = (address: string): Promise<{ lat: number, lng: number }> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!mapKey.value) {
|
||||||
|
reject(new Error(t('地图配置未加载')))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitForMap = () => {
|
||||||
|
return new Promise<void>((resolveWait, rejectWait) => {
|
||||||
|
if (map) {
|
||||||
|
resolveWait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let attempts = 0
|
||||||
|
const checkInterval = setInterval(() => {
|
||||||
|
attempts++
|
||||||
|
if (map) {
|
||||||
|
clearInterval(checkInterval)
|
||||||
|
resolveWait()
|
||||||
|
} else if (attempts > MAP_SEARCH_MAX_ATTEMPTS) {
|
||||||
|
clearInterval(checkInterval)
|
||||||
|
rejectWait(new Error(t('地图初始化超时')))
|
||||||
|
}
|
||||||
|
}, MAP_SEARCH_INTERVAL)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForMap().then(() => {
|
||||||
|
if (mapType.value === 'tencent') {
|
||||||
|
const TMap = (window as any).TMap
|
||||||
|
if (!TMap?.service) {
|
||||||
|
reject(new Error(t('腾讯地图服务未加载')))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const geocoder = new TMap.service.Geocoder({ key: mapKey.value })
|
||||||
|
geocoder.getLocation({ address }).then((result: any) => {
|
||||||
|
if (result.status === 0 && result.result?.location) {
|
||||||
|
const location = result.result.location
|
||||||
|
const lat = Number(location.lat)
|
||||||
|
const lng = Number(location.lng)
|
||||||
|
updateLocation(lat, lng)
|
||||||
|
resolve({ lat, lng })
|
||||||
|
} else {
|
||||||
|
reject(new Error(result.message || t('未找到该地址')))
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
} else if (mapType.value === 'tianditu') {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (!T) {
|
||||||
|
reject(new Error(t('天地图服务未加载')))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const geocoder = new T.Geocoder()
|
||||||
|
geocoder.getPoint(address, (result: any) => {
|
||||||
|
if (result && result.status === '0' && result.location) {
|
||||||
|
const lat = result.location.lat
|
||||||
|
const lng = result.location.lon || result.location.lng
|
||||||
|
if (lat && lng) {
|
||||||
|
updateLocation(lat, lng)
|
||||||
|
resolve({ lat, lng })
|
||||||
|
} else {
|
||||||
|
reject(new Error(t('坐标解析失败')))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error(result?.msg || t('未找到该地址')))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch(reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 添加圆
|
||||||
|
const addCircle = (center?: { lat: number; lng: number }, radius = 1000, options?: any) => {
|
||||||
|
if (!map) return null
|
||||||
|
const key = options?.key || `circle_${Date.now()}`
|
||||||
|
let circle = null
|
||||||
|
|
||||||
|
// 创建geometriesData对象
|
||||||
|
const geometriesData = { key, center, radius, ...options }
|
||||||
|
|
||||||
|
if (mapType.value === 'tencent') {
|
||||||
|
createCircle(map, geometriesData, (selectedKey) => {
|
||||||
|
// 触发selectChange事件
|
||||||
|
emit('selectChange', selectedKey)
|
||||||
|
})
|
||||||
|
// 腾讯地图的createCircle不返回值,创建一个包含key的对象添加到overlays
|
||||||
|
circle = { key, geometriesData }
|
||||||
|
} else {
|
||||||
|
circle = tdCreateCircle(map, geometriesData, (data) => {
|
||||||
|
// 触发areaChange事件,确保path数据干净,不包含循环引用
|
||||||
|
emit('areaChange', {
|
||||||
|
key: data.key,
|
||||||
|
type: 'circle',
|
||||||
|
path: {
|
||||||
|
key: data.key,
|
||||||
|
center: data.center,
|
||||||
|
radius: data.radius
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, (selectedKey) => {
|
||||||
|
// 触发selectChange事件
|
||||||
|
emit('selectChange', selectedKey)
|
||||||
|
})
|
||||||
|
// 为天地图添加geometriesData引用
|
||||||
|
if (circle) {
|
||||||
|
circle.geometriesData = geometriesData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (circle) overlays.value.push(circle)
|
||||||
|
return circle
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加多边形
|
||||||
|
const addPolygon = (paths: any[], options?: any) => {
|
||||||
|
if (!map) return null
|
||||||
|
const key = options?.key || `poly_${Date.now()}`
|
||||||
|
let poly = null
|
||||||
|
|
||||||
|
// 创建geometriesData对象
|
||||||
|
const geometriesData = { key, paths, ...options }
|
||||||
|
|
||||||
|
if (mapType.value === 'tencent') {
|
||||||
|
createPolygon(map, geometriesData, (selectedKey) => {
|
||||||
|
// 触发selectChange事件
|
||||||
|
emit('selectChange', selectedKey)
|
||||||
|
})
|
||||||
|
// 腾讯地图的createPolygon不返回值,创建一个包含key的对象添加到overlays
|
||||||
|
poly = { key, geometriesData }
|
||||||
|
} else {
|
||||||
|
poly = tdCreatePolygon(map, geometriesData, (data) => {
|
||||||
|
// 触发areaChange事件,确保path数据干净,不包含循环引用
|
||||||
|
emit('areaChange', {
|
||||||
|
key: data.key,
|
||||||
|
type: 'polygon',
|
||||||
|
path: {
|
||||||
|
key: data.key,
|
||||||
|
paths: data.paths.map((point: any) => ({
|
||||||
|
lat: point.lat,
|
||||||
|
lng: point.lng
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, (selectedKey) => {
|
||||||
|
// 触发selectChange事件
|
||||||
|
emit('selectChange', selectedKey)
|
||||||
|
})
|
||||||
|
// 为天地图添加geometriesData引用
|
||||||
|
if (poly) {
|
||||||
|
poly.geometriesData = geometriesData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poly) overlays.value.push(poly)
|
||||||
|
return poly
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除覆盖物
|
||||||
|
const removeOverlay = (overlay: any) => {
|
||||||
|
if (!map || !overlay) return
|
||||||
|
const key = overlay.key || ''
|
||||||
|
mapType.value === 'tencent' ? deleteGeometry(key) : tdDeleteGeometry(map, key)
|
||||||
|
|
||||||
|
// 通过key查找并删除覆盖物
|
||||||
|
const idx = overlays.value.findIndex(item => item.key === key)
|
||||||
|
if (idx > -1) overlays.value.splice(idx, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空所有覆盖物
|
||||||
|
const clearOverlays = () => {
|
||||||
|
if (!map) return
|
||||||
|
mapType.value === 'tencent'
|
||||||
|
? overlays.value.forEach(o => deleteGeometry(o.key))
|
||||||
|
: clearAllGeometry(map)
|
||||||
|
overlays.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectGeo = (key: string) => {
|
||||||
|
mapType.value === 'tencent' ? selectGeometry(key) : tdSelectGeometry(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听选中key变化
|
||||||
|
watch(() => props.selectedKey, (newKey) => {
|
||||||
|
if (newKey) {
|
||||||
|
selectGeo(newKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听地图key和类型变化
|
||||||
|
watch([mapKey, mapType], ([newKey, newType]) => {
|
||||||
|
if (newKey && newType) {
|
||||||
|
initMap()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getMapConfigData()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (map) {
|
||||||
|
try {
|
||||||
|
if (mapType.value === 'tencent') {
|
||||||
|
map.destroy && map.destroy()
|
||||||
|
} else if (mapType.value === 'tianditu') {
|
||||||
|
clearOverlays()
|
||||||
|
map.clearOverLays && map.clearOverLays()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
map = null
|
||||||
|
marker = null
|
||||||
|
overlays.value = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
updateLocation,
|
||||||
|
searchAddress,
|
||||||
|
addCircle,
|
||||||
|
addPolygon,
|
||||||
|
removeOverlay,
|
||||||
|
clearOverlays,
|
||||||
|
selectGeometry: selectGeo,
|
||||||
|
getMap: () => map,
|
||||||
|
getMarker: () => marker,
|
||||||
|
handleLocationChange
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@ -1,37 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="['layout-aside ease-in duration-200 flex box-border', { 'bright': !dark}]">
|
<div :class="['layout-aside ease-in duration-200 flex box-border', { 'bright': !dark}]">
|
||||||
<div class="flex flex-col border-0 border-r-[1px] border-solid border-[var(--el-color-info-light-8)] box-border overflow-hidden">
|
<div class="flex flex-col border-0 border-r-[1px] border-solid border-[var(--el-color-info-light-8)] box-border overflow-hidden">
|
||||||
<div :class="['w-[150px] one-menu hide-scrollbar', { 'expanded': systemStore.menuIsCollapse }]" >
|
<div :class="['w-[150px] one-menu hide-scrollbar', { 'expanded': systemStore.menuIsCollapse }]" >
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<template v-for="(item, index) in oneMenuData">
|
<template v-for="(item, index) in oneMenuData">
|
||||||
<div v-if="item.meta.show" :title="systemStore.menuIsCollapse ? item.meta.title : item.meta.short_title" class="menu-item my-[2px] p-2 flex w-full box-border cursor-pointer relative" :class="{'is-active':oneMenuActive===item.original_name,'hover-left': systemStore.menuIsCollapse, 'vertical': !systemStore.menuIsCollapse , 'horizontal': systemStore.menuIsCollapse }" :style="{ height: (systemStore.menuIsCollapse ) ? '40px' : '55px' }" @click="router.push({ name: item.name })">
|
<div v-if="item.meta.show" :title="systemStore.menuIsCollapse ? item.meta.title : item.meta.short_title" class="menu-item my-[2px] p-2 flex w-full box-border cursor-pointer relative" :class="{'is-active':oneMenuActive===item.original_name,'hover-left': systemStore.menuIsCollapse, 'vertical': !systemStore.menuIsCollapse , 'horizontal': systemStore.menuIsCollapse }" :style="{ height: (systemStore.menuIsCollapse ) ? '40px' : '55px' }" @click="router.push({ name: item.name })">
|
||||||
<div class="w-[20px] h-[20px] flex items-center justify-center menu-icon" :class="{'is-active':oneMenuActive===item.original_name}">
|
<div class="w-[20px] h-[20px] flex items-center justify-center menu-icon" :class="{'is-active':oneMenuActive===item.original_name}">
|
||||||
<template v-if="item.meta.icon">
|
<template v-if="item.meta.icon">
|
||||||
<el-image class="w-[20px] h-[20px] overflow-hidden" :src="item.meta.icon" fit="fill" v-if="isUrl(item.meta.icon)"/>
|
<el-image class="w-[20px] h-[20px] overflow-hidden" :src="item.meta.icon" fit="fill" v-if="isUrl(item.meta.icon)"/>
|
||||||
<icon :name="item.meta.icon" size="20px" color="#1D1F3A" v-else />
|
<icon :name="item.meta.icon" size="20px" color="#1D1F3A" v-else />
|
||||||
</template>
|
</template>
|
||||||
<icon v-else :name="'iconfont iconshezhi1'" color="#1D1F3A" />
|
<icon v-else :name="'iconfont iconshezhi1'" color="#1D1F3A" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="systemStore.menuIsCollapse" class="text-left text-[14px] mt-[3px] w-[75px] using-hidden ml-[10px]">{{ item.meta.title || item.meta.short_title }}</div>
|
<div v-if="systemStore.menuIsCollapse" class="text-left text-[14px] mt-[3px] w-[75px] using-hidden ml-[10px]">{{ item.meta.title || item.meta.short_title }}</div>
|
||||||
<div v-else class="text-center text-[12px] using-hidden mt-1">{{ item.meta.short_title || item.meta.title }}</div>
|
<div v-else class="text-center text-[12px] using-hidden mt-1">{{ item.meta.short_title || item.meta.title }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col two-menu w-[185px] " v-if="twoMenuData.length">
|
||||||
|
<el-scrollbar class="flex-1" >
|
||||||
|
<el-menu :default-active="route.name" :router="true" class="aside-menu">
|
||||||
|
<menu-item v-for="(route, index) in twoMenuData" :routes="route" :key="index" :isNewVersion="isNewVersion" />
|
||||||
|
</el-menu>
|
||||||
|
<div class="h-[48px]"></div>
|
||||||
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col two-menu w-[185px] " v-if="twoMenuData.length">
|
|
||||||
<el-scrollbar class="flex-1" >
|
|
||||||
<el-menu :default-active="route.name" :router="true" class="aside-menu">
|
|
||||||
<menu-item v-for="(route, index) in twoMenuData" :routes="route" :key="index" :isNewVersion="isNewVersion" />
|
|
||||||
</el-menu>
|
|
||||||
<div class="h-[48px]"></div>
|
|
||||||
</el-scrollbar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch, ref, computed } from 'vue'
|
import { watch, ref, computed } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
@ -53,203 +53,203 @@ const router = useRouter()
|
|||||||
const addonRouters: Record<string, any> = {}
|
const addonRouters: Record<string, any> = {}
|
||||||
const addonIndexRoute = userStore.addonIndexRoute
|
const addonIndexRoute = userStore.addonIndexRoute
|
||||||
const dark = computed(() => {
|
const dark = computed(() => {
|
||||||
return systemStore.dark
|
return systemStore.dark
|
||||||
})
|
})
|
||||||
|
|
||||||
const twoMenuData = ref<Record<string, any>[]>([])
|
const twoMenuData = ref<Record<string, any>[]>([])
|
||||||
const oneMenuData = ref<Record<string, any>[]>([])
|
const oneMenuData = ref<Record<string, any>[]>([])
|
||||||
routers.forEach(item => {
|
routers.forEach(item => {
|
||||||
item.original_name = item.name
|
item.original_name = item.name
|
||||||
if (item.meta.addon == '') {
|
if (item.meta.addon == '') {
|
||||||
if (item.meta.attr == '') {
|
if (item.meta.attr == '') {
|
||||||
if (item.children && item.children.length) {
|
if (item.children && item.children.length) {
|
||||||
item.name = findFirstValidRoute(item.children)
|
item.name = findFirstValidRoute(item.children)
|
||||||
}
|
}
|
||||||
oneMenuData.value.push(item)
|
oneMenuData.value.push(item)
|
||||||
}
|
}
|
||||||
} else if (item.meta.addon != '' && siteInfo?.apps.length <= 1 && siteInfo?.apps[0].key == item.meta.addon && item.meta.show) {
|
} else if (item.meta.addon != '' && siteInfo?.apps.length <= 1 && siteInfo?.apps[0].key == item.meta.addon && item.meta.show) {
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
item.children.forEach((citem: Record<string, any>) => {
|
item.children.forEach((citem: Record<string, any>) => {
|
||||||
citem.original_name = citem.name
|
citem.original_name = citem.name
|
||||||
if (citem.children && citem.children.length) {
|
if (citem.children && citem.children.length) {
|
||||||
citem.name = findFirstValidRoute(citem.children)
|
citem.name = findFirstValidRoute(citem.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
oneMenuData.value.unshift(...item.children)
|
||||||
|
} else {
|
||||||
|
oneMenuData.value.unshift(item)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
oneMenuData.value.unshift(...item.children)
|
|
||||||
} else {
|
} else {
|
||||||
oneMenuData.value.unshift(item)
|
addonRouters[item.meta.addon] = item
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
addonRouters[item.meta.addon] = item
|
|
||||||
}
|
|
||||||
|
|
||||||
// 排序, 功能正确,改了排序后需要把菜单排序的默认值重新调整一下【多应用一级菜单,单应用二级菜单】
|
// 排序, 功能正确,改了排序后需要把菜单排序的默认值重新调整一下【多应用一级菜单,单应用二级菜单】
|
||||||
oneMenuData.value.sort((a, b) => {
|
oneMenuData.value.sort((a, b) => {
|
||||||
if (a.meta.sort && b.meta.sort) {
|
if (a.meta.sort && b.meta.sort) {
|
||||||
return b.meta.sort - a.meta.sort
|
return b.meta.sort - a.meta.sort
|
||||||
} else if (a.meta.sort) {
|
} else if (a.meta.sort) {
|
||||||
return -1
|
return -1
|
||||||
} else if (b.meta.sort) {
|
} else if (b.meta.sort) {
|
||||||
return 1
|
return 1
|
||||||
} else {
|
} else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 多应用时将应用插入菜单
|
// 多应用时将应用插入菜单
|
||||||
if (siteInfo?.apps.length > 1) {
|
if (siteInfo?.apps.length > 1) {
|
||||||
const routers:Record<string, any>[] = []
|
const routers:Record<string, any>[] = []
|
||||||
siteInfo?.apps.forEach((item: Record<string, any>) => {
|
siteInfo?.apps.forEach((item: Record<string, any>) => {
|
||||||
if (addonRouters[item.key]) {
|
if (addonRouters[item.key]) {
|
||||||
addonRouters[item.key].name = addonIndexRoute[item.key]
|
addonRouters[item.key].name = addonIndexRoute[item.key]
|
||||||
routers.push(addonRouters[item.key])
|
routers.push(addonRouters[item.key])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
oneMenuData.value.unshift(...routers)
|
oneMenuData.value.unshift(...routers)
|
||||||
|
|
||||||
// 排序, 功能正确,改了排序后需要把菜单排序的默认值重新调整一下【多应用一级菜单,单应用二级菜单】
|
// 排序, 功能正确,改了排序后需要把菜单排序的默认值重新调整一下【多应用一级菜单,单应用二级菜单】
|
||||||
oneMenuData.value.sort((a, b) => {
|
oneMenuData.value.sort((a, b) => {
|
||||||
if (a.meta.sort && b.meta.sort) {
|
if (a.meta.sort && b.meta.sort) {
|
||||||
return b.meta.sort - a.meta.sort
|
return b.meta.sort - a.meta.sort
|
||||||
} else if (a.meta.sort) {
|
} else if (a.meta.sort) {
|
||||||
return -1
|
return -1
|
||||||
} else if (b.meta.sort) {
|
} else if (b.meta.sort) {
|
||||||
return 1
|
return 1
|
||||||
} else {
|
} else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const oneMenuActive = ref(route.matched[1].name)
|
const oneMenuActive = ref(route.matched[1].name)
|
||||||
// 从 addonKeys 中提取所有需要匹配的 key
|
// 从 addonKeys 中提取所有需要匹配的 key
|
||||||
const getAddonAllKeys = (addonData) => {
|
const getAddonAllKeys = (addonData) => {
|
||||||
if (!addonData || typeof addonData !== 'object') return [];
|
if (!addonData || typeof addonData !== 'object') return [];
|
||||||
const allKeys = [];
|
const allKeys = [];
|
||||||
Object.values(addonData).forEach(category => {
|
Object.values(addonData).forEach(category => {
|
||||||
if (Array.isArray(category.list)) {
|
if (Array.isArray(category.list)) {
|
||||||
category.list.forEach(item => {
|
category.list.forEach(item => {
|
||||||
if (item.key) allKeys.push(item.key);
|
if (item.key) allKeys.push(item.key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return allKeys;
|
return allKeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理 specialMenusKeys 子菜单 show 的方法
|
// 处理 specialMenusKeys 子菜单 show 的方法
|
||||||
const handleSpecialMenus = () => {
|
const handleSpecialMenus = () => {
|
||||||
const specialMenusKeys = storage.get('specialAppList')
|
const specialMenusKeys = storage.get('specialAppList')
|
||||||
if (Array.isArray(specialMenusKeys) && specialMenusKeys.length) {
|
if (Array.isArray(specialMenusKeys) && specialMenusKeys.length) {
|
||||||
const processedSpecialMenus = JSON.parse(JSON.stringify(specialMenusKeys));
|
const processedSpecialMenus = JSON.parse(JSON.stringify(specialMenusKeys));
|
||||||
const activeAppKey = storage.get('activeAppKey');
|
const activeAppKey = storage.get('activeAppKey');
|
||||||
|
|
||||||
// 收集所有特殊菜单的name
|
// 收集所有特殊菜单的name
|
||||||
processedSpecialMenus.forEach(menu => {
|
processedSpecialMenus.forEach(menu => {
|
||||||
if (menu.children && Array.isArray(menu.children)) {
|
if (menu.children && Array.isArray(menu.children)) {
|
||||||
const traverseChildren = (children) => {
|
const traverseChildren = (children) => {
|
||||||
children.forEach(child => {
|
children.forEach(child => {
|
||||||
if (child && child.is_show !== undefined) {
|
if (child && child.is_show !== undefined) {
|
||||||
child.is_show = (child.menu_key === activeAppKey) ? 1 : 0;
|
child.is_show = (child.menu_key === activeAppKey) ? 1 : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
traverseChildren(menu.children);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
// 过滤掉 children 为空的特殊菜单
|
||||||
traverseChildren(menu.children);
|
const filteredSpecialMenus = processedSpecialMenus.filter(menu => {
|
||||||
}
|
return menu.children && menu.children.length > 0;
|
||||||
});
|
});
|
||||||
// 过滤掉 children 为空的特殊菜单
|
return formatRouters(filteredSpecialMenus);
|
||||||
const filteredSpecialMenus = processedSpecialMenus.filter(menu => {
|
}
|
||||||
return menu.children && menu.children.length > 0;
|
return [];
|
||||||
});
|
|
||||||
return formatRouters(filteredSpecialMenus);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
};
|
};
|
||||||
watch(route, () => {
|
watch(route, () => {
|
||||||
if (route.meta.attr != '') {
|
if (route.meta.attr != '') {
|
||||||
oneMenuActive.value = route.matched[1].name
|
oneMenuActive.value = route.matched[1].name
|
||||||
twoMenuData.value = route.matched[1].children ?? []
|
twoMenuData.value = route.matched[1].children ?? []
|
||||||
} else {
|
|
||||||
// 多应用
|
|
||||||
if (siteInfo?.apps.length > 1) {
|
|
||||||
twoMenuData.value = route.matched[2].children
|
|
||||||
oneMenuActive.value = route.matched[2].name
|
|
||||||
} else {
|
} else {
|
||||||
// 单应用
|
// 多应用
|
||||||
const oneMenu = route.matched[2]
|
if (siteInfo?.apps.length > 1) {
|
||||||
if (oneMenu.meta.addon == '') {
|
twoMenuData.value = route.matched[2].children
|
||||||
oneMenuActive.value = route.matched[2].name
|
oneMenuActive.value = route.matched[2].name
|
||||||
twoMenuData.value = route.matched[2].children ?? []
|
|
||||||
} else {
|
|
||||||
if (oneMenu.meta.addon == siteInfo?.apps[0].key) {
|
|
||||||
oneMenuActive.value = route.matched[3].name
|
|
||||||
twoMenuData.value = route.matched[3].children ?? []
|
|
||||||
} else {
|
} else {
|
||||||
oneMenuActive.value = route.matched[2].name
|
// 单应用
|
||||||
twoMenuData.value = route.matched[2].children ?? []
|
const oneMenu = route.matched[2]
|
||||||
|
if (oneMenu.meta.addon == '') {
|
||||||
|
oneMenuActive.value = route.matched[2].name
|
||||||
|
twoMenuData.value = route.matched[2].children ?? []
|
||||||
|
} else {
|
||||||
|
if (oneMenu.meta.addon == siteInfo?.apps[0].key) {
|
||||||
|
oneMenuActive.value = route.matched[3].name
|
||||||
|
twoMenuData.value = route.matched[3].children ?? []
|
||||||
|
} else {
|
||||||
|
oneMenuActive.value = route.matched[2].name
|
||||||
|
twoMenuData.value = route.matched[2].children ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
// const addonKeys = storage.get('defaultAppList')
|
||||||
// const addonKeys = storage.get('defaultAppList')
|
// const addonAllKeys = getAddonAllKeys(addonKeys)
|
||||||
// const addonAllKeys = getAddonAllKeys(addonKeys)
|
// twoMenuData.value = twoMenuData.value.filter((child) =>{
|
||||||
// twoMenuData.value = twoMenuData.value.filter((child) =>{
|
// return !child.name || !addonAllKeys.includes(child.name);
|
||||||
// return !child.name || !addonAllKeys.includes(child.name);
|
// })
|
||||||
// })
|
// if(oneMenuActive.value == 'addon'){
|
||||||
// if(oneMenuActive.value == 'addon'){
|
// // 处理特殊菜单并插入到 twoMenuData 中(与 addon_list 同级)
|
||||||
// // 处理特殊菜单并插入到 twoMenuData 中(与 addon_list 同级)
|
// const processedSpecialMenus = handleSpecialMenus();
|
||||||
// const processedSpecialMenus = handleSpecialMenus();
|
// if (processedSpecialMenus.length) {
|
||||||
// if (processedSpecialMenus.length) {
|
// // 先找到 addon_list 在 twoMenuData 中的索引
|
||||||
// // 先找到 addon_list 在 twoMenuData 中的索引
|
// const addonListIndex = twoMenuData.value.findIndex(
|
||||||
// const addonListIndex = twoMenuData.value.findIndex(
|
// (item) => item.name === 'addon_list'
|
||||||
// (item) => item.name === 'addon_list'
|
// );
|
||||||
// );
|
// if (addonListIndex !== -1) {
|
||||||
// if (addonListIndex !== -1) {
|
// // 将特殊菜单插入到 addon_list 后面(同级)
|
||||||
// // 将特殊菜单插入到 addon_list 后面(同级)
|
// twoMenuData.value.splice(
|
||||||
// twoMenuData.value.splice(
|
// addonListIndex + 1,
|
||||||
// addonListIndex + 1,
|
// 0,
|
||||||
// 0,
|
// ...processedSpecialMenus
|
||||||
// ...processedSpecialMenus
|
// );
|
||||||
// );
|
// } else {
|
||||||
// } else {
|
// // 如果没有 addon_list,直接将特殊菜单添加到 twoMenuData 中
|
||||||
// // 如果没有 addon_list,直接将特殊菜单添加到 twoMenuData 中
|
// twoMenuData.value.push(...processedSpecialMenus);
|
||||||
// twoMenuData.value.push(...processedSpecialMenus);
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
const frameworkVersionList = ref([])
|
const frameworkVersionList = ref([])
|
||||||
const isNewVersion = computed(() => {
|
const isNewVersion = computed(() => {
|
||||||
if (!newVersion.value || newVersion.value.version_no === version.value) {
|
if (!newVersion.value || newVersion.value.version_no === version.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将版本号转为字符串再处理
|
// 将版本号转为字符串再处理
|
||||||
const currentVersionStr = String(version.value);
|
const currentVersionStr = String(version.value);
|
||||||
const latestVersionStr = String(newVersion.value.version_no);
|
const latestVersionStr = String(newVersion.value.version_no);
|
||||||
// 移除点号并转为数字比较
|
// 移除点号并转为数字比较
|
||||||
const currentVersionNum = parseInt(currentVersionStr.replace(/\./g, ''), 10);
|
const currentVersionNum = parseInt(currentVersionStr.replace(/\./g, ''), 10);
|
||||||
const latestVersionNum = parseInt(latestVersionStr.replace(/\./g, ''), 10);
|
const latestVersionNum = parseInt(latestVersionStr.replace(/\./g, ''), 10);
|
||||||
return latestVersionNum > currentVersionNum;
|
return latestVersionNum > currentVersionNum;
|
||||||
})
|
})
|
||||||
|
|
||||||
const getFrameworkVersionListFn = () => {
|
const getFrameworkVersionListFn = () => {
|
||||||
getFrameworkVersionList().then(({ data }) => {
|
getFrameworkVersionList().then(({ data }) => {
|
||||||
frameworkVersionList.value = data
|
frameworkVersionList.value = data
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
getFrameworkVersionListFn()
|
getFrameworkVersionListFn()
|
||||||
|
|
||||||
const newVersion: any = computed(() => {
|
const newVersion: any = computed(() => {
|
||||||
return frameworkVersionList.value.length ? frameworkVersionList.value[0] : null
|
return frameworkVersionList.value.length ? frameworkVersionList.value[0] : null
|
||||||
})
|
})
|
||||||
const version = ref('')
|
const version = ref('')
|
||||||
const getVersionsInfo = () => {
|
const getVersionsInfo = () => {
|
||||||
getVersions().then((res) => {
|
getVersions().then((res) => {
|
||||||
version.value = res.data.version.version
|
version.value = res.data.version.version
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
getVersionsInfo()
|
getVersionsInfo()
|
||||||
|
|
||||||
@ -257,148 +257,148 @@ getVersionsInfo()
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.one-menu{
|
.one-menu{
|
||||||
padding: 20px 10px 10px;
|
padding: 20px 10px 10px;
|
||||||
width: 78px;
|
width: 78px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
// transition: width 0.1s ease-out;
|
// transition: width 0.1s ease-out;
|
||||||
&.expanded {
|
&.expanded {
|
||||||
width: 185px;
|
width: 185px;
|
||||||
padding: 18px 15px 15px;
|
padding: 18px 15px 15px;
|
||||||
}
|
|
||||||
.menu-item{
|
|
||||||
border-radius: 2px;
|
|
||||||
justify-content: center;
|
|
||||||
&.vertical {
|
|
||||||
width: 55px;
|
|
||||||
height: 55px;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
.menu-item{
|
||||||
|
border-radius: 2px;
|
||||||
|
justify-content: center;
|
||||||
|
&.vertical {
|
||||||
|
width: 55px;
|
||||||
|
height: 55px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
&.horizontal {
|
&.horizontal {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.menu-icon {
|
.menu-icon {
|
||||||
// background-color: transparent; /* 默认无背景色 */
|
// background-color: transparent; /* 默认无背景色 */
|
||||||
color: #1D1F3A;
|
color: #1D1F3A;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .menu-icon.is-active {
|
// .menu-icon.is-active {
|
||||||
// background-color: var(--el-color-primary); /* 选中时背景色 */
|
// background-color: var(--el-color-primary); /* 选中时背景色 */
|
||||||
// color: white; /* 选中时图标颜色变白 */
|
// color: white; /* 选中时图标颜色变白 */
|
||||||
// border-radius: 4px; /* 可选:使图标背景为圆形 */
|
// border-radius: 4px; /* 可选:使图标背景为圆形 */
|
||||||
// }
|
// }
|
||||||
|
|
||||||
&:hover{
|
&:hover{
|
||||||
background-color: #EAEBF0 !important;
|
background-color: #EAEBF0 !important;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
// background-color: var(--el-color-primary-light-9) !important;
|
// background-color: var(--el-color-primary-light-9) !important;
|
||||||
// color:var(--el-color-primary);
|
// color:var(--el-color-primary);
|
||||||
|
}
|
||||||
|
&.is-active{
|
||||||
|
background-color: #EAEBF0 !important;
|
||||||
|
border-radius: 6px;
|
||||||
|
// background-color: var(--el-color-primary-light-9) !important;
|
||||||
|
// border: none;
|
||||||
|
// color:var(--el-color-primary);
|
||||||
|
}
|
||||||
|
span{
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.is-active{
|
.menu-item.hover-left {
|
||||||
background-color: #EAEBF0 !important;
|
justify-content: flex-start;
|
||||||
border-radius: 6px;
|
padding-left: 5px;
|
||||||
// background-color: var(--el-color-primary-light-9) !important;
|
|
||||||
// border: none;
|
|
||||||
// color:var(--el-color-primary);
|
|
||||||
}
|
}
|
||||||
span{
|
&.expanded .menu-item .text-center {
|
||||||
font-size: 14px;
|
opacity: 1;
|
||||||
margin-left: 8px;
|
}
|
||||||
|
.el-menu{
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.el-scrollbar{
|
||||||
|
height: calc(100vh - 65px);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.menu-item.hover-left {
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
&.expanded .menu-item .text-center {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.el-menu{
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
.el-scrollbar{
|
|
||||||
height: calc(100vh - 65px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.two-menu{
|
.two-menu{
|
||||||
.aside-menu:not(.el-menu--collapse) {
|
.aside-menu:not(.el-menu--collapse) {
|
||||||
width: 185px;
|
width: 185px;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
.el-menu-item{
|
.el-menu-item{
|
||||||
height: 40px;
|
height: 40px;
|
||||||
margin: 4px 15px;
|
margin: 4px 15px;
|
||||||
padding: 0 8px !important;
|
padding: 0 8px !important;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
span{
|
span{
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
&.is-active{
|
&.is-active{
|
||||||
background-color: #EAEBF0 !important;
|
background-color: #EAEBF0 !important;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
// background-color: var(--el-color-primary-light-9) !important;
|
// background-color: var(--el-color-primary-light-9) !important;
|
||||||
}
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
background-color: #EAEBF0 !important;
|
background-color: #EAEBF0 !important;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
// background-color: var(--el-color-primary-light-9) !important;
|
// background-color: var(--el-color-primary-light-9) !important;
|
||||||
// color: var(--el-color-primary);
|
// color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.el-sub-menu{
|
||||||
|
width: 185px;
|
||||||
|
margin: 4px 0;
|
||||||
|
// margin-bottom: 8px;
|
||||||
|
.el-sub-menu__title{
|
||||||
|
margin: 0 15px;
|
||||||
|
height: 40px;
|
||||||
|
padding-left: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
span{
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
&:hover{
|
||||||
|
background-color:#EAEBF0 !important;
|
||||||
|
border-radius: 6px;
|
||||||
|
// background-color: var(--el-color-primary-light-9) !important;
|
||||||
|
// color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
.el-icon.el-sub-menu__icon-arrow{
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-menu-item{
|
||||||
|
padding-left: 25px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.el-sub-menu{
|
|
||||||
width: 185px;
|
|
||||||
margin: 4px 0;
|
|
||||||
// margin-bottom: 8px;
|
|
||||||
.el-sub-menu__title{
|
|
||||||
margin: 0 15px;
|
|
||||||
height: 40px;
|
|
||||||
padding-left: 8px;
|
|
||||||
border-radius: 2px;
|
|
||||||
span{
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
&:hover{
|
|
||||||
background-color:#EAEBF0 !important;
|
|
||||||
border-radius: 6px;
|
|
||||||
// background-color: var(--el-color-primary-light-9) !important;
|
|
||||||
// color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
.el-icon.el-sub-menu__icon-arrow{
|
|
||||||
right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.el-menu-item{
|
|
||||||
padding-left: 25px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-wrap {
|
.logo-wrap {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-title {
|
.logo-title {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 0;
|
width: 0;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: var(--el-font-size-base);
|
font-size: var(--el-font-size-base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// :deep(.el-scrollbar__bar){
|
// :deep(.el-scrollbar__bar){
|
||||||
// display: none !important;
|
// display: none !important;
|
||||||
@ -408,15 +408,15 @@ getVersionsInfo()
|
|||||||
// }
|
// }
|
||||||
// 隐藏滚动条
|
// 隐藏滚动条
|
||||||
.hide-scrollbar::-webkit-scrollbar {
|
.hide-scrollbar::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
/* Chrome/Safari/Edge */
|
/* Chrome/Safari/Edge */
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-scrollbar {
|
.hide-scrollbar {
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
/* IE/Edge */
|
/* IE/Edge */
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
/* Firefox */
|
/* Firefox */
|
||||||
}
|
}
|
||||||
// .layout-aside .menu-item.is-active{
|
// .layout-aside .menu-item.is-active{
|
||||||
// position: relative;
|
// position: relative;
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
/* addon-iconfont.css */
|
@import "addon/home_service/iconfont.css";
|
||||||
|
@import "addon/o2o/iconfont.css";
|
||||||
|
@import "addon/tourism/iconfont.css";
|
||||||
|
|||||||
38
admin/src/styles/icon/addon/home_service/iconfont.css
Normal file
38
admin/src/styles/icon/addon/home_service/iconfont.css
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "o2o"; /* Project id 4412516 */
|
||||||
|
src: url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.woff2?t=1705720131974') format('woff2'),
|
||||||
|
url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.woff?t=1705720131974') format('woff'),
|
||||||
|
url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.ttf?t=1705720131974') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o {
|
||||||
|
font-family: "o2o" !important;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-danhanghuadong:before {
|
||||||
|
content: "\e66f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-yuanjiao:before {
|
||||||
|
content: "\e6c0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-gl-square:before {
|
||||||
|
content: "\ea92";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-sousuo12:before {
|
||||||
|
content: "\e699";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-sousuo11:before {
|
||||||
|
content: "\e6d4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-jishi:before {
|
||||||
|
content: "\e600";
|
||||||
|
}
|
||||||
51
admin/src/styles/icon/addon/home_service/iconfont.json
Normal file
51
admin/src/styles/icon/addon/home_service/iconfont.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"id": "4412516",
|
||||||
|
"name": "上门服务",
|
||||||
|
"font_family": "o2o",
|
||||||
|
"css_prefix_text": "o2o-icon-",
|
||||||
|
"description": "",
|
||||||
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "30621139",
|
||||||
|
"name": "单行滑动",
|
||||||
|
"font_class": "danhanghuadong",
|
||||||
|
"unicode": "e66f",
|
||||||
|
"unicode_decimal": 58991
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "7149037",
|
||||||
|
"name": "圆角",
|
||||||
|
"font_class": "yuanjiao",
|
||||||
|
"unicode": "e6c0",
|
||||||
|
"unicode_decimal": 59072
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "7594344",
|
||||||
|
"name": "20gl-square",
|
||||||
|
"font_class": "gl-square",
|
||||||
|
"unicode": "ea92",
|
||||||
|
"unicode_decimal": 60050
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10133070",
|
||||||
|
"name": "搜索",
|
||||||
|
"font_class": "sousuo12",
|
||||||
|
"unicode": "e699",
|
||||||
|
"unicode_decimal": 59033
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "14652583",
|
||||||
|
"name": "搜索",
|
||||||
|
"font_class": "sousuo11",
|
||||||
|
"unicode": "e6d4",
|
||||||
|
"unicode_decimal": 59092
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6818781",
|
||||||
|
"name": "技师",
|
||||||
|
"font_class": "jishi",
|
||||||
|
"unicode": "e600",
|
||||||
|
"unicode_decimal": 58880
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
38
admin/src/styles/icon/addon/o2o/iconfont.css
Normal file
38
admin/src/styles/icon/addon/o2o/iconfont.css
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "o2o"; /* Project id 4412516 */
|
||||||
|
src: url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.woff2?t=1705720131974') format('woff2'),
|
||||||
|
url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.woff?t=1705720131974') format('woff'),
|
||||||
|
url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.ttf?t=1705720131974') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o {
|
||||||
|
font-family: "o2o" !important;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-danhanghuadong:before {
|
||||||
|
content: "\e66f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-yuanjiao:before {
|
||||||
|
content: "\e6c0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-gl-square:before {
|
||||||
|
content: "\ea92";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-sousuo12:before {
|
||||||
|
content: "\e699";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-sousuo11:before {
|
||||||
|
content: "\e6d4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.o2o-icon-jishi:before {
|
||||||
|
content: "\e600";
|
||||||
|
}
|
||||||
51
admin/src/styles/icon/addon/o2o/iconfont.json
Normal file
51
admin/src/styles/icon/addon/o2o/iconfont.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"id": "4412516",
|
||||||
|
"name": "上门服务",
|
||||||
|
"font_family": "o2o",
|
||||||
|
"css_prefix_text": "o2o-icon-",
|
||||||
|
"description": "",
|
||||||
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "30621139",
|
||||||
|
"name": "单行滑动",
|
||||||
|
"font_class": "danhanghuadong",
|
||||||
|
"unicode": "e66f",
|
||||||
|
"unicode_decimal": 58991
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "7149037",
|
||||||
|
"name": "圆角",
|
||||||
|
"font_class": "yuanjiao",
|
||||||
|
"unicode": "e6c0",
|
||||||
|
"unicode_decimal": 59072
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "7594344",
|
||||||
|
"name": "20gl-square",
|
||||||
|
"font_class": "gl-square",
|
||||||
|
"unicode": "ea92",
|
||||||
|
"unicode_decimal": 60050
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10133070",
|
||||||
|
"name": "搜索",
|
||||||
|
"font_class": "sousuo12",
|
||||||
|
"unicode": "e699",
|
||||||
|
"unicode_decimal": 59033
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "14652583",
|
||||||
|
"name": "搜索",
|
||||||
|
"font_class": "sousuo11",
|
||||||
|
"unicode": "e6d4",
|
||||||
|
"unicode_decimal": 59092
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6818781",
|
||||||
|
"name": "技师",
|
||||||
|
"font_class": "jishi",
|
||||||
|
"unicode": "e600",
|
||||||
|
"unicode_decimal": 58880
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
58
admin/src/styles/icon/addon/tourism/iconfont.css
Normal file
58
admin/src/styles/icon/addon/tourism/iconfont.css
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "tourism"; /* Project id 4137250 */
|
||||||
|
src: url('//at.alicdn.com/t/c/font_4137250_st1ha9l0k1e.woff2?t=1687685028672') format('woff2'),
|
||||||
|
url('//at.alicdn.com/t/c/font_4137250_st1ha9l0k1e.woff?t=1687685028672') format('woff'),
|
||||||
|
url('//at.alicdn.com/t/c/font_4137250_st1ha9l0k1e.ttf?t=1687685028672') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism {
|
||||||
|
font-family: "tourism" !important;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-feiji:before {
|
||||||
|
content: "\e600";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyou:before {
|
||||||
|
content: "\e6a9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyouchanpin:before {
|
||||||
|
content: "\e63b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyou1:before {
|
||||||
|
content: "\e623";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyou2:before {
|
||||||
|
content: "\e601";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyou3:before {
|
||||||
|
content: "\e60c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyoubaochedingdan:before {
|
||||||
|
content: "\e612";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyou4:before {
|
||||||
|
content: "\e653";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyou5:before {
|
||||||
|
content: "\e610";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyouguanguang:before {
|
||||||
|
content: "\e87e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tourism-icon-lvyou6:before {
|
||||||
|
content: "\e642";
|
||||||
|
}
|
||||||
86
admin/src/styles/icon/addon/tourism/iconfont.json
Normal file
86
admin/src/styles/icon/addon/tourism/iconfont.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"id": "4137250",
|
||||||
|
"name": "旅游业",
|
||||||
|
"font_family": "tourism",
|
||||||
|
"css_prefix_text": "tourism-icon-",
|
||||||
|
"description": "",
|
||||||
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "1443",
|
||||||
|
"name": "飞机",
|
||||||
|
"font_class": "feiji",
|
||||||
|
"unicode": "e600",
|
||||||
|
"unicode_decimal": 58880
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "446824",
|
||||||
|
"name": "旅游",
|
||||||
|
"font_class": "lvyou",
|
||||||
|
"unicode": "e6a9",
|
||||||
|
"unicode_decimal": 59049
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1167173",
|
||||||
|
"name": "旅游产品",
|
||||||
|
"font_class": "lvyouchanpin",
|
||||||
|
"unicode": "e63b",
|
||||||
|
"unicode_decimal": 58939
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1354920",
|
||||||
|
"name": "旅游",
|
||||||
|
"font_class": "lvyou1",
|
||||||
|
"unicode": "e623",
|
||||||
|
"unicode_decimal": 58915
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1505555",
|
||||||
|
"name": "旅游",
|
||||||
|
"font_class": "lvyou2",
|
||||||
|
"unicode": "e601",
|
||||||
|
"unicode_decimal": 58881
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "2121726",
|
||||||
|
"name": "旅游",
|
||||||
|
"font_class": "lvyou3",
|
||||||
|
"unicode": "e60c",
|
||||||
|
"unicode_decimal": 58892
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "2357494",
|
||||||
|
"name": "旅游包车订单",
|
||||||
|
"font_class": "lvyoubaochedingdan",
|
||||||
|
"unicode": "e612",
|
||||||
|
"unicode_decimal": 58898
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "3944019",
|
||||||
|
"name": "旅游",
|
||||||
|
"font_class": "lvyou4",
|
||||||
|
"unicode": "e653",
|
||||||
|
"unicode_decimal": 58963
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "4838220",
|
||||||
|
"name": "旅游",
|
||||||
|
"font_class": "lvyou5",
|
||||||
|
"unicode": "e610",
|
||||||
|
"unicode_decimal": 58896
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "7444178",
|
||||||
|
"name": "旅游观光",
|
||||||
|
"font_class": "lvyouguanguang",
|
||||||
|
"unicode": "e87e",
|
||||||
|
"unicode_decimal": 59518
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "9748082",
|
||||||
|
"name": "旅游",
|
||||||
|
"font_class": "lvyou6",
|
||||||
|
"unicode": "e642",
|
||||||
|
"unicode_decimal": 58946
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -9,54 +9,54 @@ import storage from './storage'
|
|||||||
* @param app
|
* @param app
|
||||||
*/
|
*/
|
||||||
export function useElementIcon(app: App): void {
|
export function useElementIcon(app: App): void {
|
||||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
app.component(key, component)
|
app.component(key, component)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置主题色
|
* 设置主题色
|
||||||
*/
|
*/
|
||||||
export function setThemeColor(color: string, mode: string = 'light'): void {
|
export function setThemeColor(color: string, mode: string = 'light'): void {
|
||||||
useCssVar('--el-color-primary', null).value = color
|
useCssVar('--el-color-primary', null).value = color
|
||||||
|
|
||||||
const colors: any = {
|
const colors: any = {
|
||||||
dark: {
|
dark: {
|
||||||
'light-3': 'shade(20%)',
|
'light-3': 'shade(20%)',
|
||||||
'light-5': 'shade(30%)',
|
'light-5': 'shade(30%)',
|
||||||
'light-7': 'shade(50%)',
|
'light-7': 'shade(50%)',
|
||||||
'light-8': 'shade(60%)',
|
'light-8': 'shade(60%)',
|
||||||
'light-9': 'shade(70%)',
|
'light-9': 'shade(70%)',
|
||||||
'dark-2': 'tint(20%)'
|
'dark-2': 'tint(20%)'
|
||||||
},
|
},
|
||||||
light: {
|
light: {
|
||||||
'dark-2': 'shade(20%)',
|
'dark-2': 'shade(20%)',
|
||||||
'light-3': 'tint(30%)',
|
'light-3': 'tint(30%)',
|
||||||
'light-5': 'tint(50%)',
|
'light-5': 'tint(50%)',
|
||||||
'light-7': 'tint(70%)',
|
'light-7': 'tint(70%)',
|
||||||
'light-8': 'tint(80%)',
|
'light-8': 'tint(80%)',
|
||||||
'light-9': 'tint(90%)'
|
'light-9': 'tint(90%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(colors[mode]).forEach((key) => {
|
Object.keys(colors[mode]).forEach((key) => {
|
||||||
useCssVar('--el-color-primary' + '-' + key, null).value = colorFunction.convert(`color(${ color } ${ colors[mode][key] })`)
|
useCssVar('--el-color-primary' + '-' + key, null).value = colorFunction.convert(`color(${ color } ${ colors[mode][key] })`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前访问应用类型
|
* 获取当前访问应用类型
|
||||||
*/
|
*/
|
||||||
export function getAppType() {
|
export function getAppType() {
|
||||||
const path = location.pathname.split('/').filter((val) => {
|
const path = location.pathname.split('/').filter((val) => {
|
||||||
return val
|
return val
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!path.length) {
|
if (!path.length) {
|
||||||
return 'admin'
|
return 'admin'
|
||||||
} else {
|
} else {
|
||||||
return path[0]
|
return path[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,8 +64,8 @@ export function getAppType() {
|
|||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
export function setWindowTitle(value: string = ''): void {
|
export function setWindowTitle(value: string = ''): void {
|
||||||
const title = useTitle()
|
const title = useTitle()
|
||||||
title.value = value ? value : import.meta.env.VITE_DETAULT_TITLE
|
title.value = value ? value : import.meta.env.VITE_DETAULT_TITLE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,7 +73,7 @@ export function setWindowTitle(value: string = ''): void {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getToken(): null | string {
|
export function getToken(): null | string {
|
||||||
return storage.get('token')
|
return storage.get('token')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,7 +82,7 @@ export function getToken(): null | string {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function setToken(token: string): void {
|
export function setToken(token: string): void {
|
||||||
storage.set({ key: 'token', data: token })
|
storage.set({ key: 'token', data: token })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,7 +90,7 @@ export function setToken(token: string): void {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function removeToken(): void {
|
export function removeToken(): void {
|
||||||
storage.remove('token')
|
storage.remove('token')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,16 +100,16 @@ export function removeToken(): void {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function debounce(fn: (args?: any) => any, delay: number = 300) {
|
export function debounce(fn: (args?: any) => any, delay: number = 300) {
|
||||||
let timer: null | number = null
|
let timer: null | number = null
|
||||||
return function (...args) {
|
return function (...args) {
|
||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
timer = null
|
timer = null
|
||||||
}
|
}
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
fn.call(this, ...args)
|
fn.call(this, ...args)
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -118,7 +118,7 @@ export function debounce(fn: (args?: any) => any, delay: number = 300) {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function isUrl(str: string): boolean {
|
export function isUrl(str: string): boolean {
|
||||||
return str.indexOf('http://') != -1 || str.indexOf('https://') != -1
|
return str.indexOf('http://') != -1 || str.indexOf('https://') != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,14 +127,14 @@ export function isUrl(str: string): boolean {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function img(path: string): string {
|
export function img(path: string): string {
|
||||||
if (!path) return ''
|
if (!path) return ''
|
||||||
|
|
||||||
let imgDomain = import.meta.env.VITE_IMG_DOMAIN || location.origin
|
|
||||||
|
|
||||||
if (path.startsWith('/')) path = path.replace(/^\//, '')
|
let imgDomain = import.meta.env.VITE_IMG_DOMAIN || location.origin
|
||||||
if (imgDomain.endsWith('/')) imgDomain = imgDomain.slice(0, -1)
|
|
||||||
|
if (path.startsWith('/')) path = path.replace(/^\//, '')
|
||||||
return isUrl(path) ? path : `${imgDomain}/${path}`
|
if (imgDomain.endsWith('/')) imgDomain = imgDomain.slice(0, -1)
|
||||||
|
|
||||||
|
return isUrl(path) ? path : `${imgDomain}/${path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -143,7 +143,7 @@ export function img(path: string): string {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function assetImg(path: string) {
|
export function assetImg(path: string) {
|
||||||
return new URL('@/', import.meta.url) + path
|
return new URL('@/', import.meta.url) + path
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,15 +152,15 @@ export function assetImg(path: string) {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function strByteLength(str: string = ''): number {
|
export function strByteLength(str: string = ''): number {
|
||||||
let len = 0;
|
let len = 0;
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
if (str.charCodeAt(i) > 127 || str.charCodeAt(i) == 94) {
|
if (str.charCodeAt(i) > 127 || str.charCodeAt(i) == 94) {
|
||||||
len += 2;
|
len += 2;
|
||||||
} else {
|
} else {
|
||||||
len++;
|
len++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,22 +168,22 @@ export function strByteLength(str: string = ''): number {
|
|||||||
* @param url
|
* @param url
|
||||||
*/
|
*/
|
||||||
export function urlToRouteRaw(url: string) {
|
export function urlToRouteRaw(url: string) {
|
||||||
const query: any = {}
|
const query: any = {}
|
||||||
const [path, param] = url.split('?')
|
const [path, param] = url.split('?')
|
||||||
|
|
||||||
param && param.split('&').forEach((str: string) => {
|
param && param.split('&').forEach((str: string) => {
|
||||||
let [name, value] = str.split('=')
|
let [name, value] = str.split('=')
|
||||||
query[name] = value
|
query[name] = value
|
||||||
})
|
})
|
||||||
|
|
||||||
return { path, query }
|
return { path, query }
|
||||||
}
|
}
|
||||||
|
|
||||||
const isArray = (value: any) => {
|
const isArray = (value: any) => {
|
||||||
if (typeof Array.isArray === 'function') {
|
if (typeof Array.isArray === 'function') {
|
||||||
return Array.isArray(value)
|
return Array.isArray(value)
|
||||||
}
|
}
|
||||||
return Object.prototype.toString.call(value) === '[object Array]'
|
return Object.prototype.toString.call(value) === '[object Array]'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -192,19 +192,19 @@ const isArray = (value: any) => {
|
|||||||
* @returns {*} 克隆后的对象或者原值(不是对象)
|
* @returns {*} 克隆后的对象或者原值(不是对象)
|
||||||
*/
|
*/
|
||||||
export function deepClone(obj: object) {
|
export function deepClone(obj: object) {
|
||||||
// 对常见的“非”值,直接返回原来值
|
// 对常见的“非”值,直接返回原来值
|
||||||
if ([null, undefined, NaN, false].includes(obj)) return obj
|
if ([null, undefined, NaN, false].includes(obj)) return obj
|
||||||
if (typeof obj !== 'object' && typeof obj !== 'function') {
|
if (typeof obj !== 'object' && typeof obj !== 'function') {
|
||||||
// 原始类型直接返回
|
// 原始类型直接返回
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
const o = isArray(obj) ? [] : {}
|
const o = isArray(obj) ? [] : {}
|
||||||
for (const i in obj) {
|
for (const i in obj) {
|
||||||
if (obj.hasOwnProperty(i)) {
|
if (obj.hasOwnProperty(i)) {
|
||||||
o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
|
o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -214,91 +214,91 @@ export function deepClone(obj: object) {
|
|||||||
* @param {Number} radix
|
* @param {Number} radix
|
||||||
*/
|
*/
|
||||||
export function guid(len = 10, firstU = true, radix: any = null) {
|
export function guid(len = 10, firstU = true, radix: any = null) {
|
||||||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
|
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
|
||||||
const uuid = []
|
const uuid = []
|
||||||
radix = radix || chars.length
|
radix = radix || chars.length
|
||||||
|
|
||||||
if (len) {
|
if (len) {
|
||||||
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
|
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
|
||||||
for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
|
for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
|
||||||
} else {
|
} else {
|
||||||
let r
|
let r
|
||||||
// rfc4122标准要求返回的uuid中,某些位为固定的字符
|
// rfc4122标准要求返回的uuid中,某些位为固定的字符
|
||||||
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
|
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
|
||||||
uuid[14] = '4'
|
uuid[14] = '4'
|
||||||
|
|
||||||
for (let i = 0; i < 36; i++) {
|
for (let i = 0; i < 36; i++) {
|
||||||
if (!uuid[i]) {
|
if (!uuid[i]) {
|
||||||
r = 0 | Math.random() * 16
|
r = 0 | Math.random() * 16
|
||||||
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
|
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
|
// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
|
||||||
if (firstU) {
|
if (firstU) {
|
||||||
uuid.shift()
|
uuid.shift()
|
||||||
return `u${ uuid.join('') }`
|
return `u${ uuid.join('') }`
|
||||||
}
|
}
|
||||||
return uuid.join('')
|
return uuid.join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 金额格式化
|
* 金额格式化
|
||||||
*/
|
*/
|
||||||
export function moneyFormat(money: string): string {
|
export function moneyFormat(money: string): string {
|
||||||
return isNaN(parseFloat(money)) ? money : parseFloat(money).toFixed(2)
|
return isNaN(parseFloat(money)) ? money : parseFloat(money).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 时间戳转日期格式
|
* 时间戳转日期格式
|
||||||
*/
|
*/
|
||||||
export function timeStampTurnTime(timeStamp: any, type = "") {
|
export function timeStampTurnTime(timeStamp: any, type = "") {
|
||||||
if (timeStamp != undefined && timeStamp != "" && timeStamp > 0) {
|
if (timeStamp != undefined && timeStamp != "" && timeStamp > 0) {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
date.setTime(timeStamp * 1000);
|
date.setTime(timeStamp * 1000);
|
||||||
const y: any = date.getFullYear();
|
const y: any = date.getFullYear();
|
||||||
let m: any = date.getMonth() + 1;
|
let m: any = date.getMonth() + 1;
|
||||||
m = m < 10 ? ('0' + m) : m;
|
m = m < 10 ? ('0' + m) : m;
|
||||||
let d: any = date.getDate();
|
let d: any = date.getDate();
|
||||||
d = d < 10 ? ('0' + d) : d;
|
d = d < 10 ? ('0' + d) : d;
|
||||||
let h: any = date.getHours();
|
let h: any = date.getHours();
|
||||||
h = h < 10 ? ('0' + h) : h;
|
h = h < 10 ? ('0' + h) : h;
|
||||||
let minute: any = date.getMinutes();
|
let minute: any = date.getMinutes();
|
||||||
let second: any = date.getSeconds();
|
let second: any = date.getSeconds();
|
||||||
minute = minute < 10 ? ('0' + minute) : minute;
|
minute = minute < 10 ? ('0' + minute) : minute;
|
||||||
second = second < 10 ? ('0' + second) : second;
|
second = second < 10 ? ('0' + second) : second;
|
||||||
if (type) {
|
if (type) {
|
||||||
if (type == 'yearMonthDay') {
|
if (type == 'yearMonthDay') {
|
||||||
return y + '年' + m + '月' + d + '日';
|
return y + '年' + m + '月' + d + '日';
|
||||||
}
|
}
|
||||||
return y + '-' + m + '-' + d;
|
return y + '-' + m + '-' + d;
|
||||||
} else {
|
} else {
|
||||||
return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second;
|
return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前日期时间
|
* 获取当前日期时间
|
||||||
*/
|
*/
|
||||||
export function getCurrentDataTime(timeStamp: any) {
|
export function getCurrentDataTime(timeStamp: any) {
|
||||||
const addZero = (t) => {
|
const addZero = (t) => {
|
||||||
return t < 10 ? '0' + t : t;
|
return t < 10 ? '0' + t : t;
|
||||||
}
|
}
|
||||||
const time = new Date(timeStamp);
|
const time = new Date(timeStamp);
|
||||||
let Y = time.getFullYear(), // 年
|
let Y = time.getFullYear(), // 年
|
||||||
M = time.getMonth() + 1, // 月
|
M = time.getMonth() + 1, // 月
|
||||||
D = time.getDate(), // 日
|
D = time.getDate(), // 日
|
||||||
h = time.getHours(), // 时
|
h = time.getHours(), // 时
|
||||||
m = time.getMinutes(), // 分
|
m = time.getMinutes(), // 分
|
||||||
s = time.getSeconds(); // 秒
|
s = time.getSeconds(); // 秒
|
||||||
if (M > 12) {
|
if (M > 12) {
|
||||||
M = M - 12;
|
M = M - 12;
|
||||||
}
|
}
|
||||||
return `${Y}-${addZero(M)}-${addZero(D)} ${addZero(h)}:${addZero(m)}:${addZero(s)}`
|
return `${Y}-${addZero(M)}-${addZero(D)} ${addZero(h)}:${addZero(m)}:${addZero(s)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -306,17 +306,17 @@ export function getCurrentDataTime(timeStamp: any) {
|
|||||||
* @param {Object} date
|
* @param {Object} date
|
||||||
*/
|
*/
|
||||||
export function timeTurnTimeStamp(date: string) {
|
export function timeTurnTimeStamp(date: string) {
|
||||||
const f = date.split(' ', 2);
|
const f = date.split(' ', 2);
|
||||||
const d = (f[0] ? f[0] : '').split('-', 3);
|
const d = (f[0] ? f[0] : '').split('-', 3);
|
||||||
const t = (f[1] ? f[1] : '').split(':', 3);
|
const t = (f[1] ? f[1] : '').split(':', 3);
|
||||||
return (new Date(
|
return (new Date(
|
||||||
parseInt(d[0], 10) || null,
|
parseInt(d[0], 10) || null,
|
||||||
(parseInt(d[1], 10) || 1) - 1,
|
(parseInt(d[1], 10) || 1) - 1,
|
||||||
parseInt(d[2], 10) || null,
|
parseInt(d[2], 10) || null,
|
||||||
parseInt(t[0], 10) || null,
|
parseInt(t[0], 10) || null,
|
||||||
parseInt(t[1], 10) || null,
|
parseInt(t[1], 10) || null,
|
||||||
parseInt(t[2], 10) || null
|
parseInt(t[2], 10) || null
|
||||||
)).getTime() / 1000;
|
)).getTime() / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -324,15 +324,15 @@ export function timeTurnTimeStamp(date: string) {
|
|||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
export function filterDigit(event: any) {
|
export function filterDigit(event: any) {
|
||||||
event.target.value = event.target.value.replace(/[^\d\.]/g, '');
|
event.target.value = event.target.value.replace(/[^\d\.]/g, '');
|
||||||
event.target.value = event.target.value.replace(/^\./g, '');
|
event.target.value = event.target.value.replace(/^\./g, '');
|
||||||
event.target.value = event.target.value.replace(/\.{2,}/g, '.');
|
event.target.value = event.target.value.replace(/\.{2,}/g, '.');
|
||||||
// 限制最多两位小数
|
// 限制最多两位小数
|
||||||
const decimalParts = event.target.value.split('.');
|
const decimalParts = event.target.value.split('.');
|
||||||
if (decimalParts.length > 1 && decimalParts[1].length > 2) {
|
if (decimalParts.length > 1 && decimalParts[1].length > 2) {
|
||||||
// 如果有小数部分且超过两位,则截取前两位
|
// 如果有小数部分且超过两位,则截取前两位
|
||||||
event.target.value = `${ decimalParts[0] }.${ decimalParts[1].slice(0, 2) }`;
|
event.target.value = `${ decimalParts[0] }.${ decimalParts[1].slice(0, 2) }`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -340,7 +340,7 @@ export function filterDigit(event: any) {
|
|||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
export function filterNumber(event: any) {
|
export function filterNumber(event: any) {
|
||||||
event.target.value = event.target.value.replace(/[^\d]/g, '');
|
event.target.value = event.target.value.replace(/[^\d]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -348,8 +348,8 @@ export function filterNumber(event: any) {
|
|||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
export function filterSpecial(event: any) {
|
export function filterSpecial(event: any) {
|
||||||
event.target.value = event.target.value.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '')
|
event.target.value = event.target.value.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '')
|
||||||
event.target.value = event.target.value.replace(/[`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]/g, '')
|
event.target.value = event.target.value.replace(/[`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]/g, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -357,7 +357,7 @@ export function filterSpecial(event: any) {
|
|||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
export function filterBlank(event: any) {
|
export function filterBlank(event: any) {
|
||||||
event.target.value = event.target.value.replace(/\s/g, '');
|
event.target.value = event.target.value.replace(/\s/g, '');
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 设置表格分页数据的本地存储
|
* 设置表格分页数据的本地存储
|
||||||
@ -366,23 +366,23 @@ export function filterBlank(event: any) {
|
|||||||
* @param where
|
* @param where
|
||||||
*/
|
*/
|
||||||
export function setTablePageStorage(page: any = 1, limit: any = 10, where: any = {}) {
|
export function setTablePageStorage(page: any = 1, limit: any = 10, where: any = {}) {
|
||||||
let data = storage.get('tablePageStorage');
|
let data = storage.get('tablePageStorage');
|
||||||
if (!data) {
|
if (!data) {
|
||||||
data = {};
|
data = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = location.pathname + JSON.stringify(where);
|
const key = location.pathname + JSON.stringify(where);
|
||||||
data[key] = {
|
data[key] = {
|
||||||
page,
|
page,
|
||||||
limit
|
limit
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_COUNT = 5; // 最多存储 5 个页面的分页缓存,超出则删除最开始的第一个页面
|
const MAX_COUNT = 5; // 最多存储 5 个页面的分页缓存,超出则删除最开始的第一个页面
|
||||||
if (Object.keys(data).length > MAX_COUNT) {
|
if (Object.keys(data).length > MAX_COUNT) {
|
||||||
delete data[Object.keys(data)[0]];
|
delete data[Object.keys(data)[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.set({ key: 'tablePageStorage', data });
|
storage.set({ key: 'tablePageStorage', data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -390,49 +390,49 @@ export function setTablePageStorage(page: any = 1, limit: any = 10, where: any =
|
|||||||
* @param where
|
* @param where
|
||||||
*/
|
*/
|
||||||
export function getTablePageStorage(where: any = {}) {
|
export function getTablePageStorage(where: any = {}) {
|
||||||
let data = storage.get('tablePageStorage');
|
let data = storage.get('tablePageStorage');
|
||||||
const key = location.pathname + JSON.stringify(where);
|
const key = location.pathname + JSON.stringify(where);
|
||||||
if (!data || !data[key]) {
|
if (!data || !data[key]) {
|
||||||
data = {
|
data = {
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 10
|
limit: 10
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
data = data[key];
|
data = data[key];
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 距离显示
|
// 距离显示
|
||||||
|
|
||||||
export function distance(distance: string | number): string {
|
export function distance(distance: string | number): string {
|
||||||
const dist = typeof distance === 'string' ? parseFloat(distance) : distance;
|
const dist = typeof distance === 'string' ? parseFloat(distance) : distance;
|
||||||
if (isNaN(dist)) return distance.toString();
|
if (isNaN(dist)) return distance.toString();
|
||||||
return dist < 1 ? parseInt((dist * 1000).toString()) + 'm' : dist.toFixed(1) + 'km'
|
return dist < 1 ? parseInt((dist * 1000).toString()) + 'm' : dist.toFixed(1) + 'km'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取图片尺寸的函数
|
// 获取图片尺寸的函数
|
||||||
export function getImageDimensions (url: string) {
|
export function getImageDimensions (url: string) {
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const imgObj = new Image()
|
const imgObj = new Image()
|
||||||
imgObj.onload = () => {
|
imgObj.onload = () => {
|
||||||
// 成功加载
|
// 成功加载
|
||||||
const size = {
|
const size = {
|
||||||
width: imgObj.naturalWidth,
|
width: imgObj.naturalWidth,
|
||||||
height: imgObj.naturalHeight
|
height: imgObj.naturalHeight
|
||||||
}
|
}
|
||||||
resolve(size)
|
resolve(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
imgObj.onerror = (err) => {
|
imgObj.onerror = (err) => {
|
||||||
// 加载失败
|
// 加载失败
|
||||||
resolve(null)
|
resolve(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置图片源,开始加载
|
// 设置图片源,开始加载
|
||||||
// 注意:如果图片跨域且服务器未设置CORS,可能会触发onerror
|
// 注意:如果图片跨域且服务器未设置CORS,可能会触发onerror
|
||||||
imgObj.src = img(url)
|
imgObj.src = img(url)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,11 @@ const geometry: any = {}
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 在地图上创建一个圆形
|
* 在地图上创建一个圆形
|
||||||
|
* @param map 地图实例
|
||||||
|
* @param geometriesData 圆形数据
|
||||||
|
* @param onSelect 图形被选中时的回调函数
|
||||||
*/
|
*/
|
||||||
export const createCircle = (map: any, geometriesData: any) => {
|
export const createCircle = (map: any, geometriesData: any, onSelect?: (key: string) => void) => {
|
||||||
const TMap = (window as any).TMap
|
const TMap = (window as any).TMap
|
||||||
const LatLng = TMap.LatLng
|
const LatLng = TMap.LatLng
|
||||||
|
|
||||||
@ -27,7 +30,11 @@ export const createCircle = (map: any, geometriesData: any) => {
|
|||||||
showBorder: true,
|
showBorder: true,
|
||||||
borderColor: `rgb(${color.toString()})`,
|
borderColor: `rgb(${color.toString()})`,
|
||||||
borderWidth: 2
|
borderWidth: 2
|
||||||
})
|
}),
|
||||||
|
highlight: new TMap.PolygonStyle({
|
||||||
|
color: 'rgba(255, 255, 0, 0.6)'
|
||||||
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
geometries: [
|
geometries: [
|
||||||
{
|
{
|
||||||
@ -38,25 +45,84 @@ export const createCircle = (map: any, geometriesData: any) => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
geometry[geometriesData.key] = { graphical: multiCircle }
|
|
||||||
|
// 如果已存在该图形,先删除
|
||||||
|
if (geometry[geometriesData.key]) {
|
||||||
|
try {
|
||||||
|
geometry[geometriesData.key].graphical.remove(geometriesData.key)
|
||||||
|
geometry[geometriesData.key].editor?.delete()
|
||||||
|
} catch (e) {
|
||||||
|
// 删除旧图形失败
|
||||||
|
// console.warn('删除旧图形失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 创建图形编辑器
|
let editor = null
|
||||||
const editor = new TMap.tools.GeometryEditor({
|
// 检查 TMap.tools 是否存在
|
||||||
map: map,
|
if (TMap.tools) {
|
||||||
overlayList: [
|
// 创建图形编辑器 - 支持拖动中心点和调整半径
|
||||||
{
|
try {
|
||||||
overlay: multiCircle,
|
editor = new TMap.tools.GeometryEditor({
|
||||||
id: geometriesData.key,
|
map: map,
|
||||||
}
|
overlayList: [
|
||||||
],
|
{
|
||||||
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT,
|
overlay: multiCircle,
|
||||||
activeOverlayId: geometriesData.key, // 激活图层
|
id: geometriesData.key,
|
||||||
selectable: true // 开启点选功能
|
selectedStyleId: 'highlight'
|
||||||
})
|
}
|
||||||
|
],
|
||||||
|
actionMode: TMap.tools.constants?.EDITOR_ACTION?.INTERACT || 0, // 交互模式,支持拖动中心点和调整半径
|
||||||
|
activeOverlayId: null, // 初始不激活,等待用户选择
|
||||||
|
selectable: true ,// 开启点选功能
|
||||||
|
snappable: true // 开启吸附
|
||||||
|
})
|
||||||
|
editor.setKeyboardDeleteEnable(false);
|
||||||
|
|
||||||
editor.on('adjust_complete', (data: any) => {
|
// 监听调整开始事件 - 禁用地图拖拽
|
||||||
geometriesData.center = { lat: data.center.lat, lng: data.center.lng }
|
editor.on('adjust_start', () => {
|
||||||
geometriesData.radius = parseInt(data.radius)
|
currentMap?.setDraggable(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听调整完成事件
|
||||||
|
editor.on('adjust_complete', (data: any) => {
|
||||||
|
if (data?.center && data.radius !== undefined) {
|
||||||
|
geometriesData.center = { lat: data.center.lat, lng: data.center.lng }
|
||||||
|
geometriesData.radius = parseInt(data.radius)
|
||||||
|
}
|
||||||
|
setTimeout(() => currentMap?.setDraggable(true), 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听调整中事件(实时更新)
|
||||||
|
editor.on('adjust', (data: any) => {
|
||||||
|
if (data?.center && data.radius !== undefined) {
|
||||||
|
geometriesData.center = { lat: data.center.lat, lng: data.center.lng }
|
||||||
|
geometriesData.radius = parseInt(data.radius)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听选中事件
|
||||||
|
editor.on('select', () => {
|
||||||
|
// 只有在用户点击地图上的图形时才触发,而不是在切换页面时
|
||||||
|
if (onSelect) {
|
||||||
|
onSelect(geometriesData.key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('创建编辑器失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为圆形添加点击事件监听器
|
||||||
|
multiCircle.on('click', () => {
|
||||||
|
// 先选中当前图形,使其高亮显示
|
||||||
|
if (editor) {
|
||||||
|
editor.select([geometriesData.key])
|
||||||
|
editor.setActiveOverlay?.(geometriesData.key)
|
||||||
|
}
|
||||||
|
// 然后触发选中回调
|
||||||
|
if (onSelect) {
|
||||||
|
onSelect(geometriesData.key)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
geometry[geometriesData.key] = { graphical: multiCircle, editor }
|
geometry[geometriesData.key] = { graphical: multiCircle, editor }
|
||||||
@ -64,10 +130,11 @@ export const createCircle = (map: any, geometriesData: any) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 在地图上创建一个多边形
|
* 在地图上创建一个多边形
|
||||||
* @param map
|
* @param map 地图实例
|
||||||
* @param geometriesData
|
* @param geometriesData 多边形数据
|
||||||
|
* @param onSelect 图形被选中时的回调函数
|
||||||
*/
|
*/
|
||||||
export const createPolygon = (map: any, geometriesData: any) => {
|
export const createPolygon = (map: any, geometriesData: any, onSelect?: (key: string) => void) => {
|
||||||
const TMap = (window as any).TMap
|
const TMap = (window as any).TMap
|
||||||
const LatLng = TMap.LatLng
|
const LatLng = TMap.LatLng
|
||||||
|
|
||||||
@ -86,6 +153,17 @@ export const createPolygon = (map: any, geometriesData: any) => {
|
|||||||
Math.floor(Math.random() * 255)
|
Math.floor(Math.random() * 255)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 如果已存在该图形,先删除
|
||||||
|
if (geometry[geometriesData.key]) {
|
||||||
|
try {
|
||||||
|
geometry[geometriesData.key].graphical.remove(geometriesData.key)
|
||||||
|
geometry[geometriesData.key].editor?.delete()
|
||||||
|
} catch (e) {
|
||||||
|
// 删除旧多边形失败
|
||||||
|
// console.warn('删除旧多边形失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const multiPolygon = new TMap.MultiPolygon({
|
const multiPolygon = new TMap.MultiPolygon({
|
||||||
map: map,
|
map: map,
|
||||||
styles: {
|
styles: {
|
||||||
@ -94,7 +172,10 @@ export const createPolygon = (map: any, geometriesData: any) => {
|
|||||||
showBorder: true,
|
showBorder: true,
|
||||||
borderColor: `rgb(${color.toString()})`,
|
borderColor: `rgb(${color.toString()})`,
|
||||||
borderWidth: 2
|
borderWidth: 2
|
||||||
})
|
}),
|
||||||
|
highlight: new TMap.PolygonStyle({
|
||||||
|
color: 'rgba(255, 255, 0, 0.6)'
|
||||||
|
})
|
||||||
},
|
},
|
||||||
geometries: [
|
geometries: [
|
||||||
{
|
{
|
||||||
@ -107,23 +188,70 @@ export const createPolygon = (map: any, geometriesData: any) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
const editor = new TMap.tools.GeometryEditor({
|
let editor = null
|
||||||
map: map,
|
// 检查 TMap.tools 是否存在
|
||||||
overlayList: [
|
if (TMap.tools) {
|
||||||
{
|
// 创建图形编辑器 - 支持拖动边框和调整形状
|
||||||
overlay: multiPolygon,
|
try {
|
||||||
id: geometriesData.key,
|
editor = new TMap.tools.GeometryEditor({
|
||||||
}
|
map: map,
|
||||||
],
|
overlayList: [
|
||||||
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT,
|
{
|
||||||
activeOverlayId: geometriesData.key, // 激活图层
|
overlay: multiPolygon,
|
||||||
selectable: true, // 开启点选功能
|
id: geometriesData.key,
|
||||||
})
|
selectedStyleId: 'highlight'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
actionMode: TMap.tools.constants?.EDITOR_ACTION?.INTERACT || 0, // 交互模式,支持拖动边框和调整形状
|
||||||
|
activeOverlayId: null, // 初始不激活,等待用户选择
|
||||||
|
selectable: true, // 开启点选功能
|
||||||
|
snappable: true // 开启吸附
|
||||||
|
})
|
||||||
|
|
||||||
editor.on('adjust_complete', (data: any) => {
|
editor.setKeyboardDeleteEnable(false);
|
||||||
geometriesData.paths = data.paths.map(item => {
|
// 监听调整开始事件 - 禁用地图拖拽
|
||||||
return { lat: item.lat, lng: item.lng}
|
editor.on('adjust_start', () => {
|
||||||
})
|
currentMap?.setDraggable(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听调整完成事件
|
||||||
|
editor.on('adjust_complete', (data: any) => {
|
||||||
|
if (data?.paths) {
|
||||||
|
geometriesData.paths = data.paths.map((item: any) => ({ lat: item.lat, lng: item.lng }))
|
||||||
|
}
|
||||||
|
setTimeout(() => currentMap?.setDraggable(true), 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听调整中事件(实时更新)
|
||||||
|
editor.on('adjust', (data: any) => {
|
||||||
|
if (data?.paths) {
|
||||||
|
geometriesData.paths = data.paths.map((item: any) => ({ lat: item.lat, lng: item.lng }))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听选中事件
|
||||||
|
editor.on('select', () => {
|
||||||
|
// 只有在用户点击地图上的图形时才触发,而不是在切换页面时
|
||||||
|
if (onSelect) {
|
||||||
|
onSelect(geometriesData.key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('创建编辑器失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为多边形添加点击事件监听器
|
||||||
|
multiPolygon.on('click', () => {
|
||||||
|
// 先选中当前图形,使其高亮显示
|
||||||
|
if (editor) {
|
||||||
|
editor.select([geometriesData.key])
|
||||||
|
editor.setActiveOverlay?.(geometriesData.key)
|
||||||
|
}
|
||||||
|
// 然后触发选中回调
|
||||||
|
if (onSelect) {
|
||||||
|
onSelect(geometriesData.key)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
geometry[geometriesData.key] = { graphical: multiPolygon, editor }
|
geometry[geometriesData.key] = { graphical: multiPolygon, editor }
|
||||||
@ -131,25 +259,68 @@ export const createPolygon = (map: any, geometriesData: any) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除图形
|
* 删除图形
|
||||||
* @param key
|
|
||||||
*/
|
*/
|
||||||
export const deleteGeometry = (key: string) => {
|
export const deleteGeometry = (key: string) => {
|
||||||
if (!geometry[key]) {
|
if (!geometry[key]) return
|
||||||
return
|
try {
|
||||||
|
geometry[key].graphical?.remove(key)
|
||||||
|
geometry[key].editor?.delete()
|
||||||
|
delete geometry[key]
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('删除图形失败:', e)
|
||||||
}
|
}
|
||||||
geometry[key].graphical.remove(key)
|
}
|
||||||
geometry[key].editor.delete()
|
|
||||||
|
/**
|
||||||
|
* 清空所有图形
|
||||||
|
*/
|
||||||
|
export const clearAllGeometry = () => {
|
||||||
|
Object.keys(geometry).forEach(deleteGeometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存地图引用,用于控制地图拖拽
|
||||||
|
let currentMap: any = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置地图引用
|
||||||
|
* @param map 地图实例
|
||||||
|
*/
|
||||||
|
export const setMapInstance = (map: any) => {
|
||||||
|
currentMap = map
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选中图形
|
* 选中图形
|
||||||
* @param key
|
|
||||||
*/
|
*/
|
||||||
export const selectGeometry = (key: string) => {
|
export const selectGeometry = (key: string) => {
|
||||||
if (!geometry[key] || !geometry[key].editor) {
|
if (!geometry[key]?.editor) return
|
||||||
return
|
|
||||||
|
try {
|
||||||
|
// 取消其他图形的选中状态
|
||||||
|
Object.keys(geometry).forEach(k => {
|
||||||
|
if (k !== key && geometry[k]?.editor) {
|
||||||
|
geometry[k].editor.deselect?.()
|
||||||
|
geometry[k].editor.setActiveOverlay?.(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 选中当前图形
|
||||||
|
geometry[key].editor.select?.([key])
|
||||||
|
geometry[key].editor.setActiveOverlay?.(key)
|
||||||
|
} catch (e) {
|
||||||
|
// 删除图形失败
|
||||||
|
// console.error('选中图形失败:', e)
|
||||||
}
|
}
|
||||||
geometry[key].editor.select([key])
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消选中所有图形
|
||||||
|
*/
|
||||||
|
export const deselectAllGeometry = () => {
|
||||||
|
Object.keys(geometry).forEach(k => {
|
||||||
|
geometry[k]?.editor?.deselect?.()
|
||||||
|
geometry[k]?.editor?.setActiveOverlay?.(null)
|
||||||
|
})
|
||||||
|
currentMap?.setDraggable(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,4 +356,4 @@ export const latLngToAddress = (params: any) => {
|
|||||||
*/
|
*/
|
||||||
export const addressToLatLng = (params: any) => {
|
export const addressToLatLng = (params: any) => {
|
||||||
return jsonp(`https://apis.map.qq.com/ws/geocoder/v1/?key=${params.mapKey}&address=${params.address}&output=jsonp&callback=addressToLatLng`, { callbackName: 'addressToLatLng' })
|
return jsonp(`https://apis.map.qq.com/ws/geocoder/v1/?key=${params.mapKey}&address=${params.address}&output=jsonp&callback=addressToLatLng`, { callbackName: 'addressToLatLng' })
|
||||||
}
|
}
|
||||||
@ -21,6 +21,27 @@ interface requestResponse extends AxiosResponse {
|
|||||||
config: InternalRequestConfig
|
config: InternalRequestConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ErrorResponse {
|
||||||
|
msg: string = '';
|
||||||
|
code: number = 0;
|
||||||
|
response: any = null;
|
||||||
|
|
||||||
|
constructor(msg: string);
|
||||||
|
constructor(code: number, msg: string, response: any);
|
||||||
|
|
||||||
|
constructor(arg1?: string | number, arg2?: string, arg3?: any) {
|
||||||
|
if (typeof arg1 === 'number') {
|
||||||
|
this.code = arg1;
|
||||||
|
this.msg = arg2 || '';
|
||||||
|
this.response = arg3; // 修正点3:补上漏掉的赋值
|
||||||
|
} else {
|
||||||
|
this.msg = (arg1 as string) || '';
|
||||||
|
this.code = 0;
|
||||||
|
this.response = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Request {
|
class Request {
|
||||||
private instance: AxiosInstance;
|
private instance: AxiosInstance;
|
||||||
|
|
||||||
@ -45,7 +66,7 @@ class Request {
|
|||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
(err: any) => {
|
(err: any) => {
|
||||||
return Promise.reject(err)
|
return Promise.reject(new ErrorResponse(0, err.message, err.response))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -57,7 +78,7 @@ class Request {
|
|||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
this.handleAuthError(res.code)
|
this.handleAuthError(res.code)
|
||||||
if (res.code != 401 && response.config.showErrorMessage !== false) this.showElMessage({ message: res.msg, type: 'error', dangerouslyUseHTMLString: true, duration: 5000 })
|
if (res.code != 401 && response.config.showErrorMessage !== false) this.showElMessage({ message: res.msg, type: 'error', dangerouslyUseHTMLString: true, duration: 5000 })
|
||||||
return Promise.reject(new Error(res.msg || 'Error'))
|
return Promise.reject(res)
|
||||||
} else {
|
} else {
|
||||||
if (response.config.showSuccessMessage) ElMessage({ message: res.msg, type: 'success' })
|
if (response.config.showSuccessMessage) ElMessage({ message: res.msg, type: 'success' })
|
||||||
return res
|
return res
|
||||||
@ -67,7 +88,7 @@ class Request {
|
|||||||
},
|
},
|
||||||
(err: any) => {
|
(err: any) => {
|
||||||
this.handleNetworkError(err)
|
this.handleNetworkError(err)
|
||||||
return Promise.reject(err)
|
return Promise.reject(new ErrorResponse(0, err.message, err.response))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -195,7 +216,7 @@ class Request {
|
|||||||
this.messageCache.set(cacheKey, { timestamp: now });
|
this.messageCache.set(cacheKey, { timestamp: now });
|
||||||
ElMessage(options)
|
ElMessage(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定期清理过期缓存,防止内存泄漏
|
// 定期清理过期缓存,防止内存泄漏
|
||||||
if (this.messageCache.size > this.MAX_CACHE_SIZE) {
|
if (this.messageCache.size > this.MAX_CACHE_SIZE) {
|
||||||
for (const [key, value] of this.messageCache.entries()) {
|
for (const [key, value] of this.messageCache.entries()) {
|
||||||
|
|||||||
511
admin/src/utils/tianditu.ts
Normal file
511
admin/src/utils/tianditu.ts
Normal file
@ -0,0 +1,511 @@
|
|||||||
|
const geometry: any = {}
|
||||||
|
|
||||||
|
// 保存地图引用,用于控制地图拖拽
|
||||||
|
let currentMap: any = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置地图引用
|
||||||
|
* @param map 地图实例
|
||||||
|
*/
|
||||||
|
export const setMapInstance = (map: any) => {
|
||||||
|
currentMap = map
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在天地图上创建一个圆形
|
||||||
|
* @param map 天地图实例
|
||||||
|
* @param geometriesData 圆形数据
|
||||||
|
* @param onChange 图形变化时的回调函数
|
||||||
|
* @param onSelect 图形被选中时的回调函数
|
||||||
|
*/
|
||||||
|
export const createCircle = (map: any, geometriesData: any, onChange?: (data: any) => void, onSelect?: (key: string) => void) => {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (!T) return null
|
||||||
|
|
||||||
|
geometriesData.radius = geometriesData.radius ?? 1000
|
||||||
|
geometriesData.center = geometriesData.center ?? { lat: map.getCenter().lat, lng: map.getCenter().lng }
|
||||||
|
|
||||||
|
const color = [
|
||||||
|
Math.floor(Math.random() * 255),
|
||||||
|
Math.floor(Math.random() * 255),
|
||||||
|
Math.floor(Math.random() * 255)
|
||||||
|
]
|
||||||
|
// 保存原始样式
|
||||||
|
const originalStyle = {
|
||||||
|
color: geometriesData.color || `rgba(${color.toString()}, .4)`,
|
||||||
|
weight: geometriesData.weight || 3,
|
||||||
|
opacity: geometriesData.opacity || 0.8,
|
||||||
|
fillColor: geometriesData.fillColor || `rgba(${color.toString()}, .4)`,
|
||||||
|
fillOpacity: geometriesData.fillOpacity || 0.3
|
||||||
|
}
|
||||||
|
const circle = new T.Circle(new T.LngLat(geometriesData.center.lng, geometriesData.center.lat), geometriesData.radius, originalStyle)
|
||||||
|
|
||||||
|
// 如果已存在该图形,先删除
|
||||||
|
if (geometry[geometriesData.key]) {
|
||||||
|
try {
|
||||||
|
if (geometry[geometriesData.key].editTimer) {
|
||||||
|
clearTimeout(geometry[geometriesData.key].editTimer)
|
||||||
|
}
|
||||||
|
map.removeOverLay(geometry[geometriesData.key].graphical)
|
||||||
|
} catch (e) {
|
||||||
|
// 删除旧图形失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.addOverLay(circle)
|
||||||
|
|
||||||
|
// 保存几何数据的引用,确保在事件监听器中可用
|
||||||
|
const circleData = { ...geometriesData }
|
||||||
|
|
||||||
|
// 保存上一次的中心点和半径
|
||||||
|
let preCenter = { ...circleData.center }
|
||||||
|
let preRadius = circleData.radius
|
||||||
|
|
||||||
|
// 监听地图触摸结束事件,检测圆形变化
|
||||||
|
const handleTouchEnd = () => {
|
||||||
|
if (!circle.isEditable()) return
|
||||||
|
|
||||||
|
if (geometry[circleData.key]?.editTimer) {
|
||||||
|
clearTimeout(geometry[circleData.key].editTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry[circleData.key].editTimer = setTimeout(() => {
|
||||||
|
const curCenter = circle.getCenter()
|
||||||
|
const curRadius = circle.getRadius()
|
||||||
|
|
||||||
|
// 中心点改变了
|
||||||
|
if (curCenter.lng !== preCenter.lng || curCenter.lat !== preCenter.lat) {
|
||||||
|
circleData.center = { lat: curCenter.lat, lng: curCenter.lng }
|
||||||
|
preCenter = { ...circleData.center }
|
||||||
|
// console.log('圆形中心点改变了', circleData.center)
|
||||||
|
// 调用回调函数
|
||||||
|
if (onChange) {
|
||||||
|
onChange(circleData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 半径改变了
|
||||||
|
if (curRadius !== preRadius) {
|
||||||
|
circleData.radius = curRadius
|
||||||
|
preRadius = curRadius
|
||||||
|
// console.log('圆形半径改变了', curRadius)
|
||||||
|
// 调用回调函数
|
||||||
|
if (onChange) {
|
||||||
|
onChange(circleData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听圆形点击事件
|
||||||
|
const handleClick = () => {
|
||||||
|
// 调用选择回调函数
|
||||||
|
if (onSelect) {
|
||||||
|
onSelect(circleData.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始时不启用编辑功能,只有在选中时才启用
|
||||||
|
// circle.enableEdit()
|
||||||
|
|
||||||
|
// 添加事件监听
|
||||||
|
map.addEventListener('touchend', handleTouchEnd)
|
||||||
|
map.addEventListener('mouseup', handleTouchEnd)
|
||||||
|
circle.addEventListener('click', handleClick)
|
||||||
|
|
||||||
|
geometry[circleData.key] = { graphical: circle, handleTouchEnd, handleClick, originalStyle }
|
||||||
|
return circle
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在天地图上创建一个矩形
|
||||||
|
* @param map 天地图实例
|
||||||
|
* @param geometriesData 矩形数据
|
||||||
|
*/
|
||||||
|
export const createRectangle = (map: any, geometriesData: any) => {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (!T) return null
|
||||||
|
|
||||||
|
const { lat, lng } = map.getCenter()
|
||||||
|
|
||||||
|
geometriesData.bounds = geometriesData.bounds ?? {
|
||||||
|
southwest: { lat: lat - 0.005, lng: lng - 0.005 },
|
||||||
|
northeast: { lat: lat + 0.005, lng: lng + 0.005 }
|
||||||
|
}
|
||||||
|
|
||||||
|
const lngLats = [
|
||||||
|
new T.LngLat(geometriesData.bounds.southwest.lng, geometriesData.bounds.southwest.lat),
|
||||||
|
new T.LngLat(geometriesData.bounds.northeast.lng, geometriesData.bounds.southwest.lat),
|
||||||
|
new T.LngLat(geometriesData.bounds.northeast.lng, geometriesData.bounds.northeast.lat),
|
||||||
|
new T.LngLat(geometriesData.bounds.southwest.lng, geometriesData.bounds.northeast.lat)
|
||||||
|
]
|
||||||
|
const color = [
|
||||||
|
Math.floor(Math.random() * 255),
|
||||||
|
Math.floor(Math.random() * 255),
|
||||||
|
Math.floor(Math.random() * 255)
|
||||||
|
]
|
||||||
|
|
||||||
|
const rectangle = new T.Polygon(lngLats, {
|
||||||
|
color: geometriesData.color || `rgba(${color.toString()}, .4)`,
|
||||||
|
weight: geometriesData.weight || 3,
|
||||||
|
opacity: geometriesData.opacity || 0.8,
|
||||||
|
fillColor: geometriesData.fillColor || `rgba(${color.toString()}, .4)`,
|
||||||
|
fillOpacity: geometriesData.fillOpacity || 0.3
|
||||||
|
})
|
||||||
|
|
||||||
|
map.addOverLay(rectangle)
|
||||||
|
geometry[geometriesData.key] = { graphical: rectangle }
|
||||||
|
return rectangle
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在天地图上创建一个多边形
|
||||||
|
* @param map 天地图实例
|
||||||
|
* @param geometriesData 多边形数据
|
||||||
|
* @param onChange 图形变化时的回调函数
|
||||||
|
* @param onSelect 图形被选中时的回调函数
|
||||||
|
*/
|
||||||
|
export const createPolygon = (map: any, geometriesData: any, onChange?: (data: any) => void, onSelect?: (key: string) => void) => {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (!T) return null
|
||||||
|
|
||||||
|
const { lat, lng } = map.getCenter()
|
||||||
|
|
||||||
|
geometriesData.paths = geometriesData.paths ?? [
|
||||||
|
{ lat: lat + 0.01, lng: lng + 0.01 },
|
||||||
|
{ lat: lat - 0.01, lng: lng + 0.01 },
|
||||||
|
{ lat: lat - 0.01, lng: lng - 0.01 },
|
||||||
|
{ lat: lat + 0.01, lng: lng - 0.01 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const lngLats = geometriesData.paths.map((item: any) => new T.LngLat(item.lng, item.lat))
|
||||||
|
const color = [
|
||||||
|
Math.floor(Math.random() * 255),
|
||||||
|
Math.floor(Math.random() * 255),
|
||||||
|
Math.floor(Math.random() * 255)
|
||||||
|
]
|
||||||
|
// 保存原始样式
|
||||||
|
const originalStyle = {
|
||||||
|
color: geometriesData.color || `rgba(${color.toString()}, .4)`,
|
||||||
|
weight: geometriesData.weight || 3,
|
||||||
|
opacity: geometriesData.opacity || 0.8,
|
||||||
|
fillColor: geometriesData.fillColor || `rgba(${color.toString()}, .4)`,
|
||||||
|
fillOpacity: geometriesData.fillOpacity || 0.3
|
||||||
|
}
|
||||||
|
const polygon = new T.Polygon(lngLats, originalStyle)
|
||||||
|
|
||||||
|
// 如果已存在该图形,先删除
|
||||||
|
if (geometry[geometriesData.key]) {
|
||||||
|
try {
|
||||||
|
if (geometry[geometriesData.key].editTimer) {
|
||||||
|
clearTimeout(geometry[geometriesData.key].editTimer)
|
||||||
|
}
|
||||||
|
map.removeOverLay(geometry[geometriesData.key].graphical)
|
||||||
|
} catch (e) {
|
||||||
|
// 删除旧图形失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.addOverLay(polygon)
|
||||||
|
|
||||||
|
// 保存上一次的路径
|
||||||
|
let prePaths = JSON.parse(JSON.stringify(geometriesData.paths))
|
||||||
|
|
||||||
|
// 保存几何数据的引用,确保在事件监听器中可用
|
||||||
|
const polygonData = { ...geometriesData }
|
||||||
|
|
||||||
|
// 监听地图触摸结束事件,检测多边形变化
|
||||||
|
const handleTouchEnd = () => {
|
||||||
|
if (!polygon.isEditable()) return
|
||||||
|
|
||||||
|
if (geometry[polygonData.key]?.editTimer) {
|
||||||
|
clearTimeout(geometry[polygonData.key].editTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry[polygonData.key].editTimer = setTimeout(() => {
|
||||||
|
try {
|
||||||
|
const curLngLats = polygon.getLngLats()
|
||||||
|
|
||||||
|
// 确保 curLngLats 是数组
|
||||||
|
if (Array.isArray(curLngLats)) {
|
||||||
|
// 检查 curLngLats[0] 是否是数组(多边形可能返回嵌套数组)
|
||||||
|
let pathsArray = curLngLats
|
||||||
|
if (Array.isArray(curLngLats[0])) {
|
||||||
|
pathsArray = curLngLats[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const curPaths = pathsArray.map((lngLat: any) => {
|
||||||
|
// 检查 lngLat 是否是数组 [lng, lat]
|
||||||
|
if (Array.isArray(lngLat)) {
|
||||||
|
return {
|
||||||
|
lat: lngLat[1],
|
||||||
|
lng: lngLat[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 否则,假设是对象 { lat, lng }
|
||||||
|
return {
|
||||||
|
lat: lngLat.lat,
|
||||||
|
lng: lngLat.lng
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查路径是否改变
|
||||||
|
const pathsChanged = JSON.stringify(curPaths) !== JSON.stringify(prePaths)
|
||||||
|
|
||||||
|
if (pathsChanged) {
|
||||||
|
polygonData.paths = curPaths
|
||||||
|
prePaths = JSON.parse(JSON.stringify(polygonData.paths))
|
||||||
|
// 调用回调函数
|
||||||
|
if (onChange) {
|
||||||
|
onChange(polygonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('curLngLats 不是数组:', curLngLats)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取多边形路径失败:', error)
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听多边形点击事件
|
||||||
|
const handleClick = () => {
|
||||||
|
// 调用选择回调函数
|
||||||
|
if (onSelect) {
|
||||||
|
onSelect(polygonData.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始时不启用编辑功能,只有在选中时才启用
|
||||||
|
// polygon.enableEdit()
|
||||||
|
|
||||||
|
// 添加事件监听
|
||||||
|
map.addEventListener('touchend', handleTouchEnd)
|
||||||
|
map.addEventListener('mouseup', handleTouchEnd)
|
||||||
|
polygon.addEventListener('click', handleClick)
|
||||||
|
|
||||||
|
geometry[polygonData.key] = { graphical: polygon, handleTouchEnd, handleClick, originalStyle }
|
||||||
|
return polygon
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除图形
|
||||||
|
* @param map 地图实例
|
||||||
|
* @param key 图形key
|
||||||
|
*/
|
||||||
|
export const deleteGeometry = (map: any, key: string) => {
|
||||||
|
if (!geometry[key]) return
|
||||||
|
try {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (T && geometry[key].graphical) {
|
||||||
|
// 清除定时器
|
||||||
|
if (geometry[key].editTimer) {
|
||||||
|
clearTimeout(geometry[key].editTimer)
|
||||||
|
}
|
||||||
|
// 移除事件监听器
|
||||||
|
if (geometry[key].handleTouchEnd) {
|
||||||
|
map.removeEventListener('touchend', geometry[key].handleTouchEnd)
|
||||||
|
map.removeEventListener('mouseup', geometry[key].handleTouchEnd)
|
||||||
|
}
|
||||||
|
// 移除点击事件监听器
|
||||||
|
if (geometry[key].handleClick) {
|
||||||
|
geometry[key].graphical.removeEventListener('click', geometry[key].handleClick)
|
||||||
|
}
|
||||||
|
// 移除覆盖物
|
||||||
|
map.removeOverLay(geometry[key].graphical)
|
||||||
|
}
|
||||||
|
delete geometry[key]
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('删除图形失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有图形
|
||||||
|
* @param map 地图实例
|
||||||
|
*/
|
||||||
|
export const clearAllGeometry = (map: any) => {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (T) {
|
||||||
|
// 先清除所有图形的事件监听器和定时器
|
||||||
|
Object.keys(geometry).forEach(key => {
|
||||||
|
if (geometry[key].editTimer) {
|
||||||
|
clearTimeout(geometry[key].editTimer)
|
||||||
|
}
|
||||||
|
if (geometry[key].handleTouchEnd) {
|
||||||
|
map.removeEventListener('touchend', geometry[key].handleTouchEnd)
|
||||||
|
map.removeEventListener('mouseup', geometry[key].handleTouchEnd)
|
||||||
|
}
|
||||||
|
if (geometry[key].handleClick) {
|
||||||
|
geometry[key].graphical.removeEventListener('click', geometry[key].handleClick)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 一次性清空地图上的所有覆盖物
|
||||||
|
map.clearOverLays()
|
||||||
|
}
|
||||||
|
// 清空几何对象存储
|
||||||
|
Object.keys(geometry).forEach(key => delete geometry[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中图形
|
||||||
|
* @param key 图形key
|
||||||
|
*/
|
||||||
|
export const selectGeometry = (key: string) => {
|
||||||
|
if (!geometry[key]?.graphical) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先取消所有图形的选中状态
|
||||||
|
deselectAllGeometry()
|
||||||
|
|
||||||
|
// 设置选中状态
|
||||||
|
geometry[key].graphical.setSelected?.(true)
|
||||||
|
|
||||||
|
// 启用当前图形的编辑功能
|
||||||
|
geometry[key].graphical.enableEdit()
|
||||||
|
|
||||||
|
const color = [
|
||||||
|
Math.floor(Math.random() * 255),
|
||||||
|
Math.floor(Math.random() * 255),
|
||||||
|
Math.floor(Math.random() * 255)
|
||||||
|
]
|
||||||
|
|
||||||
|
// 修改样式,使其显示为选中状态
|
||||||
|
geometry[key].graphical.setStyle({
|
||||||
|
color: '#1890ff', // 选中时的边框颜色
|
||||||
|
weight: 2, // 选中时的边框宽度
|
||||||
|
opacity: 1, // 选中时的边框透明度
|
||||||
|
fillColor: `rgba(${color.toString()}, .6)`, // 选中时的填充颜色
|
||||||
|
fillOpacity: 1// 选中时的填充透明度
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error('选中图形失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消选中所有图形
|
||||||
|
*/
|
||||||
|
export const deselectAllGeometry = () => {
|
||||||
|
Object.keys(geometry).forEach(key => {
|
||||||
|
try {
|
||||||
|
// 取消选中状态
|
||||||
|
geometry[key]?.graphical?.setSelected?.(false)
|
||||||
|
|
||||||
|
// 禁用编辑功能
|
||||||
|
geometry[key]?.graphical?.disableEdit?.()
|
||||||
|
|
||||||
|
// 恢复原始样式
|
||||||
|
if (geometry[key]?.originalStyle) {
|
||||||
|
geometry[key].graphical.setStyle(geometry[key].originalStyle)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('取消选中图形失败:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建点标记
|
||||||
|
* @param map 天地图实例
|
||||||
|
* @returns 标记实例
|
||||||
|
*/
|
||||||
|
export const createMarker = (map: any) => {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (!T) return null
|
||||||
|
|
||||||
|
const { lat, lng } = map.getCenter()
|
||||||
|
const marker = new T.Marker(new T.LngLat(lng, lat))
|
||||||
|
map.addOverLay(marker)
|
||||||
|
return marker
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逆地址解析
|
||||||
|
* @param params 参数 { mapKey, lat, lng }
|
||||||
|
* @returns Promise
|
||||||
|
*/
|
||||||
|
export const latLngToAddress = (params: any) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (!T) {
|
||||||
|
reject(new Error('天地图服务未加载'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const geocoder = new T.Geocoder()
|
||||||
|
const lngLat = new T.LngLat(params.lng, params.lat)
|
||||||
|
|
||||||
|
geocoder.getLocation(lngLat, (result: any) => {
|
||||||
|
if (result && result.status === '0') {
|
||||||
|
const addrComp = result.addressComponent || {}
|
||||||
|
let detailAddress = ''
|
||||||
|
|
||||||
|
if (result.formattedAddress) {
|
||||||
|
detailAddress = result.formattedAddress
|
||||||
|
} else if (result.address) {
|
||||||
|
detailAddress = result.address
|
||||||
|
} else {
|
||||||
|
const parts = []
|
||||||
|
if (addrComp.road) parts.push(addrComp.road)
|
||||||
|
if (addrComp.address) parts.push(addrComp.address)
|
||||||
|
if (addrComp.poi) parts.push(addrComp.poi)
|
||||||
|
detailAddress = parts.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const addressData = {
|
||||||
|
location: { lat: params.lat, lng: params.lng },
|
||||||
|
address: addrComp ?
|
||||||
|
`${ addrComp.province || '' }${ addrComp.city || '' }${ addrComp.county || '' }` : '',
|
||||||
|
formatted_addresses: {
|
||||||
|
recommend: detailAddress
|
||||||
|
},
|
||||||
|
address_component: {
|
||||||
|
province: addrComp.province || '',
|
||||||
|
city: addrComp.city || '',
|
||||||
|
district: addrComp.county || '',
|
||||||
|
street: addrComp.road || '',
|
||||||
|
street_number: addrComp.address || ''
|
||||||
|
},
|
||||||
|
addressComponent: addrComp,
|
||||||
|
formatted_address: detailAddress
|
||||||
|
}
|
||||||
|
resolve(addressData)
|
||||||
|
} else {
|
||||||
|
reject(new Error(result?.msg || '地址解析失败'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地址解析
|
||||||
|
* @param params 参数 { mapKey, address }
|
||||||
|
* @returns Promise
|
||||||
|
*/
|
||||||
|
export const addressToLatLng = (params: any) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const T = (window as any).T
|
||||||
|
if (!T) {
|
||||||
|
reject(new Error('天地图服务未加载'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const geocoder = new T.Geocoder()
|
||||||
|
geocoder.getPoint(params.address, (result: any) => {
|
||||||
|
if (result && result.status === '0' && result.location) {
|
||||||
|
const lat = result.location.lat
|
||||||
|
const lng = result.location.lon || result.location.lng
|
||||||
|
if (lat && lng) {
|
||||||
|
resolve({ lat, lng })
|
||||||
|
} else {
|
||||||
|
reject(new Error('坐标解析失败'))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error(result?.msg || '未找到该地址'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -108,7 +108,7 @@ class ExceptionHandle extends Handle
|
|||||||
} else if ($e instanceof RouteNotFoundException) {
|
} else if ($e instanceof RouteNotFoundException) {
|
||||||
return fail('当前访问路由未定义或不匹配 路由地址:' . request()->baseUrl());
|
return fail('当前访问路由未定义或不匹配 路由地址:' . request()->baseUrl());
|
||||||
} else if($e instanceof \RuntimeException){
|
} else if($e instanceof \RuntimeException){
|
||||||
return fail($e->getMessage(), $massageData);
|
return fail($e->getMessage(), $massageData, $e->getCode() ?: 0);
|
||||||
} else {
|
} else {
|
||||||
return $this->handleException($e);
|
return $this->handleException($e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,10 @@ class Cloud extends BaseAdminController
|
|||||||
* @return \think\Response
|
* @return \think\Response
|
||||||
*/
|
*/
|
||||||
public function build() {
|
public function build() {
|
||||||
return success(data:(new CoreCloudBuildService())->cloudBuild());
|
$data = $this->request->params([
|
||||||
|
[ 'addon', [] ]
|
||||||
|
]);
|
||||||
|
return success(data:(new CoreCloudBuildService())->cloudBuild($data['addon']));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,7 +82,7 @@ class Cloud extends BaseAdminController
|
|||||||
$data = $this->request->params([
|
$data = $this->request->params([
|
||||||
[ 'url', '' ],
|
[ 'url', '' ],
|
||||||
]);
|
]);
|
||||||
$is_connected = (new CloudService(true,$data['url']))->is_connected;
|
$is_connected = (new CloudService(true, $data['url']))->is_connected;
|
||||||
return success('SUCCESS',$is_connected);
|
return success('SUCCESS',$is_connected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,8 +94,8 @@ class Cloud extends BaseAdminController
|
|||||||
public function setLocalCloudCompileConfig()
|
public function setLocalCloudCompileConfig()
|
||||||
{
|
{
|
||||||
$data = $this->request->params([
|
$data = $this->request->params([
|
||||||
|
[ 'is_open', 0],
|
||||||
[ 'url', '' ],
|
[ 'url', '' ],
|
||||||
[ 'is_open', 0 ],
|
|
||||||
]);
|
]);
|
||||||
return success('SUCCESS',(new NiucloudService())->setLocalCloudCompileConfig($data));
|
return success('SUCCESS',(new NiucloudService())->setLocalCloudCompileConfig($data));
|
||||||
}
|
}
|
||||||
@ -106,4 +109,36 @@ class Cloud extends BaseAdminController
|
|||||||
{
|
{
|
||||||
return success('SUCCESS',(new NiucloudService())->getLocalCloudCompileConfig());
|
return success('SUCCESS',(new NiucloudService())->getLocalCloudCompileConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动后台下载(SSE编译完成后调用)
|
||||||
|
* @description 启动后台下载
|
||||||
|
* @return \think\Response
|
||||||
|
*/
|
||||||
|
public function startServerDownload()
|
||||||
|
{
|
||||||
|
$data = $this->request->params([
|
||||||
|
[ 'task_id', '' ],
|
||||||
|
[ 'download_url', '' ],
|
||||||
|
[ 'authorize_code', '' ],
|
||||||
|
[ 'timestamp', '' ],
|
||||||
|
]);
|
||||||
|
return success('操作成功', (new CoreCloudBuildService())->startServerDownload(
|
||||||
|
$data['task_id'],
|
||||||
|
$data['download_url'],
|
||||||
|
$data['authorize_code'],
|
||||||
|
$data['timestamp']
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取后台下载进度
|
||||||
|
* @description 获取后台下载进度
|
||||||
|
* @return \think\Response
|
||||||
|
*/
|
||||||
|
public function getSseBuildLog()
|
||||||
|
{
|
||||||
|
$taskId = $this->request->param('task_id', '');
|
||||||
|
return success('操作成功', (new CoreCloudBuildService())->getSseBuildLog($taskId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -138,8 +138,11 @@ class Config extends BaseAdminController
|
|||||||
public function setMap()
|
public function setMap()
|
||||||
{
|
{
|
||||||
$data = $this->request->params([
|
$data = $this->request->params([
|
||||||
|
[ 'map_type', 'tianditu' ],
|
||||||
[ 'key', '' ],
|
[ 'key', '' ],
|
||||||
[ 'amap_key', ''],
|
[ 'amap_key', ''],
|
||||||
|
[ 'tianditu_map_key', ''],
|
||||||
|
[ 'tianditu_map_web_key', ''],
|
||||||
[ 'is_open', 0 ], // 是否开启定位
|
[ 'is_open', 0 ], // 是否开启定位
|
||||||
[ 'valid_time', 0 ] // 定位有效期/分钟,过期后将重新获取定位信息,0为不过期
|
[ 'valid_time', 0 ] // 定位有效期/分钟,过期后将重新获取定位信息,0为不过期
|
||||||
]);
|
]);
|
||||||
@ -154,7 +157,11 @@ class Config extends BaseAdminController
|
|||||||
*/
|
*/
|
||||||
public function getMap()
|
public function getMap()
|
||||||
{
|
{
|
||||||
return success(( new ConfigService() )->getMap());
|
$data = $this->request->params([
|
||||||
|
[ 'need_encrypt', true ],
|
||||||
|
]);
|
||||||
|
$need_encrypt = filter_var($data['need_encrypt'], FILTER_VALIDATE_BOOLEAN);
|
||||||
|
return success(( new ConfigService() )->getMap($need_encrypt));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -89,4 +89,13 @@ class Version extends BaseAdminController
|
|||||||
public function uploadLog(string $key) {
|
public function uploadLog(string $key) {
|
||||||
return success(data: (new WeappVersionService())->getUploadLog($key));
|
return success(data: (new WeappVersionService())->getUploadLog($key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接获取小程序上传日志(不更新状态)
|
||||||
|
* @param string $key
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function getUploadLogOnly(string $key) {
|
||||||
|
return success(data: (new WeappVersionService())->getUploadLogOnly($key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,6 +61,10 @@ Route::group('niucloud', function() {
|
|||||||
Route::post('build/set_local_url', 'niucloud.Cloud/setLocalCloudCompileConfig');
|
Route::post('build/set_local_url', 'niucloud.Cloud/setLocalCloudCompileConfig');
|
||||||
//获取本地服务器地址
|
//获取本地服务器地址
|
||||||
Route::get('build/get_local_url', 'niucloud.Cloud/getLocalCloudCompileConfig');
|
Route::get('build/get_local_url', 'niucloud.Cloud/getLocalCloudCompileConfig');
|
||||||
|
//启动后台下载(SSE编译完成后)
|
||||||
|
Route::post('build/start_server_download', 'niucloud.Cloud/startServerDownload');
|
||||||
|
//获取后台下载进度
|
||||||
|
Route::get('build/get_sse_build_log', 'niucloud.Cloud/getSseBuildLog');
|
||||||
})->middleware([
|
})->middleware([
|
||||||
AdminCheckToken::class,
|
AdminCheckToken::class,
|
||||||
AdminCheckRole::class,
|
AdminCheckRole::class,
|
||||||
|
|||||||
@ -44,8 +44,10 @@ Route::group('weapp', function() {
|
|||||||
Route::get('version', 'weapp.Version/lists');
|
Route::get('version', 'weapp.Version/lists');
|
||||||
//获取预览码
|
//获取预览码
|
||||||
Route::get('preview', 'weapp.Version/preview');
|
Route::get('preview', 'weapp.Version/preview');
|
||||||
//获取小程序上传日志
|
//获取小程序上传日志(会更新状态)
|
||||||
Route::get('upload/:key', 'weapp.Version/uploadLog');
|
Route::get('upload/:key', 'weapp.Version/uploadLog');
|
||||||
|
//直接获取小程序上传日志(不更新状态)
|
||||||
|
Route::get('upload_log/:key', 'weapp.Version/getUploadLogOnly');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ class Pay extends BaseApiController
|
|||||||
*/
|
*/
|
||||||
public function notify($site_id, $channel, $type, $action)
|
public function notify($site_id, $channel, $type, $action)
|
||||||
{
|
{
|
||||||
|
$this->request->siteId($site_id);
|
||||||
return (new PayService())->notify($channel, $type, $action);
|
return (new PayService())->notify($channel, $type, $action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ Route::group('pay',function () {
|
|||||||
Route::get('friendspay/info/:trade_type/:trade_id', 'pay.Pay/friendspayInfo');
|
Route::get('friendspay/info/:trade_type/:trade_id', 'pay.Pay/friendspayInfo');
|
||||||
|
|
||||||
})->middleware(ApiChannel::class)
|
})->middleware(ApiChannel::class)
|
||||||
|
->middleware(ApiCheckToken::class, false)//表示验证登录
|
||||||
->middleware(ApiLog::class);
|
->middleware(ApiLog::class);
|
||||||
|
|
||||||
Route::group('pay',function () {
|
Route::group('pay',function () {
|
||||||
@ -50,4 +51,4 @@ Route::group('transfer',function () {
|
|||||||
|
|
||||||
})->middleware(ApiChannel::class)
|
})->middleware(ApiChannel::class)
|
||||||
->middleware(ApiCheckToken::class, true)//表示验证登录
|
->middleware(ApiCheckToken::class, true)//表示验证登录
|
||||||
->middleware(ApiLog::class);
|
->middleware(ApiLog::class);
|
||||||
|
|||||||
@ -32,13 +32,13 @@ class CloudDict
|
|||||||
|
|
||||||
public static function getAppletUploadStatus($status) {
|
public static function getAppletUploadStatus($status) {
|
||||||
$status_list = [
|
$status_list = [
|
||||||
self::APPLET_UPLOADING => get_lang('dict_cloud_applet.uploading'),
|
self::APPLET_UPLOADING => get_lang('dict_cloud_applet.uploading'),//上传中
|
||||||
self::APPLET_UPLOAD_SUCCESS => get_lang('dict_cloud_applet.upload_success'),
|
self::APPLET_UPLOAD_SUCCESS => get_lang('dict_cloud_applet.upload_success'),//上传成功
|
||||||
self::APPLET_UPLOAD_FAIL => get_lang('dict_cloud_applet.upload_fail'),
|
self::APPLET_UPLOAD_FAIL => get_lang('dict_cloud_applet.upload_fail'),//上传失败
|
||||||
self::APPLET_AUDITING => get_lang('dict_cloud_applet.auditing'),
|
self::APPLET_AUDITING => get_lang('dict_cloud_applet.auditing'),//审核中
|
||||||
self::APPLET_AUDIT_FAIL => get_lang('dict_cloud_applet.audit_fail'),
|
self::APPLET_AUDIT_FAIL => get_lang('dict_cloud_applet.audit_fail'),//审核失败
|
||||||
self::APPLET_PUBLISHED => get_lang('dict_cloud_applet.published'),
|
self::APPLET_PUBLISHED => get_lang('dict_cloud_applet.published'),//已发布
|
||||||
self::APPLET_AUDIT_UNDO => get_lang('dict_cloud_applet.undo')
|
self::APPLET_AUDIT_UNDO => get_lang('dict_cloud_applet.undo')//已撤回
|
||||||
];
|
];
|
||||||
return $status_list[$status] ?? '';
|
return $status_list[$status] ?? '';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,9 +56,6 @@ class Index extends BaseInstall
|
|||||||
//sodium
|
//sodium
|
||||||
$sodium = extension_loaded('sodium');
|
$sodium = extension_loaded('sodium');
|
||||||
$system_variables[] = [ "name" => "sodium", "need" => "开启", "status" => $sodium ];
|
$system_variables[] = [ "name" => "sodium", "need" => "开启", "status" => $sodium ];
|
||||||
//imagick
|
|
||||||
$imagick = extension_loaded('imagick');
|
|
||||||
$system_variables[] = [ "name" => "imagick", "need" => "开启", "status" => $imagick ];
|
|
||||||
|
|
||||||
$root_path = str_replace("\\", DIRECTORY_SEPARATOR, dirname(__FILE__, 4));
|
$root_path = str_replace("\\", DIRECTORY_SEPARATOR, dirname(__FILE__, 4));
|
||||||
$root_path = str_replace("../", DIRECTORY_SEPARATOR, $root_path);
|
$root_path = str_replace("../", DIRECTORY_SEPARATOR, $root_path);
|
||||||
@ -91,7 +88,7 @@ class Index extends BaseInstall
|
|||||||
$this->assign("name", $name);
|
$this->assign("name", $name);
|
||||||
$this->assign("verison", $verison);
|
$this->assign("verison", $verison);
|
||||||
$this->assign("dirs_list", $dirs_list);
|
$this->assign("dirs_list", $dirs_list);
|
||||||
if ($verison && $pdo && $curl && $openssl && $gd && $fileinfo && $is_dir && $imagick) {
|
if ($verison && $pdo && $curl && $openssl && $gd && $fileinfo && $is_dir) {
|
||||||
$continue = true;
|
$continue = true;
|
||||||
} else {
|
} else {
|
||||||
$continue = false;
|
$continue = false;
|
||||||
@ -419,7 +416,7 @@ class Index extends BaseInstall
|
|||||||
}
|
}
|
||||||
|
|
||||||
//如果数据库不存在,我们就进行创建。
|
//如果数据库不存在,我们就进行创建。
|
||||||
$dbsql = "CREATE DATABASE `$dbname`";
|
$dbsql = "CREATE DATABASE `$dbname` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci";
|
||||||
$db_create = mysqli_query($conn, $dbsql);
|
$db_create = mysqli_query($conn, $dbsql);
|
||||||
if (!$db_create) {
|
if (!$db_create) {
|
||||||
return fail('创建数据库失败,请确认是否有足够的权限!');
|
return fail('创建数据库失败,请确认是否有足够的权限!');
|
||||||
|
|||||||
@ -79,7 +79,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="onetd">数据库编码:</td>
|
<td class="onetd">数据库编码:</td>
|
||||||
<td>
|
<td>
|
||||||
<label class="install-code">UTF8</label>
|
<label class="install-code">utf8mb4</label>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -271,6 +271,7 @@ return [
|
|||||||
'PAY_NOT_FOUND_TRADE' => '找不到可支付的交易',
|
'PAY_NOT_FOUND_TRADE' => '找不到可支付的交易',
|
||||||
|
|
||||||
'MERCHANT_TRANSFER_SCENARIOS_THAT_DO_NOT_EXIST' => '不存在的商户转账场景',
|
'MERCHANT_TRANSFER_SCENARIOS_THAT_DO_NOT_EXIST' => '不存在的商户转账场景',
|
||||||
|
'MERCHANT_TRANSFER_SCENE_ID_MIN_4' => '场景ID最短为4位',
|
||||||
//退款相关
|
//退款相关
|
||||||
'REFUND_NOT_EXIST' => '退款单据不存在',
|
'REFUND_NOT_EXIST' => '退款单据不存在',
|
||||||
//订单相关 8***
|
//订单相关 8***
|
||||||
@ -319,7 +320,7 @@ return [
|
|||||||
'NEED_TO_AUTHORIZE_FIRST' => '使用云服务需先进行授权',
|
'NEED_TO_AUTHORIZE_FIRST' => '使用云服务需先进行授权',
|
||||||
'WEAPP_UPLOADING' => '小程序有正在上传的版本,请等待上一版本上传完毕后再进行操作',
|
'WEAPP_UPLOADING' => '小程序有正在上传的版本,请等待上一版本上传完毕后再进行操作',
|
||||||
'CLOUD_BUILD_TASK_EXIST' => '已有正在执行中的编译任务',
|
'CLOUD_BUILD_TASK_EXIST' => '已有正在执行中的编译任务',
|
||||||
'CONNECT_FAIL' => '连接失败',
|
'CONNECT_FAIL' => '云编译服务连接失败',
|
||||||
|
|
||||||
//核销相关
|
//核销相关
|
||||||
'VERIFY_TYPE_ERROR' => '核销类型错误',
|
'VERIFY_TYPE_ERROR' => '核销类型错误',
|
||||||
|
|||||||
@ -110,13 +110,22 @@ class AuthService extends BaseAdminService
|
|||||||
if (strpos($rule, $item) !== false) return;
|
if (strpos($rule, $item) !== false) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$authinfo = (new CoreAuthService())->getAuthInfo()['data'] ?? [];;
|
$authinfo = [];
|
||||||
|
try {
|
||||||
|
$authinfo = (new CoreAuthService())->getAuthInfo()['data'] ?? [];;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
if (empty($authinfo)) return;
|
if (empty($authinfo)) return;
|
||||||
|
|
||||||
if (!$this->isCheckDomain()) return;
|
if (!$this->isCheckDomain()) return;
|
||||||
|
|
||||||
$site_address = $authinfo['site_address'] ?? '';
|
$site_address = $authinfo['site_address'] ?? '';
|
||||||
$domain = request()->domain();
|
$domain = request()->domain();
|
||||||
|
|
||||||
|
// 如果是站点域名不进行验证
|
||||||
|
$site_id = (new CoreSiteService())->getSiteIdByDomain($domain);
|
||||||
|
if (!empty($site_id)) return;
|
||||||
|
|
||||||
if (!empty($site_address) && strpos($domain, $site_address) !== false) return;
|
if (!empty($site_address) && strpos($domain, $site_address) !== false) return;
|
||||||
|
|
||||||
throw new CommonException("授权域名校验失败!请确保当前访问域名与授权码绑定的域名一致");
|
throw new CommonException("授权域名校验失败!请确保当前访问域名与授权码绑定的域名一致");
|
||||||
|
|||||||
@ -96,7 +96,6 @@ class NiucloudService extends BaseAdminService
|
|||||||
* @return \app\model\sys\SysConfig|bool|\think\Model
|
* @return \app\model\sys\SysConfig|bool|\think\Model
|
||||||
*/
|
*/
|
||||||
public function setLocalCloudCompileConfig($data){
|
public function setLocalCloudCompileConfig($data){
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'baseUri' => $data['url'],
|
'baseUri' => $data['url'],
|
||||||
'isOpen' => $data['is_open'],
|
'isOpen' => $data['is_open'],
|
||||||
|
|||||||
@ -29,6 +29,8 @@ use think\db\exception\ModelNotFoundException;
|
|||||||
*/
|
*/
|
||||||
class NiuSmsService extends BaseAdminService
|
class NiuSmsService extends BaseAdminService
|
||||||
{
|
{
|
||||||
|
public $template_model = null;
|
||||||
|
public $niu_service = null;
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|||||||
@ -44,6 +44,9 @@ class TransferService extends BaseAdminService
|
|||||||
$config = $core_transfer_service->getWechatTransferSceneConfig($this->site_id) ?? [];
|
$config = $core_transfer_service->getWechatTransferSceneConfig($this->site_id) ?? [];
|
||||||
$scene_list = TransferDict::getWechatTransferScene();
|
$scene_list = TransferDict::getWechatTransferScene();
|
||||||
if(empty($scene_list[$scene])) throw new AdminException('MERCHANT_TRANSFER_SCENARIOS_THAT_DO_NOT_EXIST');
|
if(empty($scene_list[$scene])) throw new AdminException('MERCHANT_TRANSFER_SCENARIOS_THAT_DO_NOT_EXIST');
|
||||||
|
if (!empty($data['scene_id']) && strlen($data['scene_id']) <4) {
|
||||||
|
throw new AdminException('MERCHANT_TRANSFER_SCENE_ID_MIN_4');
|
||||||
|
}
|
||||||
$config[$scene] = $data['scene_id'];
|
$config[$scene] = $data['scene_id'];
|
||||||
$core_transfer_service->setWechatTransferSceneConfig($this->site_id, $config);
|
$core_transfer_service->setWechatTransferSceneConfig($this->site_id, $config);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -105,25 +105,16 @@ class AreaService extends BaseAdminService
|
|||||||
*/
|
*/
|
||||||
public function getAddress(string $address){
|
public function getAddress(string $address){
|
||||||
$map = (new ConfigService())->getMap();
|
$map = (new ConfigService())->getMap();
|
||||||
$url = "https://apis.map.qq.com/ws/geocoder/v1/?address=".$address."&key=".$map['key'];
|
$map_type = $map['map_type'] ? $map['map_type'] : 'tianditu';
|
||||||
$curl = curl_init();
|
if ($map_type == 'tencent') {
|
||||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
$map_service = new \app\service\core\map\CoreQqMap($this->site_id);
|
||||||
curl_setopt($curl, CURLOPT_HEADER, 0);
|
$res = $map_service->addressToDetail(['address' => $address]);
|
||||||
curl_setopt($curl, CURLOPT_URL, $url);
|
}else{
|
||||||
curl_setopt($curl, CURLOPT_TIMEOUT, 1);
|
$map_service = new \app\service\core\map\CoreTiandituMap($this->site_id);
|
||||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
$res = $map_service->addressToDetail(['address' => $address]);
|
||||||
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
|
|
||||||
|
|
||||||
$res = curl_exec($curl);
|
|
||||||
$res = json_decode($res, true);
|
|
||||||
if($res){
|
|
||||||
curl_close($curl);
|
|
||||||
return $res;
|
|
||||||
}else {
|
|
||||||
$error = curl_errno($curl);
|
|
||||||
curl_close($curl);
|
|
||||||
return $error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return is_string($res) ? json_decode($res,true) : $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,30 +124,20 @@ class AreaService extends BaseAdminService
|
|||||||
*/
|
*/
|
||||||
public function getAddressInfo(string $location){
|
public function getAddressInfo(string $location){
|
||||||
$map = (new ConfigService())->getMap();
|
$map = (new ConfigService())->getMap();
|
||||||
$url = "https://apis.map.qq.com/ws/geocoder/v1/?location=".$location."&key=".$map['key'];
|
$map_type = $map['map_type'] ? $map['map_type'] : 'tianditu';
|
||||||
$curl = curl_init();
|
|
||||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($curl, CURLOPT_HEADER, 0);
|
|
||||||
curl_setopt($curl, CURLOPT_URL, $url);
|
|
||||||
curl_setopt($curl, CURLOPT_TIMEOUT, 1);
|
|
||||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
|
||||||
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
|
|
||||||
|
|
||||||
// 设置 Referer 头(需替换为你的授权域名)
|
if ($map_type == 'tencent') {
|
||||||
curl_setopt($curl, CURLOPT_HTTPHEADER, [
|
$map_service = new \app\service\core\map\CoreQqMap($this->site_id);
|
||||||
'Referer: ' . $this->request->domain()
|
$res = $map_service->locationToDetail(['location' => $location]);
|
||||||
]);
|
} else {
|
||||||
|
$map_service = new \app\service\core\map\CoreTiandituMap($this->site_id);
|
||||||
$res = curl_exec($curl);
|
$loc_arr = explode(',', $location);
|
||||||
$res = json_decode($res, true);
|
$lat = $loc_arr[0] ?? '';
|
||||||
if($res){
|
$lon = $loc_arr[1] ?? '';
|
||||||
curl_close($curl);
|
$res = $map_service->locationToDetail(['lat' => $lat, 'lon' => $lon]);
|
||||||
return $res;
|
|
||||||
}else {
|
|
||||||
$error = curl_errno($curl);
|
|
||||||
curl_close($curl);
|
|
||||||
return $error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return is_string($res) ? json_decode($res, true) : $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAreaId($name, $level){
|
public function getAreaId($name, $level){
|
||||||
|
|||||||
@ -136,7 +136,11 @@ class AttachmentService extends BaseAdminService
|
|||||||
}
|
}
|
||||||
return $this->getPageList($this->model, $where, 'att_id,path,real_name,att_type,url', 'att_id desc', each:function($item, $key)
|
return $this->getPageList($this->model, $where, 'att_id,path,real_name,att_type,url', 'att_id desc', each:function($item, $key)
|
||||||
{
|
{
|
||||||
$item[ 'thumb' ] = get_thumb_images($this->site_id, $item[ 'url' ], FileDict::SMALL);
|
if ($item['att_type'] == 'image') {
|
||||||
|
$item[ 'thumb' ] = get_thumb_images($this->site_id, $item[ 'url' ], FileDict::SMALL);
|
||||||
|
} else {
|
||||||
|
$item[ 'thumb' ] = $item[ 'url' ];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,4 +255,4 @@ class AttachmentService extends BaseAdminService
|
|||||||
return SysAttachmentCategory::where($where)->field('id,name,type')->order('id desc')->select()->toArray();
|
return SysAttachmentCategory::where($where)->field('id,name,type')->order('id desc')->select()->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace app\service\admin\sys;
|
namespace app\service\admin\sys;
|
||||||
|
|
||||||
|
use app\dict\common\CommonDict;
|
||||||
use app\service\admin\site\SiteService;
|
use app\service\admin\site\SiteService;
|
||||||
use app\service\core\channel\CoreH5Service;
|
use app\service\core\channel\CoreH5Service;
|
||||||
use app\service\core\sys\CoreConfigService;
|
use app\service\core\sys\CoreConfigService;
|
||||||
@ -134,9 +135,34 @@ class ConfigService extends BaseAdminService
|
|||||||
*/
|
*/
|
||||||
public function setMap(array $value)
|
public function setMap(array $value)
|
||||||
{
|
{
|
||||||
|
$old_config = $this->getMap();
|
||||||
|
|
||||||
|
// 检测数据是否发生变化,如果没有变化,则保持未加密前的数据
|
||||||
|
if (!empty($value[ 'key' ]) && $value[ 'key' ] == CommonDict::ENCRYPT_STR) {
|
||||||
|
$value[ 'key' ] = $old_config[ 'key' ];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测数据是否发生变化,如果没有变化,则保持未加密前的数据
|
||||||
|
if (!empty($value[ 'amap_key' ]) && $value[ 'amap_key' ] == CommonDict::ENCRYPT_STR) {
|
||||||
|
$value[ 'amap_key' ] = $old_config[ 'amap_key' ];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测数据是否发生变化,如果没有变化,则保持未加密前的数据
|
||||||
|
if (!empty($value[ 'tianditu_map_key' ]) && $value[ 'tianditu_map_key' ] == CommonDict::ENCRYPT_STR) {
|
||||||
|
$value[ 'tianditu_map_key' ] = $old_config[ 'tianditu_map_key' ];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测数据是否发生变化,如果没有变化,则保持未加密前的数据
|
||||||
|
if (!empty($value[ 'tianditu_map_web_key' ]) && $value[ 'tianditu_map_web_key' ] == CommonDict::ENCRYPT_STR) {
|
||||||
|
$value[ 'tianditu_map_web_key' ] = $old_config[ 'tianditu_map_web_key' ];
|
||||||
|
}
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
|
'map_type' => $value[ 'map_type' ] ,
|
||||||
'key' => $value[ 'key' ],
|
'key' => $value[ 'key' ],
|
||||||
'amap_key' => $value['amap_key'],
|
'amap_key' => $value['amap_key'],
|
||||||
|
'tianditu_map_key' => $value['tianditu_map_key'],
|
||||||
|
'tianditu_map_web_key' => $value['tianditu_map_web_key'],
|
||||||
'is_open' => $value[ 'is_open' ], // 是否开启定位
|
'is_open' => $value[ 'is_open' ], // 是否开启定位
|
||||||
'valid_time' => $value[ 'valid_time' ] // 定位有效期/分钟,过期后将重新获取定位信息,0为不过期
|
'valid_time' => $value[ 'valid_time' ] // 定位有效期/分钟,过期后将重新获取定位信息,0为不过期
|
||||||
];
|
];
|
||||||
@ -148,23 +174,45 @@ class ConfigService extends BaseAdminService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取地图key
|
* 获取地图key
|
||||||
|
* @param bool $need_encrypt
|
||||||
*/
|
*/
|
||||||
public function getMap()
|
public function getMap($need_encrypt = false)
|
||||||
{
|
{
|
||||||
$info = ( new CoreConfigService() )->getConfig($this->site_id, 'MAPKEY');
|
$info = ( new CoreConfigService() )->getConfig($this->site_id, 'MAPKEY');
|
||||||
if (empty($info)) {
|
if (empty($info)) {
|
||||||
$info = [];
|
$info = [];
|
||||||
$info[ 'value' ] = [
|
$info[ 'value' ] = [
|
||||||
|
'map_type' => 'tianditu',
|
||||||
'key' => '',
|
'key' => '',
|
||||||
'amap_key' => '',
|
'amap_key' => '',
|
||||||
|
'tianditu_map_key' => '',
|
||||||
|
'tianditu_map_web_key' => '',
|
||||||
'is_open' => 1, // 是否开启定位
|
'is_open' => 1, // 是否开启定位
|
||||||
'valid_time' => 5 // 定位有效期/分钟,过期后将重新获取定位信息,0为不过期
|
'valid_time' => 5 // 定位有效期/分钟,过期后将重新获取定位信息,0为不过期
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
if (!empty($info[ 'value' ]) && $need_encrypt) {
|
||||||
|
// 加密敏感信息
|
||||||
|
if (!empty($info[ 'value' ][ 'key' ])) {
|
||||||
|
$info[ 'value' ][ 'key' ] = CommonDict::ENCRYPT_STR;
|
||||||
|
}
|
||||||
|
if (!empty($info[ 'value' ][ 'amap_key' ])) {
|
||||||
|
$info[ 'value' ][ 'amap_key' ] = CommonDict::ENCRYPT_STR;
|
||||||
|
}
|
||||||
|
if (!empty($info[ 'value' ][ 'tianditu_map_key' ])) {
|
||||||
|
$info[ 'value' ][ 'tianditu_map_key' ] = CommonDict::ENCRYPT_STR;
|
||||||
|
}
|
||||||
|
if (!empty($info[ 'value' ][ 'tianditu_map_web_key' ])) {
|
||||||
|
$info[ 'value' ][ 'tianditu_map_web_key' ] = CommonDict::ENCRYPT_STR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$info[ 'value' ][ 'is_open' ] = $info[ 'value' ][ 'is_open' ] ?? 1;
|
$info[ 'value' ][ 'is_open' ] = $info[ 'value' ][ 'is_open' ] ?? 1;
|
||||||
$info[ 'value' ][ 'valid_time' ] = $info[ 'value' ][ 'valid_time' ] ?? 5;
|
$info[ 'value' ][ 'valid_time' ] = $info[ 'value' ][ 'valid_time' ] ?? 5;
|
||||||
|
$info[ 'value' ][ 'map_type' ] = $info[ 'value' ][ 'map_type' ] ?? 'tianditu';
|
||||||
$info[ 'value' ][ 'amap_key' ] = $info[ 'value' ][ 'amap_key' ] ?? '';
|
$info[ 'value' ][ 'amap_key' ] = $info[ 'value' ][ 'amap_key' ] ?? '';
|
||||||
|
$info[ 'value' ][ 'tianditu_map_key' ] = $info[ 'value' ][ 'tianditu_map_key' ] ?? '';
|
||||||
|
$info[ 'value' ][ 'tianditu_map_web_key' ] = $info[ 'value' ][ 'tianditu_map_web_key' ] ?? '';
|
||||||
|
|
||||||
return $info[ 'value' ];
|
return $info[ 'value' ];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@ use core\exception\CloudBuildException;
|
|||||||
use core\exception\CommonException;
|
use core\exception\CommonException;
|
||||||
use core\util\DbBackup;
|
use core\util\DbBackup;
|
||||||
use core\util\niucloud\BaseNiucloudClient;
|
use core\util\niucloud\BaseNiucloudClient;
|
||||||
|
use core\util\niucloud\CloudService;
|
||||||
use think\facade\Cache;
|
use think\facade\Cache;
|
||||||
use think\facade\Db;
|
use think\facade\Db;
|
||||||
use think\facade\Log;
|
use think\facade\Log;
|
||||||
@ -228,28 +229,31 @@ class UpgradeService extends BaseAdminService
|
|||||||
$response = ( new CoreAddonCloudService() )->upgradeAddon($upgrade);
|
$response = ( new CoreAddonCloudService() )->upgradeAddon($upgrade);
|
||||||
if (isset($response[ 'code' ]) && $response[ 'code' ] == 0) throw new CommonException($response[ 'msg' ]);
|
if (isset($response[ 'code' ]) && $response[ 'code' ] == 0) throw new CommonException($response[ 'msg' ]);
|
||||||
|
|
||||||
|
$key = uniqid();
|
||||||
|
$upgrade_dir = $this->upgrade_dir . $key . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
|
if (!is_dir($upgrade_dir)) {
|
||||||
|
dir_mkdir($upgrade_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否需要备份
|
||||||
|
$is_need_backup = $data['is_need_backup'] ?? true;
|
||||||
|
if (!$is_need_backup) {
|
||||||
|
unset($this->steps['backupCode']);
|
||||||
|
unset($this->steps['backupSql']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否需要云编译
|
||||||
|
$is_need_cloudbuild = $data['is_need_cloudbuild'] ?? true;
|
||||||
|
if (!$is_need_cloudbuild) {
|
||||||
|
unset($this->steps['cloudBuild']);
|
||||||
|
unset($this->steps['gteCloudBuildLog']);
|
||||||
|
} else {
|
||||||
|
// 校验云编译服务
|
||||||
|
(new CloudService())->checkLocal();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$key = uniqid();
|
|
||||||
$upgrade_dir = $this->upgrade_dir . $key . DIRECTORY_SEPARATOR;
|
|
||||||
|
|
||||||
if (!is_dir($upgrade_dir)) {
|
|
||||||
dir_mkdir($upgrade_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否需要备份
|
|
||||||
$is_need_backup = $data['is_need_backup'] ?? true;
|
|
||||||
if (!$is_need_backup) {
|
|
||||||
unset($this->steps['backupCode']);
|
|
||||||
unset($this->steps['backupSql']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否需要云编译
|
|
||||||
$is_need_cloudbuild = $data['is_need_cloudbuild'] ?? true;
|
|
||||||
if (!$is_need_cloudbuild) {
|
|
||||||
unset($this->steps['cloudBuild']);
|
|
||||||
unset($this->steps['gteCloudBuildLog']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$upgrade_task = [
|
$upgrade_task = [
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
'upgrade' => $upgrade,
|
'upgrade' => $upgrade,
|
||||||
@ -731,7 +735,15 @@ class UpgradeService extends BaseAdminService
|
|||||||
*/
|
*/
|
||||||
public function cloudBuild()
|
public function cloudBuild()
|
||||||
{
|
{
|
||||||
( new CoreCloudBuildService() )->cloudBuild();
|
try {
|
||||||
|
( new CoreCloudBuildService() )->cloudBuild();
|
||||||
|
} catch (CommonException $e) {
|
||||||
|
if ($e->getCode() == 601) {
|
||||||
|
( new CoreCloudBuildService() )->cloudBuild(['checkLocal' => false]);
|
||||||
|
} else {
|
||||||
|
throw new CommonException($e->getMessage(), $e->getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -13,6 +13,7 @@ namespace app\service\admin\weapp;
|
|||||||
|
|
||||||
use app\dict\common\CommonDict;
|
use app\dict\common\CommonDict;
|
||||||
use app\model\sys\SysConfig;
|
use app\model\sys\SysConfig;
|
||||||
|
use app\service\core\sys\CoreConfigService;
|
||||||
use app\service\core\weapp\CoreWeappConfigService;
|
use app\service\core\weapp\CoreWeappConfigService;
|
||||||
use app\service\core\wxoplatform\CoreOplatformService;
|
use app\service\core\wxoplatform\CoreOplatformService;
|
||||||
use core\base\BaseAdminService;
|
use core\base\BaseAdminService;
|
||||||
@ -80,6 +81,12 @@ class WeappConfigService extends BaseAdminService
|
|||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getWeappStaticInfo(){
|
public function getWeappStaticInfo(){
|
||||||
|
$local_cloud_compile_config = (new CoreConfigService())->getConfig(0, 'LOCAL_CLOUD_COMPILE_CONFIG')['value'] ?? [];
|
||||||
|
$baseUri = $local_cloud_compile_config['baseUri'] ?? '';
|
||||||
|
if (empty($baseUri)) $baseUri = 'oss.niucloud.com';
|
||||||
|
$baseUri = str_replace('http://', '', $baseUri);
|
||||||
|
$baseUri = str_replace('https://', '', $baseUri);
|
||||||
|
|
||||||
$domain = request()->domain();
|
$domain = request()->domain();
|
||||||
$domain = str_replace('http://', 'https://', $domain);
|
$domain = str_replace('http://', 'https://', $domain);
|
||||||
return [
|
return [
|
||||||
@ -88,7 +95,7 @@ class WeappConfigService extends BaseAdminService
|
|||||||
'socket_url' => "wss://".request()->host(),
|
'socket_url' => "wss://".request()->host(),
|
||||||
'upload_url' => $domain,
|
'upload_url' => $domain,
|
||||||
'download_url' => $domain,
|
'download_url' => $domain,
|
||||||
'upload_ip' => gethostbyname('oss.niucloud.com')
|
'upload_ip' => gethostbyname($baseUri)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -124,6 +124,8 @@ class WeappVersionService extends BaseAdminService
|
|||||||
*/
|
*/
|
||||||
public function del(int $id)
|
public function del(int $id)
|
||||||
{
|
{
|
||||||
|
$info = $this->model->where([['id', '=', $id], ['site_id', '=', $this->site_id]])->find();
|
||||||
|
$this->stopUpload($info['task_key']);
|
||||||
$this->model->where([['id', '=', $id], ['site_id', '=', $this->site_id]])->delete();
|
$this->model->where([['id', '=', $id], ['site_id', '=', $this->site_id]])->delete();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -139,14 +141,38 @@ class WeappVersionService extends BaseAdminService
|
|||||||
|
|
||||||
if (isset($build_log['data']) && isset($build_log['data'][0]) && is_array($build_log['data'][0])) {
|
if (isset($build_log['data']) && isset($build_log['data'][0]) && is_array($build_log['data'][0])) {
|
||||||
$last = end($build_log['data'][0]);
|
$last = end($build_log['data'][0]);
|
||||||
|
file_put_contents(runtime_path() . 'debug_log.txt', date('Y-m-d H:i:s') . " | key: $key | last_code: {$last['code']} | last_percent: {$last['percent']}\n", FILE_APPEND);
|
||||||
if ($last['code'] == 0) {
|
if ($last['code'] == 0) {
|
||||||
(new WeappVersion())->update(['status' => CloudDict::APPLET_UPLOAD_FAIL, 'fail_reason' => $last['msg'] ?? '', 'update_time' => time()], ['task_key' => $key]);
|
$res = (new WeappVersion())->where(['task_key' => $key])->update(['status' => CloudDict::APPLET_UPLOAD_FAIL, 'fail_reason' => $last['msg'] ?? '', 'update_time' => time()]);
|
||||||
|
file_put_contents(runtime_path() . 'debug_log.txt', date('Y-m-d H:i:s') . " | fail update result: $res\n", FILE_APPEND);
|
||||||
return $build_log;
|
return $build_log;
|
||||||
}
|
}
|
||||||
if ($last['percent'] == 100) {
|
if ($last['percent'] == 100) {
|
||||||
(new WeappVersion())->update(['status' => CloudDict::APPLET_UPLOAD_SUCCESS, 'update_time' => time()], ['task_key' => $key]);
|
$res = (new WeappVersion())->where(['task_key' => $key])->update(['status' => CloudDict::APPLET_UPLOAD_SUCCESS, 'update_time' => time()]);
|
||||||
|
file_put_contents(runtime_path() . 'debug_log.txt', date('Y-m-d H:i:s') . " | success update result: $res\n", FILE_APPEND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $build_log;
|
return $build_log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接获取小程序上传日志(不更新状态)
|
||||||
|
* @param string $key
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function getUploadLogOnly(string $key)
|
||||||
|
{
|
||||||
|
return (new CoreWeappCloudService())->getWeappCompileLog($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取小程序上传日志
|
||||||
|
* @param string $key
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function stopUpload(string $key)
|
||||||
|
{
|
||||||
|
return (new CoreWeappCloudService())->stopUploadAndClearLogs($key);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ use core\exception\AuthException;
|
|||||||
use think\db\exception\DataNotFoundException;
|
use think\db\exception\DataNotFoundException;
|
||||||
use think\db\exception\DbException;
|
use think\db\exception\DbException;
|
||||||
use think\db\exception\ModelNotFoundException;
|
use think\db\exception\ModelNotFoundException;
|
||||||
|
use think\facade\Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录服务层
|
* 登录服务层
|
||||||
@ -78,8 +79,14 @@ class RegisterService extends BaseApiService
|
|||||||
}
|
}
|
||||||
$member_id = ( new MemberService() )->add($data);
|
$member_id = ( new MemberService() )->add($data);
|
||||||
$data[ 'member_id' ] = $member_id;
|
$data[ 'member_id' ] = $member_id;
|
||||||
event('MemberRegister', $data);
|
|
||||||
SetMemberNoJob::dispatch([ 'site_id' => $this->site_id, 'member_id' => $member_id ]);
|
SetMemberNoJob::dispatch([ 'site_id' => $this->site_id, 'member_id' => $member_id ]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
event('MemberRegister', $data);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::write('MemberRegister event error');
|
||||||
|
Log::write($e->getTrace());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$member_info = $member_service->findMemberInfo([ 'member_id' => $member_id, 'site_id' => $this->site_id ]);
|
$member_info = $member_service->findMemberInfo([ 'member_id' => $member_id, 'site_id' => $this->site_id ]);
|
||||||
if ($member_info->isEmpty()) throw new AuthException('MEMBER_NOT_EXIST');//账号不存在
|
if ($member_info->isEmpty()) throw new AuthException('MEMBER_NOT_EXIST');//账号不存在
|
||||||
|
|||||||
@ -107,29 +107,24 @@ class AreaService extends BaseApiService
|
|||||||
*/
|
*/
|
||||||
public function getAddressByLatlng($params)
|
public function getAddressByLatlng($params)
|
||||||
{
|
{
|
||||||
$url = 'https://apis.map.qq.com/ws/geocoder/v1/';
|
|
||||||
$map = ( new ConfigService() )->getMap();
|
$map = ( new ConfigService() )->getMap();
|
||||||
|
$map_type = $map['map_type'] ?? 'tianditu';
|
||||||
|
|
||||||
$get_data = array(
|
if ($map_type == 'tencent') {
|
||||||
'key' => $map[ 'key' ],
|
$map_service = new \app\service\core\map\CoreQqMap($this->site_id);
|
||||||
'location' => $params[ 'latlng' ],
|
$res = $map_service->locationToDetail(['location' => $params['latlng']]);
|
||||||
'get_poi' => 0, // 是否返回周边POI列表:1.返回;0不返回(默认)
|
} else {
|
||||||
);
|
$map_service = new \app\service\core\map\CoreTiandituMap($this->site_id);
|
||||||
|
$loc_arr = explode(',', $params['latlng']);
|
||||||
|
$lat = $loc_arr[0] ?? '';
|
||||||
|
$lon = $loc_arr[1] ?? '';
|
||||||
|
$res = $map_service->locationToDetail(['lat' => $lat, 'lon' => $lon]);
|
||||||
|
}
|
||||||
|
if (is_string($res)) {
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
$url = $url . '?' . http_build_query($get_data);
|
|
||||||
$curl = curl_init();
|
|
||||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($curl, CURLOPT_HEADER, 0);
|
|
||||||
curl_setopt($curl, CURLOPT_URL, $url);
|
|
||||||
curl_setopt($curl, CURLOPT_TIMEOUT, 1);
|
|
||||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
|
||||||
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
|
|
||||||
|
|
||||||
$res = curl_exec($curl);
|
|
||||||
$res = json_decode($res, true);
|
|
||||||
if ($res) {
|
if ($res) {
|
||||||
curl_close($curl);
|
|
||||||
|
|
||||||
if ($res[ 'status' ] == 0) {
|
if ($res[ 'status' ] == 0) {
|
||||||
$return_array = $res[ 'result' ][ 'address_component' ] ?? []; // 地址部件,address不满足需求时可自行拼接
|
$return_array = $res[ 'result' ][ 'address_component' ] ?? []; // 地址部件,address不满足需求时可自行拼接
|
||||||
$address_reference = $res[ 'result' ][ 'address_reference' ] ?? [];
|
$address_reference = $res[ 'result' ][ 'address_reference' ] ?? [];
|
||||||
@ -224,9 +219,7 @@ class AreaService extends BaseApiService
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$error = curl_errno($curl);
|
throw new ApiException('地图接口请求失败');
|
||||||
curl_close($curl);
|
|
||||||
throw new ApiException($error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ use core\exception\CommonException;
|
|||||||
use core\util\niucloud\BaseNiucloudClient;
|
use core\util\niucloud\BaseNiucloudClient;
|
||||||
use core\util\niucloud\CloudService;
|
use core\util\niucloud\CloudService;
|
||||||
use think\facade\Cache;
|
use think\facade\Cache;
|
||||||
|
use think\facade\Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@ -86,7 +87,7 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
'authorize_code' => $this->auth_code,
|
'authorize_code' => $this->auth_code,
|
||||||
'timestamp' => $install_task['timestamp']
|
'timestamp' => $install_task['timestamp']
|
||||||
];
|
];
|
||||||
$response = (new CloudService())->httpPost('cloud/build?' . http_build_query($query), [
|
$response = (new CloudService(true))->httpPost('cloud/build?' . http_build_query($query), [
|
||||||
'multipart' => [
|
'multipart' => [
|
||||||
[
|
[
|
||||||
'name' => 'file',
|
'name' => 'file',
|
||||||
@ -122,11 +123,11 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
'authorize_code' => $this->auth_code,
|
'authorize_code' => $this->auth_code,
|
||||||
'timestamp' => $install_task['timestamp']
|
'timestamp' => $install_task['timestamp']
|
||||||
];
|
];
|
||||||
$build_log = (new CloudService())->httpGet('cloud/get_build_logs?' . http_build_query($query));
|
$build_log = (new CloudService(true))->httpGet('cloud/get_build_logs?' . http_build_query($query));
|
||||||
|
|
||||||
if (isset($build_log['data']) && isset($build_log['data'][0]) && is_array($build_log['data'][0])) {
|
if (isset($build_log['data']) && isset($build_log['data'][0]) && is_array($build_log['data'][0])) {
|
||||||
$last = end($build_log['data'][0]);
|
$last = end($build_log['data'][0]);
|
||||||
if ($last['percent'] == 100 && $last['code'] == 0) {
|
if ((int) $last['code'] == 0) {
|
||||||
(new CoreAddonInstallService($addon))->installExceptionHandle();
|
(new CoreAddonInstallService($addon))->installExceptionHandle();
|
||||||
$install_task['error'] = 'ADDON_INSTALL_FAIL';
|
$install_task['error'] = 'ADDON_INSTALL_FAIL';
|
||||||
Cache::set('install_task', $install_task, 10);
|
Cache::set('install_task', $install_task, 10);
|
||||||
@ -162,8 +163,8 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
|
|
||||||
$cache = Cache::get('build_success_' . $addon);
|
$cache = Cache::get('build_success_' . $addon);
|
||||||
|
|
||||||
if (is_null($cache)) {
|
if (is_null($cache) || !isset($cache[ 'index' ])) {
|
||||||
$response = (new CloudService())->request('HEAD','cloud/build_download?' . http_build_query($query), [
|
$response = (new CloudService(true))->request('HEAD','cloud/build_download?' . http_build_query($query), [
|
||||||
'headers' => ['Range' => 'bytes=0-']
|
'headers' => ['Range' => 'bytes=0-']
|
||||||
]);
|
]);
|
||||||
$length = $response->getHeader('Content-range');
|
$length = $response->getHeader('Content-range');
|
||||||
@ -187,7 +188,7 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
$end = ($cache['index'] + 1) * $chunk_size;
|
$end = ($cache['index'] + 1) * $chunk_size;
|
||||||
$end = min($end, $cache['length']);
|
$end = min($end, $cache['length']);
|
||||||
|
|
||||||
$response = (new CloudService())->request('GET','cloud/build_download?' . http_build_query($query), [
|
$response = (new CloudService(true))->request('GET','cloud/build_download?' . http_build_query($query), [
|
||||||
'headers' => ['Range' => "bytes={$start}-{$end}"]
|
'headers' => ['Range' => "bytes={$start}-{$end}"]
|
||||||
]);
|
]);
|
||||||
fwrite($zip_resource, $response->getBody());
|
fwrite($zip_resource, $response->getBody());
|
||||||
@ -225,10 +226,18 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
|
|
||||||
Cache::set('build_success_' . $addon, null);
|
Cache::set('build_success_' . $addon, null);
|
||||||
} else {
|
} else {
|
||||||
Cache::set('build_success_' . $addon, null);
|
if (!isset($cache[ 'retry' ])) {
|
||||||
// 调用插件安装异常处理
|
unlink($zip_file);
|
||||||
(new CoreAddonInstallService($addon))->installExceptionHandle();
|
$cache['retry'] = 1;
|
||||||
throw new CommonException('Zip decompression failed');
|
unset($cache['index']);
|
||||||
|
Cache::set('build_success_' . $addon, $cache);
|
||||||
|
$log[] = [ 'code' => 1, 'msg' => '编译包解压失败,尝试重新下载', 'action' => '编译包解压失败,尝试重新下载', 'percent' => '100' ];
|
||||||
|
} else {
|
||||||
|
Cache::set('build_success_' . $addon, null);
|
||||||
|
// 调用插件安装异常处理
|
||||||
|
(new CoreAddonInstallService($addon))->installExceptionHandle();
|
||||||
|
throw new CommonException('Zip decompression failed');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,7 +260,7 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
'token' => $action_token['data']['token'] ?? ''
|
'token' => $action_token['data']['token'] ?? ''
|
||||||
];
|
];
|
||||||
// 获取文件大小
|
// 获取文件大小
|
||||||
$response = (new CloudService())->request('HEAD','cloud/download?' . http_build_query($query), [
|
$response = (new CloudService(false, 'http://oss.niucloud.com/'))->request('HEAD','cloud/download?' . http_build_query($query), [
|
||||||
'headers' => ['Range' => 'bytes=0-']
|
'headers' => ['Range' => 'bytes=0-']
|
||||||
]);
|
]);
|
||||||
$length = $response->getHeader('Content-range');
|
$length = $response->getHeader('Content-range');
|
||||||
@ -263,7 +272,7 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
$zip_file = $temp_dir . $addon . '.zip';
|
$zip_file = $temp_dir . $addon . '.zip';
|
||||||
$zip_resource = fopen($zip_file, 'w');
|
$zip_resource = fopen($zip_file, 'w');
|
||||||
|
|
||||||
$response = (new CloudService())->request('GET','cloud/download?' . http_build_query($query), [
|
$response = (new CloudService(false, 'http://oss.niucloud.com/'))->request('GET','cloud/download?' . http_build_query($query), [
|
||||||
'headers' => ['Range' => "bytes=0-{$length}"]
|
'headers' => ['Range' => "bytes=0-{$length}"]
|
||||||
]);
|
]);
|
||||||
fwrite($zip_resource, $response->getBody());
|
fwrite($zip_resource, $response->getBody());
|
||||||
@ -286,7 +295,7 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
'token' => $action_token['data']['token'] ?? ''
|
'token' => $action_token['data']['token'] ?? ''
|
||||||
];
|
];
|
||||||
// 获取文件大小
|
// 获取文件大小
|
||||||
$response = (new CloudService())->httpGet('cloud/upgrade?' . http_build_query($query));
|
$response = (new CloudService(false, 'http://oss.niucloud.com/'))->httpGet('cloud/upgrade?' . http_build_query($query));
|
||||||
$response['token'] = $query['token'];
|
$response['token'] = $query['token'];
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
@ -309,7 +318,7 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
$chunk_size = 1 * 1024 * 1024;
|
$chunk_size = 1 * 1024 * 1024;
|
||||||
|
|
||||||
if ($index == -1) {
|
if ($index == -1) {
|
||||||
$response = (new CloudService())->request('HEAD','cloud/upgrade/download?' . http_build_query($query), [
|
$response = (new CloudService(false, 'http://oss.niucloud.com/'))->request('HEAD','cloud/upgrade/download?' . http_build_query($query), [
|
||||||
'headers' => ['Range' => 'bytes=0-']
|
'headers' => ['Range' => 'bytes=0-']
|
||||||
]);
|
]);
|
||||||
$length = $response->getHeader('Content-range');
|
$length = $response->getHeader('Content-range');
|
||||||
@ -327,7 +336,7 @@ class CoreAddonCloudService extends CoreCloudBaseService
|
|||||||
$end = ($index + 1) * $chunk_size;
|
$end = ($index + 1) * $chunk_size;
|
||||||
$end = min($end, $length);
|
$end = min($end, $length);
|
||||||
|
|
||||||
$response = (new CloudService())->request('GET','cloud/upgrade/download?' . http_build_query($query), [
|
$response = (new CloudService(false, 'http://oss.niucloud.com/'))->request('GET','cloud/upgrade/download?' . http_build_query($query), [
|
||||||
'headers' => ['Range' => "bytes={$start}-{$end}"]
|
'headers' => ['Range' => "bytes={$start}-{$end}"]
|
||||||
]);
|
]);
|
||||||
fwrite($zip_resource, $response->getBody());
|
fwrite($zip_resource, $response->getBody());
|
||||||
|
|||||||
@ -18,7 +18,9 @@ use app\service\core\menu\CoreMenuService;
|
|||||||
use app\service\core\schedule\CoreScheduleInstallService;
|
use app\service\core\schedule\CoreScheduleInstallService;
|
||||||
use core\exception\AddonException;
|
use core\exception\AddonException;
|
||||||
use core\exception\CommonException;
|
use core\exception\CommonException;
|
||||||
|
use core\util\niucloud\CloudService;
|
||||||
use core\util\Terminal;
|
use core\util\Terminal;
|
||||||
|
use EasyWeChat\Kernel\Exceptions\Exception;
|
||||||
use think\db\exception\DbException;
|
use think\db\exception\DbException;
|
||||||
use think\db\exception\PDOException;
|
use think\db\exception\PDOException;
|
||||||
use think\facade\Cache;
|
use think\facade\Cache;
|
||||||
@ -224,7 +226,16 @@ class CoreAddonInstallService extends CoreAddonBaseService
|
|||||||
$this->backupFrontend();
|
$this->backupFrontend();
|
||||||
|
|
||||||
$tips = [];
|
$tips = [];
|
||||||
if ($mode != 'cloud') $tips[] = get_lang('dict_addon.install_after_update');
|
if ($mode != 'cloud') {
|
||||||
|
$tips[] = get_lang('dict_addon.install_after_update');
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
(new CloudService())->checkLocal();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Cache::set('install_task', null);
|
||||||
|
throw new CommonException($e->getMessage(), $e->getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->addon_list as $addon) {
|
foreach ($this->addon_list as $addon) {
|
||||||
$this->install_task['addon'] = $addon;
|
$this->install_task['addon'] = $addon;
|
||||||
@ -625,6 +636,7 @@ class CoreAddonInstallService extends CoreAddonBaseService
|
|||||||
$addon_info = $core_addon_service->getInfoByKey($this->addon);
|
$addon_info = $core_addon_service->getInfoByKey($this->addon);
|
||||||
if (empty($addon_info)) throw new AddonException('NOT_UNINSTALL');
|
if (empty($addon_info)) throw new AddonException('NOT_UNINSTALL');
|
||||||
if (!$this->uninstallSql()) throw new AddonException('ADDON_SQL_FAIL');
|
if (!$this->uninstallSql()) throw new AddonException('ADDON_SQL_FAIL');
|
||||||
|
if (!$this->uninstallDir()) throw new AddonException('ADDON_DIR_FAIL');
|
||||||
|
|
||||||
// 卸载菜单
|
// 卸载菜单
|
||||||
$this->uninstallMenu();
|
$this->uninstallMenu();
|
||||||
|
|||||||
@ -89,4 +89,4 @@ class HttpHelper
|
|||||||
}
|
}
|
||||||
return $response_data;
|
return $response_data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,41 +39,30 @@ class CoreMapService extends BaseCoreService
|
|||||||
*/
|
*/
|
||||||
public function getPolyline($site_id, $params)
|
public function getPolyline($site_id, $params)
|
||||||
{
|
{
|
||||||
|
|
||||||
$url = 'https://apis.map.qq.com/ws/direction/v1/driving/';
|
|
||||||
$map = $this->getMapConfig($site_id);
|
$map = $this->getMapConfig($site_id);
|
||||||
|
$map_type = $map['map_type'] ?? 'tianditu';
|
||||||
|
|
||||||
$get_data = [
|
if ($map_type == 'tencent') {
|
||||||
'key' => $map[ 'key' ],
|
$map_service = new \app\service\core\map\CoreQqMap($site_id);
|
||||||
'from' => $params[ 'from' ],
|
|
||||||
'to' => $params[ 'to' ], // 是否返回周边POI列表:1.返回;0不返回(默认)
|
|
||||||
];
|
|
||||||
|
|
||||||
$url = $url . '?' . http_build_query($get_data);
|
$res = $map_service->getPolyline(['from' => $params[ 'from' ], 'to' => $params[ 'to' ]]);
|
||||||
$curl = curl_init();
|
|
||||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($curl, CURLOPT_HEADER, 0);
|
|
||||||
curl_setopt($curl, CURLOPT_URL, $url);
|
|
||||||
curl_setopt($curl, CURLOPT_TIMEOUT, 1);
|
|
||||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
|
||||||
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
|
|
||||||
|
|
||||||
$res = curl_exec($curl);
|
|
||||||
$res = json_decode($res, true);
|
|
||||||
if ($res) {
|
|
||||||
curl_close($curl);
|
|
||||||
|
|
||||||
if ($res[ 'status' ] == 0) {
|
|
||||||
return $res['result'];
|
|
||||||
} else {
|
|
||||||
throw new CommonException('请检查地图配置:'.$res[ 'message' ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$error = curl_errno($curl);
|
$map_service = new \app\service\core\map\CoreTiandituMap($site_id);
|
||||||
curl_close($curl);
|
$from_arr = explode(',', $params['from']);
|
||||||
throw new CommonException($error);
|
$to_arr = explode(',', $params['to']);
|
||||||
|
|
||||||
|
$from = isset($from_arr[1]) ? $from_arr[1] . ',' . $from_arr[0] : $params['from'];
|
||||||
|
$to = isset($to_arr[1]) ? $to_arr[1] . ',' . $to_arr[0] : $params['to'];
|
||||||
|
|
||||||
|
$postStr = [
|
||||||
|
'orig' => $from,
|
||||||
|
'dest' => $to,
|
||||||
|
'style' => '0' // 0: 最快路线
|
||||||
|
];
|
||||||
|
$res = $map_service->getPolyline(['data' => $postStr]);
|
||||||
}
|
}
|
||||||
|
return is_string($res) ? json_decode($res, true) : $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
112
niucloud/app/service/core/map/CoreQqMap.php
Normal file
112
niucloud/app/service/core/map/CoreQqMap.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\service\core\map;
|
||||||
|
|
||||||
|
|
||||||
|
use app\service\core\sys\CoreConfigService;
|
||||||
|
use core\base\BaseCoreService;
|
||||||
|
use core\exception\CommonException;
|
||||||
|
|
||||||
|
class CoreQqMap extends BaseCoreService
|
||||||
|
{
|
||||||
|
protected $key = '';
|
||||||
|
protected $curlRequest;
|
||||||
|
public function __construct($site_id = 0)
|
||||||
|
{
|
||||||
|
$mp_config = ( new CoreConfigService() )->getConfig($site_id, 'MAPKEY');
|
||||||
|
|
||||||
|
if (empty($mp_config['value']['key'])){
|
||||||
|
throw new CommonException('请检查腾讯地图配置');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->key = $mp_config['value']['key'] ?? '';
|
||||||
|
$this->curlRequest = new CurlRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过地址字符串获取详情
|
||||||
|
* @param $param
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
public function addressToDetail($param)
|
||||||
|
{
|
||||||
|
|
||||||
|
$url = 'https://apis.map.qq.com/ws/geocoder/v1/';
|
||||||
|
$query_data = [
|
||||||
|
'key' => $this->key,
|
||||||
|
'address' => $param['address'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->curlRequest->get($url, $query_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过ip获取详情
|
||||||
|
* @param $ip
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
public function ipToDetail($param)
|
||||||
|
{
|
||||||
|
$url = 'https://apis.map.qq.com/ws/location/v1/ip';
|
||||||
|
$query_data = [
|
||||||
|
'key' => $this->key,
|
||||||
|
];
|
||||||
|
if($param['ip']){
|
||||||
|
$query_data['ip'] = $param['ip'];
|
||||||
|
}
|
||||||
|
return $this->curlRequest->get($url, $query_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过经纬度获取详情
|
||||||
|
* @param $param
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
public function locationToDetail($param)
|
||||||
|
{
|
||||||
|
$url = 'https://apis.map.qq.com/ws/geocoder/v1/';
|
||||||
|
$query_data = [
|
||||||
|
'key' => $this->key,
|
||||||
|
'location' => $param['location'],//$latitude.','.$longitude
|
||||||
|
'get_poi' => 0,//是否返回周边POI列表:1.返回;0不返回(默认)
|
||||||
|
];
|
||||||
|
return $this->curlRequest->get($url, $query_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取规划路线
|
||||||
|
* @param $params
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPolyline($params)
|
||||||
|
{
|
||||||
|
$url = 'https://apis.map.qq.com/ws/direction/v1/driving/';
|
||||||
|
|
||||||
|
$get_data = [
|
||||||
|
'key' => $this->key,
|
||||||
|
'from' => $params[ 'from' ],
|
||||||
|
'to' => $params[ 'to' ], // 是否返回周边POI列表:1.返回;0不返回(默认)
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = $this->curlRequest->get($url, $get_data);
|
||||||
|
if (is_string($res)) {
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($res) {
|
||||||
|
if ($res[ 'status' ] == 0) {
|
||||||
|
return $res['result'];
|
||||||
|
} else {
|
||||||
|
throw new CommonException('请检查地图配置:'.$res[ 'message' ]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new CommonException('获取规划路线失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey(){
|
||||||
|
return $this->key;
|
||||||
|
}
|
||||||
|
}
|
||||||
342
niucloud/app/service/core/map/CoreTiandituMap.php
Normal file
342
niucloud/app/service/core/map/CoreTiandituMap.php
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\service\core\map;
|
||||||
|
|
||||||
|
use app\service\core\map\CurlRequest;
|
||||||
|
use app\service\core\sys\CoreConfigService;
|
||||||
|
use core\base\BaseService;
|
||||||
|
use core\exception\CommonException;
|
||||||
|
|
||||||
|
class CoreTiandituMap extends BaseService
|
||||||
|
{
|
||||||
|
protected $key = '';
|
||||||
|
protected $curlRequest;
|
||||||
|
|
||||||
|
public function __construct($site_id = 0)
|
||||||
|
{
|
||||||
|
$mp_config = ( new CoreConfigService() )->getConfig($site_id, 'MAPKEY');
|
||||||
|
|
||||||
|
if (empty($mp_config['value']['tianditu_map_key'])){
|
||||||
|
throw new CommonException('请检查天地图配置');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->key = $mp_config['value']['tianditu_map_key'] ?? '';
|
||||||
|
$this->curlRequest = new CurlRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过地址字符串获取详情(地理编码)
|
||||||
|
* @param $param
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function addressToDetail($param)
|
||||||
|
{
|
||||||
|
$url = 'https://api.tianditu.gov.cn/geocoder';
|
||||||
|
if (empty($this->key)) throw new CommonException('配置错误');
|
||||||
|
$query_data = [
|
||||||
|
'tk' => $this->key,
|
||||||
|
'ds' => json_encode(['keyWord' => $param['address']]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = json_decode($this->curlRequest->get($url, $query_data), true);
|
||||||
|
$keyword = $res['location']['keyWord'] ?? '';
|
||||||
|
$province = '';
|
||||||
|
$city = '';
|
||||||
|
$district = '';
|
||||||
|
|
||||||
|
// 解析省市区
|
||||||
|
$pattern = '/^(?<province>[^省]+省|.+?自治区|上海市|北京市|天津市|重庆市|澳门特别行政区|香港特别行政区|台湾省)?(?<city>[^市]+市|.+?自治州|.+?地区|.+?盟)?(?<district>[^县]+县|[^区]+区|[^市]+市|.+?旗|.+?海域|.+?岛)?/u';
|
||||||
|
if (preg_match($pattern, $keyword, $matches)) {
|
||||||
|
$province = $matches['province'] ?? '';
|
||||||
|
$city = $matches['city'] ?? '';
|
||||||
|
$district = $matches['district'] ?? '';
|
||||||
|
|
||||||
|
// 处理直辖市:如果省份是直辖市,且城市为空,则将城市设置为与省份相同(如:北京市朝阳区 -> province: 北京市, city: 北京市, district: 朝阳区)
|
||||||
|
$direct_cities = ['北京市', '上海市', '天津市', '重庆市'];
|
||||||
|
if (in_array($province, $direct_cities)) {
|
||||||
|
// 如果直辖市匹配到了province,但后面直接跟着区,导致city为空或被错误匹配到了district里
|
||||||
|
if (empty($city)) {
|
||||||
|
$city = $province;
|
||||||
|
} elseif (str_ends_with($city, '区') || str_ends_with($city, '县')) {
|
||||||
|
// 如果city被错误地匹配成了区县
|
||||||
|
$district = $city;
|
||||||
|
$city = $province;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 将天地图数据格式统一为腾讯地图格式
|
||||||
|
return [
|
||||||
|
'status' => isset($res['status']) ? (int)$res['status'] : -1,
|
||||||
|
'message' => ($res['msg'] == 'ok') ? 'success' : 'error',
|
||||||
|
'request_id' => '',
|
||||||
|
'result' => [
|
||||||
|
'title' => $keyword,
|
||||||
|
'location' => [
|
||||||
|
'lng' => isset($res['location']['lon']) ? (float)$res['location']['lon'] : 0,
|
||||||
|
'lat' => isset($res['location']['lat']) ? (float)$res['location']['lat'] : 0,
|
||||||
|
],
|
||||||
|
'ad_info' => [
|
||||||
|
'adcode' => ''
|
||||||
|
],
|
||||||
|
'address_components' => [
|
||||||
|
'province' => $province,
|
||||||
|
'city' => $city,
|
||||||
|
'district' => $district,
|
||||||
|
'street' => '',
|
||||||
|
'street_number' => ''
|
||||||
|
],
|
||||||
|
'similarity' => '',
|
||||||
|
'deviation' => '',
|
||||||
|
'reliability' => '',
|
||||||
|
'level' => '',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过经纬度获取详情(逆地理编码)
|
||||||
|
* @param $param
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function locationToDetail($param)
|
||||||
|
{
|
||||||
|
$url = 'https://api.tianditu.gov.cn/geocoder';
|
||||||
|
$query_data = [
|
||||||
|
'tk' => $this->key,
|
||||||
|
'type' => 'geocode',
|
||||||
|
'postStr' => json_encode(['lon' => $param['lon'], 'lat' => $param['lat'], 'ver' => 1]),
|
||||||
|
];
|
||||||
|
$res = $this->curlRequest->get($url, $query_data);
|
||||||
|
|
||||||
|
// 解析后的结果格式化以匹配腾讯地图
|
||||||
|
if (is_string($res)) {
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
}
|
||||||
|
if ($res['status'] == 400) throw new CommonException($res['msg']);
|
||||||
|
|
||||||
|
$t_result = $res['result'];
|
||||||
|
$t_component = $t_result['addressComponent'] ?? [];
|
||||||
|
|
||||||
|
$province = $t_component['province'] ?? '';
|
||||||
|
$city = $t_component['city'] ?? '';
|
||||||
|
$district = $t_component['county'] ?? '';
|
||||||
|
|
||||||
|
// 直辖市处理
|
||||||
|
$direct_cities = ['北京市', '上海市', '天津市', '重庆市'];
|
||||||
|
if (in_array($province, $direct_cities) && empty($city)) {
|
||||||
|
$city = $province;
|
||||||
|
}
|
||||||
|
|
||||||
|
$formatted_res = [
|
||||||
|
'status' => 0,
|
||||||
|
'message' => 'Success',
|
||||||
|
'request_id' => '',
|
||||||
|
'result' => [
|
||||||
|
'location' => [
|
||||||
|
'lat' => isset($t_result['location']['lat']) ? (float)$t_result['location']['lat'] : 0,
|
||||||
|
'lng' => isset($t_result['location']['lon']) ? (float)$t_result['location']['lon'] : 0,
|
||||||
|
],
|
||||||
|
'address' => $t_result['formatted_address'] ?? '',
|
||||||
|
'address_component' => [
|
||||||
|
'nation' => $t_component['nation'] ?? '中国',
|
||||||
|
'province' => $province,
|
||||||
|
'city' => $city,
|
||||||
|
'district' => $district,
|
||||||
|
'street' => $t_component['road'] ?? '',
|
||||||
|
'street_number' => $t_component['address'] ?? ''
|
||||||
|
],
|
||||||
|
'ad_info' => [
|
||||||
|
'nation_code' => '',
|
||||||
|
'adcode' => $t_component['county_code'] ?? '',
|
||||||
|
'phone_area_code' => '',
|
||||||
|
'city_code' => $t_component['city_code'] ?? '',
|
||||||
|
'name' => implode(',', array_filter([$t_component['nation'] ?? '中国', $province, $city, $district])),
|
||||||
|
'location' => [
|
||||||
|
'lat' => isset($t_result['location']['lat']) ? (float)$t_result['location']['lat'] : 0,
|
||||||
|
'lng' => isset($t_result['location']['lon']) ? (float)$t_result['location']['lon'] : 0,
|
||||||
|
],
|
||||||
|
'nation' => $t_component['nation'] ?? '中国',
|
||||||
|
'province' => $province,
|
||||||
|
'city' => $city,
|
||||||
|
'district' => $district,
|
||||||
|
'_distance' => 0
|
||||||
|
],
|
||||||
|
'address_reference' => [
|
||||||
|
'town' => [
|
||||||
|
'id' => '',
|
||||||
|
'title' => '',
|
||||||
|
'location' => [
|
||||||
|
'lat' => 0,
|
||||||
|
'lng' => 0
|
||||||
|
],
|
||||||
|
'_distance' => 0,
|
||||||
|
'_dir_desc' => ''
|
||||||
|
],
|
||||||
|
'landmark_l2' => [
|
||||||
|
'id' => '',
|
||||||
|
'title' => '',
|
||||||
|
'location' => [
|
||||||
|
'lat' => 0,
|
||||||
|
'lng' => 0
|
||||||
|
],
|
||||||
|
'_distance' => 0,
|
||||||
|
'_dir_desc' => ''
|
||||||
|
],
|
||||||
|
'street' => [
|
||||||
|
'id' => '',
|
||||||
|
'title' => '',
|
||||||
|
'location' => [
|
||||||
|
'lat' => 0,
|
||||||
|
'lng' => 0
|
||||||
|
],
|
||||||
|
'_distance' => 0,
|
||||||
|
'_dir_desc' => ''
|
||||||
|
],
|
||||||
|
'street_number' => [
|
||||||
|
'id' => '',
|
||||||
|
'title' => '',
|
||||||
|
'location' => [
|
||||||
|
'lat' => 0,
|
||||||
|
'lng' => 0
|
||||||
|
],
|
||||||
|
'_distance' => 0,
|
||||||
|
'_dir_desc' => ''
|
||||||
|
],
|
||||||
|
'crossroad' => [
|
||||||
|
'id' => '',
|
||||||
|
'title' => '',
|
||||||
|
'location' => [
|
||||||
|
'lat' => 0,
|
||||||
|
'lng' => 0
|
||||||
|
],
|
||||||
|
'_distance' => 0,
|
||||||
|
'_dir_desc' => ''
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'formatted_addresses' => [
|
||||||
|
'recommend' => $t_component['poi'] ?? '',
|
||||||
|
'rough' => $t_component['poi'] ?? '',
|
||||||
|
'standard_address' => $t_result['formatted_address'] ?? ''
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
return $formatted_res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey()
|
||||||
|
{
|
||||||
|
return $this->key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取规划路线
|
||||||
|
* @param $params
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPolyline($params)
|
||||||
|
{
|
||||||
|
$url = 'http://api.tianditu.gov.cn/drive';
|
||||||
|
|
||||||
|
$query_data = [
|
||||||
|
'tk' => $this->key,
|
||||||
|
'type' => 'search',
|
||||||
|
'postStr' => json_encode($params['data'])
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = $this->curlRequest->get($url, $query_data);
|
||||||
|
|
||||||
|
if (is_string($res)) {
|
||||||
|
$xmlString = trim($res);
|
||||||
|
// 转成 SimpleXML 对象
|
||||||
|
$xml = simplexml_load_string($xmlString, 'SimpleXMLElement', LIBXML_NOCDATA);
|
||||||
|
// 转 JSON 再转数组(最简单稳定)
|
||||||
|
$json = json_encode($xml);
|
||||||
|
$res = json_decode($json, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 天地图无错误状态码是 0 或者没有报错
|
||||||
|
if ($res && isset($res['distance']) && isset($res['routelatlon'])) {
|
||||||
|
// 转换为腾讯地图的路线格式
|
||||||
|
|
||||||
|
// 天地图的 routelatlon 格式:"lon,lat;lon,lat;..."
|
||||||
|
$points_str = explode(';', trim($res['routelatlon'], ';'));
|
||||||
|
$polyline = [];
|
||||||
|
|
||||||
|
$prev_lat = 0;
|
||||||
|
$prev_lon = 0;
|
||||||
|
|
||||||
|
foreach ($points_str as $idx => $point) {
|
||||||
|
if (empty($point)) continue;
|
||||||
|
$coords = explode(',', $point);
|
||||||
|
if (count($coords) != 2) continue;
|
||||||
|
|
||||||
|
// 腾讯地图的 polyline 是差分压缩格式
|
||||||
|
// 第一点是实际坐标浮点数(不乘1000000),后面的点是与前一点的差值 * 1000000
|
||||||
|
$lon = (float)$coords[0];
|
||||||
|
$lat = (float)$coords[1];
|
||||||
|
|
||||||
|
$lat_val = round($lat * 1000000);
|
||||||
|
$lon_val = round($lon * 1000000);
|
||||||
|
|
||||||
|
if ($idx == 0) {
|
||||||
|
$polyline[] = $lat;
|
||||||
|
$polyline[] = $lon;
|
||||||
|
} else {
|
||||||
|
$polyline[] = $lat_val - $prev_lat;
|
||||||
|
$polyline[] = $lon_val - $prev_lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
$prev_lat = $lat_val;
|
||||||
|
$prev_lon = $lon_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 steps
|
||||||
|
$steps = [];
|
||||||
|
if (isset($res['simple']['item'])) {
|
||||||
|
// 如果只有一个步骤,可能不会是数组包数组的形式,需要判断处理
|
||||||
|
$items = isset($res['simple']['item'][0]) ? $res['simple']['item'] : [$res['simple']['item']];
|
||||||
|
|
||||||
|
// 为了简化 polyline_idx,我们用均分或大致的起止索引
|
||||||
|
// 由于天地图和腾讯地图的点位对应关系不同,这里我们做一个简单的映射
|
||||||
|
// 实际业务中往往主要用到距离、耗时和完整的 polyline,不一定严格依赖每一步的 idx
|
||||||
|
$current_idx = 0;
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$step_distance = (float)($item['streetDistance'] ?? 0);
|
||||||
|
$steps[] = [
|
||||||
|
"instruction" => $item['strguide'] ?? '',
|
||||||
|
"polyline_idx" => [0, max(0, count($polyline) / 2 - 1)], // 简化处理,实际要精确匹配需要遍历坐标点比对
|
||||||
|
"road_name" => $item['linkStreetName'] ?? (is_string($item['streetNames']) ? trim($item['streetNames'], ',') : ''),
|
||||||
|
"dir_desc" => "",
|
||||||
|
"distance" => $step_distance,
|
||||||
|
"act_desc" => "",
|
||||||
|
"accessorial_desc" => ""
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$routes = [
|
||||||
|
[
|
||||||
|
"mode" => "DRIVING",
|
||||||
|
"distance" => (int)($res['distance'] * 1000), // 天地图是公里,转为米
|
||||||
|
"duration" => (int)($res['duration'] / 60), // 天地图 duration 是秒,腾讯地图是分钟
|
||||||
|
"traffic_light_count" => 0,
|
||||||
|
"toll" => 0,
|
||||||
|
"restriction" => [
|
||||||
|
"status" => 1
|
||||||
|
],
|
||||||
|
"polyline" => $polyline,
|
||||||
|
"steps" => $steps,
|
||||||
|
"tags" => ["大众常走"],
|
||||||
|
"taxi_fare" => [
|
||||||
|
"fare" => 0
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
return [
|
||||||
|
'routes' => $routes
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw new CommonException('获取规划路线失败:' . ($res['message'] ?? '未知错误'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
niucloud/app/service/core/map/CurlRequest.php
Normal file
92
niucloud/app/service/core/map/CurlRequest.php
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\service\core\map;
|
||||||
|
|
||||||
|
class CurlRequest
|
||||||
|
{
|
||||||
|
public static function httpRequest($method, $url, $postfields = NULL)
|
||||||
|
{
|
||||||
|
$ci = curl_init();
|
||||||
|
curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
|
||||||
|
curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 100);
|
||||||
|
curl_setopt($ci, CURLOPT_TIMEOUT, 100);
|
||||||
|
curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
|
||||||
|
curl_setopt($ci, CURLOPT_ENCODING, "");
|
||||||
|
curl_setopt($ci, CURLOPT_HEADER, FALSE);
|
||||||
|
|
||||||
|
switch ($method) {
|
||||||
|
case 'POST':
|
||||||
|
curl_setopt($ci, CURLOPT_POST, TRUE);
|
||||||
|
if (!empty($postfields)) {
|
||||||
|
curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'GET':
|
||||||
|
$url = $url . '?' . http_build_query($postfields);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt($ci, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ci, CURLOPT_HTTPHEADER, []);
|
||||||
|
curl_setopt($ci, CURLINFO_HEADER_OUT, TRUE);
|
||||||
|
//TODO 只有本地使用 外网不用设置
|
||||||
|
curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, FALSE);
|
||||||
|
curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, FALSE);
|
||||||
|
curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
|
||||||
|
$response = curl_exec($ci);
|
||||||
|
|
||||||
|
$httpCode = curl_getinfo($ci, CURLINFO_HTTP_CODE);
|
||||||
|
$httpInfo = curl_getinfo($ci);
|
||||||
|
|
||||||
|
curl_close($ci);
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET请求
|
||||||
|
* @param string $url
|
||||||
|
* @param array $data
|
||||||
|
* @return mixed
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function get(string $url, array $data = [])
|
||||||
|
{
|
||||||
|
return $this->httpRequest('GET', $url, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST请求
|
||||||
|
* @param string $url
|
||||||
|
* @param array $data
|
||||||
|
* @return mixed
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function post(string $url, array $data = [])
|
||||||
|
{
|
||||||
|
return $this->httpRequest('POST', $url, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT请求
|
||||||
|
* @param string $url
|
||||||
|
* @param array $data
|
||||||
|
* @return mixed
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function put(string $url, array $data = [])
|
||||||
|
{
|
||||||
|
return $this->httpRequest('PUT', $url, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE请求
|
||||||
|
* @param string $url
|
||||||
|
* @param array $data
|
||||||
|
* @return mixed
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function delete(string $url, array $data = [])
|
||||||
|
{
|
||||||
|
return $this->httpRequest('DELETE', $url, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ use app\dict\addon\AddonDict;
|
|||||||
use app\model\addon\Addon;
|
use app\model\addon\Addon;
|
||||||
use app\service\core\addon\CoreAddonBaseService;
|
use app\service\core\addon\CoreAddonBaseService;
|
||||||
use app\service\core\addon\CoreAddonDevelopDownloadService;
|
use app\service\core\addon\CoreAddonDevelopDownloadService;
|
||||||
|
use app\service\core\addon\CoreAddonService;
|
||||||
use app\service\core\addon\WapTrait;
|
use app\service\core\addon\WapTrait;
|
||||||
use core\base\BaseCoreService;
|
use core\base\BaseCoreService;
|
||||||
use core\exception\CloudBuildException;
|
use core\exception\CloudBuildException;
|
||||||
@ -105,6 +106,7 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
|
|
||||||
// 是否通过校验
|
// 是否通过校验
|
||||||
$data[ 'is_pass' ] = !in_array(false, $check_res);
|
$data[ 'is_pass' ] = !in_array(false, $check_res);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,14 +115,25 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
* @return array
|
* @return array
|
||||||
* @throws GuzzleException
|
* @throws GuzzleException
|
||||||
*/
|
*/
|
||||||
public function cloudBuild()
|
public function cloudBuild($param = [])
|
||||||
{
|
{
|
||||||
if (empty($this->auth_code)) {
|
if (empty($this->auth_code)) {
|
||||||
throw new CommonException('CLOUD_BUILD_AUTH_CODE_NOT_FOUND');
|
throw new CommonException('CLOUD_BUILD_AUTH_CODE_NOT_FOUND');
|
||||||
}
|
}
|
||||||
if ($this->build_task) throw new CommonException('CLOUD_BUILD_TASK_EXIST');
|
if ($this->build_task) throw new CommonException('CLOUD_BUILD_TASK_EXIST');
|
||||||
|
|
||||||
$action_token = ( new CoreModuleService() )->getActionToken('cloudbuild', [ 'data' => [ 'product_key' => BaseNiucloudClient::PRODUCT ] ]);
|
// 全部插件
|
||||||
|
$all_addon = array_keys((new CoreAddonService())->getInstallAddonList());
|
||||||
|
// 排除的插件
|
||||||
|
$exclude_addon = [];
|
||||||
|
if (isset($param['addon']) && !empty($param['addon'])) $exclude_addon = array_values(array_diff($all_addon, $param['addon']));
|
||||||
|
|
||||||
|
$action_token = [ 'data' => [] ];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$action_token = ( new CoreModuleService() )->getActionToken('cloudbuild', [ 'data' => [ 'product_key' => BaseNiucloudClient::PRODUCT ] ]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
}
|
||||||
|
|
||||||
// 上传任务key
|
// 上传任务key
|
||||||
$task_key = uniqid();
|
$task_key = uniqid();
|
||||||
@ -134,18 +147,24 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
// 拷贝手机端文件
|
// 拷贝手机端文件
|
||||||
$wap_is_compile = ( new Addon() )->where([ [ 'compile', 'like', '%wap%' ] ])->field('id')->findOrEmpty();
|
$wap_is_compile = ( new Addon() )->where([ [ 'compile', 'like', '%wap%' ] ])->field('id')->findOrEmpty();
|
||||||
if ($wap_is_compile->isEmpty()) {
|
if ($wap_is_compile->isEmpty()) {
|
||||||
dir_copy($this->root_path . 'uni-app', $package_dir . 'uni-app', exclude_dirs: [ 'node_modules', 'unpackage', 'dist', '.git' ]);
|
dir_copy($this->root_path . 'uni-app', $package_dir . 'uni-app', exclude_dirs: [ 'node_modules', 'unpackage', 'dist', '.git', ...$exclude_addon ]);
|
||||||
$this->handleUniapp($package_dir . 'uni-app');
|
// 如果有排除的插件
|
||||||
|
if (!empty($exclude_addon)) {
|
||||||
|
// 处理pages.json
|
||||||
|
$this->handlePageCode($package_dir . 'uni-app' . DIRECTORY_SEPARATOR .'src' . DIRECTORY_SEPARATOR, $param['addon']);
|
||||||
|
// 处理diy-group
|
||||||
|
$this->compileDiyComponentsCode($package_dir . 'uni-app'. DIRECTORY_SEPARATOR .'src' . DIRECTORY_SEPARATOR, $exclude_addon[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 拷贝admin端文件
|
// 拷贝admin端文件
|
||||||
$admin_is_compile = ( new Addon() )->where([ [ 'compile', 'like', '%admin%' ] ])->field('id')->findOrEmpty();
|
$admin_is_compile = ( new Addon() )->where([ [ 'compile', 'like', '%admin%' ] ])->field('id')->findOrEmpty();
|
||||||
if ($admin_is_compile->isEmpty()) {
|
if ($admin_is_compile->isEmpty()) {
|
||||||
dir_copy($this->root_path . 'admin', $package_dir . 'admin', exclude_dirs: [ 'node_modules', 'dist', '.vscode', '.idea', '.git' ]);
|
dir_copy($this->root_path . 'admin', $package_dir . 'admin', exclude_dirs: [ 'node_modules', 'dist', '.vscode', '.idea', '.git', ...$exclude_addon ]);
|
||||||
}
|
}
|
||||||
// 拷贝web端文件
|
// 拷贝web端文件
|
||||||
$web_is_compile = ( new Addon() )->where([ [ 'compile', 'like', '%web%' ] ])->field('id')->findOrEmpty();
|
$web_is_compile = ( new Addon() )->where([ [ 'compile', 'like', '%web%' ] ])->field('id')->findOrEmpty();
|
||||||
if ($web_is_compile->isEmpty()) {
|
if ($web_is_compile->isEmpty()) {
|
||||||
dir_copy($this->root_path . 'web', $package_dir . 'web', exclude_dirs: [ 'node_modules', '.output', '.nuxt', '.git' ]);
|
dir_copy($this->root_path . 'web', $package_dir . 'web', exclude_dirs: [ 'node_modules', '.output', '.nuxt', '.git', ...$exclude_addon ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->handleCustomPort($package_dir);
|
$this->handleCustomPort($package_dir);
|
||||||
@ -159,7 +178,10 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
'token' => $action_token[ 'data' ][ 'token' ] ?? ''
|
'token' => $action_token[ 'data' ][ 'token' ] ?? ''
|
||||||
];
|
];
|
||||||
set_time_limit(0);
|
set_time_limit(0);
|
||||||
$response = ( new CloudService(true) )->httpPost('cloud/build?' . http_build_query($query), [
|
|
||||||
|
$param['checkLocal'] = $param['checkLocal'] ?? true;
|
||||||
|
|
||||||
|
$response = ( new CloudService($param['checkLocal']) )->httpPost('cloud/build?' . http_build_query($query), [
|
||||||
'multipart' => [
|
'multipart' => [
|
||||||
[
|
[
|
||||||
'name' => 'file',
|
'name' => 'file',
|
||||||
@ -173,17 +195,45 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
|
|
||||||
$this->build_task = [
|
$this->build_task = [
|
||||||
'task_key' => $task_key,
|
'task_key' => $task_key,
|
||||||
'timestamp' => $query[ 'timestamp' ]
|
'timestamp' => $query[ 'timestamp' ],
|
||||||
|
'checkLocal' => $param['checkLocal'],
|
||||||
|
'task_id' => $response['data']['task_id'] ?? '',
|
||||||
|
'auth_code' => $this->auth_code
|
||||||
];
|
];
|
||||||
Cache::set($this->cache_key, $this->build_task);
|
Cache::set($this->cache_key, $this->build_task);
|
||||||
|
|
||||||
return $this->build_task;
|
return $this->build_task;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleUniapp(string $dir)
|
private function handlePageCode($compile_path, $addon_arr)
|
||||||
{
|
{
|
||||||
$addon = ( new Addon() )->where([ [ 'status', '=', AddonDict::ON ] ])->value('key', '');
|
$pages = [];
|
||||||
$this->compileDiyComponentsCode($dir . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, $addon);
|
foreach ($addon_arr as $addon) {
|
||||||
|
if (!file_exists($this->geAddonPackagePath($addon) . 'uni-app-pages.php')) continue;
|
||||||
|
$uniapp_pages = require $this->geAddonPackagePath($addon) . 'uni-app-pages.php';
|
||||||
|
if (empty($uniapp_pages[ 'pages' ])) continue;
|
||||||
|
|
||||||
|
$page_begin = strtoupper($addon) . '_PAGE_BEGIN';
|
||||||
|
$page_end = strtoupper($addon) . '_PAGE_END';
|
||||||
|
|
||||||
|
// 对0.2.0之前的版本做处理
|
||||||
|
$uniapp_pages[ 'pages' ] = preg_replace_callback('/(.*)(\\r\\n.*\/\/ PAGE_END.*)/s', function ($match) {
|
||||||
|
return $match[ 1 ] . ( substr($match[ 1 ], -1) == ',' ? '' : ',' ) . $match[ 2 ];
|
||||||
|
}, $uniapp_pages[ 'pages' ]);
|
||||||
|
|
||||||
|
$uniapp_pages[ 'pages' ] = str_replace('PAGE_BEGIN', $page_begin, $uniapp_pages[ 'pages' ]);
|
||||||
|
$uniapp_pages[ 'pages' ] = str_replace('PAGE_END', $page_end, $uniapp_pages[ 'pages' ]);
|
||||||
|
$uniapp_pages[ 'pages' ] = str_replace('{{addon_name}}', $addon, $uniapp_pages[ 'pages' ]);
|
||||||
|
|
||||||
|
$pages[] = $uniapp_pages[ 'pages' ];
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = @file_get_contents($compile_path . "pages.json");
|
||||||
|
$content = preg_replace_callback('/(.*\/\/ \{\{ PAGE_BEGAIN \}\})(.*)(\/\/ \{\{ PAGE_END \}\}.*)/s', function ($match) use ($pages) {
|
||||||
|
return $match[ 1 ] . PHP_EOL . implode(PHP_EOL, $pages) . PHP_EOL . $match[ 3 ];
|
||||||
|
}, $content);
|
||||||
|
|
||||||
|
// 找到页面路由文件 pages.json,写入内容
|
||||||
|
return file_put_contents($compile_path . "pages.json", $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleCustomPort(string $package_dir)
|
private function handleCustomPort(string $package_dir)
|
||||||
@ -226,10 +276,16 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
'authorize_code' => $this->auth_code,
|
'authorize_code' => $this->auth_code,
|
||||||
'timestamp' => $this->build_task[ 'timestamp' ]
|
'timestamp' => $this->build_task[ 'timestamp' ]
|
||||||
];
|
];
|
||||||
$build_log = ( new CloudService(true) )->httpGet('cloud/get_build_logs?' . http_build_query($query));
|
$build_log = ( new CloudService($this->build_task['checkLocal'] ?? false) )->httpGet('cloud/get_build_logs?' . http_build_query($query));
|
||||||
|
|
||||||
if (isset($build_log[ 'data' ]) && isset($build_log[ 'data' ][ 0 ]) && is_array($build_log[ 'data' ][ 0 ])) {
|
if (isset($build_log[ 'data' ]) && isset($build_log[ 'data' ][ 0 ]) && is_array($build_log[ 'data' ][ 0 ])) {
|
||||||
$last = end($build_log[ 'data' ][ 0 ]);
|
$last = end($build_log[ 'data' ][ 0 ]);
|
||||||
|
foreach ($build_log[ 'data' ][ 0 ] as $item) {
|
||||||
|
if ($item['code'] == 0) {
|
||||||
|
$build_log[ 'error_analysis' ] = $this->buildResultAnalysis($item[ 'msg' ]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($last[ 'percent' ] == 100 && $last[ 'code' ] == 1) {
|
if ($last[ 'percent' ] == 100 && $last[ 'code' ] == 1) {
|
||||||
$build_log[ 'data' ][ 0 ] = $this->buildSuccess($build_log[ 'data' ][ 0 ]);
|
$build_log[ 'data' ][ 0 ] = $this->buildSuccess($build_log[ 'data' ][ 0 ]);
|
||||||
}
|
}
|
||||||
@ -237,6 +293,15 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
return $build_log;
|
return $build_log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编译异常分析
|
||||||
|
* @param $msg
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function buildResultAnalysis($msg) {
|
||||||
|
return ( new CoreModuleService() )->buildResultAnalysis($msg);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编译完成
|
* 编译完成
|
||||||
* @param array $log
|
* @param array $log
|
||||||
@ -253,7 +318,7 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
$temp_dir = runtime_path() . 'backup' . DIRECTORY_SEPARATOR . 'cloud_build' . DIRECTORY_SEPARATOR . $this->build_task[ 'task_key' ] . DIRECTORY_SEPARATOR;
|
$temp_dir = runtime_path() . 'backup' . DIRECTORY_SEPARATOR . 'cloud_build' . DIRECTORY_SEPARATOR . $this->build_task[ 'task_key' ] . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
if (!isset($this->build_task[ 'index' ])) {
|
if (!isset($this->build_task[ 'index' ])) {
|
||||||
$response = ( new CloudService(true) )->request('HEAD', 'cloud/build_download?' . http_build_query($query), [
|
$response = ( new CloudService($this->build_task['checkLocal'] ?? false) )->request('HEAD', 'cloud/build_download?' . http_build_query($query), [
|
||||||
'headers' => [ 'Range' => 'bytes=0-' ]
|
'headers' => [ 'Range' => 'bytes=0-' ]
|
||||||
]);
|
]);
|
||||||
$length = $response->getHeader('Content-range');
|
$length = $response->getHeader('Content-range');
|
||||||
@ -271,7 +336,7 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
$end = ( $this->build_task[ 'index' ] + 1 ) * $chunk_size;
|
$end = ( $this->build_task[ 'index' ] + 1 ) * $chunk_size;
|
||||||
$end = min($end, $this->build_task[ 'length' ]);
|
$end = min($end, $this->build_task[ 'length' ]);
|
||||||
|
|
||||||
$response = ( new CloudService(true) )->request('GET', 'cloud/build_download?' . http_build_query($query), [
|
$response = ( new CloudService($this->build_task['checkLocal'] ?? false) )->request('GET', 'cloud/build_download?' . http_build_query($query), [
|
||||||
'headers' => [ 'Range' => "bytes={$start}-{$end}" ]
|
'headers' => [ 'Range' => "bytes={$start}-{$end}" ]
|
||||||
]);
|
]);
|
||||||
fwrite($zip_resource, $response->getBody());
|
fwrite($zip_resource, $response->getBody());
|
||||||
@ -290,9 +355,21 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
$zip->extractTo($temp_dir . 'download');
|
$zip->extractTo($temp_dir . 'download');
|
||||||
$zip->close();
|
$zip->close();
|
||||||
|
|
||||||
|
if (is_dir($temp_dir . 'download' . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'admin')) {
|
||||||
|
@del_target_dir(public_path() .'admin', true);
|
||||||
|
}
|
||||||
|
if (is_dir($temp_dir . 'download' . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'web')) {
|
||||||
|
@del_target_dir(public_path() .'web', true);
|
||||||
|
}
|
||||||
|
if (is_dir($temp_dir . 'download' . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'wap')) {
|
||||||
|
@del_target_dir(public_path() .'wap', true);
|
||||||
|
}
|
||||||
|
|
||||||
$exclude_files = ['favicon.ico', 'niucloud.ico'];
|
$exclude_files = ['favicon.ico', 'niucloud.ico'];
|
||||||
dir_copy($temp_dir . 'download', root_path(), exclude_files: $exclude_files);
|
dir_copy($temp_dir . 'download', root_path(), exclude_files: $exclude_files);
|
||||||
|
|
||||||
|
$this->buildResultAnalysis('success');
|
||||||
|
|
||||||
$this->clearTask();
|
$this->clearTask();
|
||||||
} else {
|
} else {
|
||||||
// 压缩包解压失败 尝试重新下载
|
// 压缩包解压失败 尝试重新下载
|
||||||
@ -304,6 +381,7 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
$log[] = [ 'code' => 1, 'msg' => '编译包解压失败,尝试重新下载', 'action' => '编译包解压失败,尝试重新下载', 'percent' => '100' ];
|
$log[] = [ 'code' => 1, 'msg' => '编译包解压失败,尝试重新下载', 'action' => '编译包解压失败,尝试重新下载', 'percent' => '100' ];
|
||||||
} else {
|
} else {
|
||||||
$log[] = [ 'code' => 0, 'msg' => '编译包解压失败', 'action' => '编译包解压', 'percent' => '100' ];
|
$log[] = [ 'code' => 0, 'msg' => '编译包解压失败', 'action' => '编译包解压', 'percent' => '100' ];
|
||||||
|
$this->buildResultAnalysis('编译包解压失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,8 +400,276 @@ class CoreCloudBuildService extends BaseCoreService
|
|||||||
public function clearTask()
|
public function clearTask()
|
||||||
{
|
{
|
||||||
if (!$this->build_task) return;
|
if (!$this->build_task) return;
|
||||||
|
|
||||||
|
if (isset($this->build_task['task_id']) && !empty($this->build_task['task_id'])) {
|
||||||
|
try {
|
||||||
|
( new CloudService($this->build_task['checkLocal'] ?? false) )->httpPost('cloud/cancel', [
|
||||||
|
'json' => [
|
||||||
|
'task_id' => $this->build_task['task_id']
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$temp_dir = runtime_path() . 'backup' . DIRECTORY_SEPARATOR . 'cloud_build' . DIRECTORY_SEPARATOR . $this->build_task[ 'task_key' ] . DIRECTORY_SEPARATOR;
|
$temp_dir = runtime_path() . 'backup' . DIRECTORY_SEPARATOR . 'cloud_build' . DIRECTORY_SEPARATOR . $this->build_task[ 'task_key' ] . DIRECTORY_SEPARATOR;
|
||||||
@del_target_dir($temp_dir, true);
|
@del_target_dir($temp_dir, true);
|
||||||
Cache::set($this->cache_key, null);
|
Cache::set($this->cache_key, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件定义的package目录
|
||||||
|
* @param string $addon
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function geAddonPackagePath(string $addon)
|
||||||
|
{
|
||||||
|
return root_path() . 'addon' . DIRECTORY_SEPARATOR . $addon . DIRECTORY_SEPARATOR . 'package' . DIRECTORY_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSE编译完成后,PHP后台执行下载解压部署
|
||||||
|
* @param string $taskId 任务ID
|
||||||
|
* @param string $downloadUrl 下载链接
|
||||||
|
* @param string $authorizeCode 授权码
|
||||||
|
* @param string $timestamp 时间戳
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function startServerDownload(string $taskId, string $downloadUrl, string $authorizeCode, string $timestamp)
|
||||||
|
{
|
||||||
|
$taskKey = 'sse_build_' . $taskId;
|
||||||
|
$tempDir = runtime_path() . 'backup' . DIRECTORY_SEPARATOR . 'cloud_build' . DIRECTORY_SEPARATOR . $taskKey . DIRECTORY_SEPARATOR;
|
||||||
|
if (!is_dir($tempDir)) {
|
||||||
|
mkdir($tempDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$downloadInfo = [
|
||||||
|
'task_id' => $taskId,
|
||||||
|
'task_key' => $taskKey,
|
||||||
|
'download_url' => $downloadUrl,
|
||||||
|
'authorize_code' => $authorizeCode,
|
||||||
|
'timestamp' => $timestamp,
|
||||||
|
'temp_dir' => $tempDir,
|
||||||
|
'zip_file' => $tempDir . 'download.zip',
|
||||||
|
'chunk_size' => 1 * 1024 * 1024,
|
||||||
|
'status' => 'downloading',
|
||||||
|
'msg' => '准备下载...'
|
||||||
|
];
|
||||||
|
|
||||||
|
Cache::set('cloud_build_' . $taskKey, $downloadInfo, 7200);
|
||||||
|
|
||||||
|
return ['code' => 1, 'msg' => '初始化成功', 'data' => [
|
||||||
|
'task_key' => $taskKey,
|
||||||
|
'cache_key' => 'cloud_build_' . $taskKey,
|
||||||
|
'cache_exists' => Cache::has('cloud_build_' . $taskKey)
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取SSE编译后续操作的进度(参考 buildSuccess 逻辑)
|
||||||
|
* @param string $taskId 任务ID
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getSseBuildLog(string $taskId)
|
||||||
|
{
|
||||||
|
$taskKey = 'sse_build_' . $taskId;
|
||||||
|
$cacheKey = 'cloud_build_' . $taskKey;
|
||||||
|
|
||||||
|
$hasBefore = Cache::has($cacheKey);
|
||||||
|
$downloadInfo = Cache::get($cacheKey);
|
||||||
|
|
||||||
|
if (empty($downloadInfo)) {
|
||||||
|
return ['code' => 0, 'msg' => '任务不存在或已过期', 'status' => 'error', 'debug' => [
|
||||||
|
'task_id' => $taskId,
|
||||||
|
'task_key' => $taskKey,
|
||||||
|
'cache_key' => $cacheKey,
|
||||||
|
'cache_has_before' => $hasBefore
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = [
|
||||||
|
'authorize_code' => $downloadInfo['authorize_code'],
|
||||||
|
'timestamp' => $downloadInfo['timestamp']
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isset($downloadInfo['index'])) {
|
||||||
|
$response = ( new CloudService(true) )->request('HEAD', 'cloud/build_download?' . http_build_query($query), [
|
||||||
|
'headers' => ['Range' => 'bytes=0-']
|
||||||
|
]);
|
||||||
|
$contentRange = $response->getHeader('Content-range');
|
||||||
|
$length = (int) explode("/", $contentRange[0])[1];
|
||||||
|
$chunkSize = $downloadInfo['chunk_size'];
|
||||||
|
$step = (int) ceil($length / $chunkSize);
|
||||||
|
|
||||||
|
$downloadInfo['index'] = 0;
|
||||||
|
$downloadInfo['length'] = $length;
|
||||||
|
$downloadInfo['step'] = $step;
|
||||||
|
$downloadInfo['downloaded_bytes'] = 0;
|
||||||
|
$downloadInfo['percent'] = 0;
|
||||||
|
$downloadInfo['msg'] = '开始下载...';
|
||||||
|
|
||||||
|
Cache::set('cloud_build_' . $taskKey, $downloadInfo, 7200);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 1,
|
||||||
|
'status' => 'downloading',
|
||||||
|
'percent' => 0,
|
||||||
|
'downloaded_bytes' => 0,
|
||||||
|
'total_bytes' => $length,
|
||||||
|
'msg' => '开始下载...'
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$zipFile = $downloadInfo['zip_file'];
|
||||||
|
$zipResource = fopen($zipFile, 'a');
|
||||||
|
|
||||||
|
if (($downloadInfo['index'] + 1) <= $downloadInfo['step']) {
|
||||||
|
$start = $downloadInfo['index'] * $downloadInfo['chunk_size'];
|
||||||
|
$end = ($downloadInfo['index'] + 1) * $downloadInfo['chunk_size'];
|
||||||
|
$end = min($end, $downloadInfo['length']);
|
||||||
|
$expectedBytes = $end - $start;
|
||||||
|
|
||||||
|
$response = ( new CloudService(true) )->request('GET', 'cloud/build_download?' . http_build_query($query), [
|
||||||
|
'headers' => ['Range' => "bytes={$start}-{$end}"]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$body = $response->getBody();
|
||||||
|
$actualBytes = strlen($body);
|
||||||
|
$contentRange = $response->getHeader('Content-Range');
|
||||||
|
$contentRangeStr = is_array($contentRange) ? ($contentRange[0] ?? '') : $contentRange;
|
||||||
|
|
||||||
|
if ($actualBytes != $expectedBytes) {
|
||||||
|
$downloadInfo['downloaded_bytes'] = filesize($zipFile);
|
||||||
|
$downloadInfo['msg'] = "分片{$downloadInfo['index']}大小不符: 期望{$expectedBytes}, 实际{$actualBytes}";
|
||||||
|
Cache::set('cloud_build_' . $taskKey, $downloadInfo, 7200);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 1,
|
||||||
|
'status' => 'downloading',
|
||||||
|
'percent' => $downloadInfo['percent'],
|
||||||
|
'downloaded_bytes' => $downloadInfo['downloaded_bytes'],
|
||||||
|
'total_bytes' => $downloadInfo['length'],
|
||||||
|
'msg' => $downloadInfo['msg'],
|
||||||
|
'debug' => [
|
||||||
|
'chunk_index' => $downloadInfo['index'],
|
||||||
|
'expected_bytes' => $expectedBytes,
|
||||||
|
'actual_bytes' => $actualBytes,
|
||||||
|
'content_range' => $contentRangeStr
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite($zipResource, $body);
|
||||||
|
fclose($zipResource);
|
||||||
|
|
||||||
|
$downloadInfo['index'] += 1;
|
||||||
|
$downloadInfo['downloaded_bytes'] = filesize($zipFile);
|
||||||
|
$downloadInfo['percent'] = round($downloadInfo['index'] / $downloadInfo['step'] * 100);
|
||||||
|
$downloadInfo['msg'] = '编译包下载中,已下载' . $downloadInfo['percent'] . '%';
|
||||||
|
|
||||||
|
Cache::set('cloud_build_' . $taskKey, $downloadInfo, 7200);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 1,
|
||||||
|
'status' => 'downloading',
|
||||||
|
'percent' => $downloadInfo['percent'],
|
||||||
|
'downloaded_bytes' => $downloadInfo['downloaded_bytes'],
|
||||||
|
'total_bytes' => $downloadInfo['length'],
|
||||||
|
'msg' => $downloadInfo['msg']
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
fclose($zipResource);
|
||||||
|
|
||||||
|
$zip = new \ZipArchive();
|
||||||
|
$zipOpenResult = $zip->open($zipFile);
|
||||||
|
if ($zipOpenResult === true) {
|
||||||
|
$extractDir = $downloadInfo['temp_dir'] . 'download' . DIRECTORY_SEPARATOR;
|
||||||
|
dir_mkdir($extractDir);
|
||||||
|
|
||||||
|
$zipContents = [];
|
||||||
|
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||||
|
$zipContents[] = $zip->getNameIndex($i);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->extractTo($extractDir);
|
||||||
|
$zip->close();
|
||||||
|
|
||||||
|
$downloadInfo['msg'] = '解压成功,文件数:' . count($zipContents);
|
||||||
|
Cache::set('cloud_build_' . $taskKey, $downloadInfo, 7200);
|
||||||
|
|
||||||
|
$tempDir = $downloadInfo['temp_dir'];
|
||||||
|
|
||||||
|
if (is_dir($tempDir . 'download' . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'admin')) {
|
||||||
|
@del_target_dir(public_path() . 'admin', true);
|
||||||
|
}
|
||||||
|
if (is_dir($tempDir . 'download' . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'web')) {
|
||||||
|
@del_target_dir(public_path() . 'web', true);
|
||||||
|
}
|
||||||
|
if (is_dir($tempDir . 'download' . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'wap')) {
|
||||||
|
@del_target_dir(public_path() . 'wap', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$excludeFiles = ['favicon.ico', 'niucloud.ico'];
|
||||||
|
dir_copy($tempDir . 'download', root_path(), exclude_files: $excludeFiles);
|
||||||
|
|
||||||
|
$this->buildResultAnalysis('success');
|
||||||
|
|
||||||
|
$downloadInfo['status'] = 'completed';
|
||||||
|
$downloadInfo['percent'] = 100;
|
||||||
|
$downloadInfo['msg'] = '部署完成';
|
||||||
|
Cache::set('cloud_build_' . $taskKey, $downloadInfo, 7200);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 1,
|
||||||
|
'status' => 'completed',
|
||||||
|
'percent' => 100,
|
||||||
|
'downloaded_bytes' => $downloadInfo['downloaded_bytes'],
|
||||||
|
'total_bytes' => $downloadInfo['length'],
|
||||||
|
'msg' => '部署完成'
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
if (!isset($downloadInfo['retry'])) {
|
||||||
|
unlink($zipFile);
|
||||||
|
$downloadInfo['retry'] = 1;
|
||||||
|
unset($downloadInfo['index']);
|
||||||
|
Cache::set('cloud_build_' . $taskKey, $downloadInfo, 7200);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 1,
|
||||||
|
'status' => 'downloading',
|
||||||
|
'percent' => 0,
|
||||||
|
'downloaded_bytes' => 0,
|
||||||
|
'total_bytes' => $downloadInfo['length'] ?? 0,
|
||||||
|
'msg' => '编译包解压失败,尝试重新下载',
|
||||||
|
'debug' => [
|
||||||
|
'zip_file' => $zipFile,
|
||||||
|
'zip_size' => file_exists($zipFile) ? filesize($zipFile) : 'not exists',
|
||||||
|
'zip_open_result' => $zipOpenResult,
|
||||||
|
'extract_dir' => $downloadInfo['temp_dir'] . 'download' . DIRECTORY_SEPARATOR,
|
||||||
|
'temp_dir' => $downloadInfo['temp_dir']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$downloadInfo['status'] = 'error';
|
||||||
|
$downloadInfo['msg'] = '编译包解压失败';
|
||||||
|
Cache::set('cloud_build_' . $taskKey, $downloadInfo, 7200);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 0,
|
||||||
|
'status' => 'error',
|
||||||
|
'percent' => $downloadInfo['percent'] ?? 0,
|
||||||
|
'msg' => '编译包解压失败'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$downloadInfo['status'] = 'error';
|
||||||
|
$downloadInfo['msg'] = $e->getMessage();
|
||||||
|
Cache::set('cloud_build_' . $taskKey, $downloadInfo, 7200);
|
||||||
|
|
||||||
|
return ['code' => 0, 'msg' => $e->getMessage(), 'status' => 'error'];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -164,4 +164,18 @@ class CoreModuleService extends BaseNiucloudClient
|
|||||||
{
|
{
|
||||||
return $this->httpGet('store/app_version/list', [ 'product_key' => self::PRODUCT, 'app_key' => $app_key ])[ 'data' ] ?? false;
|
return $this->httpGet('store/app_version/list', [ 'product_key' => self::PRODUCT, 'app_key' => $app_key ])[ 'data' ] ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编译异常分析
|
||||||
|
* @param string $msg
|
||||||
|
* @return array|\core\util\niucloud\Response|object|ResponseInterface
|
||||||
|
* @throws GuzzleException
|
||||||
|
*/
|
||||||
|
public function buildResultAnalysis(string $msg)
|
||||||
|
{
|
||||||
|
$params = [
|
||||||
|
'msg' => $msg,
|
||||||
|
];
|
||||||
|
return $this->httpPost('build_error_analysis', $params)['data'] ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -323,7 +323,17 @@ class CorePayService extends BaseCoreService
|
|||||||
public function createByTrade($site_id, $trade_type, $trade_id)
|
public function createByTrade($site_id, $trade_type, $trade_id)
|
||||||
{
|
{
|
||||||
//创建新的支付单据
|
//创建新的支付单据
|
||||||
$data = array_values(array_filter(event('PayCreate', [ 'site_id' => $site_id, 'trade_type' => $trade_type, 'trade_id' => $trade_id ])))[ 0 ] ?? [];
|
$event_result = event('PayCreate', [ 'site_id' => $site_id, 'trade_type' => $trade_type, 'trade_id' => $trade_id ]);
|
||||||
|
|
||||||
|
// PHP 8.0+ 兼容性处理:确保 event 返回值是数组
|
||||||
|
if (!is_array($event_result)) {
|
||||||
|
$event_result = [];
|
||||||
|
}
|
||||||
|
// 过滤掉 false/null 等无效值,只保留有效的数组元素
|
||||||
|
$filtered = array_values(array_filter($event_result, function($item) {
|
||||||
|
return is_array($item) && !empty($item);
|
||||||
|
}));
|
||||||
|
$data = !empty($filtered) ? $filtered[0] : [];
|
||||||
if (empty($data)) throw new PayException('PAY_NOT_FOUND_TRADE');//找不到可支付的交易
|
if (empty($data)) throw new PayException('PAY_NOT_FOUND_TRADE');//找不到可支付的交易
|
||||||
|
|
||||||
if (isset($data[ 'status' ]) && $data[ 'money' ] == 0) {
|
if (isset($data[ 'status' ]) && $data[ 'money' ] == 0) {
|
||||||
|
|||||||
@ -97,11 +97,13 @@ class CoreTransferService extends BaseCoreService
|
|||||||
'transfer_payment_code' => $data[ 'transfer_payment_code' ] ?? ''
|
'transfer_payment_code' => $data[ 'transfer_payment_code' ] ?? ''
|
||||||
);
|
);
|
||||||
$transfer->save($transfer_data);
|
$transfer->save($transfer_data);
|
||||||
|
Log::write('【scene_data】'.json_encode($transfer_data,256));
|
||||||
switch ($transfer_type) {
|
switch ($transfer_type) {
|
||||||
case TransferDict::WECHAT:
|
case TransferDict::WECHAT:
|
||||||
// $out_batch_no = create_no();
|
// $out_batch_no = create_no();
|
||||||
$transfer_account = $data[ 'transfer_payee' ] ?? [];
|
$transfer_account = $data[ 'transfer_payee' ] ?? [];
|
||||||
$scene_data = ( new CoreTransferSceneService() )->getSceneInfoByType($site_id, $transfer[ 'trade_type' ]);
|
$scene_data = ( new CoreTransferSceneService() )->getSceneInfoByType($site_id, $transfer[ 'trade_type' ]);
|
||||||
|
Log::write('【scene_data】'.json_encode($scene_data,256));
|
||||||
//通过业务获取业务场景
|
//通过业务获取业务场景
|
||||||
$temp_infos = $scene_data[ 'infos' ] ?? [];//转账场景信息
|
$temp_infos = $scene_data[ 'infos' ] ?? [];//转账场景信息
|
||||||
if (!empty($temp_infos)) {
|
if (!empty($temp_infos)) {
|
||||||
|
|||||||
@ -112,10 +112,6 @@ class CoreSysConfigService extends BaseCoreService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 前端不展示关键信息
|
|
||||||
if (!empty($info[ 'value' ][ 'key' ])) {
|
|
||||||
unset($info[ 'value' ][ 'key' ]);
|
|
||||||
}
|
|
||||||
$info[ 'value' ][ 'is_open' ] = $info[ 'value' ][ 'is_open' ] ?? 1;
|
$info[ 'value' ][ 'is_open' ] = $info[ 'value' ][ 'is_open' ] ?? 1;
|
||||||
$info[ 'value' ][ 'valid_time' ] = $info[ 'value' ][ 'valid_time' ] ?? 5;
|
$info[ 'value' ][ 'valid_time' ] = $info[ 'value' ][ 'valid_time' ] ?? 5;
|
||||||
return $info[ 'value' ];
|
return $info[ 'value' ];
|
||||||
|
|||||||
@ -64,7 +64,7 @@ class CoreWeappCloudService extends CoreCloudBaseService
|
|||||||
*/
|
*/
|
||||||
public function uploadWeapp(array $data)
|
public function uploadWeapp(array $data)
|
||||||
{
|
{
|
||||||
if (strpos($this->config[ 'base_url' ], 'https://') === false) throw new CommonException('CURR_SITE_IS_NOT_OPEN_SSL');
|
// if (strpos($this->config[ 'base_url' ], 'https://') === false) throw new CommonException('CURR_SITE_IS_NOT_OPEN_SSL');
|
||||||
$this->site_id = $data[ 'site_id' ] ?? 0;
|
$this->site_id = $data[ 'site_id' ] ?? 0;
|
||||||
|
|
||||||
if (empty($this->config[ 'app_id' ])) throw new CommonException('WEAPP_APPID_EMPTY');
|
if (empty($this->config[ 'app_id' ])) throw new CommonException('WEAPP_APPID_EMPTY');
|
||||||
@ -126,7 +126,8 @@ class CoreWeappCloudService extends CoreCloudBaseService
|
|||||||
|
|
||||||
if (isset($response[ 'code' ]) && $response[ 'code' ] == 0) throw new CommonException($response[ 'msg' ]);
|
if (isset($response[ 'code' ]) && $response[ 'code' ] == 0) throw new CommonException($response[ 'msg' ]);
|
||||||
|
|
||||||
return [ 'key' => $query[ 'timestamp' ] ];
|
$task_id = $response[ 'data' ][ 'task_id' ] ?? $query[ 'timestamp' ];
|
||||||
|
return [ 'key' => $task_id ];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -306,16 +307,53 @@ class CoreWeappCloudService extends CoreCloudBaseService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取小程序编译日志
|
* 获取小程序编译日志
|
||||||
* @param string $timestamp
|
* @param string $taskId 任务ID (格式: authorize_code_timestamp)
|
||||||
* @return void
|
* @return \Psr\Http\Message\ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function getWeappCompileLog(string $timestamp)
|
public function getWeappCompileLog(string $taskId)
|
||||||
|
{
|
||||||
|
$query = [
|
||||||
|
'task_id' => $taskId
|
||||||
|
];
|
||||||
|
return ( new CloudService(true) )->httpGet('cloud/get_weapp_logs?' . http_build_query($query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中断上传并清除日志
|
||||||
|
*/
|
||||||
|
public function stopUploadAndClearLogs(string $timestamp)
|
||||||
|
{
|
||||||
|
// 1. 中断上传
|
||||||
|
$this->stopWeappUpload($timestamp);
|
||||||
|
|
||||||
|
// 2. 删除日志(del_weapp_logs)
|
||||||
|
return $this->delWeappLogs($timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中断小程序上传
|
||||||
|
* @param string $timestamp
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function stopWeappUpload(string $timestamp)
|
||||||
{
|
{
|
||||||
$query = [
|
$query = [
|
||||||
'authorize_code' => $this->auth_code,
|
'authorize_code' => $this->auth_code,
|
||||||
'timestamp' => $timestamp
|
'timestamp' => $timestamp
|
||||||
];
|
];
|
||||||
return ( new CloudService(true) )->httpGet('cloud/get_weapp_logs?' . http_build_query($query));
|
return ( new CloudService(true) )->httpGet('cloud/stop_weapp_upload?' . http_build_query($query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除小程序上传日志
|
||||||
|
*/
|
||||||
|
public function delWeappLogs(string $timestamp)
|
||||||
|
{
|
||||||
|
$query = [
|
||||||
|
'authorize_code' => $this->auth_code,
|
||||||
|
'timestamp' => $timestamp
|
||||||
|
];
|
||||||
|
return (new CloudService(true))->httpGet('cloud/del_weapp_logs?' . http_build_query($query));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -66,12 +66,18 @@ class CoreOplatformConfigService extends BaseCoreService
|
|||||||
public function getStaticInfo(){
|
public function getStaticInfo(){
|
||||||
$wap_domain = (new CoreSysConfigService())->getSceneDomain(0)['wap_url'] ?? '';
|
$wap_domain = (new CoreSysConfigService())->getSceneDomain(0)['wap_url'] ?? '';
|
||||||
|
|
||||||
|
$local_cloud_compile_config = (new CoreConfigService())->getConfig(0, 'LOCAL_CLOUD_COMPILE_CONFIG')['value'] ?? [];
|
||||||
|
$baseUri = $local_cloud_compile_config['baseUri'] ?? '';
|
||||||
|
if (empty($baseUri)) $baseUri = 'oss.niucloud.com';
|
||||||
|
$baseUri = str_replace('http://', '', $baseUri);
|
||||||
|
$baseUri = str_replace('https://', '', $baseUri);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'auth_serve_url' => (string)url('/adminapi/wxoplatform/server',[],'', true), // 授权事件接收配置
|
'auth_serve_url' => (string)url('/adminapi/wxoplatform/server',[],'', true), // 授权事件接收配置
|
||||||
'message_serve_url' => (string)url('/adminapi/wxoplatform/message/$APPID$', [],'',true), // 消息与事件接收配置
|
'message_serve_url' => (string)url('/adminapi/wxoplatform/message/$APPID$', [],'',true), // 消息与事件接收配置
|
||||||
'auth_launch_domain' => parse_url(request()->domain())['host'] ?? '', // 授权发起页域名
|
'auth_launch_domain' => parse_url(request()->domain())['host'] ?? '', // 授权发起页域名
|
||||||
'wechat_auth_domain' => parse_url($wap_domain)['host'] ?? '', // 公众号开发域名
|
'wechat_auth_domain' => parse_url($wap_domain)['host'] ?? '', // 公众号开发域名
|
||||||
'upload_ip' => gethostbyname('oss.niucloud.com')
|
'upload_ip' => gethostbyname($baseUri)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ class Upgrade
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->handleDiyData();
|
$this->handleDiyData();
|
||||||
|
$this->handleDiyFormData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,6 +34,9 @@ class Upgrade
|
|||||||
if (!empty($list)) {
|
if (!empty($list)) {
|
||||||
foreach ($list as $k => $v) {
|
foreach ($list as $k => $v) {
|
||||||
$diy_data = json_decode($v['value'], true);
|
$diy_data = json_decode($v['value'], true);
|
||||||
|
if (!isset($diy_data['value'])){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
foreach ($diy_data['value'] as $k1=>$v1) {
|
foreach ($diy_data['value'] as $k1=>$v1) {
|
||||||
if (in_array($v1['componentName'],$components_names)){
|
if (in_array($v1['componentName'],$components_names)){
|
||||||
$diy_data['value'][$k1]['mode'] = 'aspectFill';
|
$diy_data['value'][$k1]['mode'] = 'aspectFill';
|
||||||
@ -45,4 +49,39 @@ class Upgrade
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理自定义数据
|
||||||
|
* @return void
|
||||||
|
* @throws \think\db\exception\DataNotFoundException
|
||||||
|
* @throws \think\db\exception\DbException
|
||||||
|
* @throws \think\db\exception\ModelNotFoundException
|
||||||
|
*/
|
||||||
|
private function handleDiyFormData()
|
||||||
|
{
|
||||||
|
$diy_form_model = new DiyForm();
|
||||||
|
$where = [
|
||||||
|
['value', '<>', '']
|
||||||
|
];
|
||||||
|
$field = 'form_id,value';
|
||||||
|
$list = $diy_form_model->where($where)->field($field)->select()->toArray();
|
||||||
|
|
||||||
|
$components_names = ['GoodsList','ManyGoodsList','ShopExchangeGoods','ShopGoodsRecommend','SingleRecommend','ShopNewcomer','ShopGoodsRanking','ShopGoodsHot'];
|
||||||
|
if (!empty($list)) {
|
||||||
|
foreach ($list as $k => $v) {
|
||||||
|
$diy_data = $v['value'];
|
||||||
|
if (!isset($diy_data['value'])){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($diy_data['value'] as $k1=>$v1) {
|
||||||
|
if (in_array($v1['componentName'],$components_names)){
|
||||||
|
$diy_data['value'][$k1]['mode'] = 'aspectFill';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$diy_data = json_encode($diy_data);
|
||||||
|
$diy_form_model->where([['form_id', '=', $v['form_id']]])->update(['value' => $diy_data]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
2
niucloud/app/upgrade/v124/upgrade.sql
Normal file
2
niucloud/app/upgrade/v124/upgrade.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE weapp_version
|
||||||
|
CHANGE COLUMN task_key task_key varchar(255) NOT NULL DEFAULT '' COMMENT '上传任务key';
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'version' => '1.2.1',
|
'version' => '1.2.4',
|
||||||
'code' => '202603170001'
|
'code' => '202606060001'
|
||||||
];
|
];
|
||||||
|
|||||||
@ -15,40 +15,37 @@ class CloudService
|
|||||||
{
|
{
|
||||||
use HasHttpRequests;
|
use HasHttpRequests;
|
||||||
|
|
||||||
private $baseUri = 'http://oss.niucloud.com/';
|
private $baseUri = 'http://go.site.niucloud.com/';
|
||||||
|
|
||||||
public $is_connected = false;
|
public $is_connected = false;
|
||||||
|
|
||||||
public function __construct($checkLocal = false, $local_cloud_compile = '') {
|
public function __construct($checkLocal = false, $local_cloud_compile = '') {
|
||||||
$this->baseUri = 'http://' . gethostbyname('oss.niucloud.com') . ':8000/';
|
if (!empty($local_cloud_compile)) $this->baseUri = $local_cloud_compile;
|
||||||
if ($checkLocal) $this->is_connected = $this->checkLocal($local_cloud_compile);
|
if ($checkLocal) $this->is_connected = $this->checkLocal($local_cloud_compile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkLocal($local_cloud_compile) {
|
public function checkLocal($local_cloud_compile = '') {
|
||||||
$baseUri = $this->baseUri;
|
$baseUri = $this->baseUri;
|
||||||
|
|
||||||
$local_cloud_compile_config = (new CoreConfigService())->getConfig(0, 'LOCAL_CLOUD_COMPILE_CONFIG')['value'] ?? [];
|
|
||||||
if (!empty($local_cloud_compile_config) && isset($local_cloud_compile_config['isOpen']) && $local_cloud_compile_config['isOpen'] == 1){
|
|
||||||
$baseUri = $local_cloud_compile_config['baseUri'] ?? '';
|
|
||||||
if (empty($baseUri)){
|
|
||||||
throw new CommonException("已开启`第三方云编译`,但未配置云编译服务器地址,详情查看:平台端》云编译》第三方云编译");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($local_cloud_compile)){
|
if (!empty($local_cloud_compile)){
|
||||||
$baseUri = $local_cloud_compile;
|
$baseUri = $local_cloud_compile;
|
||||||
|
} else {
|
||||||
|
$local_cloud_compile_config = (new CoreConfigService())->getConfig(0, 'LOCAL_CLOUD_COMPILE_CONFIG')['value'] ?? [];
|
||||||
|
$isOpen = $local_cloud_compile_config['isOpen'] ?? 1;
|
||||||
|
|
||||||
|
if ($isOpen){
|
||||||
|
$baseUri = $local_cloud_compile_config['baseUri'] ?? '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$is_connected = false;
|
$is_connected = false;
|
||||||
try {
|
try {
|
||||||
$res = (new Client(['base_uri' => $baseUri ]))->request("GET", '', []);
|
$res = (new Client(['base_uri' => $baseUri ]))->request("GET", '', []);
|
||||||
// dd($res->getBody()->getContents());
|
|
||||||
if ($res->getStatusCode() == '200' && $res->getBody()->getContents() == '欢迎使用NiuCloud编译服务!') {
|
if ($res->getStatusCode() == '200' && $res->getBody()->getContents() == '欢迎使用NiuCloud编译服务!') {
|
||||||
$this->baseUri = $baseUri;
|
$this->baseUri = $baseUri;
|
||||||
$is_connected = true;
|
$is_connected = true;
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw new CommonException('CONNECT_FAIL');
|
|
||||||
}
|
}
|
||||||
return $is_connected;
|
return $is_connected;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ trait AccessToken
|
|||||||
{
|
{
|
||||||
$this->access_token = '';
|
$this->access_token = '';
|
||||||
Cache::delete($this->access_token_cache);
|
Cache::delete($this->access_token_cache);
|
||||||
if (file_exists(public_path() . 'access_token.txt')) unlink(file_exists(public_path() . 'access_token.txt'));
|
if (file_exists(public_path() . 'access_token.txt')) unlink(public_path() . 'access_token.txt');
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
2
niucloud/public/.gitignore
vendored
2
niucloud/public/.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
/.htaccess
|
/.htaccess
|
||||||
upload
|
upload
|
||||||
nginx.htaccess
|
nginx.htaccess
|
||||||
.htaccess
|
.htaccess
|
||||||
@ -1 +0,0 @@
|
|||||||
import{d as l,r as d,u as i,o as p,c as u,a as t,b as m,e as x,w as v,f,E as h,p as b,g,h as I,i as w,t as S}from"./index-69eae4f0.js";/* empty css */import{_ as B}from"./_plugin-vue_export-helper-c27b6911.js";const k=""+new URL("error-ab7e4004.png",import.meta.url).href,o=e=>(b("data-v-4f4088b5"),e=e(),g(),e),y={class:"error"},C={class:"flex items-center"},E=o(()=>t("div",null,[t("img",{class:"w-[240px]",src:k})],-1)),N={class:"text-left ml-[100px]"},R=o(()=>t("div",{class:"error-text text-[28px] font-bold"},"404错误!",-1)),U=o(()=>t("div",{class:"text-[#222] text-[20px] mt-[15px]"},"哎呀,出错了!您访问的页面不存在...",-1)),V=o(()=>t("div",{class:"text-[#c4c2c2] text-[12px] mt-[5px]"},"尝试检查URL的错误,然后点击浏览器刷新按钮。",-1)),L={class:"mt-[40px]"},$=l({__name:"404",setup(e){let s=null;const a=d(5),n=i();return s=setInterval(()=>{a.value===0?(clearInterval(s),n.go(-1)):a.value--},1e3),p(()=>{s&&clearInterval(s)}),(r,c)=>{const _=h;return I(),u("div",y,[t("div",C,[m(r.$slots,"content",{},()=>[E],!0),t("div",N,[R,U,V,t("div",L,[x(_,{class:"bottom",onClick:c[0]||(c[0]=D=>f(n).go(-1))},{default:v(()=>[w(S(a.value)+" 秒后返回上一页",1)]),_:1})])])])])}}});const z=B($,[["__scopeId","data-v-4f4088b5"]]);export{z as default};
|
|
||||||
1
niucloud/public/admin/assets/404-45293806.js
Normal file
1
niucloud/public/admin/assets/404-45293806.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{d as l,r as d,u as i,o as p,c as m,a as t,b as u,e as x,w as v,f,E as h,p as b,g as I,h as g,i as S,t as w}from"./index-8eead49b.js";/* empty css */import{_ as B}from"./error-54aee623.js";import{_ as k}from"./_plugin-vue_export-helper-c27b6911.js";const o=e=>(b("data-v-4f4088b5"),e=e(),I(),e),y={class:"error"},C={class:"flex items-center"},E=o(()=>t("div",null,[t("img",{class:"w-[240px]",src:B})],-1)),N={class:"text-left ml-[100px]"},V=o(()=>t("div",{class:"error-text text-[28px] font-bold"},"404错误!",-1)),R=o(()=>t("div",{class:"text-[#222] text-[20px] mt-[15px]"},"哎呀,出错了!您访问的页面不存在...",-1)),U=o(()=>t("div",{class:"text-[#c4c2c2] text-[12px] mt-[5px]"},"尝试检查URL的错误,然后点击浏览器刷新按钮。",-1)),$={class:"mt-[40px]"},D=l({__name:"404",setup(e){let s=null;const a=d(5),c=i();return s=setInterval(()=>{a.value===0?(clearInterval(s),c.go(-1)):a.value--},1e3),p(()=>{s&&clearInterval(s)}),(_,n)=>{const r=h;return g(),m("div",y,[t("div",C,[u(_.$slots,"content",{},()=>[E],!0),t("div",N,[V,R,U,t("div",$,[x(r,{class:"bottom",onClick:n[0]||(n[0]=L=>f(c).go(-1))},{default:v(()=>[S(w(a.value)+" 秒后返回上一页",1)]),_:1})])])])])}}});const A=k(D,[["__scopeId","data-v-4f4088b5"]]);export{A as default};
|
||||||
@ -1 +0,0 @@
|
|||||||
import{dD as f}from"./index-69eae4f0.js";export{f as default};
|
|
||||||
1
niucloud/public/admin/assets/App-6c7eb125.js
Normal file
1
niucloud/public/admin/assets/App-6c7eb125.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{dY as f}from"./index-8eead49b.js";export{f as default};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
.upload-demo[data-v-d439b536]{width:100%;border:1px dashed #dcdcdc;border-radius:6px;padding:20px;text-align:center}.copy-icon[data-v-d439b536]{cursor:pointer;margin-left:8px;color:#606266;font-size:18px}.copy-icon[data-v-d439b536]:hover{color:#409eff}
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
import z from"./VerifySlide-f94c7d34.js";import g from"./VerifyPoints-b9e4aa92.js";import{P as k,r as o,m as w,bb as T,Y as V,Z as B,h as p,c as u,a as c,i as N,C as y,y as d,v as C,bc as P,x as v}from"./index-69eae4f0.js";import{_ as j}from"./_plugin-vue_export-helper-c27b6911.js";import"./index-209b33df.js";const O={name:"Vue2Verify",components:{VerifySlide:z,VerifyPoints:g},props:{captchaType:{type:String,required:!0},figure:{type:Number},arith:{type:Number},mode:{type:String,default:"pop"},vSpace:{type:Number},explain:{type:String},imgSize:{type:Object,default(){return{width:"310px",height:"155px"}}},blockSize:{type:Object},barSize:{type:Object}},setup(m){const{captchaType:i,figure:e,arith:t,mode:n,vSpace:h,explain:f,imgSize:R,blockSize:W,barSize:Y}=k(m),a=o(!1),r=o(void 0),s=o(void 0),l=o({}),S=w(()=>n.value=="pop"?a.value:!0),b=()=>{l.value.refresh&&l.value.refresh()},x=()=>{a.value=!1,b()},_=()=>{n.value=="pop"&&(a.value=!0)};return T(()=>{switch(i.value){case"blockPuzzle":r.value="2",s.value="VerifySlide";break;case"clickWord":r.value="",s.value="VerifyPoints";break}}),{clickShow:a,verifyType:r,componentType:s,instance:l,showBox:S,closeBox:x,show:_}}},D={key:0,class:"verifybox-top"},E=c("i",{class:"iconfont icon-close"},null,-1),q=[E];function I(m,i,e,t,n,h){return V((p(),u("div",{class:v(e.mode=="pop"?"mask":"")},[c("div",{class:v(e.mode=="pop"?"verifybox":""),style:d({"max-width":parseInt(e.imgSize.width)+30+"px"})},[e.mode=="pop"?(p(),u("div",D,[N(" 请完成安全验证 "),c("span",{class:"verifybox-close",onClick:i[0]||(i[0]=(...f)=>t.closeBox&&t.closeBox(...f))},q)])):y("",!0),c("div",{class:"verifybox-bottom",style:d({padding:e.mode=="pop"?"15px":"0"})},[t.componentType?(p(),C(P(t.componentType),{key:0,captchaType:e.captchaType,type:t.verifyType,figure:e.figure,arith:e.arith,mode:e.mode,vSpace:e.vSpace,explain:e.explain,imgSize:e.imgSize,blockSize:e.blockSize,barSize:e.barSize,ref:"instance"},null,8,["captchaType","type","figure","arith","mode","vSpace","explain","imgSize","blockSize","barSize"])):y("",!0)],4)],6)],2)),[[B,t.showBox]])}const J=j(O,[["render",I]]);export{J as default};
|
|
||||||
1
niucloud/public/admin/assets/Verify-92009dcf.js
Normal file
1
niucloud/public/admin/assets/Verify-92009dcf.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import z from"./VerifySlide-02eb59d1.js";import g from"./VerifyPoints-e2bb084a.js";import{P as k,r as o,m as w,bc as T,Y as V,Z as B,h as p,c as u,a as c,i as N,C as y,y as d,v as C,bb as P,x as v}from"./index-8eead49b.js";import{_ as j}from"./_plugin-vue_export-helper-c27b6911.js";import"./index-b4a6dc9f.js";const O={name:"Vue2Verify",components:{VerifySlide:z,VerifyPoints:g},props:{captchaType:{type:String,required:!0},figure:{type:Number},arith:{type:Number},mode:{type:String,default:"pop"},vSpace:{type:Number},explain:{type:String},imgSize:{type:Object,default(){return{width:"310px",height:"155px"}}},blockSize:{type:Object},barSize:{type:Object}},setup(m){const{captchaType:i,figure:e,arith:t,mode:n,vSpace:h,explain:f,imgSize:R,blockSize:W,barSize:Y}=k(m),a=o(!1),r=o(void 0),s=o(void 0),l=o({}),S=w(()=>n.value=="pop"?a.value:!0),b=()=>{l.value.refresh&&l.value.refresh()},x=()=>{a.value=!1,b()},_=()=>{n.value=="pop"&&(a.value=!0)};return T(()=>{switch(i.value){case"blockPuzzle":r.value="2",s.value="VerifySlide";break;case"clickWord":r.value="",s.value="VerifyPoints";break}}),{clickShow:a,verifyType:r,componentType:s,instance:l,showBox:S,closeBox:x,show:_}}},D={key:0,class:"verifybox-top"},E=c("i",{class:"iconfont icon-close"},null,-1),q=[E];function I(m,i,e,t,n,h){return V((p(),u("div",{class:v(e.mode=="pop"?"mask":"")},[c("div",{class:v(e.mode=="pop"?"verifybox":""),style:d({"max-width":parseInt(e.imgSize.width)+30+"px"})},[e.mode=="pop"?(p(),u("div",D,[N(" 请完成安全验证 "),c("span",{class:"verifybox-close",onClick:i[0]||(i[0]=(...f)=>t.closeBox&&t.closeBox(...f))},q)])):y("",!0),c("div",{class:"verifybox-bottom",style:d({padding:e.mode=="pop"?"15px":"0"})},[t.componentType?(p(),C(P(t.componentType),{key:0,captchaType:e.captchaType,type:t.verifyType,figure:e.figure,arith:e.arith,mode:e.mode,vSpace:e.vSpace,explain:e.explain,imgSize:e.imgSize,blockSize:e.blockSize,barSize:e.barSize,ref:"instance"},null,8,["captchaType","type","figure","arith","mode","vSpace","explain","imgSize","blockSize","barSize"])):y("",!0)],4)],6)],2)),[[B,t.showBox]])}const J=j(O,[["render",I]]);export{J as default};
|
||||||
@ -1 +0,0 @@
|
|||||||
import{r as F,a as M,b as K,c as Y}from"./index-209b33df.js";import{P as Z,bd as G,r as s,q as m,aZ as X,h as H,c as I,a as l,y as A,Y as Q,Z as U,F as $,V as ee,t as q,ax as te}from"./index-69eae4f0.js";import{_ as ae}from"./_plugin-vue_export-helper-c27b6911.js";const ie={name:"VerifyPoints",props:{mode:{type:String,default:"fixed"},captchaType:{type:String},vSpace:{type:Number,default:5},imgSize:{type:Object,default(){return{width:"310px",height:"155px"}}},barSize:{type:Object,default(){return{width:"310px",height:"40px"}}}},setup(N,f){const{mode:_,captchaType:e,vSpace:L,imgSize:R,barSize:c}=Z(N),{proxy:n}=G(),h=s(""),z=s(3),p=m([]),a=m([]),o=s(1),O=s(""),w=m([]),v=s(""),u=m({imgHeight:0,imgWidth:0,barHeight:0,barWidth:0}),y=m([]),d=s(""),b=s(void 0),x=s(void 0),j=s(!0),C=s(!0),V=()=>{p.splice(0,p.length),a.splice(0,a.length),o.value=1,B(),te(()=>{const{imgHeight:i,imgWidth:t,barHeight:g,barWidth:r}=F(n);u.imgHeight=i,u.imgWidth=t,u.barHeight=g,u.barWidth=r,n.$parent.$emit("ready",n)})};X(()=>{V(),n.$el.onselectstart=function(){return!1}});const S=s(null),D=i=>{if(a.push(k(S,i)),o.value==z.value){o.value=P(k(S,i));const t=J(a,u);a.length=0,a.push(...t),setTimeout(()=>{const g=h.value?M(v.value+"---"+JSON.stringify(a),h.value):v.value+"---"+JSON.stringify(a),r={captchaType:e.value,captcha_code:h.value?M(JSON.stringify(a),h.value):JSON.stringify(a),captcha_key:v.value};K(r).then(W=>{W.code==1?(b.value="#4cae4c",x.value="#5cb85c",d.value="验证成功",C.value=!1,_.value=="pop"&&setTimeout(()=>{n.$parent.clickShow=!1,T()},1500),n.$parent.$emit("success",{captchaVerification:g})):(n.$parent.$emit("error",n),b.value="#d9534f",x.value="#d9534f",d.value="验证失败",setTimeout(()=>{T()},700))})},400)}o.value<z.value&&(o.value=P(k(S,i)))},k=function(i,t){const g=t.offsetX,r=t.offsetY;return{x:g,y:r}},P=function(i){return y.push(Object.assign({},i)),o.value+1},T=function(){y.splice(0,y.length),b.value="#000",x.value="#ddd",C.value=!0,p.splice(0,p.length),a.splice(0,a.length),o.value=1,B(),d.value="验证失败",j.value=!0};function B(){const i={captchaType:e.value};Y(i).then(t=>{t.code==1?(O.value=t.data.originalImageBase64,v.value=t.data.token,h.value=t.data.secretKey,w.value=t.data.wordList,d.value="请依次点击【"+w.value.join(",")+"】"):d.value=t.msg})}const J=function(i,t){return i.map(r=>{const W=Math.round(310*r.x/parseInt(t.imgWidth)),E=Math.round(155*r.y/parseInt(t.imgHeight));return{x:W,y:E}})};return{secretKey:h,checkNum:z,fontPos:p,checkPosArr:a,num:o,pointBackImgBase:O,pointTextList:w,backToken:v,setSize:u,tempPoints:y,text:d,barAreaColor:b,barAreaBorderColor:x,showRefresh:j,bindingClick:C,init:V,canvas:S,canvasClick:D,getMousePos:k,createPoint:P,refresh:T,getPictrue:B,pointTransfrom:J}}},ne={style:{position:"relative"}},se={class:"verify-img-out"},oe=l("i",{class:"iconfont icon-refresh"},null,-1),re=[oe],ce=["src"],le={class:"verify-msg"};function he(N,f,_,e,L,R){return H(),I("div",ne,[l("div",se,[l("div",{class:"verify-img-panel",style:A({width:e.setSize.imgWidth,height:e.setSize.imgHeight,"background-size":e.setSize.imgWidth+" "+e.setSize.imgHeight,"margin-bottom":_.vSpace+"px"})},[Q(l("div",{class:"verify-refresh",style:{"z-index":"3"},onClick:f[0]||(f[0]=(...c)=>e.refresh&&e.refresh(...c))},re,512),[[U,e.showRefresh]]),l("img",{src:"data:image/png;base64,"+e.pointBackImgBase,ref:"canvas",alt:"",style:{width:"100%",height:"100%",display:"block"},onClick:f[1]||(f[1]=c=>e.bindingClick?e.canvasClick(c):void 0)},null,8,ce),(H(!0),I($,null,ee(e.tempPoints,(c,n)=>(H(),I("div",{key:n,class:"point-area",style:A({"background-color":"#1abd6c",color:"#fff","z-index":9999,width:"20px",height:"20px","text-align":"center","line-height":"20px","border-radius":"50%",position:"absolute",top:parseInt(c.y-10)+"px",left:parseInt(c.x-10)+"px"})},q(n+1),5))),128))],4)]),l("div",{class:"verify-bar-area",style:A({width:e.setSize.imgWidth,color:this.barAreaColor,"border-color":this.barAreaBorderColor,"line-height":this.barSize.height})},[l("span",le,q(e.text),1)],4)])}const fe=ae(ie,[["render",he]]);export{fe as default};
|
|
||||||
1
niucloud/public/admin/assets/VerifyPoints-e2bb084a.js
Normal file
1
niucloud/public/admin/assets/VerifyPoints-e2bb084a.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{r as F,a as M,b as K,c as Y}from"./index-b4a6dc9f.js";import{P as Z,bd as G,r as s,q as m,aZ as X,h as H,c as I,a as l,y as A,Y as Q,Z as U,F as $,V as ee,t as q,ax as te}from"./index-8eead49b.js";import{_ as ae}from"./_plugin-vue_export-helper-c27b6911.js";const ie={name:"VerifyPoints",props:{mode:{type:String,default:"fixed"},captchaType:{type:String},vSpace:{type:Number,default:5},imgSize:{type:Object,default(){return{width:"310px",height:"155px"}}},barSize:{type:Object,default(){return{width:"310px",height:"40px"}}}},setup(N,f){const{mode:_,captchaType:e,vSpace:L,imgSize:R,barSize:c}=Z(N),{proxy:n}=G(),h=s(""),z=s(3),p=m([]),a=m([]),o=s(1),O=s(""),w=m([]),v=s(""),u=m({imgHeight:0,imgWidth:0,barHeight:0,barWidth:0}),y=m([]),d=s(""),b=s(void 0),x=s(void 0),j=s(!0),C=s(!0),V=()=>{p.splice(0,p.length),a.splice(0,a.length),o.value=1,B(),te(()=>{const{imgHeight:i,imgWidth:t,barHeight:g,barWidth:r}=F(n);u.imgHeight=i,u.imgWidth=t,u.barHeight=g,u.barWidth=r,n.$parent.$emit("ready",n)})};X(()=>{V(),n.$el.onselectstart=function(){return!1}});const S=s(null),D=i=>{if(a.push(k(S,i)),o.value==z.value){o.value=P(k(S,i));const t=J(a,u);a.length=0,a.push(...t),setTimeout(()=>{const g=h.value?M(v.value+"---"+JSON.stringify(a),h.value):v.value+"---"+JSON.stringify(a),r={captchaType:e.value,captcha_code:h.value?M(JSON.stringify(a),h.value):JSON.stringify(a),captcha_key:v.value};K(r).then(W=>{W.code==1?(b.value="#4cae4c",x.value="#5cb85c",d.value="验证成功",C.value=!1,_.value=="pop"&&setTimeout(()=>{n.$parent.clickShow=!1,T()},1500),n.$parent.$emit("success",{captchaVerification:g})):(n.$parent.$emit("error",n),b.value="#d9534f",x.value="#d9534f",d.value="验证失败",setTimeout(()=>{T()},700))})},400)}o.value<z.value&&(o.value=P(k(S,i)))},k=function(i,t){const g=t.offsetX,r=t.offsetY;return{x:g,y:r}},P=function(i){return y.push(Object.assign({},i)),o.value+1},T=function(){y.splice(0,y.length),b.value="#000",x.value="#ddd",C.value=!0,p.splice(0,p.length),a.splice(0,a.length),o.value=1,B(),d.value="验证失败",j.value=!0};function B(){const i={captchaType:e.value};Y(i).then(t=>{t.code==1?(O.value=t.data.originalImageBase64,v.value=t.data.token,h.value=t.data.secretKey,w.value=t.data.wordList,d.value="请依次点击【"+w.value.join(",")+"】"):d.value=t.msg})}const J=function(i,t){return i.map(r=>{const W=Math.round(310*r.x/parseInt(t.imgWidth)),E=Math.round(155*r.y/parseInt(t.imgHeight));return{x:W,y:E}})};return{secretKey:h,checkNum:z,fontPos:p,checkPosArr:a,num:o,pointBackImgBase:O,pointTextList:w,backToken:v,setSize:u,tempPoints:y,text:d,barAreaColor:b,barAreaBorderColor:x,showRefresh:j,bindingClick:C,init:V,canvas:S,canvasClick:D,getMousePos:k,createPoint:P,refresh:T,getPictrue:B,pointTransfrom:J}}},ne={style:{position:"relative"}},se={class:"verify-img-out"},oe=l("i",{class:"iconfont icon-refresh"},null,-1),re=[oe],ce=["src"],le={class:"verify-msg"};function he(N,f,_,e,L,R){return H(),I("div",ne,[l("div",se,[l("div",{class:"verify-img-panel",style:A({width:e.setSize.imgWidth,height:e.setSize.imgHeight,"background-size":e.setSize.imgWidth+" "+e.setSize.imgHeight,"margin-bottom":_.vSpace+"px"})},[Q(l("div",{class:"verify-refresh",style:{"z-index":"3"},onClick:f[0]||(f[0]=(...c)=>e.refresh&&e.refresh(...c))},re,512),[[U,e.showRefresh]]),l("img",{src:"data:image/png;base64,"+e.pointBackImgBase,ref:"canvas",alt:"",style:{width:"100%",height:"100%",display:"block"},onClick:f[1]||(f[1]=c=>e.bindingClick?e.canvasClick(c):void 0)},null,8,ce),(H(!0),I($,null,ee(e.tempPoints,(c,n)=>(H(),I("div",{key:n,class:"point-area",style:A({"background-color":"#1abd6c",color:"#fff","z-index":9999,width:"20px",height:"20px","text-align":"center","line-height":"20px","border-radius":"50%",position:"absolute",top:parseInt(c.y-10)+"px",left:parseInt(c.x-10)+"px"})},q(n+1),5))),128))],4)]),l("div",{class:"verify-bar-area",style:A({width:e.setSize.imgWidth,color:this.barAreaColor,"border-color":this.barAreaBorderColor,"line-height":this.barSize.height})},[l("span",le,q(e.text),1)],4)])}const fe=ae(ie,[["render",he]]);export{fe as default};
|
||||||
1
niucloud/public/admin/assets/VerifySlide-02eb59d1.js
Normal file
1
niucloud/public/admin/assets/VerifySlide-02eb59d1.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
import{d as V,k as B,u as N,r as x,aZ as S,h as T,c as j,e as o,w as s,a as t,t as n,f as e,s as a,i as h,B as I,aH as R,aI as $,E as q,a_ as D,a$ as F,b0 as H,K,b1 as M,a8 as P}from"./index-69eae4f0.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{g as Q}from"./aliapp-02c8b274.js";const U={class:"main-container"},Z={class:"flex justify-between items-center"},z={class:"text-page-title"},G={class:"p-[20px]"},J={class:"panel-title !text-sm"},L={class:"text-[14px] font-[700]"},O={class:"text-[#999]"},W={class:"mt-[20px] mb-[40px] h-[32px]"},X={class:"text-[14px] font-[700]"},Y={class:"text-[#999]"},tt={class:"mt-[20px] mb-[40px] h-[32px]"},et={class:"text-[14px] font-[700]"},st={class:"text-[#999]"},at=t("div",{class:"mt-[20px] mb-[40px] h-[32px]"},null,-1),ot={class:"text-[14px] font-[700]"},nt={class:"text-[#999]"},lt={class:"flex justify-center"},ct={class:"w-[100%] h-[100%] flex items-center justify-center bg-[#f5f7fa]"},pt={class:"mt-[22px] text-center"},it={class:"text-[12px]"},bt=V({__name:"access",setup(_t){const f=B(),d=N(),v=f.meta.title,_=x("/channel/aliapp"),p=x("");S(async()=>{const c=await Q();p.value=c.data.qr_code});const w=c=>{window.open(c,"_blank")},b=c=>{d.push({path:_.value})};return(c,l)=>{const g=R,C=$,m=q,i=D,E=F,u=H,y=K,k=M,A=P;return T(),j("div",U,[o(A,{class:"card !border-none",shadow:"never"},{default:s(()=>[t("div",Z,[t("span",z,n(e(v)),1)]),o(C,{modelValue:_.value,"onUpdate:modelValue":l[0]||(l[0]=r=>_.value=r),class:"my-[20px]",onTabChange:b},{default:s(()=>[o(g,{label:e(a)("weappAccessFlow"),name:"/channel/aliapp"},null,8,["label"])]),_:1},8,["modelValue"]),t("div",G,[t("h3",J,n(e(a)("weappInlet")),1),o(k,null,{default:s(()=>[o(u,{span:20},{default:s(()=>[o(E,{active:4,direction:"vertical"},{default:s(()=>[o(i,null,{title:s(()=>[t("p",L,n(e(a)("weappAttestation")),1)]),description:s(()=>[t("span",O,n(e(a)("weappAttest")),1),t("div",W,[o(m,{type:"primary",onClick:l[1]||(l[1]=r=>w("https://open.alipay.com/develop/manage"))},{default:s(()=>[h(n(e(a)("clickAccess")),1)]),_:1})])]),_:1}),o(i,null,{title:s(()=>[t("p",X,n(e(a)("weappSetting")),1)]),description:s(()=>[t("span",Y,n(e(a)("emplace")),1),t("div",tt,[o(m,{type:"primary",plain:"",onClick:l[2]||(l[2]=r=>e(d).push("/channel/aliapp/config"))},{default:s(()=>[h(n(e(a)("weappSettingBtn")),1)]),_:1})])]),_:1}),o(i,null,{title:s(()=>[t("p",et,n(e(a)("uploadVersion")),1)]),description:s(()=>[t("span",st,n(e(a)("releaseCourse")),1),at]),_:1}),o(i,null,{title:s(()=>[t("p",ot,n(e(a)("completeAccess")),1)]),description:s(()=>[t("span",nt,n(e(a)("releaseCourse")),1)]),_:1})]),_:1})]),_:1}),o(u,{span:4},{default:s(()=>[t("div",lt,[o(y,{class:"w-[180px] h-[180px]",src:p.value?e(I)(p.value):""},{error:s(()=>[t("div",ct,[t("span",null,n(p.value?e(a)("fileErr"):e(a)("emptyQrCode")),1)])]),_:1},8,["src"])]),t("div",pt,[t("p",it,n(e(a)("clickAccess2")),1)])]),_:1})]),_:1})])]),_:1})])}}});export{bt as default};
|
|
||||||
1
niucloud/public/admin/assets/access-25e8e612.js
Normal file
1
niucloud/public/admin/assets/access-25e8e612.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{d as $,k as q,u as j,r as u,aZ as F,b6 as I,o as R,h as w,c as y,e as a,w as s,a as n,t as o,f as e,s as t,i as r,F as U,v as z,B as L,aH as M,aI as D,E as G,a_ as H,a$ as K,b0 as P,K as Q,b1 as Z,a8 as J}from"./index-8eead49b.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{g as O}from"./wechat-cb4e6ea9.js";import{a as X}from"./wxoplatform-105b4bfa.js";const Y={class:"main-container"},ee={class:"flex justify-between items-center"},te={class:"text-page-title"},ae={class:"p-[20px]"},se={class:"panel-title !text-sm"},ne={class:"text-[14px] font-[700]"},oe={class:"text-[#999]"},le={class:"mt-[20px] mb-[40px] h-[32px]"},ce={class:"text-[14px] font-[700]"},ie={class:"text-[#999]"},pe={class:"mt-[20px] mb-[40px] h-[32px]"},re={class:"text-[14px] font-[700]"},_e={class:"text-[#999]"},de={class:"mt-[20px] mb-[40px] h-[32px]"},me={class:"flex justify-center"},ue={class:"w-[100%] h-[100%] flex items-center justify-center bg-[#f5f7fa]"},he={class:"mt-[22px] text-center"},fe={class:"text-[12px]"},Be=$({__name:"access",setup(ve){const k=q(),_=j(),C=k.meta.title,h=u("/channel/wechat"),d=u(""),f=u({}),v=u({}),b=async()=>{await O().then(({data:l})=>{f.value=l,d.value=l.qr_code})};F(async()=>{await b(),await I().then(({data:l})=>{v.value=l}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&b()})}),R(()=>{document.removeEventListener("visibilitychange",()=>{})});const E=l=>{window.open(l,"_blank")},A=l=>{_.push({path:h.value})},S=()=>{X().then(({data:l})=>{window.open(l)})};return(l,c)=>{const m=M,B=D,i=G,x=H,V=K,g=P,N=Q,T=Z,W=J;return w(),y("div",Y,[a(W,{class:"card !border-none",shadow:"never"},{default:s(()=>[n("div",ee,[n("span",te,o(e(C)),1)]),a(B,{modelValue:h.value,"onUpdate:modelValue":c[0]||(c[0]=p=>h.value=p),class:"my-[20px]",onTabChange:A},{default:s(()=>[a(m,{label:e(t)("wechatAccessFlow"),name:"/channel/wechat"},null,8,["label"]),a(m,{label:e(t)("customMenu"),name:"/channel/wechat/menu"},null,8,["label"]),a(m,{label:e(t)("wechatTemplate"),name:"/channel/wechat/message"},null,8,["label"]),a(m,{label:e(t)("reply"),name:"/channel/wechat/reply"},null,8,["label"])]),_:1},8,["modelValue"]),n("div",ae,[n("h3",se,o(e(t)("wechatInlet")),1),a(T,null,{default:s(()=>[a(g,{span:20},{default:s(()=>[a(V,{class:"!mt-[10px]",active:3,direction:"vertical"},{default:s(()=>[a(x,null,{title:s(()=>[n("p",ne,o(e(t)("wechatAttestation")),1)]),description:s(()=>[n("span",oe,o(e(t)("wechatAttestation1")),1),n("div",le,[a(i,{type:"primary",onClick:c[1]||(c[1]=p=>E("https://mp.weixin.qq.com/"))},{default:s(()=>[r(o(e(t)("clickAccess")),1)]),_:1})])]),_:1}),a(x,null,{title:s(()=>[n("p",ce,o(e(t)("wechatSetting")),1)]),description:s(()=>[n("span",ie,o(e(t)("wechatSetting1")),1),n("div",pe,[v.value.app_id&&v.value.app_secret?(w(),y(U,{key:0},[a(i,{type:"primary",onClick:c[2]||(c[2]=p=>e(_).push("/channel/wechat/config"))},{default:s(()=>[r(o(f.value.app_id?e(t)("seeConfig"):e(t)("clickSetting")),1)]),_:1}),a(i,{type:"primary",plain:"",onClick:S},{default:s(()=>[r(o(f.value.is_authorization?e(t)("refreshAuth"):e(t)("authWechat")),1)]),_:1})],64)):(w(),z(i,{key:1,type:"primary",onClick:c[3]||(c[3]=p=>e(_).push("/channel/wechat/config"))},{default:s(()=>[r(o(e(t)("clickSetting")),1)]),_:1}))])]),_:1}),a(x,null,{title:s(()=>[n("p",re,o(e(t)("wechatAccess")),1)]),description:s(()=>[n("span",_e,o(e(t)("wechatAccess")),1),n("div",de,[a(i,{type:"primary",plain:"",onClick:c[4]||(c[4]=p=>e(_).push("/channel/wechat/course"))},{default:s(()=>[r(o(e(t)("releaseCourse")),1)]),_:1})])]),_:1})]),_:1})]),_:1}),a(g,{span:4},{default:s(()=>[n("div",me,[a(N,{class:"w-[180px] h-[180px]",src:d.value?e(L)(d.value):""},{error:s(()=>[n("div",ue,[n("span",null,o(d.value?e(t)("fileErr"):e(t)("emptyQrCode")),1)])]),_:1},8,["src"])]),n("div",he,[n("p",fe,o(e(t)("clickAccess2")),1)])]),_:1})]),_:1})])]),_:1})])}}});export{Be as default};
|
||||||
1
niucloud/public/admin/assets/access-4e12d8de.js
Normal file
1
niucloud/public/admin/assets/access-4e12d8de.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{d as V,k as B,u as N,r as x,aZ as S,h as T,c as j,e as o,w as s,a as t,t as n,f as e,s as a,i as h,B as I,aH as R,aI as $,E as q,a_ as D,a$ as F,b0 as H,K,b1 as M,a8 as P}from"./index-8eead49b.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{g as Q}from"./aliapp-72a6151f.js";const U={class:"main-container"},Z={class:"flex justify-between items-center"},z={class:"text-page-title"},G={class:"p-[20px]"},J={class:"panel-title !text-sm"},L={class:"text-[14px] font-[700]"},O={class:"text-[#999]"},W={class:"mt-[20px] mb-[40px] h-[32px]"},X={class:"text-[14px] font-[700]"},Y={class:"text-[#999]"},tt={class:"mt-[20px] mb-[40px] h-[32px]"},et={class:"text-[14px] font-[700]"},st={class:"text-[#999]"},at=t("div",{class:"mt-[20px] mb-[40px] h-[32px]"},null,-1),ot={class:"text-[14px] font-[700]"},nt={class:"text-[#999]"},lt={class:"flex justify-center"},ct={class:"w-[100%] h-[100%] flex items-center justify-center bg-[#f5f7fa]"},pt={class:"mt-[22px] text-center"},it={class:"text-[12px]"},bt=V({__name:"access",setup(_t){const f=B(),d=N(),v=f.meta.title,_=x("/channel/aliapp"),p=x("");S(async()=>{const c=await Q();p.value=c.data.qr_code});const w=c=>{window.open(c,"_blank")},b=c=>{d.push({path:_.value})};return(c,l)=>{const g=R,C=$,m=q,i=D,E=F,u=H,y=K,k=M,A=P;return T(),j("div",U,[o(A,{class:"card !border-none",shadow:"never"},{default:s(()=>[t("div",Z,[t("span",z,n(e(v)),1)]),o(C,{modelValue:_.value,"onUpdate:modelValue":l[0]||(l[0]=r=>_.value=r),class:"my-[20px]",onTabChange:b},{default:s(()=>[o(g,{label:e(a)("weappAccessFlow"),name:"/channel/aliapp"},null,8,["label"])]),_:1},8,["modelValue"]),t("div",G,[t("h3",J,n(e(a)("weappInlet")),1),o(k,null,{default:s(()=>[o(u,{span:20},{default:s(()=>[o(E,{active:4,direction:"vertical"},{default:s(()=>[o(i,null,{title:s(()=>[t("p",L,n(e(a)("weappAttestation")),1)]),description:s(()=>[t("span",O,n(e(a)("weappAttest")),1),t("div",W,[o(m,{type:"primary",onClick:l[1]||(l[1]=r=>w("https://open.alipay.com/develop/manage"))},{default:s(()=>[h(n(e(a)("clickAccess")),1)]),_:1})])]),_:1}),o(i,null,{title:s(()=>[t("p",X,n(e(a)("weappSetting")),1)]),description:s(()=>[t("span",Y,n(e(a)("emplace")),1),t("div",tt,[o(m,{type:"primary",plain:"",onClick:l[2]||(l[2]=r=>e(d).push("/channel/aliapp/config"))},{default:s(()=>[h(n(e(a)("weappSettingBtn")),1)]),_:1})])]),_:1}),o(i,null,{title:s(()=>[t("p",et,n(e(a)("uploadVersion")),1)]),description:s(()=>[t("span",st,n(e(a)("releaseCourse")),1),at]),_:1}),o(i,null,{title:s(()=>[t("p",ot,n(e(a)("completeAccess")),1)]),description:s(()=>[t("span",nt,n(e(a)("releaseCourse")),1)]),_:1})]),_:1})]),_:1}),o(u,{span:4},{default:s(()=>[t("div",lt,[o(y,{class:"w-[180px] h-[180px]",src:p.value?e(I)(p.value):""},{error:s(()=>[t("div",ct,[t("span",null,n(p.value?e(a)("fileErr"):e(a)("emptyQrCode")),1)])]),_:1},8,["src"])]),t("div",pt,[t("p",it,n(e(a)("clickAccess2")),1)])]),_:1})]),_:1})])]),_:1})])}}});export{bt as default};
|
||||||
1
niucloud/public/admin/assets/access-61851911.js
Normal file
1
niucloud/public/admin/assets/access-61851911.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{d as B,k as T,u as $,r as c,aZ as I,b6 as M,o as R,h as W,c as q,e,w as t,a as s,t as o,f as n,s as a,i as u,aH as A,aI as L,E as U,a_ as j,a$ as D,b0 as F,b1 as G,a8 as H}from"./index-8eead49b.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{g as P}from"./wechat-cb4e6ea9.js";const Z={class:"main-container"},z={class:"flex justify-between items-center"},J={class:"text-page-title"},K={class:"p-[20px]"},O={class:"panel-title !text-sm"},Q={class:"text-[14px] font-[700]"},X={class:"text-[#999]"},Y={class:"mt-[20px] mb-[40px] h-[32px]"},tt={class:"text-[14px] font-[700]"},et={class:"mt-[20px] mb-[40px] h-[32px]"},nt={class:"text-[14px] font-[700]"},st={class:"mt-[20px] mb-[40px] h-[32px]"},dt=B({__name:"access",setup(at){const f=T(),_=$(),x=f.meta.title,r=c("/channel/app"),b=c(""),g=c({}),w=c({}),h=async()=>{await P().then(({data:l})=>{g.value=l,b.value=l.qr_code})};I(async()=>{await h(),await M().then(({data:l})=>{w.value=l}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&h()})}),R(()=>{document.removeEventListener("visibilitychange",()=>{})});const y=l=>{window.open(l,"_blank")},C=l=>{_.push({path:r.value})};return(l,i)=>{const v=A,E=L,d=U,m=j,k=D,V=F,S=G,N=H;return W(),q("div",Z,[e(N,{class:"card !border-none",shadow:"never"},{default:t(()=>[s("div",z,[s("span",J,o(n(x)),1)]),e(E,{modelValue:r.value,"onUpdate:modelValue":i[0]||(i[0]=p=>r.value=p),class:"my-[20px]",onTabChange:C},{default:t(()=>[e(v,{label:n(a)("accessFlow"),name:"/channel/app"},null,8,["label"]),e(v,{label:n(a)("versionManage"),name:"/channel/app/version"},null,8,["label"])]),_:1},8,["modelValue"]),s("div",K,[s("h3",O,o(n(a)("appInlet")),1),e(S,null,{default:t(()=>[e(V,{span:20},{default:t(()=>[e(k,{class:"!mt-[10px]",active:3,direction:"vertical"},{default:t(()=>[e(m,null,{title:t(()=>[s("p",Q,o(n(a)("uniappApp")),1)]),description:t(()=>[s("span",X,o(n(a)("appAttestation1")),1),s("div",Y,[e(d,{type:"primary",onClick:i[1]||(i[1]=p=>y("https://dcloud.io/"))},{default:t(()=>[u(o(n(a)("toCreate")),1)]),_:1})])]),_:1}),e(m,null,{title:t(()=>[s("p",tt,o(n(a)("appSetting")),1)]),description:t(()=>[s("div",et,[e(d,{type:"primary",onClick:i[2]||(i[2]=p=>n(_).push("/channel/app/config"))},{default:t(()=>[u(o(n(a)("settingInfo")),1)]),_:1})])]),_:1}),e(m,null,{title:t(()=>[s("p",nt,o(n(a)("versionManage")),1)]),description:t(()=>[s("div",st,[e(d,{type:"primary",plain:"",onClick:i[3]||(i[3]=p=>n(_).push("/channel/app/version"))},{default:t(()=>[u(o(n(a)("releaseVersion")),1)]),_:1})])]),_:1})]),_:1})]),_:1})]),_:1})])]),_:1})])}}});export{dt as default};
|
||||||
@ -1 +0,0 @@
|
|||||||
import{d as B,k as T,u as $,r as c,aZ as I,b6 as M,o as R,h as W,c as q,e,w as t,a as s,t as o,f as n,s as a,i as u,aH as A,aI as L,E as U,a_ as j,a$ as D,b0 as F,b1 as G,a8 as H}from"./index-69eae4f0.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{g as P}from"./wechat-3cea9b68.js";const Z={class:"main-container"},z={class:"flex justify-between items-center"},J={class:"text-page-title"},K={class:"p-[20px]"},O={class:"panel-title !text-sm"},Q={class:"text-[14px] font-[700]"},X={class:"text-[#999]"},Y={class:"mt-[20px] mb-[40px] h-[32px]"},tt={class:"text-[14px] font-[700]"},et={class:"mt-[20px] mb-[40px] h-[32px]"},nt={class:"text-[14px] font-[700]"},st={class:"mt-[20px] mb-[40px] h-[32px]"},dt=B({__name:"access",setup(at){const f=T(),_=$(),x=f.meta.title,r=c("/channel/app"),b=c(""),g=c({}),w=c({}),h=async()=>{await P().then(({data:l})=>{g.value=l,b.value=l.qr_code})};I(async()=>{await h(),await M().then(({data:l})=>{w.value=l}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&h()})}),R(()=>{document.removeEventListener("visibilitychange",()=>{})});const y=l=>{window.open(l,"_blank")},C=l=>{_.push({path:r.value})};return(l,i)=>{const v=A,E=L,d=U,m=j,k=D,V=F,S=G,N=H;return W(),q("div",Z,[e(N,{class:"card !border-none",shadow:"never"},{default:t(()=>[s("div",z,[s("span",J,o(n(x)),1)]),e(E,{modelValue:r.value,"onUpdate:modelValue":i[0]||(i[0]=p=>r.value=p),class:"my-[20px]",onTabChange:C},{default:t(()=>[e(v,{label:n(a)("accessFlow"),name:"/channel/app"},null,8,["label"]),e(v,{label:n(a)("versionManage"),name:"/channel/app/version"},null,8,["label"])]),_:1},8,["modelValue"]),s("div",K,[s("h3",O,o(n(a)("appInlet")),1),e(S,null,{default:t(()=>[e(V,{span:20},{default:t(()=>[e(k,{class:"!mt-[10px]",active:3,direction:"vertical"},{default:t(()=>[e(m,null,{title:t(()=>[s("p",Q,o(n(a)("uniappApp")),1)]),description:t(()=>[s("span",X,o(n(a)("appAttestation1")),1),s("div",Y,[e(d,{type:"primary",onClick:i[1]||(i[1]=p=>y("https://dcloud.io/"))},{default:t(()=>[u(o(n(a)("toCreate")),1)]),_:1})])]),_:1}),e(m,null,{title:t(()=>[s("p",tt,o(n(a)("appSetting")),1)]),description:t(()=>[s("div",et,[e(d,{type:"primary",onClick:i[2]||(i[2]=p=>n(_).push("/channel/app/config"))},{default:t(()=>[u(o(n(a)("settingInfo")),1)]),_:1})])]),_:1}),e(m,null,{title:t(()=>[s("p",nt,o(n(a)("versionManage")),1)]),description:t(()=>[s("div",st,[e(d,{type:"primary",plain:"",onClick:i[3]||(i[3]=p=>n(_).push("/channel/app/version"))},{default:t(()=>[u(o(n(a)("releaseVersion")),1)]),_:1})])]),_:1})]),_:1})]),_:1})]),_:1})])]),_:1})])}}});export{dt as default};
|
|
||||||
1
niucloud/public/admin/assets/access-cf866f9d.js
Normal file
1
niucloud/public/admin/assets/access-cf866f9d.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
import{d as $,k as q,u as j,r as u,aZ as F,b6 as I,o as R,h as w,c as y,e as a,w as s,a as n,t as o,f as e,s as t,i as r,F as U,v as z,B as L,aH as M,aI as D,E as G,a_ as H,a$ as K,b0 as P,K as Q,b1 as Z,a8 as J}from"./index-69eae4f0.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{g as O}from"./wechat-3cea9b68.js";import{a as X}from"./wxoplatform-871d008b.js";const Y={class:"main-container"},ee={class:"flex justify-between items-center"},te={class:"text-page-title"},ae={class:"p-[20px]"},se={class:"panel-title !text-sm"},ne={class:"text-[14px] font-[700]"},oe={class:"text-[#999]"},le={class:"mt-[20px] mb-[40px] h-[32px]"},ce={class:"text-[14px] font-[700]"},ie={class:"text-[#999]"},pe={class:"mt-[20px] mb-[40px] h-[32px]"},re={class:"text-[14px] font-[700]"},_e={class:"text-[#999]"},de={class:"mt-[20px] mb-[40px] h-[32px]"},me={class:"flex justify-center"},ue={class:"w-[100%] h-[100%] flex items-center justify-center bg-[#f5f7fa]"},he={class:"mt-[22px] text-center"},fe={class:"text-[12px]"},Be=$({__name:"access",setup(ve){const k=q(),_=j(),C=k.meta.title,h=u("/channel/wechat"),d=u(""),f=u({}),v=u({}),b=async()=>{await O().then(({data:l})=>{f.value=l,d.value=l.qr_code})};F(async()=>{await b(),await I().then(({data:l})=>{v.value=l}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&b()})}),R(()=>{document.removeEventListener("visibilitychange",()=>{})});const E=l=>{window.open(l,"_blank")},A=l=>{_.push({path:h.value})},S=()=>{X().then(({data:l})=>{window.open(l)})};return(l,c)=>{const m=M,B=D,i=G,x=H,V=K,g=P,N=Q,T=Z,W=J;return w(),y("div",Y,[a(W,{class:"card !border-none",shadow:"never"},{default:s(()=>[n("div",ee,[n("span",te,o(e(C)),1)]),a(B,{modelValue:h.value,"onUpdate:modelValue":c[0]||(c[0]=p=>h.value=p),class:"my-[20px]",onTabChange:A},{default:s(()=>[a(m,{label:e(t)("wechatAccessFlow"),name:"/channel/wechat"},null,8,["label"]),a(m,{label:e(t)("customMenu"),name:"/channel/wechat/menu"},null,8,["label"]),a(m,{label:e(t)("wechatTemplate"),name:"/channel/wechat/message"},null,8,["label"]),a(m,{label:e(t)("reply"),name:"/channel/wechat/reply"},null,8,["label"])]),_:1},8,["modelValue"]),n("div",ae,[n("h3",se,o(e(t)("wechatInlet")),1),a(T,null,{default:s(()=>[a(g,{span:20},{default:s(()=>[a(V,{class:"!mt-[10px]",active:3,direction:"vertical"},{default:s(()=>[a(x,null,{title:s(()=>[n("p",ne,o(e(t)("wechatAttestation")),1)]),description:s(()=>[n("span",oe,o(e(t)("wechatAttestation1")),1),n("div",le,[a(i,{type:"primary",onClick:c[1]||(c[1]=p=>E("https://mp.weixin.qq.com/"))},{default:s(()=>[r(o(e(t)("clickAccess")),1)]),_:1})])]),_:1}),a(x,null,{title:s(()=>[n("p",ce,o(e(t)("wechatSetting")),1)]),description:s(()=>[n("span",ie,o(e(t)("wechatSetting1")),1),n("div",pe,[v.value.app_id&&v.value.app_secret?(w(),y(U,{key:0},[a(i,{type:"primary",onClick:c[2]||(c[2]=p=>e(_).push("/channel/wechat/config"))},{default:s(()=>[r(o(f.value.app_id?e(t)("seeConfig"):e(t)("clickSetting")),1)]),_:1}),a(i,{type:"primary",plain:"",onClick:S},{default:s(()=>[r(o(f.value.is_authorization?e(t)("refreshAuth"):e(t)("authWechat")),1)]),_:1})],64)):(w(),z(i,{key:1,type:"primary",onClick:c[3]||(c[3]=p=>e(_).push("/channel/wechat/config"))},{default:s(()=>[r(o(e(t)("clickSetting")),1)]),_:1}))])]),_:1}),a(x,null,{title:s(()=>[n("p",re,o(e(t)("wechatAccess")),1)]),description:s(()=>[n("span",_e,o(e(t)("wechatAccess")),1),n("div",de,[a(i,{type:"primary",plain:"",onClick:c[4]||(c[4]=p=>e(_).push("/channel/wechat/course"))},{default:s(()=>[r(o(e(t)("releaseCourse")),1)]),_:1})])]),_:1})]),_:1})]),_:1}),a(g,{span:4},{default:s(()=>[n("div",me,[a(N,{class:"w-[180px] h-[180px]",src:d.value?e(L)(d.value):""},{error:s(()=>[n("div",ue,[n("span",null,o(d.value?e(t)("fileErr"):e(t)("emptyQrCode")),1)])]),_:1},8,["src"])]),n("div",he,[n("p",fe,o(e(t)("clickAccess2")),1)])]),_:1})]),_:1})])]),_:1})])}}});export{Be as default};
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user