电子地图管理

This commit is contained in:
joylink_zhaoerwei 2024-11-18 17:07:07 +08:00
parent 2544f5e131
commit a4e63a4ab6
12 changed files with 2075 additions and 57 deletions

View File

@ -0,0 +1,138 @@
import { api } from 'src/boot/axios';
import { PageDto } from './ApiCommon';
import { DraftDataType } from './DraftApi';
export enum LineType {
UNKNOWN,
UR = 'UR',
IR = 'IR',
CR = 'CR',
}
export const lineTypeOption = [
{
label: '城市轨道交通',
value: LineType.UR,
},
{
label: '城际轨道交通',
value: LineType.IR,
},
{
label: '市域轨道交通',
value: LineType.CR,
},
];
export const searchLineTypeOption = [
{
label: '全部',
value: LineType.UNKNOWN,
},
...lineTypeOption,
];
export interface DraftItem {
id: number;
name: string;
dataType: DraftDataType;
options: string;
data: string;
userId: number;
defaultReleaseDataId: number;
isShared: boolean;
createdAt: string;
updatedAt: string;
defaultReleaseDataName: string;
userName: string;
}
export interface LineInfoOptionsInput {
lineType: LineType;
city?: string;
lineCode?: string;
}
export interface PagingQueryParams {
paging: {
page: number;
itemsPerPage: number;
};
query: {
dataType: DraftDataType;
options?: LineInfoOptionsInput;
name?: string;
isShared?: boolean;
userId?: number;
};
}
export interface DraftEmDataDto {
draftData: DraftItem;
options: LineInfoOptionsInput;
}
export async function draftPageQuery(
params: PagingQueryParams
): Promise<PageDto<DraftEmDataDto>> {
const query = `
query userDraftEmDataPaging($paging: PageQueryDto, $query: UserDraftEmDataFilterDto) {
userDraftEmDataPaging(paging: $paging, query: $query) {
total
items {
draftData {id name dataType userName defaultReleaseDataId createdAt updatedAt isShared defaultReleaseDataName}
options {lineType city lineCode}
}
}
}
`;
const response = await api.post('', {
query,
variables: params,
});
return response.data.data.userDraftEmDataPaging;
}
export async function sharedDraftPageQuery(
params: PagingQueryParams
): Promise<PageDto<DraftEmDataDto>> {
const query = `
query sharedDraftEmDataPaging($paging: PageQueryDto, $query: SharedDraftEmDataFilterDto) {
sharedDraftEmDataPaging(paging: $paging, query: $query) {
total
items {
draftData {id name dataType userName userId defaultReleaseDataId createdAt updatedAt isShared defaultReleaseDataName}
options {lineType city lineCode}
}
}
}
`;
const response = await api.post('', {
query,
variables: params,
});
return response.data.data.sharedDraftEmDataPaging;
}
/**
* 稿
* @param params
* @returns
*/
interface CreateDraftEmDto {
input: {
name: string;
options: LineInfoOptionsInput;
};
}
export function createDraft(params: CreateDraftEmDto) {
const mutation = `
mutation createDraftEmData($input: CreateDraftEmDto) {
createDraftEmData(input: $input) {
name
}
}
`;
return api.post('', {
query: mutation,
variables: params,
});
}

View File

@ -0,0 +1,63 @@
import { api } from 'src/boot/axios';
import { PageDto } from './ApiCommon';
import { DraftDataType } from './DraftApi';
import { LineInfoOptionsInput } from './ElectronicMapDraftApi';
export interface PublishItem {
id: number;
name: string;
dataType: DraftDataType;
options: string;
data: string;
usedVersionId: number;
userId: number;
isPublished: boolean;
createdAt: string;
updatedAt: string;
description: string;
userName: string;
}
interface PagingQueryParams {
page: {
page: number;
itemsPerPage: number;
};
query: {
dataType: DraftDataType;
userId?: number;
name?: string;
isPublished?: boolean;
options?: LineInfoOptionsInput;
};
}
export interface PublishEmDataDto {
releaseData: PublishItem;
options: LineInfoOptionsInput;
}
/**
*
* @param params
* @returns
*/
export async function publishPageQuery(
params: PagingQueryParams
): Promise<PageDto<PublishEmDataDto>> {
const query = `
query releaseEmDataPaging($page: PageQueryDto, $query: ReleaseEmDataFilterDto) {
releaseEmDataPaging(page: $page, query: $query) {
total
items {
releaseData {id name dataType usedVersionId userName isPublished createdAt updatedAt description }
options {lineType city lineCode}
}
}
}
`;
const response = await api.post('', {
query,
variables: params,
});
return response.data.data.releaseEmDataPaging;
}

View File

