diff --git a/code/VideoAccess-VCMP/api/app/lib/controllers/application/index.js b/code/VideoAccess-VCMP/api/app/lib/controllers/application/index.js index 0e0130f..3594158 100644 --- a/code/VideoAccess-VCMP/api/app/lib/controllers/application/index.js +++ b/code/VideoAccess-VCMP/api/app/lib/controllers/application/index.js @@ -29,19 +29,29 @@ async function check (ctx) { } async function edit (ctx, next) { - let errMsg = '创建应用失败' const transaction = await ctx.fs.dc.orm.transaction(); try { const { models } = ctx.fs.dc; const { userId } = ctx.fs.api const data = ctx.request.body; - if (data.id) { + let findOption = { where: { name: data.name } } + + if (data.appId) { + findOption.where.id = { $ne: data.appId } + } + + const applicationRes = await models.Application.findOne(findOption) + if (applicationRes) { + throw '已有相同应用名称' + } + + if (data.appId) { // 修改 const storageData = Object.assign({}, data,) await models.Application.update(storageData, { where: { - id: data.id + id: data.appId }, transaction }) @@ -66,13 +76,125 @@ async function edit (ctx, next) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { - message: errMsg + message: typeof error == 'string' ? error : undefined + } + } +} + +async function get (ctx) { + try { + const models = ctx.fs.dc.models; + const { userId, token } = ctx.fs.api + const { limit, page, orderBy, orderDirection } = ctx.query + let findOption = { + where: { + // createUserId: userId, + }, + order: [ + [orderBy || 'id', orderDirection || 'DESC'] //查询排序 + ], + } + + if (limit) { + findOption.limit = limit + } + if (page && limit) { + findOption.offset = page * limit + } + const applicationRes = await models.Application.findAndCountAll(findOption) + + + let createUserIds = new Set() + let cameraIds = [] + for (let c of applicationRes.rows) { + cameraIds.push(c.id) + createUserIds.add(c.createUserId) + } + + // 查用户信息 + const createUserRes = await ctx.app.fs.authRequest.get(`user/${[...createUserIds].join(',') || -1}/message`, { query: { token } }) + + for (let { dataValues: n } of applicationRes.rows) { + const corCreateUser = createUserRes.find(u => u.id == n.createUserId) + n.createUser = { + name: corCreateUser ? corCreateUser.username : '' + } + } + + ctx.status = 200; + ctx.body = { + total: applicationRes.count, + data: applicationRes.rows } + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = {} } } +async function put (ctx) { + try { + const { models } = ctx.fs.dc; + const data = ctx.request.body; + + // TODO 向视频服务发送通知 + + // 库记录 + await models.Application.update({ + forbidden: data.forbidden + }, { + where: { + id: data.appId + } + }) + + ctx.status = 204; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = {} + } +} + +async function del (ctx, next) { + const transaction = await ctx.fs.dc.orm.transaction(); + try { + const models = ctx.fs.dc.models; + const { token } = ctx.fs.api + const { appId } = ctx.params + + const { appKey } = await models.Application.findOne({ + where: { + id: appId + }, + }) || {} + + await models.Application.destroy({ + where: { + id: appId + }, + transaction + }) + + await ctx.app.fs.authRequest.delete(`oauth2/token/invalidate_all`, { + query: { token, appKey } + }) + + await transaction.commit(); + ctx.status = 204; + } catch (error) { + await transaction.rollback(); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = {} + } +} module.exports = { - check, edit, + get, + put, + del, + check, }; \ No newline at end of file diff --git a/code/VideoAccess-VCMP/api/app/lib/routes/application/index.js b/code/VideoAccess-VCMP/api/app/lib/routes/application/index.js index cdd1d63..3cb99b3 100644 --- a/code/VideoAccess-VCMP/api/app/lib/routes/application/index.js +++ b/code/VideoAccess-VCMP/api/app/lib/routes/application/index.js @@ -5,12 +5,21 @@ const application = require('../../controllers/application'); module.exports = function (app, router, opts) { + app.fs.api.logAttr['GET/application'] = { content: '获取应用信息', visible: false }; + router.get('/application', application.get); + + app.fs.api.logAttr['POST/application'] = { content: '创建/修改应用', visible: false }; + router.post('/application', application.edit); + + app.fs.api.logAttr['PUT/application'] = { content: '禁用应用', visible: false } + router.put('/application', application.put); + + app.fs.api.logAttr['DEL/application/:appId'] = { content: '删除应用', visible: false }; + router.del('/application/:appId', application.del); + app.fs.api.logAttr['GET/application/check'] = { content: '检查应用状态', visible: false }; router.get('/application/check', application.check); - // app.fs.api.logAttr['GET/application'] = { content: '获取应用信息', visible: false }; - // router.get('/application', application.get); - app.fs.api.logAttr['POST/application'] = { content: '创建/修改应用', visible: false }; router.post('/application', application.edit); diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/copy.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/copy.png new file mode 100644 index 0000000..6a3771a Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/copy.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/del.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/del.png new file mode 100644 index 0000000..ff9d808 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/del.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/detail_icon.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/detail_icon.png new file mode 100644 index 0000000..0059b3f Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/detail_icon.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/idCopy.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/idCopy.png new file mode 100644 index 0000000..c502758 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/idCopy.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/nextStep.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/nextStep.png new file mode 100644 index 0000000..87c6714 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/nextStep.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/question.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/question.png new file mode 100644 index 0000000..ccf2986 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/question.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/release.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/release.png new file mode 100644 index 0000000..34c0664 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/release.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/stepfour.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/stepfour.png new file mode 100644 index 0000000..52f3378 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/stepfour.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/stepone.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/stepone.png new file mode 100644 index 0000000..5f8d4d6 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/stepone.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/stepthree.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/stepthree.png new file mode 100644 index 0000000..24ffef9 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/stepthree.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/steptwo.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/steptwo.png new file mode 100644 index 0000000..3a01d05 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/steptwo.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/text.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/text.png new file mode 100644 index 0000000..cb6fb6d Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/text.png differ diff --git a/code/VideoAccess-VCMP/web/client/assets/images/imageImg/图科技蓝_小@2x.png b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/图科技蓝_小@2x.png new file mode 100644 index 0000000..b6eb491 Binary files /dev/null and b/code/VideoAccess-VCMP/web/client/assets/images/imageImg/图科技蓝_小@2x.png differ diff --git a/code/VideoAccess-VCMP/web/client/src/sections/application/actions/application.js b/code/VideoAccess-VCMP/web/client/src/sections/application/actions/application.js new file mode 100644 index 0000000..e0fb457 --- /dev/null +++ b/code/VideoAccess-VCMP/web/client/src/sections/application/actions/application.js @@ -0,0 +1,54 @@ +"use strict"; + +import { basicAction } from "@peace/utils"; +import { ApiTable } from "$utils"; + +export function getApplication (query) { + return (dispatch) => + basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_APPLICATION", + query: query, + url: `${ApiTable.getApplication}`, + msg: { option: "获取应用信息" }, + reducer: { name: "applicationData", params: { noClear: true } }, + }); +} + +export function putApplication (data) { + return (dispatch) => + basicAction({ + type: "put", + dispatch: dispatch, + actionType: "PUT_APPLICATION", + data, + url: `${ApiTable.putApplication}`, + msg: { option: data?.forbidden ? "禁用" : "启用" }, //禁用摄像头 + reducer: {}, + }); +} + +export function delApplication (orgId) { + return (dispatch) => + basicAction({ + type: "del", + dispatch: dispatch, + actionType: "DEL_APPLICATION", + url: `${ApiTable.delApplication.replace("{appId}", orgId)}`, + msg: { option: "删除" }, //删除应用 + reducer: {}, + }); +} + +export function postApplication (data) { + return (dispatch) => + basicAction({ + type: "post", + dispatch: dispatch, + data, + actionType: "POST_CHANGE_NVR", + msg: { option: data?.appId ? "修改" : "添加" }, + url: `${ApiTable.postApplication}`, + }); +} \ No newline at end of file diff --git a/code/VideoAccess-VCMP/web/client/src/sections/application/actions/index.js b/code/VideoAccess-VCMP/web/client/src/sections/application/actions/index.js index 7ed1088..0a0ac1f 100644 --- a/code/VideoAccess-VCMP/web/client/src/sections/application/actions/index.js +++ b/code/VideoAccess-VCMP/web/client/src/sections/application/actions/index.js @@ -1,5 +1,8 @@ 'use strict'; -export default { +import * as application from './application' + +export default { + ...application } \ No newline at end of file diff --git a/code/VideoAccess-VCMP/web/client/src/sections/application/components/applyModal.jsx b/code/VideoAccess-VCMP/web/client/src/sections/application/components/applyModal.jsx index a363bb5..e7ed4b9 100644 --- a/code/VideoAccess-VCMP/web/client/src/sections/application/components/applyModal.jsx +++ b/code/VideoAccess-VCMP/web/client/src/sections/application/components/applyModal.jsx @@ -2,79 +2,85 @@ import React, { useState, useEffect, useRef } from "react"; import { connect } from "react-redux"; import { Button, Form, Modal, } from "@douyinfe/semi-ui"; -const ApplyModal = ({ close, modalName, visible }) => { - const form = useRef(); +const ApplyModal = ({ dispatch, actions, close, modalName, visible, appData }) => { + const { applicationCenter } = actions; + const appDatas = appData || {} + const form = useRef(); - - const handleOk = () => { - form.current - .validate() - .then((values) => { - console.log(values); - // close() + const handleOk = () => { + form.current + .validate() + .then((values) => { + if (appDatas?.id) { + values.appId = appDatas?.id + } + dispatch(applicationCenter.postApplication(values)).then((res) => { + console.log(res); + if (res.success) { + close() + form.current.reset() + } }) + }) + } - } - - return close()} - onOk={handleOk} - > -
console.log(values)} - getFormApi={(formApi) => (form.current = formApi)} - > - - - {[{ name: 'web', id: 'web' }, { name: 'app', id: 'app' }, { name: '小程序', id: '小程序' }, { name: '其他', id: '其他' }].map((item, index) => ( - - {item.name} - - ))} - - -
+ return { close(); form.current.reset() }} + onOk={handleOk} + > +
(form.current = formApi)} + > + + + {[{ name: 'web', value: 'web' }, { name: 'app', value: 'app' }, { name: '小程序', value: 'wxapp' }, { name: '其他', value: 'other' }].map((item, index) => ( + + {item.name} + + ))} + + +
} function mapStateToProps (state) { - const { auth, global, members } = state; - return { - loading: members.isRequesting, - user: auth.user, - actions: global.actions, - global: global, - members: members.data, - }; + const { auth, global, members } = state; + return { + loading: members.isRequesting, + user: auth.user, + actions: global.actions, + global: global, + members: members.data, + }; } export default connect(mapStateToProps)(ApplyModal); diff --git a/code/VideoAccess-VCMP/web/client/src/sections/application/containers/applicationCenter.jsx b/code/VideoAccess-VCMP/web/client/src/sections/application/containers/applicationCenter.jsx index 73bffbd..a9b28ab 100644 --- a/code/VideoAccess-VCMP/web/client/src/sections/application/containers/applicationCenter.jsx +++ b/code/VideoAccess-VCMP/web/client/src/sections/application/containers/applicationCenter.jsx @@ -3,15 +3,15 @@ import { connect } from "react-redux"; import moment from "moment"; import qs from "qs"; import { - Button, - Form, - Table, - Pagination, - Popover, - Tag, - Skeleton, - Popconfirm, - Row, + Button, + Form, + Table, + Pagination, + Popover, + Tag, + Skeleton, + Popconfirm, + Row, } from "@douyinfe/semi-ui"; import { SimpleFileDownButton, VideoPlayModal, SkeletonScreen, Setup } from "$components"; // import "../style.less"; @@ -22,349 +22,377 @@ import ApplyModal from "../components/applyModal"; import '../style.less' const ApplicationCenter = (props) => { - const { dispatch, actions, user, loading, equipmentWarehouseCamera } = props; - // const { equipmentWarehouse } = actions; - const [cameraModal, setCameraModal] = useState(false); - const [remarksModal, setRemarksModal] = useState(false); - const [videoPlay, setVideoPlay] = useState(false); - const [modalName, setModalName] = useState(false); //创建或修改 - const [setup, setSetup] = useState(false); //表格设置是否显现 - const [applyModal, setApplyModal] = useState(false); - const [cameraSetup, setcameraSetup] = useState(false); - const [setupp, setSetupp] = useState([]); - const [venderList, setvenderList] = useState([]); //厂商信息 - const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息 - const [search, setSearch] = useState({}); //搜索条件 - const [rowId, setRowId] = useState(); //表格数据id - const [cameraData, setCameraData] = useState({}); //表格传递数据 - const [modify, setModify] = useState(false); //修改 - const [parentCamera, setParentCamera] = useState(""); //级联摄像头父级设备 - const [addNvr, setAddNvr] = useState(false); //nvr页面传递参数打开NVR摄像头添加弹框 - const [nvrNumber, setNvrNumber] = useState(); - const [videoObj, setVideoObj] = useState(); //播放条件 - const [axyData, setAxyData] = useState(); - const [cameraRemarks, setCameraRemarks] = useState([]);//备注 - const api = useRef(); - const searchData = useRef({}) - const limits = useRef(); //每页实际条数 - const page = useRef(query.page); - const deviceClickb = useRef(true) - const APPLICATION = "application"; + const { dispatch, actions, user, loading, applicationData } = props; + const { applicationCenter } = actions; + const [modalName, setModalName] = useState(false); //创建或修改 + const [setup, setSetup] = useState(false); //表格设置是否显现 + const [applyModal, setApplyModal] = useState(false); + const [setupp, setSetupp] = useState([]); + const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息 + const [appData, setAppData] = useState(null); //应用id + const APPLICATION = 'application' + const pageLimit = useRef({ limit: 10, page: 0 }); + const limits = useRef(); //每页实际条数 + const columns = [ + { + title: "序号", + dataIndex: "", + render: (text, r, index) => { + return index + 1; + }, + }, + { + title: "应用名称", + dataIndex: "name", + key: "name", + render: (text, r, index) => { + return r?.name.length > 8 ? + {r?.name} + + } + >{ + `${r?.name.substr(0, 8)}...`} + : r?.name + }, + }, + { + title: "APPID", + dataIndex: "appKey", + key: "appId", - const columns = [ - { - title: "序号", - dataIndex: "", - render: (text, r, index) => { - return index + 1; - }, - }, - { - title: "应用名称", - dataIndex: "name", - key: "name", - }, - { - title: "APPID", - dataIndex: "appId", - key: "appId", - - }, - { - title: "Secret Key", - dataIndex: "secretKey", - key: "secretKey", - - }, - { - title: "操作", - width: "20%", - dataIndex: "", - render: (_, row) => { - return ( -
- - {row.forbidden ? ( - - ) : ( - { + }, + { + title: "操作", + width: "20%", + dataIndex: "", + render: (_, row) => { + return ( +
+ + {row?.forbidden ? ( + + ) : ( + 禁用后,应用系统引入的页面及能力将会暂时失效,请谨慎操作。
} + arrowPointAtCenter={false} + showArrow={true} + position="topRight" + onConfirm={() => { + dispatch(applicationCenter.putApplication({ appId: row?.id, forbidden: !row?.forbidden })).then(() => { + setQuery({ limit: pageLimit.current.limit, page: pageLimit.current.page }) + }) + }} + > + +
+ )} + 删除后,应用系统引入的页面及能力将会永久失效,请谨慎操作。
} + arrowPointAtCenter={false} + width={300} + showArrow={true} + position="topRight" + onConfirm={() => { + dispatch(applicationCenter.delApplication(row?.id)).then(() => { + if (pageLimit.current.page > 0 && limits.current < 2) { + setQuery({ limit: pageLimit.current.limit, page: pageLimit.current.page - 1 }) + } else { + setQuery({ limit: pageLimit.current.limit, page: pageLimit.current.page }) + } + }) + }} + > + + - }} - > - - - )} - { + + ); + }, + } + ]; - }} - > - - + //获取表格属性设置 + function attribute () { + const arr = localStorage.getItem(APPLICATION) + ? JSON.parse(localStorage.getItem(APPLICATION)) + : []; - - ); + const column = [ + { + title: "创建时间", + dataIndex: "createTime", + key: "createTime", + render: (_, r, index) => { + return moment(r.createTime).format("YYYY-MM-DD HH:mm:ss"); }, - } - ]; - - //获取表格属性设置 - function attribute () { - const arr = localStorage.getItem(APPLICATION) - ? JSON.parse(localStorage.getItem(APPLICATION)) - : []; - - const column = [ - { - title: "创建时间", - dataIndex: "createTime", - key: "createTime", + }, + { + title: "创建账号", + dataIndex: "createUserId", + key: "account", + render: (_, r, index) => { + return r?.createUser?.name }, - { - title: "创建账号", - dataIndex: "account", - key: "account", + }, + { + title: "应用类型", + dataIndex: "type", + key: "applicationType", + render: (_, r, index) => { + const types = { web: 'web', app: 'app', wxapp: '小程序', other: '其他' } + return r?.type?.map((item, index) => types[item] + ';') }, - { - title: "应用类型", - dataIndex: "applicationType", - key: "applicationType", + }, + ]; - }, - ]; + for (let i = 0; i < arr.length; i++) { + let colum = column.filter((item) => { + return item.key === arr[i]; + }); + columns.splice(i + 4, 0, colum[0]); + } + setSetupp(columns); + } + const tableList = [//表格属性 + { + title: '详情信息', + list: [ + { name: "创建时间", value: "createTime" }, + { name: "创建账号", value: "account" }, + { name: "应用类型", value: "applicationType" }, + ] + }, + ]; - for (let i = 0; i < arr.length; i++) { - let colum = column.filter((item) => { - return item.key === arr[i]; - }); - columns.splice(i + 4, 0, colum[0]); - } - setSetupp(columns); - } - const tableList = [//表格属性 - { - title: '详情信息', - list: [ - { name: "创建时间", value: "createTime" }, - { name: "创建账号", value: "account" }, - { name: "应用类型", value: "applicationType" }, - ] - }, - ]; + //获取应用信息 + const details = (data) => { + pageLimit.current = query + dispatch(applicationCenter.getApplication(pageLimit.current)).then((res) => { + limits.current = res.payload.data.data.length + }); + } + useEffect(() => { + details() + }, [query]) - useEffect(() => { - //初始化表格显示设置 - localStorage.getItem(APPLICATION) == null - ? localStorage.setItem( - APPLICATION, - JSON.stringify(["createTime",]) - ) - : ""; - attribute(); - }, []) - return ( - <> -
-