master
parent
f812ce5d43
commit
139ddd5a67
6
.env
6
.env
|
@ -4,9 +4,9 @@ VITE_APP_NAME=垃圾回收监管平台
|
|||
# 服务器基础地址
|
||||
VITE_HTTP_SERVER_BASE_URL=/api
|
||||
VITE_WS_SERVER_BASE_URL=/ws
|
||||
VITE_OSS_UPLOAD_BASE_URL=http://218.94.108.114:19000/iot
|
||||
VITE_OSS_DOWNLOAD_BASE_URL=/api/file/oss/download
|
||||
VITE_OSS_BUCKET_NAME=iot
|
||||
VITE_OSS_UPLOAD_BASE_URL=http://localhost:9000/zsy
|
||||
VITE_OSS_DOWNLOAD_BASE_URL=/api/oss/download
|
||||
VITE_OSS_BUCKET_NAME=zsy
|
||||
|
||||
# 接口超时时间
|
||||
VITE_HTTP_SERVER_TIMEOUT=10000
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"axios": "1.7.4",
|
||||
"decimal.js": "^10.4.3",
|
||||
"echarts": "^6.0.0",
|
||||
"flv.js": "^1.6.2",
|
||||
"gridstack": "^12.0.0",
|
||||
"logan-web": "^1.1.0",
|
||||
"luxon": "^3.4.4",
|
||||
|
@ -3267,6 +3268,16 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/flv.js": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/flv.js/-/flv.js-1.6.2.tgz",
|
||||
"integrity": "sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"es6-promise": "^4.2.8",
|
||||
"webworkify-webpack": "^2.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
|
@ -6559,6 +6570,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webworkify-webpack": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/webworkify-webpack/-/webworkify-webpack-2.1.5.tgz",
|
||||
"integrity": "sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"axios": "1.7.4",
|
||||
"decimal.js": "^10.4.3",
|
||||
"echarts": "^6.0.0",
|
||||
"flv.js": "^1.6.2",
|
||||
"gridstack": "^12.0.0",
|
||||
"logan-web": "^1.1.0",
|
||||
"luxon": "^3.4.4",
|
||||
|
|
|
@ -11,11 +11,12 @@ interface IconfontJson {
|
|||
}[];
|
||||
}
|
||||
|
||||
export default function iconfontDts(outFile: string) {
|
||||
export default function iconfontProcess(outPath: string) {
|
||||
return function (content: string) {
|
||||
const json = JSON.parse(content) as IconfontJson
|
||||
const names = json.glyphs.map(glyph => glyph.font_class)
|
||||
console.log('正在生成文件:', outFile)
|
||||
const dtsFile = outPath + '/iconfont.d.ts'
|
||||
console.log('正在生成文件:', dtsFile)
|
||||
const dts = `export {}
|
||||
|
||||
declare global {
|
||||
|
@ -24,8 +25,13 @@ declare global {
|
|||
}
|
||||
}
|
||||
`
|
||||
fs.writeFileSync(outFile, dts, {encoding: 'utf-8'})
|
||||
console.log('文件生成完成')
|
||||
fs.writeFileSync(dtsFile, dts, {encoding: 'utf-8'})
|
||||
|
||||
const tsFile = outPath + '/icons.ts'
|
||||
const ts = `export default reactive([${'\n ' + names.map(name => `{name: '${name}'}`).join(',\n ') + '\n'}])`
|
||||
console.log('正在生成文件:', tsFile)
|
||||
fs.writeFileSync(tsFile, ts, {encoding: 'utf-8'})
|
||||
|
||||
console.log('文件生成完成')
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
import { get } from '@/common/utils/http-util.ts'
|
||||
import { bucketName } from '@/common'
|
||||
import {
|
||||
get,
|
||||
getFileUrl
|
||||
} from '@/common/utils/http-util.ts'
|
||||
import { bucketName, } from '@/common'
|
||||
|
||||
interface PresignedUrl extends Record<string, string | undefined> {
|
||||
bucketName?: string
|
||||
|
@ -8,9 +11,12 @@ interface PresignedUrl extends Record<string, string | undefined> {
|
|||
|
||||
export default {
|
||||
obtainPresignedUrl(filename: string) {
|
||||
return get<PresignedUrl>('/file/oss/obtain_presigned_url', {filename, bucketName})
|
||||
return get<PresignedUrl>('/oss/obtain_presigned_url', {filename, bucketName})
|
||||
},
|
||||
download(objectName: string) {
|
||||
return get('/file/oss/download/' + objectName)
|
||||
return get('/oss/download/' + objectName)
|
||||
},
|
||||
fileUrl(filename: string) {
|
||||
return getFileUrl(filename)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ export const appName = import.meta.env.VITE_APP_NAME
|
|||
export const serverBaseUrl = import.meta.env.VITE_HTTP_SERVER_BASE_URL ?? '/'
|
||||
export const uploadBaseUrl = import.meta.env.VITE_OSS_UPLOAD_BASE_URL ?? '/'
|
||||
export const downloadBaseUrl = import.meta.env.VITE_OSS_DOWNLOAD_BASE_URL ?? '/'
|
||||
export const bucketName = import.meta.env.VITE_OSS_BUCKET_NAME ?? 'iot'
|
||||
export const bucketName = import.meta.env.VITE_OSS_BUCKET_NAME ?? 'zsy'
|
||||
|
||||
/**
|
||||
* Axios 超时时间
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
downloadBaseUrl,
|
||||
serverBaseUrl,
|
||||
serverTimeout
|
||||
} from '@/common'
|
||||
|
@ -6,6 +7,7 @@ import axios, { AxiosRequestConfig } from 'axios'
|
|||
import * as qs from 'qs'
|
||||
import Toast from '@/components/toast'
|
||||
import { useAppUserStore } from '@/common/app/app-user-store.ts'
|
||||
import { throttle } from '@/common/utils/index.ts'
|
||||
|
||||
/**
|
||||
* HTTP 统一响应结构
|
||||
|
@ -41,24 +43,13 @@ const paramsSerializer = (params: any) => {
|
|||
|
||||
/**
|
||||
* 统一错误处理函数
|
||||
*
|
||||
* @param code 响应码
|
||||
* @param msg 响应信息
|
||||
* @param message 响应信息(详细)
|
||||
*/
|
||||
function errHandler({code, msg, message}: R) {
|
||||
switch (code) {
|
||||
case 450401:
|
||||
console.log(msg, message)
|
||||
break
|
||||
case 450403:
|
||||
console.log(msg, message)
|
||||
break
|
||||
default:
|
||||
Toast.error(message ?? '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
/* function errHandler(r?: R) {
|
||||
Toast.error(r?.message ?? '操作失败')
|
||||
} */
|
||||
const errHandler = throttle(500, (r?: R) => {
|
||||
Toast.error(r?.message ?? '操作失败')
|
||||
})
|
||||
/**
|
||||
* axios 实例
|
||||
*/
|
||||
|
@ -98,7 +89,7 @@ httpUtil.interceptors.response.use(
|
|||
return Promise.resolve(response)
|
||||
}
|
||||
if (response.data == null) {
|
||||
response.data = {code: 99999, msg: '无响应内容', message: '无响应内容', data: null, headers: response.headers}
|
||||
response.data = {code: 0, msg: '无响应内容', message: '无响应内容', data: null, headers: response.headers}
|
||||
}
|
||||
response.data.headers = response.headers
|
||||
if (response.data.code === 0) {
|
||||
|
@ -113,14 +104,14 @@ httpUtil.interceptors.response.use(
|
|||
error.response.data = {...error.response.data, headers: error.response.headers}
|
||||
} else if (error.request != null) {
|
||||
error.response = {
|
||||
data: {code: 99999, msg: '服务器未响', message: '服务器未响', data: null},
|
||||
data: {code: 9999, msg: '网络异常', message: '网络异常', data: null},
|
||||
}
|
||||
} else {
|
||||
error.response = {
|
||||
data: {code: 55555, msg: '请求发送失败', message: '请求发送失败', data: null},
|
||||
data: {code: 5555, msg: '请求发送失败', message: '请求发送失败', data: null},
|
||||
}
|
||||
}
|
||||
return Promise.reject(error)
|
||||
return Promise.reject(error.response.data)
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -132,11 +123,11 @@ httpUtil.interceptors.response.use(
|
|||
* @param disposeErr 是否处理错误响应,默认-->true
|
||||
*/
|
||||
export function get<T = any>(url: string, params?: any, disposeErr: boolean = true) {
|
||||
return httpUtil.get<R<T>>(url, {params, paramsSerializer})
|
||||
return httpUtil.get<R<T>>(url, {params, paramsSerializer, responseType: 'json'})
|
||||
.then(({data}) => data)
|
||||
.catch(res => {
|
||||
if (disposeErr) errHandler(res.response.data)
|
||||
return Promise.reject(res.response.data)
|
||||
if (disposeErr) errHandler(res)
|
||||
return Promise.reject(res)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -149,11 +140,11 @@ export function get<T = any>(url: string, params?: any, disposeErr: boolean = tr
|
|||
* @param disposeErr 是否处理错误响应,默认-->true
|
||||
*/
|
||||
export function post<T>(url: string, body?: any, config?: AxiosConfig, disposeErr: boolean = true) {
|
||||
return httpUtil.post<R<T>>(url, body, config)
|
||||
return httpUtil.post<R<T>>(url, body, {...config, responseType: 'json'})
|
||||
.then(({data}) => data)
|
||||
.catch(res => {
|
||||
if (disposeErr) errHandler(res.response.data)
|
||||
return Promise.reject(res.response.data)
|
||||
if (disposeErr) errHandler(res)
|
||||
return Promise.reject(res)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -173,12 +164,12 @@ export function postForm<T>(url: string, body: any, config?: AxiosConfig, dispos
|
|||
'Content-Type': ContentType.FORM,
|
||||
},
|
||||
params: config?.params,
|
||||
responseType: config?.responseType,
|
||||
responseType: config?.responseType ?? 'json',
|
||||
})
|
||||
.then(({data}) => data)
|
||||
.catch(res => {
|
||||
if (disposeErr) errHandler(res.response.data)
|
||||
return Promise.reject(res.response.data)
|
||||
if (disposeErr) errHandler(res)
|
||||
return Promise.reject(res)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -198,12 +189,12 @@ export function postMltForm<T>(url: string, body: any, config?: AxiosConfig, dis
|
|||
'Content-Type': ContentType.MLT_FORM,
|
||||
},
|
||||
params: config?.params,
|
||||
responseType: config?.responseType,
|
||||
responseType: config?.responseType ?? 'json',
|
||||
})
|
||||
.then(({data}) => data)
|
||||
.catch(res => {
|
||||
if (disposeErr) errHandler(res.response.data)
|
||||
return Promise.reject(res.response.data)
|
||||
if (disposeErr) errHandler(res)
|
||||
return Promise.reject(res)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -258,14 +249,21 @@ export function download(url: string, params?: any, disposeErr: boolean = true)
|
|||
window.URL.revokeObjectURL(blobURL)
|
||||
})
|
||||
.catch(res => {
|
||||
if (disposeErr) errHandler(res.response.data)
|
||||
return Promise.reject(res.response.data)
|
||||
if (disposeErr) errHandler(res)
|
||||
return Promise.reject(res)
|
||||
})
|
||||
}
|
||||
|
||||
export function getFileUrl(filename: string) {
|
||||
const appUserStore = useAppUserStore()
|
||||
return downloadBaseUrl + filename + '?authorization=' + appUserStore.token
|
||||
}
|
||||
|
||||
export default {
|
||||
get,
|
||||
post,
|
||||
postForm,
|
||||
postMltForm,
|
||||
download,
|
||||
getFileUrl,
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ declare module 'vue' {
|
|||
IxFormItem: typeof import('@idux/components/form')['IxFormItem']
|
||||
IxFormWrapper: typeof import('@idux/components/form')['IxFormWrapper']
|
||||
IxIcon: typeof import('@idux/components/icon')['IxIcon']
|
||||
IxIconClose: typeof import('@idux/components/icon')['IxIconClose']
|
||||
IxInput: typeof import('@idux/components/input')['IxInput']
|
||||
IxInputNumber: typeof import('@idux/components/input-number')['IxInputNumber']
|
||||
IxLayoutSiderTrigger: typeof import('@idux/components/layout')['IxLayoutSiderTrigger']
|
||||
|
@ -43,12 +44,14 @@ declare module 'vue' {
|
|||
IxModal: typeof import('@idux/components/modal')['IxModal']
|
||||
IxPagination: typeof import('@idux/components/pagination')['IxPagination']
|
||||
IxPopconfirm: typeof import('@idux/components/popconfirm')['IxPopconfirm']
|
||||
IxPopover: typeof import('@idux/components/popover')['IxPopover']
|
||||
IxProLayout: typeof import('@idux/pro/layout')['IxProLayout']
|
||||
IxPwdInput: typeof import('./../components/input/IxPwdInput.vue')['default']
|
||||
IxRadio: typeof import('@idux/components/radio')['IxRadio']
|
||||
IxRadioGroup: typeof import('@idux/components/radio')['IxRadioGroup']
|
||||
IxRow: typeof import('@idux/components/grid')['IxRow']
|
||||
IxSelect: typeof import('@idux/components/select')['IxSelect']
|
||||
IxSelector: typeof import('@idux/components/selector')['IxSelector']
|
||||
IxSpace: typeof import('@idux/components/space')['IxSpace']
|
||||
IxTable: typeof import('@idux/components/table')['IxTable']
|
||||
IxTabs: typeof import('@idux/components/tabs')['IxTabs']
|
||||
|
|
|
@ -114,21 +114,20 @@ const columns: TableColumn[] = [
|
|||
|
||||
const pagination = reactive<TablePagination>({
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
pageSize: 3,
|
||||
total: 100,
|
||||
size: 'sm',
|
||||
showTotal: true,
|
||||
onChange(pageIndex: number, pageSize: number) {
|
||||
console.log('------', pageIndex, pageSize)
|
||||
pagination.pageIndex = pageIndex
|
||||
pagination.pageSize = pageSize
|
||||
searchHandler()
|
||||
}
|
||||
})
|
||||
|
||||
const tableSpin = ref(false)
|
||||
|
||||
function searchHandler() {
|
||||
console.log('------', formGroup.getValue())
|
||||
tableSpin.value = true
|
||||
DisposeRecodeApi.paging({
|
||||
...formGroup.getValue(),
|
||||
|
@ -218,7 +217,7 @@ onMounted(() => {
|
|||
.dispose-recode {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 150%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
|
|
|
@ -20,13 +20,9 @@ const data = reactive<DisposeRecodeTypes.DisposeRecodeData>({
|
|||
})
|
||||
|
||||
onMounted(() => {
|
||||
console.log(ins)
|
||||
let container = props.container
|
||||
if (container) {
|
||||
container.style.position = 'relative'
|
||||
for (let child of container.children) {
|
||||
console.log(child)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -37,10 +37,7 @@ import {
|
|||
type ProLayoutType
|
||||
} from '@idux/pro/layout'
|
||||
import Strings from '@/common/utils/strings.ts'
|
||||
import {
|
||||
appName,
|
||||
downloadBaseUrl
|
||||
} from '@/common'
|
||||
import { appName } from '@/common'
|
||||
import { useAppSettingStore } from '@/common/app/app-setting-store.ts'
|
||||
import colls from '@/common/utils/colls.ts'
|
||||
import {
|
||||
|
@ -49,10 +46,11 @@ import {
|
|||
} from '@/common/app/contants.ts'
|
||||
import Nav from '@/common/router/nav.ts'
|
||||
import Iconfont from '@/components/iconfont/Iconfont.vue'
|
||||
import AppApi from '@/common/app/app-api.ts'
|
||||
|
||||
const appSettingStore = useAppSettingStore()
|
||||
const logoImage = computed(() => {
|
||||
return Strings.isBlank(appSettingStore.logo) ? defaultLogo : downloadBaseUrl + appSettingStore.logo!
|
||||
return Strings.isBlank(appSettingStore.logo) ? defaultLogo : AppApi.fileUrl(appSettingStore.logo!)
|
||||
})
|
||||
const logo = {
|
||||
image: logoImage.value,
|
||||
|
@ -131,7 +129,7 @@ function openPageHandler(options: MenuClickOptions) {
|
|||
|
||||
.page-change-enter-active,
|
||||
.page-change-leave-active {
|
||||
transition: all .3s ease-in;
|
||||
transition: opacity .3s ease-in, transform .3s ease-in;
|
||||
}
|
||||
|
||||
.page-change-enter-from {
|
||||
|
|
|
@ -33,11 +33,11 @@ import TabList from '@/pages/main-zone/tab-list/TabList.vue'
|
|||
import Strings from '@/common/utils/strings.ts'
|
||||
import { useAppSettingStore } from '@/common/app/app-setting-store.ts'
|
||||
import UserPanel from '@/pages/main-zone/header-bar/UserPanel.vue'
|
||||
import { downloadBaseUrl } from '@/common'
|
||||
import AppApi from '@/common/app/app-api.ts'
|
||||
|
||||
const appSettingStore = useAppSettingStore()
|
||||
const logo = computed(() => {
|
||||
return Strings.isBlank(appSettingStore.logo) ? defaultLogo : downloadBaseUrl + appSettingStore.logo!
|
||||
return Strings.isBlank(appSettingStore.logo) ? defaultLogo : AppApi.fileUrl(appSettingStore.logo!)
|
||||
})
|
||||
|
||||
const menuPanel = ref<InstanceType<typeof MenuPanel>>()
|
||||
|
|
|
@ -48,10 +48,10 @@ import ModifyPwdForm from '@/pages/main-zone/header-bar/ModifyPwdForm.vue'
|
|||
import UserInfo from '@/pages/main-zone/header-bar/UserInfo.vue'
|
||||
import { VKey } from '@idux/cdk'
|
||||
import Nav from '@/common/router/nav.ts'
|
||||
import { downloadBaseUrl } from '@/common'
|
||||
import MenuPanelApi from '@/pages/main-zone/menu-panel/menu-panel-api.ts'
|
||||
import Toast from '@/components/toast'
|
||||
import { reloadUserInfo } from '@/common/app'
|
||||
import AppApi from '@/common/app/app-api.ts'
|
||||
|
||||
const appSettingStore = useAppSettingStore()
|
||||
const appUserStore = useAppUserStore()
|
||||
|
@ -66,7 +66,7 @@ const tabsDtaSource = [
|
|||
]
|
||||
|
||||
const avatar = computed(() => {
|
||||
return Strings.isBlank(appUserStore.avatar) ? defaultAvatar : downloadBaseUrl + appUserStore.avatar!
|
||||
return Strings.isBlank(appUserStore.avatar) ? defaultAvatar : AppApi.fileUrl(appUserStore.avatar!)
|
||||
})
|
||||
const tenantName = computed(() => {
|
||||
return appUserStore.tenantName
|
||||
|
|
|
@ -35,7 +35,7 @@ const leftFuns = computed<PageTypes.Fun[]>(() => {
|
|||
menuForm.value!.reset()
|
||||
})
|
||||
.catch(_ => {
|
||||
Toast.success('添加失败')
|
||||
Toast.error('添加失败')
|
||||
})
|
||||
.finally(() => {
|
||||
Toast.close(toastId)
|
||||
|
|
|
@ -58,6 +58,20 @@
|
|||
</IxFormItem>
|
||||
</IxCol>
|
||||
</IxRow>
|
||||
<IxRow>
|
||||
<IxCol :span="6">
|
||||
<IxFormItem
|
||||
:gutter="[0, 10]"
|
||||
label="图标">
|
||||
<IxPopover closable header="图标列表" placement="bottom" trigger="click">
|
||||
<IxInput control="icon"/>
|
||||
<template #content>
|
||||
<IxTable v-model:selectedRowKeys="selectedRowKeys" :columns="columns" :dataSource="iconTableDataSource" :pagination="pagination" get-key="name"/>
|
||||
</template>
|
||||
</IxPopover>
|
||||
</IxFormItem>
|
||||
</IxCol>
|
||||
</IxRow>
|
||||
</IxForm>
|
||||
</template>
|
||||
|
||||
|
@ -75,7 +89,61 @@ import { TreeSelectNode } from '@idux/components/tree-select/src/types'
|
|||
import { SelectData } from '@idux/components/select/src/types'
|
||||
import MenuApi from '@/pages/sys/menus/menu-api.ts'
|
||||
import Colls from '@/common/utils/colls.ts'
|
||||
import icons from '@/components/iconfont/icons.ts'
|
||||
import {
|
||||
TableColumn,
|
||||
TableColumnSelectable
|
||||
} from '@idux/components/table'
|
||||
import Iconfont from '@/components/iconfont/Iconfont.vue'
|
||||
import { TablePagination } from '@idux/components/table/src/types'
|
||||
|
||||
interface IconData {
|
||||
name: string
|
||||
}
|
||||
|
||||
const selectedRowKeys = ref<string[]>([])
|
||||
|
||||
const iconTableDataSource = ref<IconData[]>(icons.filter((_, i) => {
|
||||
return i >= 0 && i < 5
|
||||
}))
|
||||
const selectableColumn = reactive<TableColumnSelectable<IconData>>({
|
||||
type: 'selectable',
|
||||
align: 'center',
|
||||
multiple: false,
|
||||
showIndex: false,
|
||||
trigger: 'click',
|
||||
onChange: (selectedKeys) => {
|
||||
menuForm.get('icon')?.setValue(selectedKeys[0] as string)
|
||||
},
|
||||
})
|
||||
const columns: TableColumn<IconData>[] = [
|
||||
selectableColumn,
|
||||
{
|
||||
title: '图标',
|
||||
dataKey: 'name',
|
||||
customCell: ({record}: { record: IconData }) => {
|
||||
return h(Iconfont, {name: record.name})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataKey: 'name',
|
||||
},
|
||||
]
|
||||
const pagination = reactive<TablePagination>({
|
||||
pageIndex: 1,
|
||||
pageSize: 5,
|
||||
total: icons.length,
|
||||
size: 'sm',
|
||||
showTotal: true,
|
||||
onChange(pageIndex: number, pageSize: number) {
|
||||
pagination.pageIndex = pageIndex
|
||||
pagination.pageSize = pageSize
|
||||
iconTableDataSource.value = icons.filter((_, i) => {
|
||||
return i >= (pageIndex - 1) * pageSize && i < pageIndex * pageSize
|
||||
})
|
||||
}
|
||||
})
|
||||
const props = withDefaults(defineProps<{ status?: 'create' | 'view' | 'edit' }>(),
|
||||
{
|
||||
status: 'create',
|
||||
|
@ -89,6 +157,7 @@ const menuForm = useFormGroup<MenuTypes.MenuForm>({
|
|||
sort: [ 0 ],
|
||||
routeName: [ '' ],
|
||||
sn: [ undefined ],
|
||||
icon: [ undefined ],
|
||||
})
|
||||
const menuCategoryCtrl = menuForm.get('menuCategory')
|
||||
const routeNameCtrl = menuForm.get('routeName')
|
||||
|
@ -140,6 +209,9 @@ defineExpose({
|
|||
},
|
||||
setValue(data: MenuTypes.MenuForm) {
|
||||
menuForm.setValue(data)
|
||||
if (data.icon != null) {
|
||||
selectedRowKeys.value = [ data.icon ]
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
menuForm.reset()
|
||||
|
|
|
@ -14,7 +14,7 @@ declare global {
|
|||
// 菜单名称
|
||||
title: string
|
||||
// 图标
|
||||
icon: string
|
||||
icon?: string
|
||||
// 层级; >= 1
|
||||
tier: number
|
||||
// 排序
|
||||
|
@ -28,7 +28,7 @@ declare global {
|
|||
}
|
||||
|
||||
type SearchForm = Partial<Pick<SysMenu, 'title' | 'routeName'>>
|
||||
type MenuForm = Pick<SysMenu, 'pid' | 'menuCategory' | 'title' | 'sort' | 'routeName' | 'sn'>
|
||||
type MenuForm = Pick<SysMenu, 'pid' | 'menuCategory' | 'title' | 'sort' | 'routeName' | 'sn' | 'icon'>
|
||||
type AddForm = Pick<SysMenu, 'pid' | 'menuCategory' | 'title' | 'sort' | 'routeName' | 'sn'>
|
||||
type ModifyForm = Pick<SysMenu, 'id' | 'pid' | 'menuCategory' | 'title' | 'sort' | 'routeName'>
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<IxInput control="nickname" placeholder="请输入用户昵称"></IxInput>
|
||||
</IxFormItem>
|
||||
<IxFormItem :gutter="[0, 10]" label="账号">
|
||||
<IxInput control="routeName" placeholder="请输入账号"></IxInput>
|
||||
<IxInput control="username" placeholder="请输入账号"></IxInput>
|
||||
</IxFormItem>
|
||||
<button style="display: none" type="submit"/>
|
||||
</IxForm>
|
||||
|
@ -28,7 +28,7 @@
|
|||
</template>
|
||||
<template #avatar="{record}">
|
||||
<img v-if="!record.avatar" :src="defaultAvatar" alt="头像" style="width: 2rem">
|
||||
<img v-else :src="record.avatar" alt="头像" style="width: 2rem">
|
||||
<img v-else :src="AppApi.fileUrl(record.avatar)" alt="头像" style="width: 2rem">
|
||||
</template>
|
||||
</IxTable>
|
||||
<IxRow justify="end">
|
||||
|
@ -56,6 +56,7 @@ import Toast from '@/components/toast'
|
|||
import UserApi from '@/pages/sys/user/user-api.ts'
|
||||
import defaultAvatar from '@/assets/images/avatar.png'
|
||||
import { useUserDetailStore } from '@/pages/sys/user/user-detail/user-detail-store.ts'
|
||||
import AppApi from '@/common/app/app-api.ts'
|
||||
|
||||
const searchForm = useFormGroup<UserTypes.SearchForm>({
|
||||
nickname: [ undefined ],
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
<IxForm :control="searchForm" :control-col="{span:18}" :label-col="{span:6}"
|
||||
label-align="end"
|
||||
layout="horizontal"
|
||||
@submit="searchHandler">
|
||||
@submit.prevent="searchHandler">
|
||||
<IxRow>
|
||||
<IxCol :span="6">
|
||||
<IxFormItem :gutter="[0, 10]" align="center" label="角色编码">
|
||||
|
@ -161,6 +161,7 @@ const pagination = reactive<G.Pagination>({
|
|||
size: 100,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const searchForm = useFormGroup<RoleTypes.SearchForm>({
|
||||
roleCode: [ undefined ],
|
||||
roleName: [ undefined ]
|
||||
|
@ -243,7 +244,7 @@ const columns = computed<TableColumn<RoleTypes.SysRole>[]>(() => ([
|
|||
|
||||
const genderDataSource = computed<SelectData[]>(() => Object.entries(Gender).map(([ key, label ]) => ({key, label})))
|
||||
|
||||
const files = ref()
|
||||
const files = ref<UploadFile []>()
|
||||
|
||||
|
||||
function updateFormStatus() {
|
||||
|
@ -262,6 +263,13 @@ defineExpose({
|
|||
},
|
||||
setValue(data: UserTypes.SysUser) {
|
||||
formGroup.setValue(data)
|
||||
if (data.avatar != null) {
|
||||
files.value = [ {
|
||||
key: data.avatar,
|
||||
name: data.avatar,
|
||||
thumbUrl: AppApi.fileUrl(data.avatar),
|
||||
} ]
|
||||
}
|
||||
if (data.roles != null) {
|
||||
selectedRowKeys.value = data.roles.map(it => it.id as string)
|
||||
}
|
||||
|
|
|
@ -6,10 +6,13 @@ import { TablePagination } from '@idux/components/table/src/types'
|
|||
import CreateTsp from '@/pages/tsp/create-tsp.vue'
|
||||
import TspApi from '@/pages/tsp/tsp-api.ts'
|
||||
import { ref } from 'vue'
|
||||
import VideoPanel from '@/pages/tsp/VideoPanel.vue'
|
||||
import Toast from '@/components/toast'
|
||||
|
||||
const createTsp = ref<InstanceType<typeof CreateTsp> | null>(null)
|
||||
const videoPanel = ref<InstanceType<typeof VideoPanel> | null>(null)
|
||||
const dataSource: SelectData[] = [
|
||||
{key: '', label: '所有'},
|
||||
{key: '', label: '全部状态'},
|
||||
{key: 'ZhengChang', label: '正常运营'},
|
||||
{key: 'FenZhengChang', label: '非正常运营'},
|
||||
]
|
||||
|
@ -21,9 +24,9 @@ const pagination = reactive<TablePagination>({
|
|||
size: 'sm',
|
||||
showTotal: true,
|
||||
onChange(pageIndex: number, pageSize: number) {
|
||||
console.log('------', pageIndex, pageSize)
|
||||
pagination.pageIndex = pageIndex
|
||||
pagination.pageSize = pageSize
|
||||
searchHandler()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -89,6 +92,14 @@ function searchHandler() {
|
|||
})
|
||||
}
|
||||
|
||||
function playVideo(record: TspTypes.TspData) {
|
||||
if (record.videoUrl == null) {
|
||||
Toast.error('该收纳点未配置视频监控')
|
||||
return
|
||||
}
|
||||
videoPanel.value?.open(record.videoUrl, record.pointName)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
searchHandler()
|
||||
TspApi.statistics()
|
||||
|
@ -154,8 +165,8 @@ onMounted(() => {
|
|||
<Iconfont name="mapmarker" wrapper/>
|
||||
<span>{{ record.pointName }}</span>
|
||||
</template>
|
||||
<template #action>
|
||||
<IxButton class="video-btn" icon="eye" mode="text">查看监控</IxButton>
|
||||
<template #action="{record}">
|
||||
<IxButton class="video-btn" icon="eye" mode="text" @click="playVideo(record)">查看监控</IxButton>
|
||||
</template>
|
||||
<template #status="{record}">
|
||||
<IxTag v-if="record.status === 'ZhengChang'" status="success">{{ record.statusTxt }}</IxTag>
|
||||
|
@ -164,11 +175,13 @@ onMounted(() => {
|
|||
</IxTable>
|
||||
</IxCard>
|
||||
<CreateTsp ref="createTsp"/>
|
||||
<VideoPanel ref="videoPanel"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.tsp {
|
||||
position relative
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
<script lang="ts" setup>
|
||||
import FlvJs from 'flv.js'
|
||||
import { nanoid } from 'nanoid'
|
||||
import Toast from '@/components/toast'
|
||||
|
||||
const show = ref(false)
|
||||
const fullscreen = ref(false)
|
||||
// 0-->未初始化、1-->播放中、2-->暂停
|
||||
const status = ref(0)
|
||||
const video = ref<HTMLVideoElement | null>(null)
|
||||
const containerElement = ref<HTMLElement | null>(null)
|
||||
let videoUrl: string = ''
|
||||
const header = ref<string>('视频监控')
|
||||
// http://localhost:8888/live?port=1935&app=live&stream=stream1
|
||||
// ffmpeg -re -i "C:\Users\24955\Downloads\1.mp4" -c:v libx264 -c:a aac -f flv rtmp://localhost:1935/live/stream1
|
||||
const id = nanoid()
|
||||
let flvPlayer: FlvJs.Player | null = null
|
||||
|
||||
/* const logListener = (type: any, str: any) => {
|
||||
console.log(type, str)
|
||||
} */
|
||||
|
||||
function toggleScreen() {
|
||||
if (fullscreen.value) {
|
||||
fullscreen.value = false
|
||||
document.exitFullscreen()
|
||||
} else {
|
||||
fullscreen.value = true
|
||||
containerElement.value!.requestFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
function flvErrorHandler(type: string, message: string) {
|
||||
switch (type) {
|
||||
case FlvJs.ErrorTypes.NETWORK_ERROR:
|
||||
console.log(message)
|
||||
Toast.error('网络异常,播放失败')
|
||||
break
|
||||
case FlvJs.ErrorTypes.MEDIA_ERROR:
|
||||
console.log(message)
|
||||
Toast.error('视频格式错误,播放失败')
|
||||
break
|
||||
default:
|
||||
console.log(message)
|
||||
Toast.error('视频播放失败')
|
||||
}
|
||||
}
|
||||
|
||||
function createPlayer() {
|
||||
if (FlvJs.isSupported()) {
|
||||
flvPlayer = FlvJs.createPlayer({
|
||||
type: 'flv',
|
||||
url: videoUrl
|
||||
})
|
||||
|
||||
flvPlayer.on(FlvJs.Events.ERROR, flvErrorHandler)
|
||||
flvPlayer.attachMediaElement(video.value!)
|
||||
|
||||
flvPlayer?.load()
|
||||
play()
|
||||
} else {
|
||||
Toast.error('当前浏览器不支持视频播放')
|
||||
}
|
||||
}
|
||||
|
||||
function play() {
|
||||
flvPlayer?.play()
|
||||
status.value = 1
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
// FlvJs.LoggingControl.removeLogListener(logListener)
|
||||
flvPlayer?.off(FlvJs.Events.ERROR, flvErrorHandler)
|
||||
flvPlayer?.pause()
|
||||
flvPlayer?.unload()
|
||||
flvPlayer?.detachMediaElement()
|
||||
flvPlayer?.destroy()
|
||||
flvPlayer = null
|
||||
status.value = 0
|
||||
}
|
||||
|
||||
function reload() {
|
||||
destroy()
|
||||
createPlayer()
|
||||
play()
|
||||
}
|
||||
|
||||
function pauseOrPlay() {
|
||||
if (status.value === 1) {
|
||||
flvPlayer?.pause()
|
||||
status.value = 2
|
||||
} else {
|
||||
play()
|
||||
}
|
||||
}
|
||||
|
||||
function closeHandler() {
|
||||
destroy()
|
||||
containerElement.value!.style!.width = '0'
|
||||
containerElement.value!.style!.height = '0'
|
||||
videoUrl = ''
|
||||
if (fullscreen.value) toggleScreen()
|
||||
show.value = false
|
||||
}
|
||||
|
||||
function handleFullscreenChange() {
|
||||
fullscreen.value = document.fullscreenElement === containerElement.value
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||
})
|
||||
|
||||
|
||||
defineExpose({
|
||||
open(url: string, title?: string) {
|
||||
status.value = 0
|
||||
videoUrl = url
|
||||
header.value = title ?? '视频监控'
|
||||
show.value = true
|
||||
containerElement.value!.style!.width = '100%'
|
||||
containerElement.value!.style!.height = '100%'
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :id="id" ref="containerElement" class="ix_modal-container">
|
||||
<IxModal v-model:visible="show" :container="containerElement!"
|
||||
:header="header" :mask="false"
|
||||
:on-after-open="createPlayer"
|
||||
:on-before-close="closeHandler"
|
||||
type="default" width="100%">
|
||||
<video ref="video" class="video-element" controls></video>
|
||||
|
||||
<template #footer>
|
||||
<IxTooltip placement="top" title="刷新">
|
||||
<IxButton icon="sync" @click="reload"/>
|
||||
</IxTooltip>
|
||||
<IxTooltip :title="status === 1?'暂停':'播放'" placement="top">
|
||||
<IxButton :icon="status === 1?'pause':'play'" mode="primary" @click="pauseOrPlay"/>
|
||||
</IxTooltip>
|
||||
<IxTooltip :title="fullscreen?'退出全屏':'全屏'" placement="top">
|
||||
<IxButton :icon="fullscreen?'fullscreen-exit':'fullscreen'" @click="toggleScreen"/>
|
||||
</IxTooltip>
|
||||
</template>
|
||||
</IxModal>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.ix_modal-container {
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
|
||||
:deep(.ix-modal) {
|
||||
width 100%
|
||||
height 100%
|
||||
|
||||
.ix-modal-body {
|
||||
width 100%
|
||||
overflow hidden
|
||||
border-top: 1px dashed #E1E5EB;
|
||||
border-bottom: 1px dashed #E1E5EB;
|
||||
|
||||
.video-element {
|
||||
width 100%
|
||||
height 100%
|
||||
}
|
||||
}
|
||||
|
||||
.ix-modal-footer {
|
||||
justify-content center
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 隐藏全屏按钮 */
|
||||
video::-webkit-media-controls-fullscreen-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏播放按钮 */
|
||||
video::-webkit-media-controls-play-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏进度条 */
|
||||
video::-webkit-media-controls-timeline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏当前时间显示 */
|
||||
video::-webkit-media-controls-current-time-display {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏剩余时间显示 */
|
||||
video::-webkit-media-controls-time-remaining-display {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏音量按钮 */
|
||||
video::-webkit-media-controls-mute-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏音量控制条 */
|
||||
video::-webkit-media-controls-volume-slider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏所有控件 */
|
||||
video::-webkit-media-controls-enclosure {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -8,6 +8,7 @@ declare global {
|
|||
microdistrict: string
|
||||
propertyManagement: string
|
||||
status: 'ZhengChang' | 'FenZhengChang'
|
||||
videoUrl?: string
|
||||
}
|
||||
|
||||
interface AddParam {
|
||||
|
|
|
@ -16,7 +16,7 @@ import processHtml from './plugin/html-process'
|
|||
import zipDist from './plugin/zip-dist'
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
||||
import { fileWatcher } from './plugin/file-watcher'
|
||||
import iconfontDts from './plugin/iconfont-dts'
|
||||
import iconfontProcess from './plugin/iconfont-process'
|
||||
|
||||
let viteConfig: UserConfigFnObject = configEnv => {
|
||||
const env = loadEnv(configEnv.mode, process.cwd(), '')
|
||||
|
@ -51,7 +51,7 @@ let viteConfig: UserConfigFnObject = configEnv => {
|
|||
Icons(),
|
||||
fileWatcher({
|
||||
file: './public/iconfont/ali/iconfont.json',
|
||||
fn: iconfontDts('./src/components/iconfont/iconfont.d.ts'),
|
||||
fn: iconfontProcess('./src/components/iconfont'),
|
||||
}),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
|
@ -91,6 +91,11 @@ let viteConfig: UserConfigFnObject = configEnv => {
|
|||
target: env.VITE_HTTP_PROXY_TARGET,
|
||||
rewrite: path => env.VITE_HTTP_SERVER_BASE_URL == null || env.VITE_HTTP_SERVER_BASE_URL == '/' ? path : path.replace(new RegExp(env.VITE_HTTP_SERVER_BASE_URL), ''),
|
||||
} as ProxyOptions,
|
||||
[env.VITE_OSS_UPLOAD_BASE_URL]: {
|
||||
proxyTimeout: 10000,
|
||||
target: 'http://localhost:9000',
|
||||
rewrite: path => env.VITE_OSS_UPLOAD_BASE_URL == null || env.VITE_OSS_UPLOAD_BASE_URL == '/' ? path : path.replace(new RegExp(env.VITE_OSS_UPLOAD_BASE_URL), ''),
|
||||
} as ProxyOptions,
|
||||
[env.VITE_WS_SERVER_BASE_URL]: {
|
||||
ws: true,
|
||||
target: env.VITE_WS_PROXY_TARGET,
|
||||
|
|
Loading…
Reference in New Issue