@ -115,8 +115,9 @@ export async function login(userLoginDto: LoginInfo) {
} }
export interface User { export interface User {
id: string; id: number;
name: string; username: string;
nickname: string;
mobile: string; mobile: string;
email: string; email: string;
roles: []; roles: [];
@ -171,7 +172,7 @@ export async function getLoginUserInfo() {
const query = ` const query = `
query loginUserInfo { query loginUserInfo {
loginUserInfo{ loginUserInfo{
id id username nickname mobile email roles createdAt updatedAt
} }
} }
`; `;
@ -181,20 +182,46 @@ export async function getLoginUserInfo() {
return response.data.data; return response.data.data;
} }
// /** export enum Role {
// * 获取jwt令牌(mqtt验证) UNKNOWN,
// */ ADMIN = 'ADMIN',
// export async function getJwt() { USER = 'USER',
// const query = ` ORG_MANAGER = 'ORG_MANAGER', //组织管理员
// query getJwt { ORG_TEACHER = 'ORG_TEACHER', //组织教师
// getJwt ORG_STUDENT = 'ORG_STUDENT', //组织学生
// } ORG_GUEST = 'ORG_GUEST', //组织访客
// `; }
// const response = await api.post('', {
// query: query, export const rolesOptions = [
// }); { label: '系统管理员', value: Role.ADMIN },
// return response.data.data; { label: '普通用户', value: Role.USER },
// } { label: '组织管理员', value: Role.ORG_MANAGER },
{ label: '组织教师', value: Role.ORG_TEACHER },
{ label: '组织学生', value: Role.ORG_STUDENT },
{ label: '组织访客', value: Role.ORG_GUEST },
];
/**
*
* @param variables
* @returns
*/
export async function updateUserRoles(variables: {
userId: number;
roles: Role[];
}) {
const mutation = `
mutation updateUserRoles($userId: Int,$roles: [Role!]) {
updateUserRoles(userId: $userId,roles: $roles,){
id
}
}
`;
return api.post('', {
query: mutation,
variables,
});
}
/** /**
* *

View File

@ -1,11 +1,31 @@
import { toUint8Array } from 'js-base64'; import { fromUint8Array, toUint8Array } from 'js-base64';
import { GraphicData, IDrawApp, IGraphicStorage, newDrawApp } from 'jl-graphic'; import {
import { getDraft } from 'src/api/DraftApi'; calculateDistanceFromPointToLine,
CombinationKey,
ContextMenu,
distance2,
getRectangleCenter,
GraphicData,
IDrawApp,
IGraphicStorage,
KeyListener,
MenuItemOptions,
newDrawApp,
} from 'jl-graphic';
import { getDraft, saveDraft } from 'src/api/DraftApi';
import { electronicMapGraphicData } from 'src/protos/electronicMap_graphic_data'; import { electronicMapGraphicData } from 'src/protos/electronicMap_graphic_data';
import { useDrawStore } from 'src/stores/electronicMap-draw-store'; import { useDrawStore } from 'src/stores/electronicMap-draw-store';
import { PlatformDraw } from 'src/graphics/electronicMap/platform/PlatformDrawAssistant'; import { PlatformDraw } from 'src/graphics/electronicMap/platform/PlatformDrawAssistant';
import { PlatformTemplate } from 'src/graphics/electronicMap/platform/Platform'; import {
Platform,
PlatformTemplate,
} from 'src/graphics/electronicMap/platform/Platform';
import { PlatformData } from './graphics/electronicMap/PlatformInteraction'; import { PlatformData } from './graphics/electronicMap/PlatformInteraction';
import { errorNotify, successNotify } from 'src/utils/CommonNotify';
import { common } from 'src/protos/common';
import { toStorageTransform } from './graphics/GraphicDataBase';
import { Section } from 'src/graphics/electronicMap/section/Section';
import { OneClickGenerateDraw, OneClickGenerateTemplate } from 'src/graphics/electronicMap/trainWindow/oneClickDrawAssistant';
let electronicMapDrawApp: IDrawApp | null = null; let electronicMapDrawApp: IDrawApp | null = null;
@ -20,19 +40,166 @@ export function destroyElectronicMapDrawApp(): void {
} }
} }
const UndoOptions: MenuItemOptions = {
name: '撤销',
};
const RedoOptions: MenuItemOptions = {
name: '重做',
};
const SelectAllOptions: MenuItemOptions = {
name: '全选',
};
export const DefaultEmCanvasMenu = new ContextMenu({
name: '绘制-画布菜单',
groups: [
{
items: [UndoOptions, RedoOptions],
},
{
items: [SelectAllOptions],
},
],
});
export function initElectronicMapDrawApp(): IDrawApp { export function initElectronicMapDrawApp(): IDrawApp {
electronicMapDrawApp = newDrawApp({ electronicMapDrawApp = newDrawApp({
dataLoader: loadThDrawDatas, dataLoader: loadElectronicMapDrawDatas,
}); });
const app = electronicMapDrawApp; const app = electronicMapDrawApp;
new PlatformDraw( new OneClickGenerateDraw(app, new OneClickGenerateTemplate()),
app, new PlatformDraw(app, new PlatformTemplate(new PlatformData()));
new PlatformTemplate(new PlatformData())
// 画布右键菜单
app.registerMenu(DefaultEmCanvasMenu);
app.canvas.on('_rightclick', (e) => {
if (app.drawing) return;
UndoOptions.disabled = !app.opRecord.hasUndo;
RedoOptions.disabled = !app.opRecord.hasRedo;
UndoOptions.handler = () => {
app.opRecord.undo();
};
RedoOptions.handler = () => {
app.opRecord.redo();
};
SelectAllOptions.handler = () => {
app.selectAllGraphics();
};
DefaultEmCanvasMenu.open(e.global);
});
app.on('destroy', () => {
generateAxleCountingConfig =
new electronicMapGraphicData.GenerateAxleCountingConfig();
});
app.addKeyboardListener(
new KeyListener({
value: 'KeyS',
global: true,
combinations: [CombinationKey.Ctrl],
onPress: () => {
saveDrawToServer(saveDrawDatas(app));
},
})
); );
// KeyA 用于区段复制--控制生成的区段位置
const graphicCopyPlugin = app.app.graphicCopyPlugin;
const copySectionListener = new KeyListener({
value: 'KeyA',
global: true,
onPress: () => {
graphicCopyPlugin.updateMoveLimit('sectionPointLimit');
},
});
graphicCopyPlugin.addGraphicControlers([
{
controlerList: [copySectionListener],
check: () => {
if (
graphicCopyPlugin.copys.length == 1 &&
graphicCopyPlugin.copys[0].type == Section.Type
)
return true;
return false;
},
moveLimitOption: {
moveLimitName: 'sectionPointLimit',
moveLimit: (e) => {
const mousePos = app.toCanvasCoordinates(e.global);
const selectSection = app.selectedGraphics[0] as Section;
let selectSectionLeft = selectSection.localToCanvasPoint(
selectSection.getStartPoint()
);
let selectSectionRight = selectSection.localToCanvasPoint(
selectSection.getEndPoint()
);
[selectSectionLeft, selectSectionRight] =
selectSectionLeft.x < selectSectionRight.x
? [selectSectionLeft, selectSectionRight]
: [selectSectionRight, selectSectionLeft];
//要移动到目标位的区段
const sections = app.queryStore.queryByType<Section>(Section.Type);
const minDistanceSection = sections.reduce((prev, cur) => {
const prevDistance = calculateDistanceFromPointToLine(
prev.localToCanvasPoint(prev.getStartPoint()),
prev.localToCanvasPoint(prev.getEndPoint()),
mousePos
);
const curDistance = calculateDistanceFromPointToLine(
cur.localToCanvasPoint(cur.getStartPoint()),
cur.localToCanvasPoint(cur.getEndPoint()),
mousePos
);
return prevDistance > curDistance ||
(prevDistance == curDistance &&
distance2(
prev.localToCanvasPoint(prev.getStartPoint()),
mousePos
) >
distance2(
cur.localToCanvasPoint(cur.getStartPoint()),
mousePos
))
? cur
: prev;
});
const minDistanceRefSectionsPos =
minDistanceSection.localToCanvasPoint(
getRectangleCenter(
minDistanceSection.lineGraphic.getLocalBounds()
)
);
let minDistanceSectionLeft = minDistanceSection.localToCanvasPoint(
minDistanceSection.getStartPoint()
);
let minDistanceSectionRight = minDistanceSection.localToCanvasPoint(
minDistanceSection.getEndPoint()
);
[minDistanceSectionLeft, minDistanceSectionRight] =
minDistanceSectionLeft.x < minDistanceSectionRight.x
? [minDistanceSectionLeft, minDistanceSectionRight]
: [minDistanceSectionRight, minDistanceSectionLeft];
if (mousePos.x > minDistanceRefSectionsPos.x) {
graphicCopyPlugin.container.position.x =
minDistanceSectionRight.x - selectSectionLeft.x;
graphicCopyPlugin.container.position.y =
minDistanceSectionRight.y - selectSectionLeft.y;
} else {
graphicCopyPlugin.container.position.x =
minDistanceSectionLeft.x - selectSectionRight.x;
graphicCopyPlugin.container.position.y =
minDistanceSectionLeft.y - selectSectionRight.y;
}
},
},
},
]);
return electronicMapDrawApp; return electronicMapDrawApp;
} }
export async function loadThDrawDatas(): Promise<IGraphicStorage> { export async function loadElectronicMapDrawDatas(): Promise<IGraphicStorage> {
const drawStore = useDrawStore(); const drawStore = useDrawStore();
const id = drawStore.draftId; const id = drawStore.draftId;
if (!id) { if (!id) {
@ -46,6 +213,9 @@ export async function loadThDrawDatas(): Promise<IGraphicStorage> {
); );
const datas: GraphicData[] = []; const datas: GraphicData[] = [];
generateAxleCountingConfig = storage.generateAxleCountingConfig; generateAxleCountingConfig = storage.generateAxleCountingConfig;
storage.Platforms.forEach((platform) => {
datas.push(new PlatformData(platform));
});
console.log(storage, 'storage'); console.log(storage, 'storage');
return Promise.resolve({ return Promise.resolve({
canvasProperty: storage.canvas, canvasProperty: storage.canvas,
@ -58,8 +228,41 @@ export async function loadThDrawDatas(): Promise<IGraphicStorage> {
} }
} }
export function saveDrawDatas() { export function saveDrawDatas(app: IDrawApp) {
console.log(111); const storage = new electronicMapGraphicData.ElectronicMapGraphicStorage();
const canvasData = app.canvas.saveData();
storage.canvas = new common.Canvas({
width: canvasData.width,
height: canvasData.height,
backgroundColor: canvasData.backgroundColor,
viewportTransform: toStorageTransform(canvasData.viewportTransform),
});
const graphics = app.queryStore.getAllGraphics();
graphics.forEach((g) => {
if (g instanceof Platform) {
const platformData = g.saveData();
storage.Platforms.push((platformData as PlatformData).data);
}
});
storage.generateAxleCountingConfig = generateAxleCountingConfig;
const base64 = fromUint8Array(storage.serialize());
console.log('保存数据', storage);
return base64;
}
export function saveDrawToServer(base64: string) {
const drawStore = useDrawStore();
const id = drawStore.draftId;
if (!id) {
return;
}
saveDraft({ id, data: base64 })
.then(() => {
successNotify('保存数据成功!');
})
.catch((err) => {
errorNotify(err.message, err);
});
} }
//一键生成计轴配置 //一键生成计轴配置

View File

@ -72,7 +72,7 @@
<q-item-label caption>ID</q-item-label> <q-item-label caption>ID</q-item-label>
</q-item-section> </q-item-section>
<q-item-section avatar> <q-item-section avatar>
<q-item-label>{{ userInfo?.id }}</q-item-label> <q-item-label>{{ userInfo.id }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item> <q-item>
@ -80,7 +80,15 @@
<q-item-label caption>用户名</q-item-label> <q-item-label caption>用户名</q-item-label>
</q-item-section> </q-item-section>
<q-item-section avatar> <q-item-section avatar>
<q-item-label>{{ userInfo?.name }}</q-item-label> <q-item-label>{{ userInfo.username }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>昵称</q-item-label>
</q-item-section>
<q-item-section avatar>
<q-item-label>{{ userInfo.nickname }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item> <q-item>
@ -89,7 +97,7 @@
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label avatar style="text-align: right"> <q-item-label avatar style="text-align: right">
{{ getRoleName(userInfo?.roles) }} {{ getRoleName(userInfo.roles) }}
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
@ -98,15 +106,27 @@
<q-item-label caption>电话</q-item-label> <q-item-label caption>电话</q-item-label>
</q-item-section> </q-item-section>
<q-item-section avatar> <q-item-section avatar>
<q-item-label>{{ userInfo?.mobile }}</q-item-label> <q-item-label>{{ userInfo.mobile }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item> <q-item>
<q-item-section> <q-item-section>
<q-item-label caption>注册时间</q-item-label> <q-item-label caption>创建时间</q-item-label>
</q-item-section> </q-item-section>
<q-item-section avatar> <q-item-section avatar>
<q-item-label>{{ userInfo?.register_time }}</q-item-label> <q-item-label>{{
formatTime(userInfo.createdAt)
}}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>更新时间</q-item-label>
</q-item-section>
<q-item-section avatar>
<q-item-label>{{
formatTime(userInfo.updatedAt)
}}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
@ -123,10 +143,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue'; import { ref, reactive } from 'vue';
import SysMenu from 'src/components/SysMenu.vue'; import SysMenu from 'src/components/SysMenu.vue';
// import { useRoute } from 'vue-router'; import { useRouter } from 'vue-router';
import { clearJwtToken } from 'src/configs/TokenManage'; import { clearJwtToken } from 'src/configs/TokenManage';
import { Dialog } from 'quasar'; import { Dialog } from 'quasar';
import { getLoginUserInfo, rolesOptions } from 'src/api/UserApi';
import { errorNotify } from 'src/utils/CommonNotify';
const router = useRouter();
const leftDrawerOpen = ref(false); const leftDrawerOpen = ref(false);
function toggleLeftDrawer() { function toggleLeftDrawer() {
leftDrawerOpen.value = !leftDrawerOpen.value; leftDrawerOpen.value = !leftDrawerOpen.value;
@ -152,9 +175,23 @@ function onLeftResize(size: { width: number; height: number }) {
} }
const showInfo = ref(false); const showInfo = ref(false);
const userInfo = null; const userInfo = ref({
function showUserInfo() { username: '',
nickname: '',
mobile: '',
email: '',
createdAt: '',
updatedAt: '',
roles: [],
});
async function showUserInfo() {
const res = await getLoginUserInfo();
if (res.data?.errors && res.data?.errors.length) {
errorNotify('报错', res.data.errors[0].message);
} else {
showInfo.value = true; showInfo.value = true;
userInfo.value = res.loginUserInfo;
}
} }
function closeShowInfo() { function closeShowInfo() {
showInfo.value = false; showInfo.value = false;
@ -168,15 +205,22 @@ function logOut() {
persistent: true, persistent: true,
}).onOk(() => { }).onOk(() => {
clearJwtToken(); clearJwtToken();
// router.push({ name: 'login' }); router.push({ name: 'login' });
}); });
} }
function getRoleName(list: { id: number; name: string }[] | undefined) { function formatTime(data: string) {
return new Date(data).toLocaleString();
}
function getRoleName(roles: string[]) {
let name = ''; let name = '';
if (list && list.length > 0) { if (roles && roles.length > 0) {
const arr = list.map((item) => { const arr = roles.map((role) => {
return item.name; for (let i = 0; i < rolesOptions.length; i++) {
if (rolesOptions[i].value == role) {
return rolesOptions[i].label;
}
}
}); });
name = arr.join(''); name = arr.join('');
} }

View File

@ -137,7 +137,9 @@ import { Platform } from 'src/graphics/electronicMap/platform/Platform';
import { Section } from 'src/graphics/electronicMap/section/Section'; import { Section } from 'src/graphics/electronicMap/section/Section';
import { Turnout } from 'src/graphics/electronicMap/turnout/Turnout'; import { Turnout } from 'src/graphics/electronicMap/turnout/Turnout';
import { Signal } from 'src/graphics/electronicMap/signal/Signal'; */ import { Signal } from 'src/graphics/electronicMap/signal/Signal'; */
import { saveDrawToServer } from 'src/drawApp/commonApp'; import { saveDrawDatas, saveDrawToServer } from 'src/drawApp/electronicMapApp';
import { saveAsDraft } from 'src/api/DraftApi';
import { successNotify } from 'src/utils/CommonNotify';
const $q = useQuasar(); const $q = useQuasar();
const route = useRoute(); const route = useRoute();
@ -287,8 +289,8 @@ function onResize() {
} }
function saveAllDrawDatas() { function saveAllDrawDatas() {
let base64 = ''; const drawApp = drawStore.getDrawApp();
saveDrawToServer(base64); saveDrawToServer(saveDrawDatas(drawApp));
} }
function buildRelations() { function buildRelations() {
@ -308,4 +310,19 @@ function backConfirm() {
const saveAsDialog = ref(false); const saveAsDialog = ref(false);
const saveAsName = ref(''); const saveAsName = ref('');
async function saveAs(name: string) {
const res = await saveAsDraft({
id: +route.params.id as number,
name,
});
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
successNotify('另存为草稿成功');
saveAsDialog.value = false;
}
}
</script> </script>

View File

@ -0,0 +1,838 @@
<template>
<div class="q-pa-md">
<q-table
ref="tableRef"
title="草稿图"
:style="{ height: tableHeight + 'px' }"
:rows="rows"
:columns="columnDefs"
row-key="id"
v-model:pagination="pagination"
:rows-per-page-options="[10, 20, 50, 100]"
:loading="loading"
:filter="filter"
binary-state-sort
@request="onRequest"
>
<template v-slot:top-right>
<div class="q-gutter-md q-mt-none row justify-center items-start">
<q-input dense debounce="1000" v-model="filter.name" label="名称" />
<q-select
dense
v-model="filter.lineType"
:options="searchLineTypeOption"
emit-value
map-options
options-dense
class="ellipsis"
label="线路类型"
style="width: 130px"
/>
<q-select
v-if="route.name == 'emDraft'"
dense
v-model="filter.isShared"
:options="isSharedOption"
class="ellipsis"
label="共享相关"
style="width: 130px"
/>
<q-btn
color="primary"
v-if="route.name == 'emDraft'"
label="新建"
@click="showCreateDialog"
/>
<q-btn
v-if="route.name !== 'emDraft'"
flat
round
color="primary"
icon="search"
/>
</div>
</template>
<template v-slot:header-cell-name="props">
<q-th :props="props">
{{ props.col.label }}
<q-icon name="edit" />
</q-th>
</template>
<template v-slot:body-cell-name="props">
<q-td :props="props" class="cursor-pointer">
{{ props.row.name }}
<q-popup-edit
title="编辑名称"
v-model="props.row.name"
buttons
:cover="false"
:validate="caloriesRangeValidation"
@hide="caloriesRangeValidation"
@update:model-value="saveRowDataName(props.row)"
v-slot="scope"
>
<q-input
v-model="scope.value"
hint="请输入名称!"
:error="errorCalories"
:error-message="errorMessageCalories"
dense
autofocus
@keyup.enter="scope.set"
/>
</q-popup-edit>
</q-td>
</template>
<template v-slot:body-cell-operations="props">
<q-td :props="props">
<div class="q-gutter-sm row justify-center">
<q-btn
color="primary"
:disable="operateDisabled"
label="编辑"
@click="goToPath(props.row)"
/>
<q-btn-dropdown color="primary" label="发布">
<q-list>
<q-item
v-for="item in publishMenuConfig"
:key="item.label"
clickable
v-close-popup
@click="item.click(props.row)"
>
<q-item-section>
<q-item-label>{{ item.label }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn
v-if="
route.name == 'emDraft' || props.row.userId == authStore.userId
"
color="info"
style="width: 80px"
:disable="operateDisabled"
:label="props.row.isShared ? '取消共享' : '共享'"
@click="sharedDraftData(props.row)"
/>
<q-btn
v-if="
route.name == 'emDraft' || props.row.userId == authStore.userId
"
color="red"
:disable="operateDisabled"
label="删除"
@click="deleteData(props.row)"
/>
</div>
</q-td>
</template>
</q-table>
<q-dialog
v-model="createFormShow"
persistent
transition-show="scale"
transition-hide="scale"
>
<q-card style="width: 300px">
<q-card-section>
<q-form ref="myForm" @submit="onCreate" class="q-gutter-md">
<div class="text-h6">新建草稿图</div>
<q-input
outlined
label="名称 * "
v-model="createForm.draftName"
lazy-rules
:rules="[(val) => val.length > 0 || '请输入名称!']"
@blur="checkDraftName"
/>
<q-input
outlined
label="线路编号"
v-model="createForm.lineCode"
lazy-rules
/>
<q-input
outlined
label="所属城市"
v-model="createForm.city"
lazy-rules
/>
<q-select
v-model="createForm.lineType"
:options="lineTypeOption"
emit-value
map-options
label="线路类型"
lazy-rules
/>
<q-card-actions align="right">
<q-btn color="primary" label="创建" type="submit" />
<q-btn label="取消" v-close-popup />
</q-card-actions>
</q-form>
</q-card-section>
</q-card>
</q-dialog>
<q-dialog
v-model="publishFormShow"
persistent
transition-show="scale"
transition-hide="scale"
>
<q-card style="width: 300px">
<q-card-section>
<q-form
ref="pubForm"
@submit="publishDraftGraphics"
class="q-gutter-md"
>
<div class="text-h6">草稿发布</div>
<q-input
outlined
disable
label="草稿名称"
v-model="publishForm.draftName"
/>
<q-input
outlined
:disable="publishNameDisable"
label="发布名称 * "
v-model="publishForm.pubName"
lazy-rules
:rules="[(val) => val.length > 0 || '请输入名称!']"
@blur="checkPublishName"
/>
<q-input
outlined
label="备注 * "
v-model="publishForm.note"
lazy-rules
:rules="[(val) => val.length > 0 || '请输入备注!']"
/>
<q-card-actions align="right">
<q-btn color="primary" label="发布" type="submit" />
<q-btn label="取消" @click="canclePublish" />
</q-card-actions>
</q-form>
</q-card-section>
</q-card>
</q-dialog>
<q-dialog
v-model="publishInfoShow"
persistent
transition-show="scale"
transition-hide="scale"
>
<q-card style="width: 1400px; max-width: 80vw">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">{{ draftInfo.name }}设置默认发布</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section>
<q-table
ref="publishTableRef"
:rows="publishRows"
:style="{ height: tableHeight * 0.6 + 'px' }"
:columns="publishColumnDefs"
v-model:pagination="publishPagination"
:rows-per-page-options="[10, 20, 50, 100]"
:loading="publishLoading"
@request="publishOnRequest"
>
<template v-slot:body-cell-operations="props">
<q-td :props="props" style="width: 150px">
<div class="q-gutter-sm row justify-center">
<q-btn
v-if="props.row.id !== currentUsedPublishVersionId"
color="warning"
label="此版本设为默认发布"
@click="setDefaultPublishFn(props.row)"
/>
</div>
</q-td>
</template>
</q-table>
</q-card-section>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed, nextTick } from 'vue';
import { useQuasar, type QTableColumn, QForm } from 'quasar';
import {
deleteDraft,
DraftDataType,
sharedDraft,
setDraftRename,
draftDataExist,
} from '../api/DraftApi';
import {
DraftEmDataDto,
draftPageQuery,
PagingQueryParams,
lineTypeOption,
createDraft,
LineType,
sharedDraftPageQuery,
DraftItem,
searchLineTypeOption,
} from 'src/api/ElectronicMapDraftApi';
import { ApiError } from 'src/boot/axios';
import { useRoute, useRouter } from 'vue-router';
import {
publishDraft,
publishDraftToDefault,
setDefaultPublish,
isReleaseDataNameExists,
} from 'src/api/PublishApi';
import { useAuthStore } from 'src/stores/auth-store';
import { PageDto } from 'src/api/ApiCommon';
import { PublishItem, publishPageQuery } from 'src/api/ElectronicMapPublishApi';
const $q = useQuasar();
const router = useRouter();
const props = withDefaults(
defineProps<{
sizeHeight: number;
}>(),
{ sizeHeight: 500 }
);
const authStore = useAuthStore();
const route = useRoute();
const tableHeight = computed(() => {
return props.sizeHeight - 32;
});
onMounted(() => {
tableRef.value.requestServerInteraction();
});
const columnDefs: QTableColumn[] = [
{
name: 'name',
label: '名称',
field: 'name',
required: true,
align: 'center',
},
{
name: 'type',
label: '线路类型',
field: (row) => {
return getTypeName(row);
},
align: 'center',
},
{
name: 'userName',
label: '创建人',
field: 'userName',
align: 'center',
},
{
name: 'createdAt',
label: '创建时间',
field: (row) => new Date(row.createdAt).toLocaleString(),
align: 'center',
},
{
name: 'updatedAt',
label: '更新时间',
field: (row) => new Date(row.updatedAt).toLocaleString(),
align: 'center',
},
{
name: 'isShared',
label: '是否共享',
field: (row) => {
return row.isShared ? '是' : '否';
},
align: 'center',
},
{
name: 'defaultReleaseDataName',
label: '默认发布',
field: 'defaultReleaseDataName',
align: 'center',
},
{ name: 'operations', label: '操作', field: 'operations', align: 'center' },
];
const operateDisabled = ref(false);
const tableRef = ref();
const rows = reactive([]);
const filter = reactive({
name: '',
lineType: LineType.UNKNOWN,
isShared: '全部',
});
const loading = ref(false);
const pagination = ref({
sortBy: 'desc',
descending: false,
page: 1,
rowsPerPage: 10,
rowsNumber: 10,
});
let allRequestData: DraftEmDataDto[] = [];
async function onRequest(props: any) {
const { page, rowsPerPage } = props.pagination;
loading.value = true;
const variables: PagingQueryParams = {
paging: {
page: page,
itemsPerPage: rowsPerPage,
},
query: {
dataType: DraftDataType.EM,
},
};
if (filter.name) {
Object.assign(variables.query, { name: filter.name });
}
if (filter.lineType) {
Object.assign(variables.query, { options: { lineType: filter.lineType } });
}
if (filter.isShared !== '全部') {
Object.assign(variables.query, {
isShared: filter.isShared == '共享' ? true : false,
});
}
try {
let response: PageDto<DraftEmDataDto>;
if (route.name == 'emDraft') {
response = await draftPageQuery(variables);
} else {
response = await sharedDraftPageQuery(variables);
}
pagination.value.rowsNumber = response.total;
pagination.value.page = page;
pagination.value.rowsPerPage = rowsPerPage;
rows.splice(
0,
rows.length,
...(response.items.map((item) => item.draftData) as [])
);
allRequestData = response.items;
} catch (err) {
const error = err as ApiError;
$q.notify({
type: 'negative',
message: error.title,
});
} finally {
loading.value = false;
}
}
function getTypeName(row: DraftItem) {
const lineType = allRequestData.find((item) => item.draftData.id == row.id)
.options.lineType;
const lineTypeName = lineTypeOption.find(
(item) => item.value == lineType
).label;
return lineTypeName;
}
//
const isSharedOption = ['全部', '共享', '不共享'];
//
const createFormShow = ref(false);
function showCreateDialog() {
createFormShow.value = true;
createForm.draftName = '';
createForm.city = '';
createForm.lineCode = '';
createForm.lineType = LineType.UR;
}
const createForm = reactive({
draftName: '',
lineType: LineType.UR,
city: '',
lineCode: '',
});
const myForm = ref<QForm | null>(null);
async function checkDraftName() {
if (!createForm.draftName) return;
const res = await draftDataExist({
dataType: DraftDataType.EM,
name: createForm.draftName,
});
if (res.data.data.draftDataExist) {
$q.notify({
type: 'negative',
message: '草稿名字已存在,请换一个名字',
});
}
}
function onCreate() {
myForm.value?.validate().then(async (res) => {
if (res) {
operateDisabled.value = true;
const variables = {
input: {
name: createForm.draftName,
options: { lineType: createForm.lineType },
},
};
if (createForm.lineCode) {
Object.assign(variables.input.options, {
lineCode: createForm.lineCode,
});
}
if (createForm.city) {
Object.assign(variables.input.options, {
city: createForm.city,
});
}
const res = await createDraft(variables);
operateDisabled.value = false;
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
createFormShow.value = false;
createForm.draftName = '';
createForm.lineType = LineType.UR;
tableRef.value.requestServerInteraction();
}
}
});
}
//
function goToPath(row: DraftItem) {
let path = `/emPainting/${row.id}`;
const lineType = allRequestData.find((item) => item.draftData.id == row.id)
.options.lineType;
router.push({
path: path,
query: { lineType, isShared: row.isShared ? 1 : 2 },
});
}
//
const publishMenuConfig = [
{ label: '新发布', click: newPublish },
{ label: '发布到默认', click: publishToDefault },
{ label: '设置默认发布', click: showpublishFn },
];
const pubForm = ref<QForm | null>(null);
const publishFormShow = ref(false);
const publishForm = reactive({
id: '',
draftName: '',
pubName: '',
note: '',
});
function newPublish(row: DraftItem) {
publishFormShow.value = true;
publishForm.id = row.id + '';
publishForm.draftName = row.name;
publishForm.pubName = row.defaultReleaseDataName || row.name;
publishForm.note = '';
}
async function checkPublishName() {
if (!publishForm.draftName) return;
const res = await isReleaseDataNameExists({
dataType: DraftDataType.EM,
name: publishForm.draftName,
});
if (res.data.data.isReleaseDataNameExists) {
$q.notify({
type: 'negative',
message: '发布名字已存在,请换一个名字',
});
}
}
const publishNameDisable = ref(false);
function publishToDefault(row: DraftItem) {
if (!row.defaultReleaseDataId) {
$q.notify({
type: 'negative',
message: '未设置默认发布,请新发布或设置默认发布',
});
} else {
publishNameDisable.value = true;
newPublish(row);
}
}
function publishDraftGraphics() {
pubForm.value?.validate().then((res) => {
if (res) {
if (!publishNameDisable.value) {
const params: { draftId: number; name: string; description: string } = {
draftId: +publishForm.id,
name: publishForm.pubName,
description: publishForm.note,
};
publishDraft(params).then((response) => {
if (response.data?.errors && response.data?.errors.length) {
$q.notify({
type: 'negative',
message: response.data.errors[0].message,
});
} else {
publishNameDisable.value = false;
publishFormShow.value = false;
$q.notify({
type: 'positive',
message: '发布成功',
});
tableRef.value.requestServerInteraction();
}
});
} else {
const params: { draftId: number; description: string } = {
draftId: +publishForm.id,
description: publishForm.note,
};
publishDraftToDefault(params).then((response) => {
if (response.data?.errors && response.data?.errors.length) {
$q.notify({
type: 'negative',
message: response.data.errors[0].message,
});
} else {
publishNameDisable.value = false;
publishFormShow.value = false;
tableRef.value.requestServerInteraction();
$q.notify({
type: 'positive',
message: '发布成功',
});
}
});
}
}
});
}
function canclePublish() {
publishNameDisable.value = false;
publishFormShow.value = false;
}
//
async function sharedDraftData(row: DraftItem) {
operateDisabled.value = true;
const res = await sharedDraft(row.id, !row.isShared);
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
operateDisabled.value = false;
tableRef.value.requestServerInteraction();
}
}
//
function deleteData(row: DraftItem) {
operateDisabled.value = true;
$q.dialog({
title: '确认',
message: `确认删除草稿图 "${row.name}" 吗?`,
cancel: true,
})
.onOk(() => {
deleteDraft(row.id).then((res) => {
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
tableRef.value.requestServerInteraction();
}
});
})
.onDismiss(() => {
operateDisabled.value = false;
});
}
//稿
const errorCalories = ref(false);
const errorMessageCalories = ref('');
function caloriesRangeValidation(val?: string) {
if (val !== undefined) {
if (val.length == 0) {
errorCalories.value = true;
errorMessageCalories.value = '不能为空!';
return false;
}
}
errorCalories.value = false;
errorMessageCalories.value = '';
return true;
}
async function saveRowDataName(row: DraftItem) {
const params = {
id: row.id,
name: row.name,
};
const res = await setDraftRename(params);
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
tableRef.value.requestServerInteraction();
}
}
//
const publishInfoShow = ref(false);
const draftInfo = ref<DraftItem>({
id: 0,
name: '',
dataType: DraftDataType.EM,
options: '',
data: '',
userId: 0,
defaultReleaseDataId: 0,
isShared: false,
createdAt: '',
updatedAt: '',
defaultReleaseDataName: '',
userName: '',
});
const publishColumnDefs: QTableColumn[] = [
{
name: 'name',
label: '名称',
field: 'name',
required: true,
align: 'center',
},
{
name: 'description',
label: '描述',
field: 'description',
required: true,
align: 'center',
},
{
name: 'userId',
label: '发布人',
field: 'userId',
align: 'center',
},
{
name: 'createdAt',
label: '发布时间',
field: (row) => new Date(row.createdAt).toLocaleString(),
align: 'center',
},
{ name: 'operations', label: '操作', field: 'operations', align: 'center' },
];
const publishTableRef = ref();
const publishRows = reactive<PublishItem[]>([]);
const publishLoading = ref(false);
const publishPagination = ref({
sortBy: 'desc',
descending: false,
page: 1,
rowsPerPage: 10,
rowsNumber: 10,
});
async function publishOnRequest(props: any) {
publishLoading.value = true;
const { page, rowsPerPage } = props.pagination;
const variables = {
page: {
page: page,
itemsPerPage: rowsPerPage,
},
query: {
dataType: DraftDataType.EM,
name: filter.name,
},
};
try {
const response = await publishPageQuery(variables);
publishPagination.value.rowsNumber = response.total;
publishPagination.value.page = page;
publishPagination.value.rowsPerPage = rowsPerPage;
publishRows.splice(
0,
publishRows.length,
...(response.items.map((item) => item.releaseData) as [])
);
} catch (err) {
const error = err as ApiError;
$q.notify({
type: 'negative',
message: error.title,
});
} finally {
publishLoading.value = false;
}
}
const currentUsedPublishVersionId = ref();
function showpublishFn(row: DraftItem) {
currentUsedPublishVersionId.value = row.defaultReleaseDataId;
draftInfo.value = row;
publishInfoShow.value = true;
nextTick(() => {
publishTableRef.value.requestServerInteraction();
});
}
function setDefaultPublishFn(row: PublishItem) {
if (!draftInfo.value.id || !row.id) return;
$q.dialog({
title: '确认',
message: `确定把【${draftInfo.value.name}】的默认发布版本设为【${row.name}】吗?`,
cancel: true,
}).onOk(() => {
setDefaultPublish({ id: draftInfo.value.id, releaseDataId: row.id }).then(
(res) => {
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
$q.notify({
type: 'positive',
message: '设置默认发布成功!',
});
tableRef.value.requestServerInteraction();
publishInfoShow.value = false;
}
}
);
});
}
</script>
src/api/ElectronicMapDraftApi

