增加GraphiQL形式的数据请求+iscs草稿图的创建删除等

This commit is contained in:
joylink_zhaoerwei 2024-09-18 18:39:00 +08:00
parent 8e01da8ed5
commit f9ee903d60
8 changed files with 676 additions and 3 deletions

42
src/api/ApiCommon.ts Normal file
View File

@ -0,0 +1,42 @@
export class PageQueryDto {
current: number;
size: number;
orders?: OrderItemDto[];
constructor(current: number, size: number, orders?: OrderItemDto[]) {
this.current = current;
this.size = size;
this.orders = orders;
}
}
export class OrderItemDto {
column: string;
asc: boolean;
constructor(column: string, asc: boolean) {
this.column = column;
this.asc = asc;
}
static asc(column: string): OrderItemDto {
return new OrderItemDto(column, true);
}
static desc(column: string): OrderItemDto {
return new OrderItemDto(column, false);
}
}
export interface PageDto<T = unknown> {
data: T[];
/**
*
*/
total: number;
/**
*
*/
current: number;
/**
*
*/
size: number;
}

138
src/api/DraftApi.ts Normal file
View File

@ -0,0 +1,138 @@
import { api } from 'src/boot/axios';
import { PageDto } from './ApiCommon';
const DraftUriBase = '';
export enum DraftDataType {
UNKNOWN,
EM = 'EM',
IBP = 'IBP',
PSL = 'PSL',
ISCS = 'ISCS',
}
export interface DraftItem {
id: number;
name: string;
dataType: DraftDataType;
data: [];
userId: number;
isShared: boolean;
createdAt: string;
updatedAt: string;
}
interface PagingQueryParams {
paging: {
page: number;
itemsPerPage: number;
};
query: {
userId: number;
dataType: DraftDataType;
name?: string;
isShared?: boolean;
};
}
export async function draftPageQuery(
params: PagingQueryParams
): Promise<PageDto<DraftItem>> {
const query = `
query userDraftDataPaging($paging: PageQueryDto, $query: UserDraftDataFilterDto) {
userDraftDataPaging(paging: $paging, query: $query) {
total
data {
id name dataType createdAt updatedAt isShared
}
}
}
`;
const response = await api.post(``, {
query,
variables: params,
});
return response.data.data.userDraftDataPaging;
}
/**
* 稿
* @param params
* @returns
*/
export function createDraft(name: string) {
const mutation = `
mutation createDraftData($input: CreateDraftDataDto) {
createDraftData(input: $input) {
name
}
}
`;
const variables = {
input: {
name,
dataType: DraftDataType.ISCS,
data: [],
userId: 1,
},
};
return api.post(``, {
query: mutation,
variables,
});
}
/**
* 稿
* @param id 稿id
*/
export function deleteDraft(id: number) {
const mutation = `
mutation deleteDraftData($id: Int) {
deleteDraftData(id: $id)
}
`;
const variables = {
id,
};
return api.post(``, {
query: mutation,
variables,
});
}
/**
* 稿
* @param params
* @returns
*/
export async function getDraft(id: number): Promise<DraftItem> {
const response = await api.get(`${DraftUriBase}/${id}`);
return response.data;
}
/**
* 稿
* @param data
* @returns
*/
export function saveDraft(
id: number,
data: {
proto: string;
}
) {
return api.put(`${DraftUriBase}/${id}`, data);
}
/**
* 稿
* @param data
* @returns
*/
export async function saveAsDraft(
id: number,
data: { name: string; proto: string }
): Promise<DraftItem> {
const response = await api.post(`${DraftUriBase}/${id}/saveAs`, data);
return response.data;
}

View File

@ -0,0 +1,7 @@
import { boot } from 'quasar/wrappers';
import * as GraphicsExtras from '@pixi/graphics-extras';
// "async" is optional;
// more info on params: https://v2.quasar.dev/quasar-cli/boot-files
export default boot(async (/* { app, router, ... } */) => {
GraphicsExtras;
});

124
src/boot/axios.ts Normal file
View File