View File

@ -0,0 +1,544 @@
<template>
<div class="q-pa-md">
<q-table
ref="tableRef"
title="发布数据管理"
:style="{ height: tableHeight + 'px' }"
:rows="rows"
:columns="columnDefs"
row-key="id"
v-model:pagination="pagination"
:rows-per-page-options="[10, 20, 50, 100]"
:loading="loading"
:filter="filter"
:selection="isProject ? 'multiple' : 'none'"
v-model:selected="selected"
:selected-rows-label="getSelectedString"
binary-state-sort
@request="onRequest"
>
<template v-slot:top-right>
<div class="q-gutter-md q-mt-none row justify-center items-start">
<q-input dense debounce="1000" v-model="filter.name" label="名称" />
<q-select
dense
v-model="filter.lineType"
:options="searchLineTypeOption"
emit-value
map-options
options-dense
class="ellipsis"
label="线路类型"
style="width: 130px"
/>
<q-select
dense
v-model="filter.isPublished"
:options="isPublishedOption"
class="ellipsis"
label="上下架"
style="width: 130px"
/>
<q-btn flat round color="primary" icon="search" />
</div>
</template>
<template v-slot:header-cell-name="props">
<q-th :props="props">
{{ props.col.label }}
<q-icon name="edit" />
</q-th>
</template>
<template v-slot:body-cell-name="props">
<q-td :props="props" class="cursor-pointer">
{{ props.row.name }}
<q-popup-edit
title="编辑名称"
v-model="props.row.name"
buttons
:cover="false"
:validate="caloriesRangeValidation"
@hide="caloriesRangeValidation"
@update:model-value="saveRowDataName(props.row)"
v-slot="scope"
>
<q-input
v-model="scope.value"
hint="请输入名称!"
:error="errorCalories"
:error-message="errorMessageCalories"
dense
autofocus
@keyup.enter="scope.set"
/>
</q-popup-edit>
</q-td>
</template>
<template v-slot:body-cell-operations="props" v-if="!isProject">
<q-td :props="props" style="width: 320px">
<div class="q-gutter-sm row justify-start">
<q-btn
:color="!props.row.isPublished ? 'primary' : 'amber'"
:label="!props.row.isPublished ? '上架' : '下架'"
@click="dataReleaseFn(props.row)"
/>
<q-btn
color="primary"
label="另存到草稿"
@click="saveToDraftFn(props.row)"
/>
<q-btn
color="primary"
label="发布历史"
@click="showHistoryFn(props.row)"
/>
</div>
</q-td>
</template>
</q-table>
<q-dialog
v-model="historyInfoShow"
persistent
transition-show="scale"
transition-hide="scale"
>
<q-card style="width: 1400px; max-width: 80vw">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">{{ historyInfo.name }}发布历史</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section>
<q-table
ref="historyTableRef"
:rows="historyRows"
:style="{ height: tableHeight * 0.6 + 'px' }"
:columns="historyColumnDefs"
v-model:pagination="historyPagination"
:rows-per-page-options="[10, 20, 50, 100]"
:loading="historyLoading"
@request="historyOnRequest"
>
<template v-slot:body-cell-operations="props" v-if="!isProject">
<q-td :props="props" style="width: 150px">
<div class="q-gutter-sm row justify-center">
<q-btn
v-if="props.row.id !== currentUsedVersionId"
color="warning"
label="回退到此版本"
@click="backVersion(props.row)"
/>
</div>
</q-td>
</template>
</q-table>
</q-card-section>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed, watch } from 'vue';
import { useQuasar, type QTableColumn } from 'quasar';
import {
saveToDraft,
setPublishRename,
getPublishHistoryById,
setPublishRelease,
PublishHistoryItem,
fallbackVersion,
} from '../api/PublishApi';
import { useRoute } from 'vue-router';
import { ApiError } from 'src/boot/axios';
import { nextTick } from 'process';
import { DraftDataType } from 'src/api/DraftApi';
import {
PublishEmDataDto,
PublishItem,
publishPageQuery,
} from 'src/api/ElectronicMapPublishApi';
import {
LineType,
lineTypeOption,
searchLineTypeOption,
} from 'src/api/ElectronicMapDraftApi';
const route = useRoute();
const $q = useQuasar();
const props = withDefaults(
defineProps<{
sizeHeight: number;
selects?: [];
}>(),
{ sizeHeight: 500, selects: () => [] }
);
const tableHeight = computed(() => {
return props.sizeHeight - 32;
});
onMounted(() => {
tableRef.value.requestServerInteraction();
selected.value = props.selects;
});
const columnDefs: QTableColumn[] = [
{
name: 'name',
label: '名称',
field: 'name',
required: true,
align: 'center',
},
{
name: 'type',
label: '线路类型',
field: (row) => {
return getTypeName(row);
},
align: 'center',
},
{
name: 'description',
label: '描述',
field: 'description',
required: true,
align: 'center',
},
{
name: 'userName',
label: '发布人',
field: 'userName',
align: 'center',
},
{
name: 'createdAt',
label: '创建时间',
field: (row) => new Date(row.createdAt).toLocaleString(),
align: 'center',
},
{
name: 'updatedAt',
label: '更新时间',
field: (row) => new Date(row.updatedAt).toLocaleString(),
align: 'center',
},
{ name: 'operations', label: '操作', field: 'operations', align: 'center' },
];
const tableRef = ref();
const rows = reactive([]);
const filter = reactive({
name: '',
lineType: LineType.UNKNOWN,
isPublished: '全部',
});
const loading = ref(false);
const pagination = ref({
sortBy: 'desc',
descending: false,
page: 1,
rowsPerPage: 10,
rowsNumber: 10,
});
// eslint-disable-next-line
let allRequestData: PublishEmDataDto[] = [];
async function onRequest(props: any) {
const { page, rowsPerPage } = props.pagination;
loading.value = true;
const variables = {
page: {
page: page,
itemsPerPage: rowsPerPage,
},
query: {
dataType: DraftDataType.EM,
},
};
if (filter.name) {
Object.assign(variables.query, { name: filter.name });
}
if (filter.lineType) {
Object.assign(variables.query, { options: { lineType: filter.lineType } });
}
if (filter.isPublished !== '全部') {
Object.assign(variables.query, {
isPublished: filter.isPublished == '上架' ? true : false,
});
}
try {
const response = await publishPageQuery(variables);
pagination.value.rowsNumber = response.total;
pagination.value.page = page;
pagination.value.rowsPerPage = rowsPerPage;
rows.splice(
0,
rows.length,
...(response.items.map((item) => item.releaseData) as [])
);
allRequestData = response.items;
} catch (err) {
const error = err as ApiError;
$q.notify({
type: 'negative',
message: error.title,
});
} finally {
loading.value = false;
}
}
function getTypeName(row: PublishItem) {
const lineType = allRequestData.find((item) => item.releaseData.id == row.id)
.options.lineType;
const lineTypeName = lineTypeOption.find(
(item) => item.value == lineType
).label;
return lineTypeName || '';
}
//
const isPublishedOption = ['全部', '上架', '下架'];
//
function dataReleaseFn(row: PublishItem) {
if (row.isPublished) {
$q.dialog({
title: '确认',
message: `确定下架发布数据【${row.name}】吗?`,
cancel: true,
}).onOk(() => {
setPublishRelease({ id: row.id, isPublished: !row.isPublished }).then(
(res) => {
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
tableRef.value.requestServerInteraction();
}
}
);
});
} else {
setPublishRelease({ id: row.id, isPublished: !row.isPublished }).then(
(res) => {
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
tableRef.value.requestServerInteraction();
}
}
);
}
}
//稿
function saveToDraftFn(row: PublishItem) {
saveToDraft({
versionId: row.usedVersionId,
}).then((res) => {
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
const draftName =
res.data.data.createDraftDataFromReleaseDataVersion.name;
$q.notify({
type: 'positive',
message: `另存草稿成功且草稿图名字为${draftName}`,
});
}
});
}
//
const emit = defineEmits(['selectsed']);
const selected = ref<PublishItem[] | []>([]);
watch(
() => selected.value,
(val) => {
if (val != props.selects) {
emit('selectsed', val);
}
}
);
const isProject = computed(() => {
return route.path.includes('dataManage/project');
});
function getSelectedString() {
const nameArr = selected.value.map((item) => {
return item.name;
});
const name = nameArr.join('');
return `已选:${name}`;
}
if (isProject.value) {
const index = columnDefs.findIndex((item) => {
return item.name == 'operations';
});
if (index >= 0) {
columnDefs.splice(index, 1);
}
}
//
const errorCalories = ref(false);
const errorMessageCalories = ref('');
function caloriesRangeValidation(val?: string) {
if (val !== undefined) {
if (val.length == 0) {
errorCalories.value = true;
errorMessageCalories.value = '不能为空!';
return false;
}
}
errorCalories.value = false;
errorMessageCalories.value = '';
return true;
}
async function saveRowDataName(row: PublishItem) {
const params = {
id: row.id,
name: row.name,
};
const res = await setPublishRename(params);
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
tableRef.value.requestServerInteraction();
}
}
//
const historyInfoShow = ref(false);
const historyInfo = ref<PublishItem>({
id: 0,
name: '',
dataType: DraftDataType.EM,
options: '',
data: '',
usedVersionId: 0,
userId: 0,
isPublished: false,
createdAt: '',
updatedAt: '',
description: '',
userName: '',
});
const historyColumnDefs: QTableColumn[] = [
{
name: 'userName',
label: '发布人',
field: 'userName',
required: true,
align: 'center',
},
{
name: 'createdAt',
label: '发布时间',
field: (row) => new Date(row.createdAt).toLocaleString(),
align: 'center',
},
{
name: 'description',
label: '描述',
field: 'description',
required: true,
align: 'center',
},
{ name: 'operations', label: '操作', field: 'operations', align: 'center' },
];
const historyTableRef = ref();
const historyRows = reactive<PublishHistoryItem[]>([]);
const historyLoading = ref(false);
const historyPagination = ref({
sortBy: 'desc',
descending: false,
page: 1,
rowsPerPage: 10,
rowsNumber: 10,
});
async function historyOnRequest(props: any) {
historyLoading.value = true;
const { page, rowsPerPage } = props.pagination;
const variables = {
dataId: historyInfo.value.id,
page: {
page: page,
itemsPerPage: rowsPerPage,
},
};
try {
const response = await getPublishHistoryById(variables);
historyPagination.value.rowsNumber = response.total;
historyPagination.value.page = page;
historyPagination.value.rowsPerPage = rowsPerPage;
historyRows.splice(0, historyRows.length, ...(response.items as []));
} catch (err) {
const error = err as ApiError;
$q.notify({
type: 'negative',
message: error.title,
});
} finally {
historyLoading.value = false;
}
}
const currentUsedVersionId = ref();
function showHistoryFn(row: PublishItem) {
currentUsedVersionId.value = row.usedVersionId;
historyInfo.value = row;
historyInfoShow.value = true;
nextTick(() => {
historyTableRef.value.requestServerInteraction();
});
}
function backVersion(row: PublishHistoryItem) {
if (!historyInfo.value.id || !row.id) return;
$q.dialog({
title: '确认',
message: `确定把【${historyInfo.value.name}】回退到【${row.description}】版本吗?`,
cancel: true,
}).onOk(() => {
fallbackVersion({ id: historyInfo.value.id, versionId: row.id }).then(
(res) => {
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
$q.notify({
type: 'positive',
message: '回退版本成功!',
});
tableRef.value.requestServerInteraction();
historyInfoShow.value = false;
}
}
);
});
}
</script>

View File

@ -41,7 +41,14 @@
color="primary" color="primary"
v-if="route.name == 'iscsDraft'" v-if="route.name == 'iscsDraft'"
label="新建" label="新建"
@click="createFormShow = true" @click="showCreateDialog"
/>
<q-btn
v-if="route.name !== 'iscsDraft'"
flat
round
color="primary"
icon="search"
/> />
</div> </div>
</template> </template>
@ -436,6 +443,11 @@ const isSharedOption = ['全部', '共享', '不共享'];
// //
const createFormShow = ref(false); const createFormShow = ref(false);
function showCreateDialog() {
createFormShow.value = true;
createForm.draftName = '';
createForm.style = IscsStyle.DA_SHI_ZHI_NENG;
}
const createForm = reactive({ const createForm = reactive({
draftName: '', draftName: '',
style: IscsStyle.DA_SHI_ZHI_NENG, style: IscsStyle.DA_SHI_ZHI_NENG,

View File

@ -39,6 +39,7 @@
label="上下架" label="上下架"
style="width: 130px" style="width: 130px"
/> />
<q-btn flat round color="primary" icon="search" />
</div> </div>
</template> </template>
@ -246,7 +247,6 @@ const pagination = ref({
let allRequestData: PublishIscsDataDto[] = []; let allRequestData: PublishIscsDataDto[] = [];
async function onRequest(props: any) { async function onRequest(props: any) {
const { page, rowsPerPage } = props.pagination; const { page, rowsPerPage } = props.pagination;
const filter = props.filter;
loading.value = true; loading.value = true;
const variables = { const variables = {

View File

@ -52,15 +52,71 @@
</div> </div>
</q-td> </q-td>
</template> </template>
<template v-slot:body-cell-operations="props">
<q-td :props="props">
<div class="q-gutter-sm row justify-center">
<q-btn
color="primary"
label="编辑角色"
:disable="operateDisabled"
@click="edieUserData(props.row)"
/>
</div>
</q-td>
</template>
</q-table> </q-table>
<q-dialog
v-model="editFormShow"
persistent
transition-show="scale"
transition-hide="scale"
>
<q-card style="width: 300px">
<q-card-section>
<q-form ref="myForm" @submit="edieUserRole" class="q-gutter-md">
<div class="text-h6">修改用户角色</div>
<q-input
outlined
disable
label="用户名"
v-model="userInfo.username"
/>
<q-select
outlined
v-model="userInfo.roles"
:options="rolesOptions"
multiple
map-options
emit-value
label="用户角色"
lazy-rules
:rules="[(val) => val.length > 0 || '请选择角色!']"
></q-select>
<q-card-actions align="right">
<q-btn color="primary" label="保存" type="submit" />
<q-btn label="取消" type="reset" v-close-popup />
</q-card-actions>
</q-form>
</q-card-section>
</q-card>
</q-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'; import { ref, reactive, onMounted, computed } from 'vue';
import { useQuasar, type QTableColumn } from 'quasar'; import { useQuasar, type QTableColumn } from 'quasar';
import { PagingQueryParams, userPageQuery } from '../api/UserApi'; import {
PagingQueryParams,
rolesOptions,
updateUserRoles,
User,
userPageQuery,
} from '../api/UserApi';
import { ApiError } from 'src/boot/axios'; import { ApiError } from 'src/boot/axios';
import { successNotify } from 'src/utils/CommonNotify';
const $q = useQuasar(); const $q = useQuasar();
@ -125,6 +181,12 @@ const columnDefs: QTableColumn[] = [
field: (row) => new Date(row.updatedAt).toLocaleString(), field: (row) => new Date(row.updatedAt).toLocaleString(),
align: 'center', align: 'center',
}, },
{
name: 'operations',
label: '操作',
field: 'operations',
align: 'center',
},
]; ];
const tableRef = ref(); const tableRef = ref();
@ -178,10 +240,6 @@ async function onRequest(props: any) {
} }
} }
const rolesOptions = [
{ label: '管理员', value: 'ADMIN' },
{ label: '用户', value: 'USER' },
];
function getRolesName(role: string) { function getRolesName(role: string) {
for (let i = 0; i < rolesOptions.length; i++) { for (let i = 0; i < rolesOptions.length; i++) {
if (rolesOptions[i].value == role) { if (rolesOptions[i].value == role) {
@ -189,4 +247,40 @@ function getRolesName(role: string) {
} }
} }
} }
const editFormShow = ref(false);
const userInfo = reactive({
id: 0,
username: '',
nickname: '',
roles: [],
});
const operateDisabled = ref(false);
function edieUserData(row: User) {
userInfo.id = row.id;
userInfo.username = row.username;
if (row?.roles) {
userInfo.roles = row.roles;
} else {
userInfo.roles = [];
}
editFormShow.value = true;
}
async function edieUserRole() {
operateDisabled.value = true;
const res = await updateUserRoles({
userId: userInfo.id,
roles: userInfo.roles,
});
operateDisabled.value = false;
if (res.data?.errors && res.data?.errors.length) {
$q.notify({
type: 'negative',
message: res.data.errors[0].message,
});
} else {
successNotify('修改成功');
tableRef.value.requestServerInteraction();
}
}
</script> </script>

View File

@ -49,8 +49,8 @@ const routes: RouteRecordRaw[] = [
}, },
{ {
path: '/dataManage', path: '/iscsDataManage',
name: 'dataManage', name: 'iscsDataManage',
component: () => import('layouts/MainLayout.vue'), component: () => import('layouts/MainLayout.vue'),
meta: { meta: {
label: 'ISCS数据管理', label: 'ISCS数据管理',
@ -86,6 +86,44 @@ const routes: RouteRecordRaw[] = [
}, },
], ],
}, },
{
path: '/emDataManage',
name: 'emDataManage',
component: () => import('layouts/MainLayout.vue'),
meta: {
label: '电子地图数据管理',
icon: 'list_alt',
},
children: [
{
path: 'emDraft',
name: 'emDraft',
component: () => import('pages/ElectronicMapDraftManage.vue'),
meta: {
label: '草稿数据',
icon: 'app_registration',
},
},
{
path: 'emSharedDraft',
name: 'emSharedDraft',
component: () => import('pages/ElectronicMapDraftManage.vue'),
meta: {
label: '共享草稿数据',
icon: 'app_registration',
},
},
{
path: 'emPublish',
name: 'emPublish',
component: () => import('pages/ElectronicMapPublishManage.vue'),
meta: {
label: '发布数据',
icon: 'playlist_add_check',
},
},
],
},
{ {
path: '/iscsPainting/:id', path: '/iscsPainting/:id',
name: 'iscsPainting', name: 'iscsPainting',
@ -95,8 +133,8 @@ const routes: RouteRecordRaw[] = [
}, },
}, },
{ {
path: '/electronicMapPainting/:id', path: '/emPainting/:id',
name: 'electronicMapPainting', name: 'emPainting',
component: () => import('layouts/electronicMapDrawLayout.vue'), component: () => import('layouts/electronicMapDrawLayout.vue'),
meta: { meta: {
hidden: true, hidden: true,