@ -0,0 +1,124 @@
import axios, { AxiosInstance } from 'axios';
import { AxiosError } from 'axios';
import { Dialog } from 'quasar';
import { boot } from 'quasar/wrappers';
//import { getJwtToken } from 'src/configs/TokenManage';
import { getHttpBase } from 'src/configs/UrlManage';
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$axios: AxiosInstance;
}
}
interface ErrorData {
status: number;
title: string;
detail: string;
code: number;
}
export class ApiError {
origin: AxiosError;
/**
*
*/
code: number;
/**
*
*/
title: string;
/**
*
*/
detail?: string;
constructor(origin: AxiosError<unknown, unknown>) {
this.origin = origin;
const response = origin.response;
if (response) {
const err = response.data as ErrorData;
this.code = err.code;
this.title = err.title;
this.detail = err.detail;
} else {
this.code = origin.status || -1;
this.title = origin.message;
}
}
static from(err: AxiosError<unknown, unknown>): ApiError {
return new ApiError(err);
}
/**
*
* @returns
*/
isAuthError(): boolean {
return this.origin.response?.status === 401;
}
}
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: getHttpBase() });
let isOpenDialog = false; // 认证弹窗是否打开
//const CancelToken = axios.CancelToken;
//const source = CancelToken.source();
export default boot(({ app, router }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
// 拦截请求,添加
/* api.interceptors.request.use(
(config) => {
config.headers.Authorization = getJwtToken();
config.cancelToken = source.token;
if (isOpenDialog) {
source.cancel();
}
return config;
},
(err: AxiosError) => {
return Promise.reject(ApiError.from(err));
}
); */
api.interceptors.response.use(
(response) => {
return response;
},
(err) => {
if (err.response && err.response.status === 401 && !isOpenDialog) {
isOpenDialog = true;
Dialog.create({
title: '认证失败',
message: '认证失败或登录超时,请重新登录',
persistent: true,
})
.onOk(() => {
router.push({ name: 'login' });
isOpenDialog = false;
})
.onCancel(() => {
isOpenDialog = false;
});
}
return Promise.reject(ApiError.from(err));
}
);
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { api };

55
src/configs/UrlManage.ts Normal file
View File

@ -0,0 +1,55 @@
function getHost(): string {
if (process.env.URL_ENV == 'test') {
return 'test.joylink.club/bjrtsts-server';
} else if (process.env.URL_ENV == 'publish') {
return 'joylink.club/bjrtsts-server';
} else if (process.env.URL_ENV == 'local_test') {
return '192.168.33.233:8765';
} else if (process.env.URL_ENV == 'local_pxf') {
//北京现场
return '172.29.5.168/bjrtss-server';
}
// return '192.168.3.7:9091';
// return '192.168.3.47:9091';
// return '192.168.3.37:9091';
//return '192.168.33.207:9091'; // 张骞
// return '192.168.33.93:9091';
// return '192.168.3.37:9091'; //卫志宏
// return 'test.joylink.club/bjrtsts-service'; // 测试
return '192.168.33.233:8765';
}
export function getHttpBase() {
let protocol = 'http';
if (['publish'].includes(process.env.URL_ENV as string)) {
protocol = 'https';
}
return `${protocol}://${getHost()}`;
}
export function getWebsocketUrl() {
let protocol = 'ws';
let host = '192.168.33.233';
// let host = 'test.joylink.club';
let port = '8083';
let url = `${protocol}://${host}:${port}`;
if (process.env.URL_ENV == 'test') {
// protocol = 'wss';
host = 'test.joylink.club/bjrtsts-server';
port = '';
url = `${protocol}://${host}`;
} else if (process.env.URL_ENV == 'publish') {
protocol = 'wss';
host = 'joylink.club/bjrtsts-server';
port = '';
url = `${protocol}://${host}`;
} else if (process.env.URL_ENV == 'local_test') {
host = '192.168.33.233';
} else if (process.env.URL_ENV == 'local_pxf') {
host = '172.29.5.168';
}
return `${url}/mqtt`;
}

View File

@ -12,7 +12,7 @@
@click="toggleLeftDrawer"
/>
<q-toolbar-title> 北京玖琏 </q-toolbar-title>
<q-toolbar-title> 城市轨道交通平台 </q-toolbar-title>
<div class="q-gutter-sm no-wrap">
<q-btn

View File

@ -0,0 +1,280 @@
<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>
<q-input
dense
debounce="1000"
v-model="filter.name"
label="名称"
></q-input>
<q-btn flat round color="primary" icon="search" />
<q-btn color="primary" label="新建" @click="createFormShow = true" />
</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
color="primary"
:disable="operateDisabled"
label="发布"
@click="prePublish(props.row)"
/>
<q-btn
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="draftName"
lazy-rules
:rules="[(val) => val.length > 0 || '请输入名称!']"
/>
<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="publishGraphics" class="q-gutter-md">
<div class="text-h6">草稿发布</div>
<q-input
outlined
disable
label="草稿名称"
v-model="publishForm.draftName"
/>
<q-input
outlined
label="发布名称 * "
v-model="publishForm.pubName"
lazy-rules
:rules="[(val) => val.length > 0 || '请输入名称!']"
/>
<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="取消" v-close-popup />
</q-card-actions>
</q-form>
</q-card-section>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue';
import { useQuasar, type QTableColumn, QForm } from 'quasar';
import {
createDraft,
deleteDraft,
DraftDataType,
DraftItem,
draftPageQuery,
} from '../api/DraftApi';
import { ApiError } from 'src/boot/axios';
const $q = useQuasar();
const props = withDefaults(
defineProps<{
sizeHeight: number;
}>(),
{ sizeHeight: 500 }
);
const tableHeight = computed(() => {
return props.sizeHeight - 32;
});
onMounted(() => {
tableRef.value.requestServerInteraction();
});
const columnDefs: QTableColumn[] = [
{
name: 'name',
label: '名称',
field: 'name',
required: true,
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 operateDisabled = ref(false);
const tableRef = ref();
const rows = reactive([]);
const filter = reactive({
name: '',
});
const loading = ref(false);
const pagination = ref({
sortBy: 'desc',
descending: false,
page: 1,
rowsPerPage: 10,
rowsNumber: 10,
});
// eslint-disable-next-line
async function onRequest(props: any) {
const { page, rowsPerPage } = props.pagination;
const filter = props.filter;
loading.value = true;
const variables = {
paging: {
page: page,
itemsPerPage: rowsPerPage,
},
query: { userId: 1, dataType: DraftDataType.ISCS, name: filter.name },
};
try {
let response = await draftPageQuery(variables);
const pageData = response;
pagination.value.rowsNumber = pageData.total;
rows.splice(0, rows.length, ...(pageData.data as []));
} catch (err) {
const error = err as ApiError;
$q.notify({
type: 'negative',
message: error.title,
});
} finally {
loading.value = false;
}
}
const createFormShow = ref(false);
const draftName = ref('');
const myForm = ref<QForm | null>(null);
function onCreate() {
myForm.value?.validate().then(async (res) => {
if (res) {
operateDisabled.value = true;
try {
await createDraft(draftName.value);
createFormShow.value = false;
tableRef.value.requestServerInteraction(); //
} catch (err) {
const error = err as ApiError;
$q.notify({
type: 'negative',
message: error.title,
});
} finally {
operateDisabled.value = false;
}
}
});
}
const pubForm = ref<QForm | null>(null);
const publishFormShow = ref(false);
const publishForm = reactive({
id: '',
draftName: '',
pubName: '',
note: '',
});
function prePublish(row: DraftItem) {
publishFormShow.value = true;
publishForm.id = row.id + '';
publishForm.draftName = row.name;
publishForm.pubName = row.name;
publishForm.note = '';
}
async function deleteData(row: DraftItem) {
operateDisabled.value = true;
$q.dialog({
title: '确认',
message: `确认删除草稿图 "${row.name}" 吗?`,
cancel: true,
})
.onOk(async () => {
try {
await deleteDraft(row.id);
tableRef.value.requestServerInteraction(); //
} catch (err) {
const error = err as ApiError;
$q.notify({
type: 'negative',
message: error.title,
});
}
})
.onDismiss(() => {
operateDisabled.value = false;
});
}
</script>

View File

@ -17,8 +17,35 @@ const routes: RouteRecordRaw[] = [
// children: [{ path: '', component: () => import('pages/IndexPage.vue') }],
},
// Always leave this as last one,
// but you can also remove it
{
path: '/dataManage',
name: 'dataManage',
component: () => import('layouts/MainLayout.vue'),
meta: {
label: '数据管理',
icon: 'list_alt',
},
children: [
{
path: 'iscsDraft',
name: 'iscsDraft',
component: () => import('pages/IscsDraftManage.vue'),
meta: {
label: 'iscs草稿管理',
icon: 'app_registration',
},
},
{
path: 'iscsPublish',
name: 'iscsPublish',
component: () => import('pages/PublishManage.vue'),
meta: {
label: 'iscs发布数据管理',
icon: 'playlist_add_check',
},
},
],
},
{
path: '/:catchAll(.*)*',
meta: {