chore:平台容器框架升级,修复命令行环境丢失的问题

This commit is contained in:
wangxuefeng 2025-03-11 10:15:28 +08:00
parent 3e1a1b4a66
commit 9438489a11
68 changed files with 1800 additions and 1646 deletions

View File

@ -1,12 +1,12 @@
<template>
<el-config-provider :locale="zhCn">
<Suspense>
<router-view></router-view>
</Suspense>
</el-config-provider>
</template>
<script lang="ts" setup>
import { ElConfigProvider } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
</script>
<template>
<ElConfigProvider :locale="zhCn">
<Suspense>
<router-view />
</Suspense>
</ElConfigProvider>
</template>

View File

@ -6,83 +6,98 @@
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const effectScope: typeof import('vue')['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useId: typeof import('vue')['useId']
const useLink: typeof import('vue-router')['useLink']
const useModel: typeof import('vue')['useModel']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
const EffectScope: (typeof import('vue'))['EffectScope'];
const acceptHMRUpdate: (typeof import('pinia'))['acceptHMRUpdate'];
const computed: (typeof import('vue'))['computed'];
const createApp: (typeof import('vue'))['createApp'];
const createPinia: (typeof import('pinia'))['createPinia'];
const customRef: (typeof import('vue'))['customRef'];
const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent'];
const defineComponent: (typeof import('vue'))['defineComponent'];
const defineStore: (typeof import('pinia'))['defineStore'];
const effectScope: (typeof import('vue'))['effectScope'];
const getActivePinia: (typeof import('pinia'))['getActivePinia'];
const getCurrentInstance: (typeof import('vue'))['getCurrentInstance'];
const getCurrentScope: (typeof import('vue'))['getCurrentScope'];
const h: (typeof import('vue'))['h'];
const inject: (typeof import('vue'))['inject'];
const isProxy: (typeof import('vue'))['isProxy'];
const isReactive: (typeof import('vue'))['isReactive'];
const isReadonly: (typeof import('vue'))['isReadonly'];
const isRef: (typeof import('vue'))['isRef'];
const mapActions: (typeof import('pinia'))['mapActions'];
const mapGetters: (typeof import('pinia'))['mapGetters'];
const mapState: (typeof import('pinia'))['mapState'];
const mapStores: (typeof import('pinia'))['mapStores'];
const mapWritableState: (typeof import('pinia'))['mapWritableState'];
const markRaw: (typeof import('vue'))['markRaw'];
const nextTick: (typeof import('vue'))['nextTick'];
const onActivated: (typeof import('vue'))['onActivated'];
const onBeforeMount: (typeof import('vue'))['onBeforeMount'];
const onBeforeRouteLeave: (typeof import('vue-router'))['onBeforeRouteLeave'];
const onBeforeRouteUpdate: (typeof import('vue-router'))['onBeforeRouteUpdate'];
const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount'];
const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate'];
const onDeactivated: (typeof import('vue'))['onDeactivated'];
const onErrorCaptured: (typeof import('vue'))['onErrorCaptured'];
const onMounted: (typeof import('vue'))['onMounted'];
const onRenderTracked: (typeof import('vue'))['onRenderTracked'];
const onRenderTriggered: (typeof import('vue'))['onRenderTriggered'];
const onScopeDispose: (typeof import('vue'))['onScopeDispose'];
const onServerPrefetch: (typeof import('vue'))['onServerPrefetch'];
const onUnmounted: (typeof import('vue'))['onUnmounted'];
const onUpdated: (typeof import('vue'))['onUpdated'];
const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup'];
const provide: (typeof import('vue'))['provide'];
const reactive: (typeof import('vue'))['reactive'];
const readonly: (typeof import('vue'))['readonly'];
const ref: (typeof import('vue'))['ref'];
const resolveComponent: (typeof import('vue'))['resolveComponent'];
const setActivePinia: (typeof import('pinia'))['setActivePinia'];
const setMapStoreSuffix: (typeof import('pinia'))['setMapStoreSuffix'];
const shallowReactive: (typeof import('vue'))['shallowReactive'];
const shallowReadonly: (typeof import('vue'))['shallowReadonly'];
const shallowRef: (typeof import('vue'))['shallowRef'];
const storeToRefs: (typeof import('pinia'))['storeToRefs'];
const toRaw: (typeof import('vue'))['toRaw'];
const toRef: (typeof import('vue'))['toRef'];
const toRefs: (typeof import('vue'))['toRefs'];
const toValue: (typeof import('vue'))['toValue'];
const triggerRef: (typeof import('vue'))['triggerRef'];
const unref: (typeof import('vue'))['unref'];
const useAttrs: (typeof import('vue'))['useAttrs'];
const useCssModule: (typeof import('vue'))['useCssModule'];
const useCssVars: (typeof import('vue'))['useCssVars'];
const useId: (typeof import('vue'))['useId'];
const useLink: (typeof import('vue-router'))['useLink'];
const useModel: (typeof import('vue'))['useModel'];
const useRoute: (typeof import('vue-router'))['useRoute'];
const useRouter: (typeof import('vue-router'))['useRouter'];
const useSlots: (typeof import('vue'))['useSlots'];
const useTemplateRef: (typeof import('vue'))['useTemplateRef'];
const watch: (typeof import('vue'))['watch'];
const watchEffect: (typeof import('vue'))['watchEffect'];
const watchPostEffect: (typeof import('vue'))['watchPostEffect'];
const watchSyncEffect: (typeof import('vue'))['watchSyncEffect'];
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
export type {
Component,
ComponentPublicInstance,
ComputedRef,
DirectiveBinding,
ExtractDefaultPropTypes,
ExtractPropTypes,
ExtractPublicPropTypes,
InjectionKey,
PropType,
Ref,
MaybeRef,
MaybeRefOrGetter,
VNode,
WritableComputedRef
} from 'vue';
import('vue');
}

View File

@ -1,7 +1,8 @@
/// <reference types="vite/client" />
// / <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
@ -12,7 +13,7 @@ declare namespace NodeJS {
}
}
declare module global {
declare namespace global {
interface Window {}
}

View File

@ -1,14 +1,14 @@
import { type BlockSchema } from '@vtj/core';
import type { BlockSchema } from '@vtj/core';
import instance from './instance';
import { fi } from 'element-plus/es/locale/index.mjs';
export type LowCodeFileSchema = {
project_id: number;
publish: boolean;
active: boolean;
dsl: BlockSchema;
file_path?: string;
file_id?: string;
file_path?: string;
project_id: number;
publish: boolean;
};
function transformFile(file: LowCodeFileSchema): LowCodeFileSchema {

View File

@ -1,12 +1,13 @@
import type { HistorySchema } from '@vtj/core';
import instance from './instance';
import { type HistorySchema } from '@vtj/core';
export type LowCodeHistorySchema = {
project_id: number;
dsl?: HistorySchema;
file_id: string;
history_id: string;
id?: string;
dsl?: HistorySchema;
project_id: number;
};
function transformHistoryData(data: LowCodeHistorySchema) {
@ -20,12 +21,12 @@ export type HistoriesResponse = {
code: number;
data: {
list: Array<{
id: number;
project_id: number;
created_at: string;
dsl: Record<string, any>;
file_id: string;
history_id: string;
dsl: Record<string, any>;
created_at: string;
id: number;
project_id: number;
updated_at: string;
}>;
total: number;
@ -34,10 +35,10 @@ export type HistoriesResponse = {
};
export type GetHistoriesParams = {
project_id: number;
file_id: string;
page?: number;
per_page?: number;
project_id: number;
};
export const getHistories = async (params: GetHistoriesParams) => {

View File

@ -1,7 +1,7 @@
export * from './api';
export * from './application';
export * from './block';
export * from './file';
export * from './history';
export * from './materials';
export * from './project';
export * from './application';
export * from './history';

View File

@ -1,4 +1,4 @@
import { type MaterialDescription } from '@vtj/core';
import type { MaterialDescription } from '@vtj/core';
import instance from './instance';
@ -52,13 +52,13 @@ export const getMaterials = async (id: number): Promise<MaterialResponse> => {
};
type MaterialData = {
project_id: number;
value: Record<string, MaterialDescription>;
created_at?: string;
// 从原interface合并的字段
id?: number;
name?: string;
created_at?: string;
project_id: number;
updated_at?: string;
value: Record<string, MaterialDescription>;
};
function transformMaterialData(data: MaterialData) {

View File

@ -1,11 +1,13 @@
import { createApp } from 'vue';
import { createPersistedState } from 'pinia-plugin-persistedstate';
import router from './router';
import App from './App.vue';
import './style/index.scss';
import router from './router';
import { pinia } from './store';
import './style/index.scss';
// 添加持久化插件
pinia.use(
createPersistedState({

View File

@ -1,55 +1,105 @@
// @ts-nocheck
import {
type ProjectSchema,
type BlockSchema,
type HistorySchema,
type HistoryItem,
type MaterialDescription,
type ExtensionConfig,
type PageFile,
type BlockFile,
type NodeFromPlugin,
ProjectModel,
HistoryModel
import type {
BlockFile,
BlockSchema,
ExtensionConfig,
HistoryItem,
HistorySchema,
MaterialDescription,
NodeFromPlugin,
PageFile,
ProjectSchema
} from '@vtj/core';
import { mapToObject, Storage } from '@vtj/utils';
import { BaseService } from '@vtj/renderer';
import { isEmpty } from 'licia-es';
import {
getProject,
updateProject,
createFile,
updateFile as updateLowCodeFile,
getFile as getLowCodeFile,
deleteFile as deleteLowCodeFile,
getHistory as getLowCodeHistory,
deleteHistory as deleteLowCodeHistory,
createHistory as createLowCodeHistory,
deleteFile as deleteLowCodeFile,
deleteHistory as deleteLowCodeHistory,
getFile as getLowCodeFile,
getHistories as getLowCodeHistories,
publishFile as publishLowCodeFile,
publishAllFile as publishLowCodeAllFile,
getHistory as getLowCodeHistory,
getMaterials as getLowCodeMaterials,
getProject,
postMaterials as postLowCodeMaterials,
publishAllFile as publishLowCodeAllFile,
publishFile as publishLowCodeFile,
updateFile as updateLowCodeFile,
updateMaterials as updateLowCodeMaterials,
deleteMaterials as deleteLowCodeMaterials
updateProject
} from '@/io';
// @ts-nocheck
import { HistoryModel, ProjectModel } from '@vtj/core';
import { BaseService } from '@vtj/renderer';
import { mapToObject, Storage } from '@vtj/utils';
import { isEmpty } from 'licia-es';
const storage = new Storage({
type: 'local',
expired: 0
});
const stringifyFields = [
'config',
'pages',
'dependencies',
'blocks',
const stringifyFields = new Set([
'apis',
'meta'
];
'blocks',
'config',
'dependencies',
'meta',
'pages'
]);
let initProject: ProjectSchema;
export class LowCodeService extends BaseService {
public api = (type: string, data: any): Promise<any> => {
// console.log('api', type, data);
return Promise.resolve(true);
};
public getExtension(): Promise<ExtensionConfig | undefined> {
const extension = storage.get('extension');
console.log('ExtensionConfig', extension);
return Promise.resolve(extension as ExtensionConfig | undefined);
}
public async getFile(id: string): Promise<BlockSchema> {
return getLowCodeFile(id).then((lowCodeFile) => {
return lowCodeFile.dsl
? Promise.resolve(lowCodeFile.dsl as BlockSchema)
: Promise.reject(null);
});
}
public async getHistory(fileId: string): Promise<HistorySchema> {
const histories = await getLowCodeHistories({
project_id: initProject.id,
file_id: fileId,
per_page: 50
});
const formatDsl = {
id: histories.list[0].file_id,
items: histories.list.map((item) => {
return {
...item,
id: item.history_id,
label: item.created_at
};
})
};
const history = new HistoryModel(formatDsl);
return history.toDsl();
}
public async getHistoryItem(fId: string, id: string): Promise<HistoryItem> {
const history = await getLowCodeHistory(id);
return history;
}
public getPluginMaterial(
from: NodeFromPlugin
): Promise<MaterialDescription | null> {
return Promise.resolve(null);
}
public async init(project: ProjectSchema): Promise<ProjectSchema> {
console.log('init', project);
initProject = project;
@ -64,34 +114,85 @@ export class LowCodeService extends BaseService {
const model = new ProjectModel(remoteProject);
console.log('model', model || { id: initProject.id });
const dsl = model.toDsl();
return Promise.resolve(dsl);
return dsl;
}
public getExtension(): Promise<ExtensionConfig | undefined> {
const extension = storage.get('extension');
console.log('ExtensionConfig', extension);
return Promise.resolve(extension as ExtensionConfig | undefined);
public publish(project: ProjectSchema): Promise<boolean> {
return publishLowCodeAllFile(Number(project.id)).then((res) => {
return true;
});
}
public async saveProject(project: ProjectSchema): Promise<boolean> {
const newProject = {
...project,
...Object.fromEntries(
Object.entries(project)
.filter(([key]) => stringifyFields.includes(key))
.map(([key, value]) => [key, JSON.stringify(value)])
)
};
// 剔除引擎自行添加的 id避免接口更新冲突报错
Reflect.deleteProperty(newProject, 'id');
await updateProject(initProject.id, newProject);
public publishFile(
project: ProjectSchema,
file: BlockFile | PageFile
): Promise<boolean> {
return publishLowCodeFile(file.id).then((res) => {
return true;
});
}
public async removeFile(id: string): Promise<boolean> {
return deleteLowCodeFile(id).then(() => true);
}
// TODO: 做成数据库存储后没啥用,保留就行
public removeHistory(id: string): Promise<boolean> {
// console.log('removeHistory', id);
return Promise.resolve(true);
}
public async removeHistoryItem(fId: string, ids: string[]): Promise<boolean> {
await Promise.all(ids.map((id) => deleteLowCodeHistory(id)));
return true;
}
public getPluginMaterial(
from: NodeFromPlugin
): Promise<MaterialDescription | null> {
return Promise.resolve(null);
public async saveFile(file: BlockSchema): Promise<boolean> {
console.log('saveFile', file);
if (file.id) {
const existFile = await getLowCodeFile(file.id);
return existFile.file_id
? updateLowCodeFile(file.id, {
...existFile,
dsl: file
})
.then(() => {
return true;
})
.catch((error) => {
throw error;
})
: createFile({
project_id: initProject.id,
publish: false,
active: true,
dsl: file,
file_id: file.id
})
.then(() => {
return true;
})
.catch((error) => {
throw error;
});
}
return false;
}
public async saveHistory(history: HistorySchema): Promise<boolean> {
return true;
}
public async saveHistoryItem(
fileId: string,
historyItem: HistoryItem
): Promise<boolean> {
await createLowCodeHistory({
project_id: initProject.id,
file_id: fileId,
history_id: historyItem.id,
dsl: historyItem.dsl as HistorySchema
});
return true;
}
// TODO: 物料存储只有在发布为其他端 (比如 uinapp) 时才有用,当前版本时不需要的,暂且保留
@ -120,130 +221,25 @@ export class LowCodeService extends BaseService {
// @ts-ignore
// await deleteLowCodeMaterials(project.id);
return Promise.resolve(true);
return true;
}
public async saveFile(file: BlockSchema): Promise<boolean> {
console.log('saveFile', file);
if (file.id) {
const existFile = await getLowCodeFile(file.id);
if (existFile.file_id) {
return updateLowCodeFile(file.id, {
...existFile,
dsl: file
})
.then(() => {
return Promise.resolve(true);
})
.catch((err) => {
return Promise.reject(err);
});
} else {
return createFile({
project_id: initProject.id,
publish: false,
active: true,
dsl: file,
file_id: file.id
})
.then(() => {
return Promise.resolve(true);
})
.catch((err) => {
return Promise.reject(err);
});
}
}
return Promise.resolve(false);
public async saveProject(project: ProjectSchema): Promise<boolean> {
const newProject = {
...project,
...Object.fromEntries(
Object.entries(project)
.filter(([key]) => stringifyFields.has(key))
.map(([key, value]) => [key, JSON.stringify(value)])
)
};
// 剔除引擎自行添加的 id避免接口更新冲突报错
Reflect.deleteProperty(newProject, 'id');
await updateProject(initProject.id, newProject);
return true;
}
public async getFile(id: string): Promise<BlockSchema> {
return getLowCodeFile(id).then((lowCodeFile) => {
if (lowCodeFile.dsl) {
return Promise.resolve(lowCodeFile.dsl as BlockSchema);
} else {
return Promise.reject(null);
}
});
}
public async removeFile(id: string): Promise<boolean> {
return deleteLowCodeFile(id).then(() => Promise.resolve(true));
}
public async saveHistory(history: HistorySchema): Promise<boolean> {
return Promise.resolve(true);
}
public api = (type: string, data: any): Promise<any> => {
// console.log('api', type, data);
return Promise.resolve(true);
};
protected uploader = (file: File, projectId: string): Promise<any> => {
// console.log('uploader', file, projectId);
return Promise.resolve(true);
};
// TODO: 做成数据库存储后没啥用,保留就行
public removeHistory(id: string): Promise<boolean> {
// console.log('removeHistory', id);
return Promise.resolve(true);
}
public async getHistory(fileId: string): Promise<HistorySchema> {
const histories = await getLowCodeHistories({
project_id: initProject.id,
file_id: fileId,
per_page: 50
});
const formatDsl = {
id: histories.list[0].file_id,
items: histories.list.map((item) => {
return {
...item,
id: item.history_id,
label: item.created_at
};
})
};
const history = new HistoryModel(formatDsl);
return Promise.resolve(history.toDsl());
}
public async getHistoryItem(fId: string, id: string): Promise<HistoryItem> {
const history = await getLowCodeHistory(id);
return Promise.resolve(history);
}
public async saveHistoryItem(
fileId: string,
historyItem: HistoryItem
): Promise<boolean> {
await createLowCodeHistory({
project_id: initProject.id,
file_id: fileId,
history_id: historyItem.id,
dsl: historyItem.dsl as HistorySchema
});
return Promise.resolve(true);
}
public async removeHistoryItem(fId: string, ids: string[]): Promise<boolean> {
await Promise.all(ids.map((id) => deleteLowCodeHistory(id)));
return Promise.resolve(true);
}
public publish(project: ProjectSchema): Promise<boolean> {
return publishLowCodeAllFile(Number(project.id)).then((res) => {
return Promise.resolve(true);
});
}
public publishFile(
project: ProjectSchema,
file: PageFile | BlockFile
): Promise<boolean> {
return publishLowCodeFile(file.id).then((res) => {
return Promise.resolve(true);
});
}
}

View File

@ -1,5 +1,5 @@
import { ref, computed } from 'vue';
import axios from 'axios';
import { computed, ref } from 'vue';
import { createPinia, defineStore } from 'pinia';
// 创建 pinia 实例
@ -38,7 +38,7 @@ export const useUserStore = defineStore('user', () => {
// 应用配置 store
export const useAppStore = defineStore('app', () => {
const theme = ref<'light' | 'dark'>('light');
const theme = ref<'dark' | 'light'>('light');
const sidebarCollapsed = ref(false);
// 持久化配置

View File

@ -1,13 +1,13 @@
@use '@vtj/web/src/index.scss';
@use '@vtj/web/src/index';
html,
body,
#app {
height: 100%;
padding: 0;
margin: 0;
font-size: 14px;
height: 100%;
overflow: hidden;
font-size: 14px;
}
#vtjLink {

View File

@ -1,14 +1,16 @@
import * as Vue from 'vue';
import * as core from '@vtj/core';
import * as VtjUtils from '@vtj/utils';
import * as VtjUI from '@vtj/ui';
import * as designer from '@vtj/designer';
import * as renderer from '@vtj/renderer';
import * as VtjIcons from '@vtj/icons';
import * as ElementPlus from 'element-plus';
import type { ExtensionConfig } from '@vtj/core';
import type { EngineOptions } from '@vtj/designer';
import * as Vue from 'vue';
import * as core from '@vtj/core';
import * as designer from '@vtj/designer';
import * as VtjIcons from '@vtj/icons';
import * as renderer from '@vtj/renderer';
import * as VtjUI from '@vtj/ui';
import * as VtjUtils from '@vtj/utils';
import * as ElementPlus from 'element-plus';
export type ExtensionOptions = ExtensionConfig;
export type ExtensionFactory = (
config: ExtensionConfig
@ -20,11 +22,11 @@ export interface ExtensionOutput {
}
export class Extension {
private urls: string[] = [];
private __adapters__: Record<string, any> = {};
private __BASE_PATH__: string = '/';
private library: string = '';
private params: any[] = [];
private __BASE_PATH__: string = '/';
private __adapters__: Record<string, any> = {};
private urls: string[] = [];
constructor(private options: ExtensionOptions) {
const __VTJ_PRO__ = {
...core,
@ -62,15 +64,14 @@ export class Extension {
.filter((n) => renderer.isJSUrl(n))
.map((n) => `${base}${n}`);
renderer.loadCssUrl(css);
if (scripts.length) {
if (scripts.length > 0) {
const output = await renderer
.loadScriptUrl(scripts, this.library)
.catch(() => null);
if (output && typeof output === 'function') {
options = output.call(output, this.options, this.params);
} else {
options = output || {};
}
options =
output && typeof output === 'function'
? output.call(output, this.options, this.params)
: output || {};
}
}
return {

View File

@ -4,7 +4,7 @@ import 'element-plus/theme-chalk/index.css';
import '@vtj/ui/dist/style.css';
import '@vtj/icons/dist/style.css';
export * from './extension';
export * from '@vtj/core';
export * from '@vtj/designer';
export * from '@vtj/renderer';
export * from './extension';

View File

@ -1,10 +1,11 @@
<script lang="ts" setup>
import { ref } from 'vue';
import Postmate from 'postmate';
import { Engine, widgetManager } from '@vtj/pro';
import { request, jsonp } from '@vtj/utils';
import { useUserStore } from '@/store';
import { LowCodeService } from '@/service';
import { useUserStore } from '@/store';
import { Engine, widgetManager } from '@vtj/pro';
import { jsonp, request } from '@vtj/utils';
import Postmate from 'postmate';
const container = ref();
const service = new LowCodeService();
@ -68,7 +69,7 @@ onMounted(async () => {
<style scoped>
.designer-container {
height: 100%;
width: 100%;
height: 100%;
}
</style>

View File

@ -1,10 +1,10 @@
<script lang="ts" setup>
import { XContainer } from '@vtj/web';
import { ElEmpty } from 'element-plus';
</script>
<template>
<XContainer class="not-found" fit justify="center">
<ElEmpty description="找不到页面【404】"></ElEmpty>
<ElEmpty description="找不到页面【404】" />
</XContainer>
</template>
<script lang="ts" setup>
import { XContainer } from '@vtj/web';
import { ElEmpty } from 'element-plus';
</script>
<style lang="scss" scoped></style>

View File

@ -1,10 +1,10 @@
<template>
<XContainer class="unauthorized" fit justify="center">
<ElEmpty description="无权限访问该页面"></ElEmpty>
</XContainer>
</template>
<script lang="ts" setup>
import { XContainer } from '@vtj/ui';
import { ElEmpty } from 'element-plus';
</script>
<template>
<XContainer class="unauthorized" fit justify="center">
<ElEmpty description="无权限访问该页面" />
</XContainer>
</template>
<style lang="scss" scoped></style>

View File

@ -1,9 +1,8 @@
import { defineConfig, loadEnv } from 'vite';
import { createViteConfig } from '@vtj/cli';
import { createDevTools } from '@vtj/local';
import mkcert from 'vite-plugin-mkcert';
import AutoImport from 'unplugin-auto-import/vite';
import path from 'path';
import { defineConfig, loadEnv } from 'vite';
import mkcert from 'vite-plugin-mkcert';
const config = createViteConfig({
// proxy,

View File

@ -64,7 +64,6 @@ onMounted(() => {
.responsive-iframe {
width: 100%;
height: 100%;
border: none;
}
</style>

View File

@ -1,8 +1,8 @@
/// <reference types="vite/client" />
// / <reference types="vite/client" />
interface ImportMeta {
readonly env: {
NODE_ENV: "development" | "production";
NODE_ENV: 'development' | 'production';
VITE_API_BASE?: string;
};
}

View File

@ -1,24 +1,24 @@
<script setup lang="ts">
import { computed, watch, ref, getCurrentInstance } from 'vue'
import { ElLoading } from 'element-plus'
import Postmate from 'postmate'
import { createProvider } from '@vtj/web'
import { useQuery } from '@tanstack/vue-query'
import { LowCodeService } from './service'
import { getFile } from './io'
import { request, jsonp } from '@vtj/utils'
import { computed, watch, ref, getCurrentInstance } from 'vue';
import { ElLoading } from 'element-plus';
import Postmate from 'postmate';
import { createProvider } from '@vtj/web';
import { useQuery } from '@tanstack/vue-query';
import { LowCodeService } from './service';
import { getFile } from './io';
import { request, jsonp } from '@vtj/utils';
// import * as VtjUI from '@vtj/ui'
//
const renderer = ref()
const lowCodeService = new LowCodeService()
const renderer = ref();
const lowCodeService = new LowCodeService();
// Postmate
const postmate = new Postmate.Model({
sayHi: (data: any) => {
console.log('sayHi',data)
}
})
console.log('sayHi', data);
},
});
//
const model = {
@ -27,54 +27,54 @@ const model = {
projectId: -1,
fileId: '',
url: '',
accessToken: ''
}
accessToken: '',
};
//
const { data: file, isFetching } = useQuery({
queryKey: ['getFile'],
queryFn: async () => {
await postmate.then((parent) => {
parent.emit('some-event', 'y-code-renderer is ready')
Object.assign(model, parent.model)
localStorage.setItem('y-code-access-token', model.accessToken || '')
})
parent.emit('some-event', 'y-code-renderer is ready');
Object.assign(model, parent.model);
localStorage.setItem('y-code-access-token', model.accessToken || '');
});
return getFile(model.fileId).then(() => {
request.useRequest((req) => {
req.headers.set('Authorization', `Bearer ${model.accessToken}`)
return req
})
req.headers.set('Authorization', `Bearer ${model.accessToken}`);
return req;
});
const { provider, onReady } = createProvider({
nodeEnv: import.meta.env.NODE_ENV,
service: lowCodeService,
project: { id: model.projectId },
adapter: {
request,
jsonp
}
})
jsonp,
},
});
onReady(async () => {
const instance = getCurrentInstance()
instance?.appContext.app.use(provider)
renderer.value = await provider.getRenderComponent(model.fileId)
})
})
}
})
const instance = getCurrentInstance();
instance?.appContext.app.use(provider);
renderer.value = await provider.getRenderComponent(model.fileId);
});
});
},
});
//
watch(isFetching, (newVal) => {
if (newVal) {
ElLoading.service({ text: '低代码文件加载中...' })
ElLoading.service({ text: '低代码文件加载中...' });
} else {
ElLoading.service().close()
ElLoading.service().close();
}
})
});
</script>
<template>
<div style="padding: 20px;">
<div style="padding: 20px">
<component :is="renderer" v-if="renderer" />
</div>
</template>

View File

@ -1 +1 @@
export * from "./env";
export * from './env';

View File

@ -1,13 +1,14 @@
import { BlockSchema } from "@vtj/core";
import instance from "./instance";
import { BlockSchema } from '@vtj/core';
import instance from './instance';
export type LowCodeFileSchema = {
project_id: number;
publish: boolean;
active: boolean;
dsl: BlockSchema;
file_path?: string;
file_id?: string;
file_path?: string;
project_id: number;
publish: boolean;
};
export const getFile = async (id: string): Promise<LowCodeFileSchema> => {

View File

@ -1,2 +1,2 @@
export * from "./file";
export * from "./project";
export * from './file';
export * from './project';

View File

@ -1,4 +1,4 @@
import axios from "axios";
import axios from 'axios';
// 创建独立实例
const instance = axios.create({
@ -13,7 +13,7 @@ instance.interceptors.request.use(
},
(error) => {
return Promise.reject(error);
}
},
);
instance.interceptors.response.use(
@ -22,7 +22,7 @@ instance.interceptors.response.use(
},
(error) => {
return Promise.reject(error);
}
},
);
// 导出实例
export default instance;

View File

@ -1,4 +1,4 @@
import instance from "./instance";
import instance from './instance';
export const getProject = async (id: string) => {
const response = await instance.get(`/api/v1/projects/${id}`);

View File

@ -1,18 +1,29 @@
import { type ProjectSchema, type BlockSchema, ProjectModel } from "@vtj/core";
import { BaseService } from "@vtj/renderer";
import { getProject, getFile as getLowCodeFile } from "@/io";
import type { BlockSchema, ProjectSchema } from '@vtj/core';
import { getFile as getLowCodeFile, getProject } from '@/io';
import { ProjectModel } from '@vtj/core';
import { BaseService } from '@vtj/renderer';
let initProject: ProjectModel = {};
export class LowCodeService extends BaseService {
public async getFile(id: string): Promise<BlockSchema> {
console.log('service getFile', id);
return getLowCodeFile(id).then((lowCodeFile) => {
return lowCodeFile.dsl
? Promise.resolve(lowCodeFile.dsl as BlockSchema)
: Promise.reject(null);
});
}
public async init(project: ProjectSchema) {
console.log("init", project);
console.log('init', project);
initProject = project;
const remoteProject = await getProject(project.id);
console.log("remoteProject", remoteProject);
console.log('remoteProject', remoteProject);
const model = new ProjectModel(remoteProject);
const dsl = model.toDsl();
return Promise.resolve(dsl);
return dsl;
}
public saveProject(project: ProjectSchema): Promise<boolean> {
@ -21,7 +32,7 @@ export class LowCodeService extends BaseService {
...Object.fromEntries(
Object.entries(project)
.filter(([key]) => stringifyFields.includes(key))
.map(([key, value]) => [key, JSON.stringify(value)])
.map(([key, value]) => [key, JSON.stringify(value)]),
),
};
updateProject(initProject.id, newProject);
@ -29,15 +40,4 @@ export class LowCodeService extends BaseService {
// storage.save(`project_${model.id}`, model.toDsl());
return Promise.resolve(true);
}
public async getFile(id: string): Promise<BlockSchema> {
console.log("service getFile", id);
return getLowCodeFile(id).then((lowCodeFile) => {
if (lowCodeFile.dsl) {
return Promise.resolve(lowCodeFile.dsl as BlockSchema);
} else {
return Promise.reject(null);
}
});
}
}

View File

@ -1,15 +1,15 @@
module.exports = {
branches: ["main", "master"],
branches: ['main', 'master'],
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/changelog',
// "@semantic-release/npm",
[
"@semantic-release/git",
'@semantic-release/git',
{
assets: ["CHANGELOG.md", "package.json"],
message: "chore(release): ${nextRelease.version} [skip ci]",
assets: ['CHANGELOG.md', 'package.json'],
message: 'chore(release): ${nextRelease.version} [skip ci]',
},
],
// "@semantic-release/github",

View File

@ -7,39 +7,39 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
AButton: typeof import('ant-design-vue/es')['Button']
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
ACol: typeof import('ant-design-vue/es')['Col']
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
AFloatButton: typeof import('ant-design-vue/es')['FloatButton']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AImage: typeof import('ant-design-vue/es')['Image']
AInput: typeof import('ant-design-vue/es')['Input']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
ARow: typeof import('ant-design-vue/es')['Row']
ASelect: typeof import('ant-design-vue/es')['Select']
ASpace: typeof import('ant-design-vue/es')['Space']
ASpin: typeof import('ant-design-vue/es')['Spin']
ASwitch: typeof import('ant-design-vue/es')['Switch']
ATable: typeof import('ant-design-vue/es')['Table']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
YChart: typeof import('./src/components/common/y-chart.vue')['default']
YTable: typeof import('./src/components/common/y-table.vue')['default']
ABreadcrumb: (typeof import('ant-design-vue/es'))['Breadcrumb'];
ABreadcrumbItem: (typeof import('ant-design-vue/es'))['BreadcrumbItem'];
AButton: (typeof import('ant-design-vue/es'))['Button'];
ACheckbox: (typeof import('ant-design-vue/es'))['Checkbox'];
ACheckboxGroup: (typeof import('ant-design-vue/es'))['CheckboxGroup'];
ACol: (typeof import('ant-design-vue/es'))['Col'];
AConfigProvider: (typeof import('ant-design-vue/es'))['ConfigProvider'];
ADropdown: (typeof import('ant-design-vue/es'))['Dropdown'];
AFloatButton: (typeof import('ant-design-vue/es'))['FloatButton'];
AForm: (typeof import('ant-design-vue/es'))['Form'];
AFormItem: (typeof import('ant-design-vue/es'))['FormItem'];
AImage: (typeof import('ant-design-vue/es'))['Image'];
AInput: (typeof import('ant-design-vue/es'))['Input'];
AInputNumber: (typeof import('ant-design-vue/es'))['InputNumber'];
AInputPassword: (typeof import('ant-design-vue/es'))['InputPassword'];
AMenu: (typeof import('ant-design-vue/es'))['Menu'];
AMenuItem: (typeof import('ant-design-vue/es'))['MenuItem'];
AModal: (typeof import('ant-design-vue/es'))['Modal'];
APagination: (typeof import('ant-design-vue/es'))['Pagination'];
APopconfirm: (typeof import('ant-design-vue/es'))['Popconfirm'];
ARadio: (typeof import('ant-design-vue/es'))['Radio'];
ARadioButton: (typeof import('ant-design-vue/es'))['RadioButton'];
ARadioGroup: (typeof import('ant-design-vue/es'))['RadioGroup'];
ARangePicker: (typeof import('ant-design-vue/es'))['RangePicker'];
ARow: (typeof import('ant-design-vue/es'))['Row'];
ASelect: (typeof import('ant-design-vue/es'))['Select'];
ASpace: (typeof import('ant-design-vue/es'))['Space'];
ASpin: (typeof import('ant-design-vue/es'))['Spin'];
ASwitch: (typeof import('ant-design-vue/es'))['Switch'];
ATable: (typeof import('ant-design-vue/es'))['Table'];
RouterLink: (typeof import('vue-router'))['RouterLink'];
RouterView: (typeof import('vue-router'))['RouterView'];
YChart: (typeof import('./src/components/common/y-chart.vue'))['default'];
YTable: (typeof import('./src/components/common/y-table.vue'))['default'];
}
}

View File

@ -1 +1 @@
/// <reference types="vite/client" />
// / <reference types="vite/client" />

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />

View File

@ -1,23 +1,28 @@
<script setup>
import {
ConfigProvider,
legacyLogicalPropertiesTransformer,
StyleProvider,
theme,
} from 'ant-design-vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import { legacyLogicalPropertiesTransformer, StyleProvider, ConfigProvider, theme } from 'ant-design-vue';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
const { compactAlgorithm } = theme;
dayjs.locale('zh-cn');
ConfigProvider.config({
prefixCls: 'ycode-ant',
})
});
</script>
<template>
<a-config-provider
:theme="{
algorithm: [ compactAlgorithm],
algorithm: [compactAlgorithm],
}"
:locale="zhCN"
:transformers="[legacyLogicalPropertiesTransformer]"

View File

@ -1,4 +1,4 @@
import { get } from "@/utils/request";
import { get } from '@/utils/request';
export interface UserInfoType {
alias: string;
@ -17,18 +17,18 @@ export interface UserInfoType {
interface DropListItem {
label: string;
value: string | number;
value: number | string;
mark: string;
}
export const getUserInfo = () =>
get<UserInfoType>({
url: "/api/home/grade",
url: '/api/home/grade',
});
export const logout = () => get({ url: "/api/common/logout" });
export const logout = () => get({ url: '/api/common/logout' });
export const getProjectDrop = () =>
get<DropListItem[]>({
url: "/api/v1/project/get-project-drop",
url: '/api/v1/project/get-project-drop',
});

View File

@ -1,8 +1,8 @@
import { post } from "@/utils/request";
import { post } from '@/utils/request';
interface PreviewItemParams {
previewId: string | number;
filter?: string | [];
previewId: number | string;
filter?: [] | string;
page?: number;
perPage?: number;
}

View File

@ -1,3 +1,78 @@
<script setup>
import { computed, ref } from 'vue';
import Column from '@/plugins/antv-g2plot/column.vue';
import Line from '@/plugins/antv-g2plot/line.vue';
import { cloneDeep } from 'lodash-es';
const props = defineProps({
title: {
type: String,
default: '',
},
chartCfg: {
type: Object,
default: () => ({}),
},
filterConfig: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['toFilt']);
const chartType = ref('line');
const dateType = ref('day');
const filterData = ref({});
const rangePicker = computed(() => {
switch (dateType.value) {
case 'month': {
return 'month';
}
case 'week': {
return 'week';
}
default: {
return 'date';
}
}
});
const currentChart = computed(() => {
return props.chartCfg[chartType.value];
});
const toFilt = () => {
const cloneFilter = cloneDeep(props.filterConfig);
const filter = cloneFilter
.filter((item) => {
return (
filterData.value[item.name] !== undefined &&
filterData.value[item.name] !== null
);
})
.map((item) => {
return item.type === 'time'
? {
name: item.name,
type: item.type,
start_time: filterData.value[item.name][0].format('YYYY-MM-DD'),
end_time: filterData.value[item.name][1].format('YYYY-MM-DD'),
date_type: dateType.value,
}
: {
name: item.name,
type: item.type,
value: filterData.value[item.name],
};
});
emit('toFilt', {
filter,
});
};
</script>
<template>
<div class="chart-show-box">
<div class="chart-name">
@ -38,72 +113,6 @@
</div>
</template>
<script setup>
import { computed, ref } from "vue";
import Line from "@/plugins/antv-g2plot/line.vue";
import Column from "@/plugins/antv-g2plot/column.vue";
import { cloneDeep } from "lodash-es";
const props = defineProps({
title: {
type: String,
default: "",
},
chartCfg: {
type: Object,
default: () => ({}),
},
filterConfig: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(["toFilt"]);
const chartType = ref("line");
const dateType = ref("day");
const filterData = ref({});
const rangePicker = computed(() => {
switch(dateType.value) {
case 'week':
return 'week';
case 'month':
return 'month';
default:
return 'date';
}
});
const currentChart = computed(() => {
return props.chartCfg[chartType.value];
})
const toFilt = () => {
const cloneFilter = cloneDeep(props.filterConfig);
const filter = cloneFilter
.filter((item) => {
return filterData.value[item.name] !== undefined && filterData.value[item.name] !== null;
})
.map((item) => {
return item.type === 'time' ? {
name: item.name,
type: item.type,
start_time: filterData.value[item.name][0].format('YYYY-MM-DD'),
end_time: filterData.value[item.name][1].format('YYYY-MM-DD'),
date_type: dateType.value,
} : {
name: item.name,
type: item.type,
value: filterData.value[item.name],
}
})
emit('toFilt', {
filter,
});
};
</script>
<style lang="less" scoped>
.chart-wrap {
padding: 20px;

View File

@ -1,3 +1,139 @@
<script setup>
import { reactive, ref, watch } from 'vue';
import { CloudDownloadOutlined } from '@ant-design/icons-vue';
import { useDebounceFn } from '@vueuse/core';
import { cloneDeep } from 'lodash-es';
const props = defineProps({
previewId: {
type: Number,
default: null,
},
filterConfig: {
type: Array,
default: () => [],
},
columnConfig: {
type: Array,
default: () => [],
},
dataList: {
type: Array,
default: () => [],
},
total: {
type: Number,
default: 0,
},
title: {
type: String,
default: '',
},
isExport: {
type: Number,
default: 0,
},
});
const emit = defineEmits(['toFilt']);
const YCODE_BASEURL = import.meta.env.VITE_YCODE_BASEURL;
const filterData = ref({});
const pageState = reactive({
page: 1,
perPage: 20,
});
watch(
() => props.filterConfig,
(newVal) => {
newVal.forEach((item) => {
//
if (item.type === 'number_range' && !filterData.value[item.name]) {
filterData.value[item.name] = {
min: undefined,
max: undefined,
};
}
});
},
{ immediate: true },
);
const filterOption = (input, option) => {
return option.label.toLowerCase().includes(input.toLowerCase());
};
const getFilter = () => {
const cloneFilter = cloneDeep(props.filterConfig);
const filter = cloneFilter
.filter((item) => {
return (
filterData.value[item.name] !== undefined &&
filterData.value[item.name] !== null
);
})
.map((item) => {
if (item.type === 'time' && filterData.value[item.name]) {
//
return {
name: item.name,
type: item.type,
start_time: filterData.value[item.name][0].format('YYYY-MM-DD'),
end_time: filterData.value[item.name][1].format('YYYY-MM-DD'),
};
} else if (item.type === 'date_time' && filterData.value[item.name]) {
//
return {
name: item.name,
type: item.type,
start_time: `${filterData.value[item.name][0].format('YYYY-MM-DD HH:mm')}:00`,
end_time: `${filterData.value[item.name][1].format('YYYY-MM-DD HH:mm')}:59`,
};
} else if (item.type === 'number_range') {
//
return {
name: item.name,
type: item.type,
min: filterData.value[item.name].min
? String(filterData.value[item.name].min)
: '',
max: filterData.value[item.name].max
? String(filterData.value[item.name].max)
: '',
};
} else {
return {
name: item.name,
type: item.type,
value: filterData.value[item.name],
};
}
});
return filter;
};
const getData = () => {
emit('toFilt', {
filter: getFilter(),
page: pageState.page,
perPage: pageState.perPage,
});
};
const toFilt = useDebounceFn(() => {
pageState.page = 1;
getData();
}, 500);
const pageChange = () => {
getData();
};
</script>
<template>
<div class="y-table-container">
<div class="y-table-name">
@ -21,7 +157,7 @@
:filter-option="filterOption"
v-model:value="filterData[item.name]"
@change="toFilt"
></a-select>
/>
<!-- 输入框 -->
<a-input
v-else-if="item.type === 'text'"
@ -71,8 +207,7 @@
<a
:href="`${YCODE_BASEURL}/api/v1/preview/export?preview_id=${previewId}&filter=${JSON.stringify(getFilter())}`"
target="_blank"
><a-button type="primary"><CloudDownloadOutlined />导出</a-button></a
>
><a-button type="primary"><CloudDownloadOutlined />导出</a-button></a>
</div>
</div>
<div class="y-table-content">
@ -94,8 +229,7 @@
v-else-if="column.show_type === 'link'"
target="_blank"
:href="record[column.dataIndex]"
>{{ record[column.dataIndex] }}</a
>
>{{ record[column.dataIndex] }}</a>
<div
v-else-if="column.show_type === 'richText'"
v-html="record[column.dataIndex]"
@ -111,136 +245,13 @@
:show-size-changer="false"
size="small"
class="pagination-box"
:show-total="total => `共 ${total} 条`"
:show-total="(total) => `共 ${total} 条`"
@change="pageChange"
/>
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch } from "vue";
import { useDebounceFn } from "@vueuse/core";
import { cloneDeep } from "lodash-es";
import { CloudDownloadOutlined } from "@ant-design/icons-vue";
const YCODE_BASEURL = import.meta.env.VITE_YCODE_BASEURL
const props = defineProps({
previewId: {
type: Number,
default: null,
},
filterConfig: {
type: Array,
default: () => [],
},
columnConfig: {
type: Array,
default: () => [],
},
dataList: {
type: Array,
default: () => [],
},
total: {
type: Number,
default: 0,
},
title: {
type: String,
default: "",
},
isExport: {
type: Number,
default: 0,
},
});
const emit = defineEmits(["toFilt"]);
const filterData = ref({});
const pageState = reactive({
page: 1,
perPage: 20,
});
watch(() => props.filterConfig, (newVal) => {
newVal.forEach((item) => {
//
if (item.type === 'number_range' && !filterData.value[item.name]) {
filterData.value[item.name] = {
min: undefined,
max: undefined,
};
}
});
}, { immediate: true });
const filterOption = (input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const getFilter = () => {
const cloneFilter = cloneDeep(props.filterConfig);
const filter = cloneFilter
.filter((item) => {
return filterData.value[item.name] !== undefined && filterData.value[item.name] !== null;
})
.map((item) => {
if (item.type === 'time' && filterData.value[item.name]) {
//
return {
name: item.name,
type: item.type,
start_time: filterData.value[item.name][0].format('YYYY-MM-DD'),
end_time: filterData.value[item.name][1].format('YYYY-MM-DD'),
};
} else if (item.type === 'date_time' && filterData.value[item.name]) {
//
return {
name: item.name,
type: item.type,
start_time: filterData.value[item.name][0].format('YYYY-MM-DD HH:mm') + ':00',
end_time: filterData.value[item.name][1].format('YYYY-MM-DD HH:mm') + ':59',
};
} else if (item.type === 'number_range') {
//
return {
name: item.name,
type: item.type,
min: filterData.value[item.name].min ? String(filterData.value[item.name].min) : '',
max: filterData.value[item.name].max ? String(filterData.value[item.name].max) : '',
};
} else {
return {
name: item.name,
type: item.type,
value: filterData.value[item.name],
};
}
});
return filter
};
const getData = () => {
emit("toFilt", {
filter: getFilter(),
page: pageState.page,
perPage: pageState.perPage,
});
};
const toFilt = useDebounceFn(() => {
pageState.page = 1
getData();
}, 500);
const pageChange = () => {
getData();
};
</script>
<style lang="less" scoped>
.y-table-name {
margin-bottom: 10px;

View File

@ -1,13 +1,14 @@
// @primary-bg-color: #f8f8f8;
@import "../src/assets/styles/variable.less";
@import '../src/assets/styles/variable.less';
html,
body {
background-color: @primary-bg-color;
height: 100%;
padding: 0;
margin: 0;
height: 100%;
background-color: @primary-bg-color;
}
#app {
height: 100%;
}
@ -24,23 +25,25 @@ body {
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.15);
background-color: rgb(0 0 0 / 15%);
border-radius: 15px;
}
/* 滚动条滑块悬停 */
::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.25);
background-color: rgb(0 0 0 / 25%);
}
.normal-container {
padding: 16px 24px;
border-radius: 6px;
background-color: #fff;
border-radius: 6px;
}
.mt-8 {
margin-top: 8px;
}
.mt-16 {
margin-top: 16px;
}

View File

@ -1,18 +1,23 @@
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import Header from "./components/Header.vue";
import Sider from "./components/Sider.vue";
import { computed, onMounted, ref } from 'vue';
import {
FullscreenExitOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
FullscreenExitOutlined,
} from "@ant-design/icons-vue";
import { useEventListener } from "@vueuse/core";
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
} from '@ant-design/icons-vue';
import { useEventListener } from '@vueuse/core';
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import Header from './components/Header.vue';
import Sider from './components/Sider.vue';
const __POWERED_BY_QIANKUN__ = computed(() => {
return qiankunWindow.__POWERED_BY_QIANKUN__ || window?.proxy?.__POWERED_BY_QIANKUN__
})
return (
qiankunWindow.__POWERED_BY_QIANKUN__ ||
window?.proxy?.__POWERED_BY_QIANKUN__
);
});
// const userInfoStore = useUserInfoStore();
const isCollapsed = ref(false);
@ -23,7 +28,7 @@ onMounted(() => {
// userInfoStore.fetchUserInfo();
});
useEventListener(window, "fullscreenchange", () => {
useEventListener(window, 'fullscreenchange', () => {
isFullscreen.value = !!document.fullscreenElement;
});
@ -43,7 +48,7 @@ const handleExitFullscreen = () => {
class="left-aside"
:class="{ 'left-aside-collapsed': isCollapsed }"
>
<Sider :inlineCollapsed="isCollapsed" />
<Sider :inline-collapsed="isCollapsed" />
<div class="collapsed-icon">
<component
:is="isCollapsed ? MenuUnfoldOutlined : MenuFoldOutlined"
@ -60,7 +65,7 @@ const handleExitFullscreen = () => {
ref="container"
>
<header class="header">
<Header @requestFullscreen="handleFullscreen" />
<Header @request-fullscreen="handleFullscreen" />
</header>
<div class="i-container">
<router-view />
@ -127,8 +132,8 @@ const handleExitFullscreen = () => {
height: 100%;
}
:deep(
:where(.css-dev-only-do-not-override-1hsjdkk).ant-menu-inline-collapsed
),
:where(.css-dev-only-do-not-override-1hsjdkk).ant-menu-inline-collapsed
),
.left-aside-collapsed {
width: @aside-width-collapsed;
}

View File

@ -1,37 +1,40 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import { createProjectRouter } from "./router";
import "./global.less";
import VueGridLayout from "vue-grid-layout"; // 引入layout
import { createApp } from 'vue';
import VueGridLayout from 'vue-grid-layout'; // 引入layout
import { createPinia } from 'pinia';
import {
renderWithQiankun,
qiankunWindow,
} from "vite-plugin-qiankun/dist/helper";
renderWithQiankun,
} from 'vite-plugin-qiankun/dist/helper';
import App from './App.vue';
import { createProjectRouter } from './router';
import './global.less';
let app;
function render(props: Object = {}) {
function render(props: object = {}) {
app = createApp(App);
setStyleSheet(props.styles);
const router = createProjectRouter(props.base);
app.use(router);
app.use(VueGridLayout);
app.use(createPinia());
app.mount("#y-code-app");
app.mount('#y-code-app');
}
function setStyleSheet(styles: Object = {}) {
const styleEle = document.createElement("style");
styleEle.type = "text/css";
function setStyleSheet(styles: object = {}) {
const styleEle = document.createElement('style');
styleEle.type = 'text/css';
styleEle.innerHTML = `
:root {
--primary-color: ${styles.primaryColor || "#1677ff"};
--primary-light-color: ${styles.primaryLightColor || "#4096ff"};
--table-head-bg-color: ${styles.tableHeadBgColor || "#fafafa"};
--table-head-font-color: ${styles.tableHeadFontColor || "#191919"};
--primary-color: ${styles.primaryColor || '#1677ff'};
--primary-light-color: ${styles.primaryLightColor || '#4096ff'};
--table-head-bg-color: ${styles.tableHeadBgColor || '#fafafa'};
--table-head-font-color: ${styles.tableHeadFontColor || '#191919'};
}
`;
document.head.appendChild(styleEle);
document.head.append(styleEle);
}
const __POWERED_BY_QIANKUN__ =
@ -41,16 +44,16 @@ const __POWERED_BY_QIANKUN__ =
if (__POWERED_BY_QIANKUN__) {
renderWithQiankun({
bootstrap() {
console.log("bootstrap");
console.log('bootstrap');
return Promise.resolve();
},
mount(props) {
console.log("mount");
console.log('mount');
render(props);
return Promise.resolve();
},
unmount() {
console.log("unmount");
console.log('unmount');
if (app) {
app.unmount();
}

View File

@ -1,16 +1,13 @@
<template>
<div :class="className" :style="style" ref="container"></div>
</template>
<script setup>
import { Column } from "@antv/g2plot";
import { Column } from '@antv/g2plot';
// hooks
import useChart from "./useChart";
import useChart from './useChart';
const props = defineProps({
className: {
type: String,
default: "",
default: '',
},
style: {
type: Object,
@ -24,3 +21,7 @@ const props = defineProps({
const { container } = useChart(Column, props);
</script>
<template>
<div :class="className" :style="style" ref="container"></div>
</template>

View File

@ -1,17 +1,13 @@
<template>
<div :class="className" :style="style" ref="container"></div>
</template>
<script setup>
import { Line } from '@antv/g2plot';
import { Line } from "@antv/g2plot";
// hooks
import useChart from "./useChart";
import useChart from './useChart';
const props = defineProps({
className: {
type: String,
default: "",
default: '',
},
style: {
type: Object,
@ -25,3 +21,7 @@ const props = defineProps({
const { container } = useChart(Line, props);
</script>
<template>
<div :class="className" :style="style" ref="container"></div>
</template>

View File

@ -1,16 +1,13 @@
<template>
<div :class="className" :style="style" ref="container"></div>
</template>
<script setup>
import { Pie } from "@antv/g2plot";
import { Pie } from '@antv/g2plot';
// hooks
import useChart from "./useChart";
import useChart from './useChart';
const props = defineProps({
className: {
type: String,
default: "",
default: '',
},
style: {
type: Object,
@ -24,3 +21,7 @@ const props = defineProps({
const { container } = useChart(Pie, props);
</script>
<template>
<div :class="className" :style="style" ref="container"></div>
</template>

View File

@ -1,5 +1,6 @@
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import { cloneDeep } from "lodash-es";
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { cloneDeep } from 'lodash-es';
export default function useChart(ChartClass, props) {
const chart = ref(null); // 表格实例
@ -35,12 +36,12 @@ export default function useChart(ChartClass, props) {
onEvent(chartInstance, event);
}
};
chartInstance.on("*", handler);
chartInstance.on('*', handler);
});
onBeforeUnmount(() => {
chart.value.destroy();
chart.value.off("*", handler);
chart.value.off('*', handler);
chart.value = undefined;
});
@ -54,32 +55,32 @@ export default function useChart(ChartClass, props) {
},
{
deep: true,
}
},
);
const toDataURL = (type = "image/png", encoderOptions) => {
const toDataURL = (type = 'image/png', encoderOptions) => {
return chart.value?.chart.canvas.cfg.el.toDataURL(type, encoderOptions);
};
const downloadImage = (
name = "download",
type = "image/png",
encoderOptions
name = 'download',
type = 'image/png',
encoderOptions,
) => {
let imageName = name;
if (name.indexOf(".") === -1) {
imageName = `${name}.${type.split("/")[1]}`;
if (!name.includes('.')) {
imageName = `${name}.${type.split('/')[1]}`;
}
const base64 = chart.value?.chart.canvas.cfg.el.toDataURL(
type,
encoderOptions
encoderOptions,
);
let a = document.createElement("a");
let a = document.createElement('a');
a.href = base64;
a.download = imageName;
document.body.appendChild(a);
document.body.append(a);
a.click();
document.body.removeChild(a);
a.remove();
a = null;
return imageName;
};

View File

@ -1,16 +1,20 @@
import { createRouter, createWebHistory, type Router } from "vue-router";
import { titleGuard } from "./guards";
import routeList from "./routes";
import { qiankunWindow } from "vite-plugin-qiankun/dist/helper";
import type { Router } from 'vue-router';
let router: Router | null = null;
export const createProjectRouter = (base = "") => {
import { createRouter, createWebHistory } from 'vue-router';
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import { titleGuard } from './guards';
import routeList from './routes';
let router: null | Router = null;
export const createProjectRouter = (base = '') => {
const __POWERED_BY_QIANKUN__ =
qiankunWindow?.__POWERED_BY_QIANKUN__ ||
window.proxy?.__POWERED_BY_QIANKUN__;
router = createRouter({
history: createWebHistory(
base || (__POWERED_BY_QIANKUN__ ? "/y-code-app/" : "")
base || (__POWERED_BY_QIANKUN__ ? '/y-code-app/' : ''),
),
routes: routeList,
});

View File

@ -1,11 +1,13 @@
import Layout from "@/layout/index.vue";
import type { RendererElement, RendererNode, VNode } from 'vue';
import { h } from 'vue';
import Layout from '@/layout/index.vue';
import {
HomeOutlined,
BarChartOutlined,
AppstoreOutlined,
} from "@ant-design/icons-vue";
import { h } from "vue";
import type { VNode, RendererNode, RendererElement } from "vue";
BarChartOutlined,
HomeOutlined,
} from '@ant-design/icons-vue';
export interface RouteType {
path: string;
@ -26,85 +28,85 @@ export interface RouteType {
const routeList: RouteType[] = [
{
path: "/",
name: "layout",
path: '/',
name: 'layout',
component: Layout,
meta: { title: "首页" },
meta: { title: '首页' },
children: [
{
path: "",
name: "-",
path: '',
name: '-',
meta: {},
children: [],
redirect: "/config-manage/project-cfg",
redirect: '/config-manage/project-cfg',
},
{
path: "/config-manage",
name: "config-manage",
path: '/config-manage',
name: 'config-manage',
isMenu: true,
meta: { title: "配置管理" },
meta: { title: '配置管理' },
icon: () => h(HomeOutlined),
children: [
{
path: "project-cfg",
name: "project-cfg",
path: 'project-cfg',
name: 'project-cfg',
component: () =>
import("@/views/config-manage/project-cfg/index.vue"),
meta: { title: "项目配置" },
import('@/views/config-manage/project-cfg/index.vue'),
meta: { title: '项目配置' },
isMenu: true,
children: [],
},
{
path: "module-cfg",
name: "module-cfg",
path: 'module-cfg',
name: 'module-cfg',
component: () =>
import("@/views/config-manage/module-cfg/index.vue"),
meta: { title: "数据来源配置" },
import('@/views/config-manage/module-cfg/index.vue'),
meta: { title: '数据来源配置' },
isMenu: true,
children: [],
},
],
},
{
path: "/view-all-manage",
name: "view-all-manage",
path: '/view-all-manage',
name: 'view-all-manage',
isMenu: true,
meta: { title: "视图管理" },
meta: { title: '视图管理' },
icon: () => h(BarChartOutlined),
children: [
{
path: "view-list",
name: "view-list",
path: 'view-list',
name: 'view-list',
component: () =>
import("@/views/view-all-manage/view-list/index.vue"),
meta: { title: "视图列表" },
import('@/views/view-all-manage/view-list/index.vue'),
meta: { title: '视图列表' },
isMenu: true,
children: [],
},
{
path: "create-view",
name: "create-view",
path: 'create-view',
name: 'create-view',
component: () =>
import("@/views/view-all-manage/create-view/index.vue"),
meta: { title: "创建视图" },
import('@/views/view-all-manage/create-view/index.vue'),
meta: { title: '创建视图' },
isMenu: true,
children: [],
},
],
},
{
path: "/page-show-info",
name: "page-show-info",
path: '/page-show-info',
name: 'page-show-info',
isMenu: true,
meta: { title: "视图预览" },
meta: { title: '视图预览' },
icon: () => h(AppstoreOutlined),
children: [
{
path: "page-info",
name: "page-info",
path: 'page-info',
name: 'page-info',
component: () =>
import("@/views/page-show-info/page-info/index.vue"),
meta: { title: "项目报表" },
import('@/views/page-show-info/page-info/index.vue'),
meta: { title: '项目报表' },
isMenu: true,
children: [],
},

View File

@ -1,8 +1,10 @@
import { readonly, ref } from 'vue';
import { defineStore } from 'pinia';
import { getUserInfo } from '@/api/common';
import type { UserInfoType } from '@/api/common';
import { readonly, ref } from 'vue';
import { getUserInfo } from '@/api/common';
import { defineStore } from 'pinia';
export const useUserInfoStore = defineStore('userInfoStore', () => {
const userInfo = ref<UserInfoType>();

View File

@ -1,26 +1,27 @@
import axios, { AxiosError } from 'axios';
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { message } from 'ant-design-vue';
import axios, { AxiosError } from 'axios';
export interface ResopnseType<T> {
reason: string
message: string
data: T
ts: string
reason: string;
message: string;
data: T;
ts: string;
}
// export const YCODE_BASEURL: string = import.meta.env.VITE_YCODE_BASEURL;
// https://custom-chart-pre-api.shiyue.com
export const YCODE_BASEURL: string = 'https://custom-chart-pre-api.shiyue.com'
export const YCODE_BASEURL: string = 'https://custom-chart-pre-api.shiyue.com';
const requestType = {
const requestType = {
base: YCODE_BASEURL,
};
const baseAxios: AxiosInstance = axios.create({
baseURL: '',
timeout: 100000,
timeout: 100_000,
withCredentials: true,
});
@ -28,26 +29,30 @@ const errorHandle = (error: AxiosError) => {
if (error.response) {
const status = error.response?.status;
switch (status) {
case 401:
case 401: {
message.warning('请先登录');
window.location.href = `${YCODE_BASEURL}/login?redirect=${encodeURIComponent(window.location.href)}`;
break;
case 403:
}
case 403: {
message.warning('权限不足');
break;
case 500:
}
case 500: {
message.warning('服务器出错了…… (>_<)');
break;
default:
}
default: {
message.warning('服务器出错了…… (>_<)');
break;
}
}
return Promise.reject(error);
}
message.error(error.message);
return Promise.reject(error);
};
//响应拦截器
// 响应拦截器
baseAxios.interceptors.response.use((response: AxiosResponse) => {
const { data, status } = response;
@ -64,7 +69,9 @@ baseAxios.interceptors.response.use((response: AxiosResponse) => {
}
}, errorHandle);
type RequestConfig = Omit<AxiosRequestConfig, 'baseURL'> & { baseURL?: keyof typeof requestType }
type RequestConfig = Omit<AxiosRequestConfig, 'baseURL'> & {
baseURL?: keyof typeof requestType;
};
const request = <T = any>(config: RequestConfig) => {
const host = requestType[config.baseURL || 'base'];
return new Promise<T>((resolve, reject) => {
@ -73,8 +80,8 @@ const request = <T = any>(config: RequestConfig) => {
.then((res: T) => {
resolve(res);
})
.catch((err: unknown) => {
reject(err);
.catch((error: unknown) => {
reject(error);
});
});
};
@ -101,6 +108,7 @@ const put = <T = any>(config?: RequestConfig) =>
},
});
const del = <T = any>(config?: { url: string }) => request<ResopnseType<T>>({ ...config, method: 'DELETE' });
const del = <T = any>(config?: { url: string }) =>
request<ResopnseType<T>>({ ...config, method: 'DELETE' });
export { get, post, del, put, request };
export { del, get, post, put, request };

View File

@ -1,3 +1,121 @@
<script setup>
import { ref, watch } from 'vue';
import { getDbTableSelect } from '@/views/config-manage/module-cfg/service';
const props = defineProps({
open: {
type: Boolean,
default: false,
},
type: {
type: String,
default: 'add',
},
data: {
type: Object,
default: () => ({}),
},
projectSelect: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['ok']);
const formRules = ref({
modular_name: [
{ required: true, message: '请输入数据来源', trigger: 'submit' },
],
original_type: [{ required: true, message: '请选择', trigger: 'submit' }],
original_sql: [{ required: true, message: '请输入', trigger: 'submit' }],
table: [{ required: true, message: '请选择', trigger: 'submit' }],
});
const tableTypes = ref([]);
const formRef = ref();
const formData = ref({
project_id: undefined,
modular_name: undefined,
is_show: 0,
original_type: 1, // 1 - 2 -
original_sql: undefined,
table: undefined,
is_other_database: 0,
drive_type: 1,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
});
watch(
() => props.data,
(newVal) => {
if (props.type === 'add') {
resetFormData();
} else {
formData.value = {
modular_id: newVal.modular_id,
project_id: newVal.project_id,
modular_name: newVal.modular_name,
is_show: newVal.is_show,
original_type: newVal.original_type,
original_sql: newVal.original_sql,
table: newVal.table,
drive_type: newVal.drive_type,
is_other_database: newVal.is_other_database,
database_address: newVal.database_address,
database_port: newVal.database_port,
database_name: newVal.database_name,
database_username: newVal.database_username,
database_password: newVal.database_password,
};
}
},
);
const toGetDbTable = () => {
getDbTableSelect({ projectId: formData.value.project_id }).then((res) => {
tableTypes.value = res.data;
});
};
const isOtherChange = (val) => {
formData.value.drive_type = val ? 1 : undefined;
formData.value.database_address = undefined;
formData.value.database_port = undefined;
formData.value.database_name = undefined;
formData.value.database_username = undefined;
formData.value.database_password = undefined;
};
const resetFormData = () => {
formData.value = {
project_id: undefined,
modular_name: undefined,
is_show: 0,
original_type: 1, // 1 - 2 -
original_sql: undefined,
table: undefined,
is_other_database: 0,
drive_type: undefined,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
};
};
const handleOk = () => {
formRef.value.validate().then(() => {
emit('ok', formData.value);
});
};
</script>
<template>
<a-modal :open="open" @ok="handleOk">
<a-form
@ -24,8 +142,8 @@
<a-form-item label="展示状态" name="is_show">
<a-switch
v-model:checked="formData.is_show"
:checkedValue="1"
:unCheckedValue="0"
:checked-value="1"
:un-checked-value="0"
/>
</a-form-item>
<a-form-item label="数据源类型" name="original_type">
@ -58,8 +176,8 @@
<a-form-item label="数据库特殊配置" name="is_other_database">
<a-switch
v-model:checked="formData.is_other_database"
:checkedValue="1"
:unCheckedValue="0"
:checked-value="1"
:un-checked-value="0"
@change="isOtherChange"
/>
</a-form-item>
@ -104,124 +222,6 @@
</a-space>
</a-form-item>
</template>
</a-form>
</a-modal>
</template>
<script setup>
import { ref, watch } from "vue";
import { getDbTableSelect } from "@/views/config-manage/module-cfg/service";
const props = defineProps({
open: {
type: Boolean,
default: false,
},
type: {
type: String,
default: "add",
},
data: {
type: Object,
default: () => ({}),
},
projectSelect: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(["ok"]);
const formRules = ref({
modular_name: [
{ required: true, message: "请输入数据来源", trigger: "submit" },
],
original_type: [{ required: true, message: "请选择", trigger: "submit" }],
original_sql: [{ required: true, message: "请输入", trigger: "submit" }],
table: [{ required: true, message: "请选择", trigger: "submit" }],
});
const tableTypes = ref([]);
const formRef = ref();
const formData = ref({
project_id: undefined,
modular_name: undefined,
is_show: 0,
original_type: 1, // 1 - 2 -
original_sql: undefined,
table: undefined,
is_other_database: 0,
drive_type: 1,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
});
watch(
() => props.data,
(newVal) => {
if (props.type === "add") {
resetFormData();
} else {
formData.value = {
modular_id: newVal.modular_id,
project_id: newVal.project_id,
modular_name: newVal.modular_name,
is_show: newVal.is_show,
original_type: newVal.original_type,
original_sql: newVal.original_sql,
table: newVal.table,
drive_type: newVal.drive_type,
is_other_database: newVal.is_other_database,
database_address: newVal.database_address,
database_port: newVal.database_port,
database_name: newVal.database_name,
database_username: newVal.database_username,
database_password: newVal.database_password,
};
}
}
);
const toGetDbTable = () => {
getDbTableSelect({ projectId: formData.value.project_id }).then((res) => {
tableTypes.value = res.data;
});
};
const isOtherChange = (val) => {
formData.value.drive_type = val ? 1 : undefined
formData.value.database_address = undefined
formData.value.database_port = undefined
formData.value.database_name = undefined
formData.value.database_username = undefined
formData.value.database_password = undefined
}
const resetFormData = () => {
formData.value = {
project_id: undefined,
modular_name: undefined,
is_show: 0,
original_type: 1, // 1 - 2 -
original_sql: undefined,
table: undefined,
is_other_database: 0,
drive_type: undefined,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
};
};
const handleOk = () => {
formRef.value.validate().then(() => {
emit("ok", formData.value);
});
};
</script>

View File

@ -1,3 +1,223 @@
<script setup>
import { onMounted, reactive, ref, watch } from 'vue';
import {
originalTypes,
showTypeOpts,
viewCfgCols,
} from '@/views/config-manage/module-cfg/config';
import {
deleteField,
getFieldList,
getFieldNumSelect,
getFieldTypeSelect,
saveField,
// getFieldDetail,
} from '@/views/config-manage/module-cfg/service';
import { message } from 'ant-design-vue';
const props = defineProps({
open: {
type: Boolean,
default: false,
},
modularId: {
type: Number,
default: 0,
},
});
const listLoading = ref(false);
const fieldName = ref('');
const dataList = ref([]);
const fieldTypeSel = ref([]);
const fieldNumTypeSel = ref([]);
const specialVisible = ref(false);
const specialModalId = ref();
const pageState = reactive({
page: 1,
perPage: 20,
total: 0,
});
const editableData = reactive({});
const specialModalData = reactive({
drive_type: undefined,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
});
watch(
() => props.open,
(newVal) => {
if (newVal) {
toGetList();
}
},
);
onMounted(() => {
toGetFieldTypes();
toGetFieldNumSelect();
});
//
const toGetFieldTypes = () => {
getFieldTypeSelect().then((res) => {
fieldTypeSel.value = res.data;
});
};
//
const toGetFieldNumSelect = () => {
getFieldNumSelect().then((res) => {
fieldNumTypeSel.value = res.data;
});
};
//
const toGetList = () => {
listLoading.value = true;
getFieldList({
fieldName: fieldName.value,
modularId: props.modularId,
page: pageState.page,
perPage: pageState.perPage,
}).then((res) => {
dataList.value = res.data.list;
pageState.total = res.data.total;
});
};
const pageChange = () => {
toGetList();
};
const search = () => {
pageState.page = 1;
toGetList();
};
const addField = () => {
const item = {
field_id: `${Date.now()}`,
field_title: undefined,
field_name: undefined,
field_numerical_type_id: undefined,
is_search: 0,
field_type_id: undefined,
belong_to_table: undefined,
is_other_database: 0,
original_type: undefined,
original_sql: undefined,
sort: 0,
drive_type: undefined,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
};
dataList.value.unshift(item);
editableData[item.field_id] = {
...item,
};
};
const handleEdit = (record) => {
editableData[record.field_id] = {
...record,
};
};
const handleDelete = (record) => {
deleteField({
field_id: record.field_id,
}).then(() => {
message.success('删除成功');
toGetList();
});
};
const handleCancel = (record) => {
if (typeof record.field_id === 'string') {
dataList.value.shift();
} else {
delete editableData[record.field_id];
}
};
const openSpecialModal = (record) => {
specialVisible.value = true;
specialModalId.value = record.field_id;
if (!specialModalData[record.field_id]) {
specialModalData[record.field_id] = {
drive_type: undefined,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
};
}
};
const handleSave = (record) => {
const params = {
is_search: record.is_search,
field_type_id: record.field_type_id,
modular_id: props.modularId,
original_type: record.original_type,
original_sql: record.original_sql,
is_other_database: record.is_other_database,
};
//
if (specialModalData[record.field_id]) {
params.drive_type = specialModalData[record.field_id].drive_type;
params.database_address =
specialModalData[record.field_id].database_address;
params.database_port = specialModalData[record.field_id].database_port;
params.database_name = specialModalData[record.field_id].database_name;
params.database_username =
specialModalData[record.field_id].database_username;
params.database_password =
specialModalData[record.field_id].database_password;
}
//
const validateFields = [
{ field: 'field_title', msg: '请填写字段标题' },
{ field: 'field_name', msg: '请填写字段名称' },
{ field: 'field_numerical_type_id', msg: '请选择字段类型' },
{ field: 'belong_to_table', msg: '请填写关联表' },
{ field: 'show_type', msg: '请选择展示类型' },
];
for (const curr of validateFields) {
if (record[curr.field]) {
params[curr.field] = record[curr.field];
} else {
message.error(curr.msg);
return;
}
}
if (record.is_search && !record.field_type_id) {
message.error('请选择搜索类型');
return;
}
//
if (typeof record.field_id === 'number') {
params.field_id = record.field_id;
}
saveField(params).then(() => {
delete editableData[record.field_id];
message.success('保存成功');
toGetList();
});
};
</script>
<template>
<a-modal :open="open" title="字段管理" style="top: 30px" :footer="null">
<div class="field-manager">
@ -49,8 +269,7 @@
placeholder="请选择"
allow-clear
style="width: 120px"
>
</a-select>
/>
<template v-else>
{{ record.field_numerical_name }}
</template>
@ -63,8 +282,7 @@
placeholder="请选择"
allow-clear
style="width: 120px"
>
</a-select>
/>
<template v-else>
{{ record.show_type }}
</template>
@ -77,8 +295,7 @@
placeholder="请选择"
allow-clear
style="width: 120px"
>
</a-select>
/>
<template v-else>
{{ record.field_type_name }}
</template>
@ -87,29 +304,30 @@
<a-switch
v-if="editableData[record.field_id]"
v-model:checked="record.is_search"
:checkedValue="1"
:unCheckedValue="0"
:checked-value="1"
:un-checked-value="0"
/>
<template v-else>
{{ record.is_search ? "是" : "否" }}
{{ record.is_search ? '是' : '否' }}
</template>
</template>
<template v-if="column.dataIndex === 'is_other_database'">
<template v-if="editableData[record.field_id]">
<a-switch
v-model:checked="record.is_other_database"
:checkedValue="1"
:unCheckedValue="0"
:checked-value="1"
:un-checked-value="0"
/>
<a-button
v-if="record.is_other_database"
type="link"
@click="openSpecialModal(record)"
>请填写</a-button
>
请填写
</a-button>
</template>
<template v-else>
{{ record.is_other_database ? "是" : "否" }}
{{ record.is_other_database ? '是' : '否' }}
</template>
</template>
<template v-if="column.dataIndex === 'original_type'">
@ -119,20 +337,19 @@
v-model:value="record.original_type"
:options="originalTypes"
allow-clear
>
</a-select>
/>
<template v-else>
{{ record.original_type_name }}
</template>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space v-if="editableData[record.field_id]">
<a-button type="primary" size="small" @click="handleSave(record)"
>保存</a-button
>
<a-button size="small" @click="handleCancel(record)"
>取消</a-button
>
<a-button type="primary" size="small" @click="handleSave(record)">
保存
</a-button>
<a-button size="small" @click="handleCancel(record)">
取消
</a-button>
</a-space>
<div v-else>
<a-button type="link" @click="handleEdit(record)">修改</a-button>
@ -159,7 +376,7 @@
:open="specialVisible"
:width="640"
title="数据库特配"
:bodyStyle="{ marginTop: '30px' }"
:body-style="{ marginTop: '30px' }"
@ok="specialVisible = false"
@cancel="specialVisible = false"
>
@ -213,219 +430,6 @@
</a-modal>
</template>
<script setup>
import { onMounted, reactive, ref, watch } from "vue";
import { viewCfgCols, originalTypes, showTypeOpts } from "@/views/config-manage/module-cfg/config";
import {
getFieldTypeSelect,
getFieldNumSelect,
getFieldList,
deleteField,
saveField,
// getFieldDetail,
} from "@/views/config-manage/module-cfg/service";
import { message } from "ant-design-vue";
const props = defineProps({
open: {
type: Boolean,
default: false,
},
modularId: {
type: Number,
default: 0,
},
});
const listLoading = ref(false);
const fieldName = ref("");
const dataList = ref([]);
const fieldTypeSel = ref([]);
const fieldNumTypeSel = ref([]);
const specialVisible = ref(false);
const specialModalId = ref();
const pageState = reactive({
page: 1,
perPage: 20,
total: 0,
});
const editableData = reactive({});
const specialModalData = reactive({
drive_type: undefined,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
});
watch(
() => props.open,
(newVal) => {
if (newVal) {
toGetList();
}
},
);
onMounted(() => {
toGetFieldTypes();
toGetFieldNumSelect();
});
//
const toGetFieldTypes = () => {
getFieldTypeSelect().then((res) => {
fieldTypeSel.value = res.data;
});
};
//
const toGetFieldNumSelect = () => {
getFieldNumSelect().then((res) => {
fieldNumTypeSel.value = res.data;
})
};
//
const toGetList = () => {
listLoading.value = true;
getFieldList({
fieldName: fieldName.value,
modularId: props.modularId,
page: pageState.page,
perPage: pageState.perPage,
}).then((res) => {
dataList.value = res.data.list;
pageState.total = res.data.total;
});
};
const pageChange = () => {
toGetList();
};
const search = () => {
pageState.page = 1;
toGetList();
};
const addField = () => {
const item = {
field_id: new Date().getTime() + "",
field_title: undefined,
field_name: undefined,
field_numerical_type_id: undefined,
is_search: 0,
field_type_id: undefined,
belong_to_table: undefined,
is_other_database: 0,
original_type: undefined,
original_sql: undefined,
sort: 0,
drive_type: undefined,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
};
dataList.value.unshift(item);
editableData[item.field_id] = {
...item,
};
};
const handleEdit = (record) => {
editableData[record.field_id] = {
...record,
};
};
const handleDelete = (record) => {
deleteField({
field_id: record.field_id,
}).then(() => {
message.success('删除成功')
toGetList();
})
}
const handleCancel = (record) => {
if (typeof record.field_id === "string") {
dataList.value.shift();
} else {
delete editableData[record.field_id];
}
};
const openSpecialModal = (record) => {
specialVisible.value = true
specialModalId.value = record.field_id
if (!specialModalData[record.field_id]) {
specialModalData[record.field_id] = {
drive_type: undefined,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
}
}
}
const handleSave = (record) => {
const params = {
is_search: record.is_search,
field_type_id: record.field_type_id,
modular_id: props.modularId,
original_type: record.original_type,
original_sql: record.original_sql,
is_other_database: record.is_other_database,
};
//
if (specialModalData[record.field_id]) {
params.drive_type = specialModalData[record.field_id].drive_type
params.database_address = specialModalData[record.field_id].database_address
params.database_port = specialModalData[record.field_id].database_port
params.database_name = specialModalData[record.field_id].database_name
params.database_username = specialModalData[record.field_id].database_username
params.database_password = specialModalData[record.field_id].database_password
}
//
const validateFields = [
{ field: 'field_title', msg: "请填写字段标题" },
{ field: 'field_name', msg: "请填写字段名称" },
{ field: 'field_numerical_type_id', msg: "请选择字段类型" },
{ field: 'belong_to_table', msg: "请填写关联表" },
{ field: 'show_type', msg: "请选择展示类型" },
]
for(let i = 0; i < validateFields.length; i++) {
const curr = validateFields[i];
if (!record[curr.field]) {
message.error(curr.msg);
return;
} else {
params[curr.field] = record[curr.field];
}
}
if (record.is_search && !record.field_type_id) {
message.error("请选择搜索类型");
return;
}
//
if (typeof record.field_id === "number") {
params.field_id = record.field_id;
}
saveField(params).then(() => {
delete editableData[record.field_id];
message.success("保存成功");
toGetList();
});
};
</script>
<style lang="less" scoped>
.header-box {
margin-bottom: 10px;

View File

@ -1,41 +1,41 @@
export const moduleCfgCols = [
{ dataIndex: "modular_id", title: "编号", align: "center" },
{ dataIndex: "modular_name", title: "数据来源名称", align: "center" },
{ dataIndex: "project_name", title: "项目名称", align: "center" },
{ dataIndex: "is_show", title: "展示状态", align: "center" },
{ dataIndex: "original_type_handle", title: "数据源类型", align: "center" },
{ dataIndex: 'modular_id', title: '编号', align: 'center' },
{ dataIndex: 'modular_name', title: '数据来源名称', align: 'center' },
{ dataIndex: 'project_name', title: '项目名称', align: 'center' },
{ dataIndex: 'is_show', title: '展示状态', align: 'center' },
{ dataIndex: 'original_type_handle', title: '数据源类型', align: 'center' },
// { dataIndex: 'sort', title: '排序', align: 'center'},
{ dataIndex: "action", title: "操作", align: "center" },
{ dataIndex: 'action', title: '操作', align: 'center' },
];
export const viewCfgCols = [
{ dataIndex: "field_name", title: "字段名称", align: "center" },
{ dataIndex: "field_title", title: "字段标题", align: "center" },
{ dataIndex: 'field_name', title: '字段名称', align: 'center' },
{ dataIndex: 'field_title', title: '字段标题', align: 'center' },
{
dataIndex: "field_numerical_name",
title: "字段类型",
align: "center",
dataIndex: 'field_numerical_name',
title: '字段类型',
align: 'center',
width: 120,
},
{ dataIndex: "show_type", title: "展示类型", align: "center", width: 120 },
{ dataIndex: "field_type_name", title: "搜索类型", align: "center" },
{ dataIndex: "is_search", title: "是否可搜索", align: "center" },
{ dataIndex: "sort", title: "排序", align: "center" },
{ dataIndex: "belong_to_table", title: "所属表名称", align: "center" },
{ dataIndex: "is_other_database", title: "数据库特配", align: "center" },
{ dataIndex: "original_type", title: "数据源类型", align: "center" },
{ dataIndex: "original_sql", title: "数据源", align: "center", width: 400 },
{ dataIndex: "action", title: "操作", align: "center", width: 120 },
{ dataIndex: 'show_type', title: '展示类型', align: 'center', width: 120 },
{ dataIndex: 'field_type_name', title: '搜索类型', align: 'center' },
{ dataIndex: 'is_search', title: '是否可搜索', align: 'center' },
{ dataIndex: 'sort', title: '排序', align: 'center' },
{ dataIndex: 'belong_to_table', title: '所属表名称', align: 'center' },
{ dataIndex: 'is_other_database', title: '数据库特配', align: 'center' },
{ dataIndex: 'original_type', title: '数据源类型', align: 'center' },
{ dataIndex: 'original_sql', title: '数据源', align: 'center', width: 400 },
{ dataIndex: 'action', title: '操作', align: 'center', width: 120 },
];
export const originalTypes = [
{ label: "sql", value: 1 },
{ label: "json", value: 2 },
{ label: 'sql', value: 1 },
{ label: 'json', value: 2 },
];
export const showTypeOpts = [
{ label: "文本", value: "text" },
{ label: "图片", value: "img" },
{ label: "链接", value: "link" },
{ label: "富文本", value: "richText" },
{ label: '文本', value: 'text' },
{ label: '图片', value: 'img' },
{ label: '链接', value: 'link' },
{ label: '富文本', value: 'richText' },
];

View File

@ -1,123 +1,36 @@
<template>
<div class="normal-container module-cfg-box">
<div class="header-box">
<a-space>
<a-input
v-model:value="modularName"
placeholder="请输入数据来源名称"
allow-clear
style="width: 200px"
@change="search"
/>
<a-select
placeholder="请选项目"
v-model:value="projectId"
:options="projectSel"
allow-clear
style="width: 200px"
@change="search"
></a-select>
<a-button type="primary" @click="openCreateModal">新建</a-button>
</a-space>
</div>
<div class="content-box">
<a-table
:columns="moduleCfgCols"
:data-source="dataList"
:pagination="false"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'is_show'">
<a-switch
:checked="record.is_show"
:checkedValue="1"
:unCheckedValue="0"
@change="toChangeStatus(record.modular_id, $event)"
/>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a-button type="link" size="small" @click="openFieldModal(record)"
>字段管理</a-button
>
<a-button
type="link"
size="small"
@click="toGetModularDetail(record.modular_id)"
>编辑</a-button
>
<a-popconfirm
title="确定删除吗?"
@confirm="toDelete(record.modular_id)"
>
<a-button type="link" size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<a-pagination
v-model:current="pageState.page"
:total="pageState.total"
:page-size="pageState.perPage"
:hide-on-single-page="false"
size="small"
class="pagination-box"
@change="toGetModularList"
/>
</div>
<CreateModal
:width="700"
:open="modalState.visible"
:title="modalState.title"
:type="modalState.type"
:data="modalState.data"
:projectSelect="projectSel"
@cancel="modalState.visible = false"
@ok="toSave"
/>
<FieldModal
title="字段管理"
width="90%"
:open="fieldModalState.visible"
:modularId="fieldModalState.modularId"
@cancel="fieldModalState.visible = false"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { message } from "ant-design-vue";
import { moduleCfgCols } from "./config";
import { onMounted, reactive, ref } from 'vue';
import { message } from 'ant-design-vue';
import CreateModal from './components/create-modal.vue';
import FieldModal from './components/field-modal.vue';
import { moduleCfgCols } from './config';
import {
getModularList,
deleteModular,
getModularDetail,
getModularList,
getProjectSelect,
saveModular,
updateStatus,
} from "./service";
import CreateModal from "./components/create-modal.vue";
import FieldModal from "./components/field-modal.vue";
} from './service';
const dataList = ref([]);
const listLoading = ref(false);
const saveLoading = ref(false);
const detailLoading = ref(false);
const modularName = ref("");
const modularName = ref('');
const projectId = ref();
const projectSel = ref([]);
const modalState = reactive({
title: "",
title: '',
visible: false,
type: "", // add - edit -
type: '', // add - edit -
data: {},
});
const fieldModalState = reactive({
visible: false,
title: "",
title: '',
modularId: undefined,
});
@ -156,8 +69,8 @@ const toGetModularList = () => {
const toGetModularDetail = (modularId) => {
detailLoading.value = true;
modalState.visible = true;
modalState.title = "编辑";
modalState.type = "edit";
modalState.title = '编辑';
modalState.type = 'edit';
getModularDetail({ modularId })
.then((res) => {
modalState.data = res.data;
@ -172,7 +85,7 @@ const toSave = (data) => {
saveLoading.value = true;
saveModular(data)
.then(() => {
message.success("保存成功");
message.success('保存成功');
modalState.visible = false;
toGetModularList();
})
@ -184,7 +97,7 @@ const toSave = (data) => {
//
const toDelete = (id) => {
deleteModular({ modular_id: id }).then(() => {
message.success("删除成功");
message.success('删除成功');
search();
});
};
@ -194,7 +107,7 @@ const toChangeStatus = (id, status) => {
modular_id: id,
status,
}).then(() => {
message.success("修改成功");
message.success('修改成功');
search();
});
};
@ -202,8 +115,8 @@ const toChangeStatus = (id, status) => {
//
const openCreateModal = () => {
modalState.visible = true;
modalState.title = "新建";
modalState.type = "add";
modalState.title = '新建';
modalState.type = 'add';
modalState.data = {};
};
@ -217,6 +130,100 @@ const openFieldModal = (record) => {
fieldModalState.modularId = record.modular_id;
};
</script>
<template>
<div class="normal-container module-cfg-box">
<div class="header-box">
<a-space>
<a-input
v-model:value="modularName"
placeholder="请输入数据来源名称"
allow-clear
style="width: 200px"
@change="search"
/>
<a-select
placeholder="请选项目"
v-model:value="projectId"
:options="projectSel"
allow-clear
style="width: 200px"
@change="search"
/>
<a-button type="primary" @click="openCreateModal">新建</a-button>
</a-space>
</div>
<div class="content-box">
<a-table
:columns="moduleCfgCols"
:data-source="dataList"
:pagination="false"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'is_show'">
<a-switch
:checked="record.is_show"
:checked-value="1"
:un-checked-value="0"
@change="toChangeStatus(record.modular_id, $event)"
/>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a-button
type="link"
size="small"
@click="openFieldModal(record)"
>
字段管理
</a-button>
<a-button
type="link"
size="small"
@click="toGetModularDetail(record.modular_id)"
>
编辑
</a-button>
<a-popconfirm
title="确定删除吗?"
@confirm="toDelete(record.modular_id)"
>
<a-button type="link" size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<a-pagination
v-model:current="pageState.page"
:total="pageState.total"
:page-size="pageState.perPage"
:hide-on-single-page="false"
size="small"
class="pagination-box"
@change="toGetModularList"
/>
</div>
<CreateModal
:width="700"
:open="modalState.visible"
:title="modalState.title"
:type="modalState.type"
:data="modalState.data"
:project-select="projectSel"
@cancel="modalState.visible = false"
@ok="toSave"
/>
<FieldModal
title="字段管理"
width="90%"
:open="fieldModalState.visible"
:modular-id="fieldModalState.modularId"
@cancel="fieldModalState.visible = false"
/>
</div>
</template>
<style lang="less">
.header-box {

View File

@ -1,9 +1,9 @@
import { get, post } from "@/utils/request";
import { get, post } from '@/utils/request';
// 获取数据表配置列表
export function getModularList({ page, perPage, modularName, projectId }) {
return get({
url: "/api/v1/modular/list",
url: '/api/v1/modular/list',
params: {
page,
per_page: perPage,
@ -16,7 +16,7 @@ export function getModularList({ page, perPage, modularName, projectId }) {
// 获取数据表配置详情
export function getModularDetail({ modularId }) {
return get({
url: "/api/v1/modular/info",
url: '/api/v1/modular/info',
params: {
modular_id: modularId,
},
@ -26,7 +26,7 @@ export function getModularDetail({ modularId }) {
// 保存数据库配置
export function saveModular(data) {
return post({
url: "/api/v1/modular/save",
url: '/api/v1/modular/save',
data,
});
}
@ -34,7 +34,7 @@ export function saveModular(data) {
// 删除数据库配置
export function deleteModular(data) {
return post({
url: "/api/v1/modular/del",
url: '/api/v1/modular/del',
data,
});
}
@ -42,7 +42,7 @@ export function deleteModular(data) {
// 修改数据表状态
export function updateStatus(data) {
return post({
url: "/api/v1/modular/change-status",
url: '/api/v1/modular/change-status',
data,
});
}
@ -75,13 +75,13 @@ export function getFieldTypeSelect() {
export function getFieldNumSelect() {
return get({
url: `/api/v1/field/get-field-numerical-type-drop`,
})
});
}
// 获取字段列表
export function getFieldList({ modularId, fieldName, page, perPage }) {
return get({
url: "/api/v1/field/list",
url: '/api/v1/field/list',
params: {
modular_id: modularId,
field_name: fieldName,
@ -94,7 +94,7 @@ export function getFieldList({ modularId, fieldName, page, perPage }) {
// 获取字段详情
export function getFieldDetail({ fieldId }) {
return get({
url: "/api/v1/field/info",
url: '/api/v1/field/info',
params: {
field_id: fieldId,
},
@ -104,7 +104,7 @@ export function getFieldDetail({ fieldId }) {
// 保存字段
export function saveField(data) {
return post({
url: "/api/v1/field/save",
url: '/api/v1/field/save',
data,
});
}
@ -112,7 +112,7 @@ export function saveField(data) {
// 删除字段
export function deleteField(data) {
return post({
url: "/api/v1/field/del",
url: '/api/v1/field/del',
data,
});
}

View File

@ -1,3 +1,117 @@
<script setup>
import { ref, watch } from 'vue';
import { checkDbConnect } from '@/views/config-manage/project-cfg/service';
import { message } from 'ant-design-vue';
const props = defineProps({
open: {
type: Boolean,
default: false,
},
type: {
type: String,
default: 'add',
},
data: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(['ok']);
const formRules = {
project_name: [
{ required: true, message: '请输入项目名称', trigger: 'submit' },
],
mark: [{ required: true, message: '请输入项目标识', trigger: 'submit' }],
database_address: [
{ required: true, message: '请输入数据库地址', trigger: 'submit' },
],
database_port: [
{ required: true, message: '请输入数据库端口', trigger: 'submit' },
],
database_name: [
{ required: true, message: '请输入数据库名称', trigger: 'submit' },
],
};
const formRef = ref();
const formData = ref({
project_name: undefined,
mark: undefined,
is_show: 0,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
drive_type: 1,
});
watch(
() => props.data,
(newVal) => {
if (props.type === 'add') {
resetFormData();
} else {
formData.value = newVal;
}
},
);
//
const toCheckDbConnect = () => {
if (validateConnect()) {
checkDbConnect({
database_name: formData.value.database_name,
database_port: formData.value.database_port,
database_address: formData.value.database_address,
database_username: formData.value.database_username,
database_password: formData.value.database_password,
drive_type: formData.value.drive_type,
}).then((res) => {
message.success(res.message);
});
}
};
const validateConnect = () => {
const fields = {
database_name: '请输入数据库名称',
database_port: '请输入数据库端口',
database_address: '请输入数据库地址',
};
for (const key in fields) {
if (!formData.value[key]) {
message.error(fields[key]);
return false;
}
}
return true;
};
const resetFormData = () => {
formData.value = {
project_name: undefined,
mark: undefined,
is_show: 0,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
drive_type: 1,
};
};
const handleOk = () => {
formRef.value.validate().then(() => {
emit('ok', formData.value);
});
};
</script>
<template>
<a-modal :open="open" @ok="handleOk">
<a-form
@ -14,13 +128,16 @@
/>
</a-form-item>
<a-form-item label="项目标识" name="mark">
<a-input placeholder="请输入项目标识例如oa" v-model:value="formData.mark" />
<a-input
placeholder="请输入项目标识例如oa"
v-model:value="formData.mark"
/>
</a-form-item>
<a-form-item label="展示状态" name="is_show">
<a-switch
v-model:checked="formData.is_show"
:checkedValue="1"
:unCheckedValue="0"
:checked-value="1"
:un-checked-value="0"
/>
</a-form-item>
<a-form-item label="数据库地址" name="database_address">
@ -54,9 +171,9 @@
placeholder="请输入数据库密码"
v-model:value="formData.database_password"
/>
<a-button type="primary" @click="toCheckDbConnect"
>检测数据库连接</a-button
>
<a-button type="primary" @click="toCheckDbConnect">
检测数据库连接
</a-button>
</a-space>
</a-form-item>
<a-form-item label="数据库驱动" name="drive_type">
@ -68,116 +185,3 @@
</a-form>
</a-modal>
</template>
<script setup>
import { ref, watch } from "vue";
import { checkDbConnect } from "@/views/config-manage/project-cfg/service";
import { message } from "ant-design-vue";
const props = defineProps({
open: {
type: Boolean,
default: false,
},
type: {
type: String,
default: "add",
},
data: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(["ok"]);
const formRules = {
project_name: [
{ required: true, message: "请输入项目名称", trigger: "submit" },
],
mark: [{ required: true, message: "请输入项目标识", trigger: "submit" }],
database_address: [
{ required: true, message: "请输入数据库地址", trigger: "submit" },
],
database_port: [
{ required: true, message: "请输入数据库端口", trigger: "submit" },
],
database_name: [
{ required: true, message: "请输入数据库名称", trigger: "submit" },
],
};
const formRef = ref();
const formData = ref({
project_name: undefined,
mark: undefined,
is_show: 0,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
drive_type: 1,
});
watch(
() => props.data,
(newVal) => {
if (props.type === "add") {
resetFormData();
} else {
formData.value = newVal;
}
}
);
//
const toCheckDbConnect = () => {
if (validateConnect()) {
checkDbConnect({
database_name: formData.value.database_name,
database_port: formData.value.database_port,
database_address: formData.value.database_address,
database_username: formData.value.database_username,
database_password: formData.value.database_password,
drive_type: formData.value.drive_type,
}).then((res) => {
message.success(res.message);
});
}
};
const validateConnect = () => {
const fields = {
database_name: "请输入数据库名称",
database_port: "请输入数据库端口",
database_address: "请输入数据库地址",
};
for (const key in fields) {
if (!formData.value[key]) {
message.error(fields[key]);
return false;
}
}
return true;
};
const resetFormData = () => {
formData.value = {
project_name: undefined,
mark: undefined,
is_show: 0,
database_address: undefined,
database_port: undefined,
database_name: undefined,
database_username: undefined,
database_password: undefined,
drive_type: 1,
};
};
const handleOk = () => {
formRef.value.validate().then(() => {
emit("ok", formData.value);
});
};
</script>

View File

@ -1,9 +1,9 @@
export const projectCfgCols = [
{ dataIndex: 'project_id', title: '编号', align: 'center'},
{ dataIndex: 'project_name', title: '项目名称', align: 'center'},
{ dataIndex: 'mark', title: '项目标识', align: 'center'},
{ dataIndex: 'database_name', title: '数据库名', align: 'center'},
{ dataIndex: 'is_show', title: '展示状态', align: 'center'},
{ dataIndex: 'project_id', title: '编号', align: 'center' },
{ dataIndex: 'project_name', title: '项目名称', align: 'center' },
{ dataIndex: 'mark', title: '项目标识', align: 'center' },
{ dataIndex: 'database_name', title: '数据库名', align: 'center' },
{ dataIndex: 'is_show', title: '展示状态', align: 'center' },
// { dataIndex: 'sort', title: '排序', align: 'center'},
{ dataIndex: 'action', title: '操作', align: 'center'},
{ dataIndex: 'action', title: '操作', align: 'center' },
];

View File

@ -1,99 +1,28 @@
<template>
<div class="normal-container project-cfg-box">
<div class="header-box">
<a-space>
<a-input
v-model:value="projectName"
placeholder="请输入项目名称"
allow-clear
style="width: 200px"
@change="search"
/>
<a-button type="primary" @click="openCreateModal">新建</a-button>
</a-space>
</div>
<div class="content-box">
<a-table
:columns="projectCfgCols"
:data-source="dataList"
:pagination="false"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'is_show'">
<a-switch
v-model:checked="record.is_show"
:checkedValue="1"
:unCheckedValue="0"
@change="toChangeStatus(record.project_id, $event)"
/>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a-button
type="link"
size="small"
@click="toGetDetail(record.project_id)"
>
编辑
</a-button>
<a-popconfirm
title="确定删除?"
@confirm="toDelete(record.project_id)"
>
<a-button type="link" size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<a-pagination
v-model:current="pageState.page"
:total="pageState.total"
:page-size="pageState.perPage"
:hide-on-single-page="false"
size="small"
class="pagination-box"
@change="toGetProjectList"
/>
</div>
<CreateModal
:width="700"
:open="modalState.visible"
:title="modalState.title"
:type="modalState.type"
:data="modalState.data"
:confirmLoading="saveLoading"
@cancel="modalState.visible = false"
@ok="toSave"
/>
</div>
</template>
<script setup>
import { onMounted, ref, reactive } from "vue";
import { message } from "ant-design-vue";
import { projectCfgCols } from "./config";
import { onMounted, reactive, ref } from 'vue';
import { message } from 'ant-design-vue';
import CreateModal from './components/create-modal.vue';
import { projectCfgCols } from './config';
import {
getProjectList,
saveProject,
deleteProject,
getProjectDetail,
getProjectList,
saveProject,
updateStatus,
} from "./service";
import CreateModal from "./components/create-modal.vue";
} from './service';
const dataList = ref([]);
const listLoading = ref(false);
const saveLoading = ref(false);
const detailLoading = ref(false);
const projectName = ref("");
const projectName = ref('');
const modalState = reactive({
title: undefined,
visible: false,
type: "", // add - edit -
type: '', // add - edit -
data: {
project_name: undefined,
is_show: 0,
@ -137,7 +66,7 @@ const toSave = (data) => {
saveLoading.value = true;
saveProject(data)
.then(() => {
message.success("保存成功");
message.success('保存成功');
modalState.visible = false;
toGetProjectList();
})
@ -150,8 +79,8 @@ const toSave = (data) => {
const toGetDetail = (projectId) => {
detailLoading.value = true;
modalState.visible = true;
modalState.title = "编辑";
modalState.type = "edit";
modalState.title = '编辑';
modalState.type = 'edit';
getProjectDetail({ projectId }).then((res) => {
modalState.data = res.data;
});
@ -160,14 +89,14 @@ const toGetDetail = (projectId) => {
//
const toDelete = (id) => {
deleteProject({ projectId: id }).then(() => {
message.success("删除成功");
message.success('删除成功');
search();
});
};
const toChangeStatus = (id, status) => {
updateStatus({ project_id: id, status }).then(() => {
message.success("修改成功");
message.success('修改成功');
search();
});
};
@ -180,12 +109,85 @@ const search = () => {
//
const openCreateModal = () => {
modalState.visible = true;
modalState.title = "新建";
modalState.type = "add";
modalState.title = '新建';
modalState.type = 'add';
modalState.data = {};
};
</script>
<template>
<div class="normal-container project-cfg-box">
<div class="header-box">
<a-space>
<a-input
v-model:value="projectName"
placeholder="请输入项目名称"
allow-clear
style="width: 200px"
@change="search"
/>
<a-button type="primary" @click="openCreateModal">新建</a-button>
</a-space>
</div>
<div class="content-box">
<a-table
:columns="projectCfgCols"
:data-source="dataList"
:pagination="false"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'is_show'">
<a-switch
v-model:checked="record.is_show"
:checked-value="1"
:un-checked-value="0"
@change="toChangeStatus(record.project_id, $event)"
/>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a-button
type="link"
size="small"
@click="toGetDetail(record.project_id)"
>
编辑
</a-button>
<a-popconfirm
title="确定删除?"
@confirm="toDelete(record.project_id)"
>
<a-button type="link" size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<a-pagination
v-model:current="pageState.page"
:total="pageState.total"
:page-size="pageState.perPage"
:hide-on-single-page="false"
size="small"
class="pagination-box"
@change="toGetProjectList"
/>
</div>
<CreateModal
:width="700"
:open="modalState.visible"
:title="modalState.title"
:type="modalState.type"
:data="modalState.data"
:confirm-loading="saveLoading"
@cancel="modalState.visible = false"
@ok="toSave"
/>
</div>
</template>
<style lang="less" scope>
.header-box {
margin-bottom: 10px;

View File

@ -1,4 +1,4 @@
import { get, post } from "@/utils/request";
import { get, post } from '@/utils/request';
// 获取项目列表
export function getProjectList({ page, perPage, projectName }) {

View File

@ -46,12 +46,12 @@
</template>
<script setup>
import { ref, watch } from "vue";
import { ref, watch } from 'vue';
const props = defineProps({
type: {
type: String,
default: "add",
default: 'add',
},
data: {
type: Object,
@ -59,22 +59,22 @@ const props = defineProps({
},
});
const emit = defineEmits(["ok"]);
const emit = defineEmits(['ok']);
const formRules = {
field_title: [
{ required: true, message: "请输入字段标题", trigger: "submit" },
{ required: true, message: '请输入字段标题', trigger: 'submit' },
],
field_name: [
{ required: true, message: "请输入字段名称", trigger: "submit" },
{ required: true, message: '请输入字段名称', trigger: 'submit' },
],
field_type_id: [
{ required: true, message: "请选择字段类型", trigger: "submit" },
{ required: true, message: '请选择字段类型', trigger: 'submit' },
],
belong_to_table: [
{ required: true, message: "请输入所属表", trigger: "submit" },
{ required: true, message: '请输入所属表', trigger: 'submit' },
],
original_sql: [
{ required: true, message: "请输入sql数据源", trigger: "submit" },
{ required: true, message: '请输入sql数据源', trigger: 'submit' },
],
};
@ -92,19 +92,19 @@ const formData = ref({
watch(
() => props.type,
(newVal) => {
if (newVal === "add") {
if (newVal === 'add') {
resetFormData();
} else {
formData.value = props.data;
}
}
},
);
const resetFormData = () => {};
const handleOk = () => {
formRef.value.validate().then(() => {
emit("ok", formData.value);
emit('ok', formData.value);
});
};
</script>

View File

@ -1,10 +1,10 @@
export const viewCfgCols = [
{ dataIndex: 'field_name', title: '字段名称', align: 'center'},
{ dataIndex: 'field_title', title: '字段标题', align: 'center'},
{ dataIndex: 'field_type_name', title: '字段类型', align: 'center'},
{ dataIndex: 'is_search', title: '是否可搜索', align: 'center'},
{ dataIndex: 'sort', title: '排序', align: 'center'},
{ dataIndex: 'belong_to_table', title: '所属表名称', align: 'center'},
{ dataIndex: 'original_sql', title: 'sql数据源', align: 'center'},
{ dataIndex: 'action', title: '操作', align: 'center'},
{ dataIndex: 'field_name', title: '字段名称', align: 'center' },
{ dataIndex: 'field_title', title: '字段标题', align: 'center' },
{ dataIndex: 'field_type_name', title: '字段类型', align: 'center' },
{ dataIndex: 'is_search', title: '是否可搜索', align: 'center' },
{ dataIndex: 'sort', title: '排序', align: 'center' },
{ dataIndex: 'belong_to_table', title: '所属表名称', align: 'center' },
{ dataIndex: 'original_sql', title: 'sql数据源', align: 'center' },
{ dataIndex: 'action', title: '操作', align: 'center' },
];

View File

@ -69,26 +69,26 @@
</template>
<script setup>
import { onMounted, ref, reactive } from "vue";
import { viewCfgCols } from "./config";
import { onMounted, ref, reactive } from 'vue';
import { viewCfgCols } from './config';
import {
getFieldList,
deleteField,
saveField,
getFieldDetail,
} from "./service";
import CreateModal from "./components/create-modal.vue";
import { message } from "ant-design-vue";
} from './service';
import CreateModal from './components/create-modal.vue';
import { message } from 'ant-design-vue';
const listLoading = ref(false);
const saveLoading = ref(false);
const dataList = ref([]);
const fieldName = ref("");
const modularId = ref("");
const fieldName = ref('');
const modularId = ref('');
const modalState = reactive({
title: undefined,
visible: false,
type: "", // add - edit -
type: '', // add - edit -
data: {},
});
const pageState = reactive({
@ -123,7 +123,7 @@ const toSave = (data) => {
saveLoading.value = true;
saveField(data)
.then(() => {
message.success("保存成功");
message.success('保存成功');
})
.finally(() => {
saveLoading.value = false;
@ -133,8 +133,8 @@ const toSave = (data) => {
//
const toGetDetail = (fieldId) => {
modalState.visible = true;
modalState.title = "编辑";
modalState.type = "edit";
modalState.title = '编辑';
modalState.type = 'edit';
getFieldDetail({ fieldId }).then((res) => {
modalState.data = res.data;
});
@ -143,7 +143,7 @@ const toGetDetail = (fieldId) => {
//
const toDelete = (fieldId) => {
deleteField({ field_id: fieldId }).then(() => {
message.success("删除成功");
message.success('删除成功');
search();
});
};
@ -151,8 +151,8 @@ const toDelete = (fieldId) => {
//
const openCreateModal = () => {
modalState.visible = true;
modalState.title = "新建";
modalState.type = "add";
modalState.title = '新建';
modalState.type = 'add';
modalState.data = {};
};

View File

@ -1,9 +1,9 @@
import { get, post } from "@/utils/request";
import { get, post } from '@/utils/request';
// 获取列表
export function getFieldList({ modularId, fieldName, page, perPage }) {
return get({
url: "/api/v1/field/list",
url: '/api/v1/field/list',
params: {
modularId,
fieldName,
@ -16,7 +16,7 @@ export function getFieldList({ modularId, fieldName, page, perPage }) {
// 获取详情
export function getFieldDetail({ fieldId }) {
return get({
url: "/api/v1/field/info",
url: '/api/v1/field/info',
params: {
fieldId,
},
@ -26,7 +26,7 @@ export function getFieldDetail({ fieldId }) {
// 保存字段
export function saveField(data) {
return post({
url: "/api/v1/field/save",
url: '/api/v1/field/save',
data,
});
}
@ -34,7 +34,7 @@ export function saveField(data) {
// 删除字段
export function deleteField(data) {
return post({
url: "/api/v1/field/del",
url: '/api/v1/field/del',
data,
});
}

View File

@ -47,7 +47,7 @@
:title="item.data.preview_name"
:is-export="item.data.is_export"
@toFilt="
(params?:object) => {
(params?: object) => {
handleSingle(ids[index], params);
}
"
@ -58,8 +58,8 @@
:title="item.data.preview_name"
:filter-config="item.data.filter"
@toFilt="
(params?:object) => {
handleSingle(ids[index], params);
(params?: object) => {
handleSingle(ids[index], params);
}
"
></y-chart>
@ -87,8 +87,8 @@
:title="item.data.preview_name"
:is-export="item.data.is_export"
@toFilt="
(params?:object) => {
handleSingle(ids[index], params,);
(params?: object) => {
handleSingle(ids[index], params);
}
"
></y-table>
@ -98,7 +98,7 @@
:title="item.data.preview_name"
:filter-config="item.data.filter"
@toFilt="
(params?:object) => {
(params?: object) => {
handleSingle(ids[index], params);
}
"
@ -114,17 +114,17 @@
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef, computed, onMounted, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { BarsOutlined } from "@ant-design/icons-vue";
import { ref, shallowRef, computed, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { BarsOutlined } from '@ant-design/icons-vue';
// utils
import PLimit from "p-limit";
import PLimit from 'p-limit';
// api
import { searchInfo } from "@/api/preview/index";
import { getProjectDrop } from "@/api/common";
import { getPageInfo } from "./service";
import type { SelectProps } from "ant-design-vue";
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
import { searchInfo } from '@/api/preview/index';
import { getProjectDrop } from '@/api/common';
import { getPageInfo } from './service';
import type { SelectProps } from 'ant-design-vue';
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
interface ItemDetail {
id: number | string;
@ -143,13 +143,13 @@ interface Option extends SelectProps {
}
const VIEW_TYPE = {
TABLE: "table",
CHART: "chart",
TABLE: 'table',
CHART: 'chart',
};
const SEARCH_TYPE = {
SEARCH: "search",
INIT: "init",
SEARCH: 'search',
INIT: 'init',
};
// hooks
@ -162,8 +162,11 @@ const projectOptions = shallowRef<Option[]>();
const isDraggable = false;
const isInQiankun = computed(() => {
return qiankunWindow?.__POWERED_BY_QIANKUN__ || window?.proxy?.__POWERED_BY_QIANKUN__
})
return (
qiankunWindow?.__POWERED_BY_QIANKUN__ ||
window?.proxy?.__POWERED_BY_QIANKUN__
);
});
const layoutList = computed(() => {
return ids.value.map((item, index) => {
@ -186,9 +189,12 @@ const ids = ref<Item[]>([]);
const pLimit = PLimit(2);
watch(() => route.query.viewId, () => {
getPageInfoData()
})
watch(
() => route.query.viewId,
() => {
getPageInfoData();
},
);
onMounted(() => {
getProjectList();
@ -254,19 +260,21 @@ const getSinglePreview = (data: {
// id
const getPageInfoData = () => {
getPageInfo({ mark:projectTag.value, page_id: pageId.value ?? "-1" })
getPageInfo({ mark: projectTag.value, page_id: pageId.value ?? '-1' })
.then((res) => {
if (res.code === 200) {
if (route.query.viewId) {
ids.value = res.data?.filter((item: any) => {
return item.preview_id === Number(route.query.viewId);
}).map((item: any) => {
return {
id: item.preview_id,
data: item,
loading: false,
}
})
ids.value = res.data
?.filter((item: any) => {
return item.preview_id === Number(route.query.viewId);
})
.map((item: any) => {
return {
id: item.preview_id,
data: item,
loading: false,
};
});
} else {
ids.value = res.data?.map((item: any) => {
return {
@ -295,14 +303,13 @@ const fetchFn = (delay: number, info: ItemDetail) => {
const getAllCardsData = async () => {
let listDB = [];
for (let i in ids.value) {
listDB.push(pLimit(() => fetchFn(i === "0" ? 200 : 1000, ids.value[i])));
listDB.push(pLimit(() => fetchFn(i === '0' ? 200 : 1000, ids.value[i])));
}
await Promise.all(listDB);
//listDB
};
</script>
<style lang="less" scoped>
.view-box {
height: 100%;
width: 100%;
@ -312,7 +319,9 @@ const getAllCardsData = async () => {
border-radius: 4px;
transition: all 0.3s;
&:hover {
box-shadow: 0 0 20px 0 #0a103205, 0 14px 40px 0 #0a103208,
box-shadow:
0 0 20px 0 #0a103205,
0 14px 40px 0 #0a103208,
0 20px 60px 0 #0a10320d;
}

View File

@ -1,10 +1,10 @@
import { get, post } from "@/utils/request";
import { get, post } from '@/utils/request';
interface PageInfoParams {
mark: string;
page_id: number | string;
}
export const getPageInfo = (data: PageInfoParams) =>
get({
url: "/api/v1/preview/get-preview-info",
url: '/api/v1/preview/get-preview-info',
params: data,
});

View File

@ -1,8 +1,9 @@
@import "@/assets/styles/variable.less";
@import '@/assets/styles/variable.less';
// 设置按钮
:deep(.ycode-ant-btn-primary) {
background-color: @primary-color !important;
&:hover {
background-color: @primary-light-color !important;
}
@ -10,10 +11,10 @@
// 设置输入框
:deep(
.ycode-ant-input-affix-wrapper:not(
.ycode-ant-input-affix-wrapper-disabled
):hover
) {
.ycode-ant-input-affix-wrapper:not(
.ycode-ant-input-affix-wrapper-disabled
):hover
) {
border-color: @primary-light-color !important;
}
@ -34,6 +35,7 @@
&-focused {
border-color: @primary-light-color !important;
}
&-active-bar {
background-color: @primary-light-color !important;
}
@ -41,18 +43,20 @@
// 设置表格
:deep(.ycode-ant-table-thead > tr > th) {
background-color: @table-head-bg-color !important;
color: @table-head-font-color !important;
background-color: @table-head-bg-color !important;
}
// 设置分页器
:deep(.ycode-ant-pagination-item) {
&-active {
border-color: @primary-color !important;
a {
color: @primary-color !important;
}
}
&-link-icon {
color: @primary-color !important;
}

View File

@ -62,7 +62,11 @@
<a-button
class="preview-btn"
:loading="previewLoading"
@click="() => {toPreview({})}"
@click="
() => {
toPreview({});
}
"
>预览</a-button
>
<a-button type="primary" @click="addViewName">点击保存</a-button>
@ -113,7 +117,10 @@
/>
<!-- 数值区间 -->
<div
v-else-if="item.type === 'number_range' && previewData.filterData[item.name]"
v-else-if="
item.type === 'number_range' &&
previewData.filterData[item.name]
"
class="number_range_box"
>
<a-input-number
@ -170,7 +177,11 @@
:hide-on-single-page="false"
size="small"
class="pagination-box"
@change="() => { toPreview({}) }"
@change="
() => {
toPreview({});
}
"
/>
</div>
</div>
@ -178,7 +189,11 @@
v-else-if="previewData.type === 'chart'"
:chart-cfg="previewData.chartCfg"
:filter-config="previewData.filter"
@toFilt="() => {toPreview({})}"
@toFilt="
() => {
toPreview({});
}
"
></y-chart>
<div class="preview-area" v-else>
<div><BarChartOutlined /></div>
@ -203,21 +218,21 @@
</template>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { onMounted, reactive, ref } from 'vue';
import {
getProModularField,
preview,
saveView,
getShowTypeSelect,
getFieldOpts,
} from "./service";
import { message } from "ant-design-vue";
import { BarChartOutlined } from "@ant-design/icons-vue";
import yChart from "@/components/common/y-chart.vue";
import { cloneDeep } from "lodash-es";
} from './service';
import { message } from 'ant-design-vue';
import { BarChartOutlined } from '@ant-design/icons-vue';
import yChart from '@/components/common/y-chart.vue';
import { cloneDeep } from 'lodash-es';
const projectSel = ref([]); //
const modularSel = ref([]) //
const modularSel = ref([]); //
const showTypeSel = ref([]); //
const fieldList = ref([]); //
const xDataList = ref([]); // x
@ -235,7 +250,7 @@ const nameVisible = ref(false);
const previewName = ref();
const previewData = reactive({
type: "",
type: '',
filterConfig: [], //
columnConfig: [], //
dataList: [], //
@ -308,23 +323,23 @@ const tranformList = (list) => {
value: item.field_id,
};
});
}
};
//
const resetSelectData = () => {
showTypeId.value = undefined
isExport.value = 0
showTypeId.value = undefined;
isExport.value = 0;
fieldList.value = [];
fieldIds.value = [];
xDataList.value = [];
yDataList.value = [];
xDataId.value = undefined;
yDataId.value = [];
}
};
//
const resetPreviewData = () => {
previewData.type = "";
previewData.type = '';
previewData.filterConfig = [];
previewData.columnConfig = [];
previewData.dataList = [];
@ -336,14 +351,17 @@ const resetPreviewData = () => {
};
//
const toPreview = ({filter}) => {
const toPreview = ({ filter }) => {
previewLoading.value = true;
let filterData
let filterData;
if (!filter) {
const cloneFilter = cloneDeep(previewData.filterConfig)
const cloneFilter = cloneDeep(previewData.filterConfig);
filterData = cloneFilter
.filter((item) => {
return previewData.filterData[item.name] !== undefined && previewData.filterData[item.name] !== null;
return (
previewData.filterData[item.name] !== undefined &&
previewData.filterData[item.name] !== null
);
})
.map((item) => {
if (item.type === 'time' && previewData.filterData[item.name]) {
@ -351,24 +369,36 @@ const toPreview = ({filter}) => {
return {
name: item.name,
type: item.type,
start_time: previewData.filterData[item.name][0].format('YYYY-MM-DD'),
start_time:
previewData.filterData[item.name][0].format('YYYY-MM-DD'),
end_time: previewData.filterData[item.name][1].format('YYYY-MM-DD'),
};
} else if (item.type === 'date_time' && previewData.filterData[item.name]) {
} else if (
item.type === 'date_time' &&
previewData.filterData[item.name]
) {
//
return {
name: item.name,
type: item.type,
start_time: previewData.filterData[item.name][0].format('YYYY-MM-DD HH:mm') + ':00',
end_time: previewData.filterData[item.name][1].format('YYYY-MM-DD HH:mm') + ':59',
start_time:
previewData.filterData[item.name][0].format('YYYY-MM-DD HH:mm') +
':00',
end_time:
previewData.filterData[item.name][1].format('YYYY-MM-DD HH:mm') +
':59',
};
} else if (item.type === 'number_range') {
//
return {
name: item.name,
type: item.type,
min: previewData.filterData[item.name].min ? String(previewData.filterData[item.name].min) : '',
max: previewData.filterData[item.name].max ? String(previewData.filterData[item.name].max) : '',
min: previewData.filterData[item.name].min
? String(previewData.filterData[item.name].min)
: '',
max: previewData.filterData[item.name].max
? String(previewData.filterData[item.name].max)
: '',
};
} else {
return {
@ -395,17 +425,22 @@ const toPreview = ({filter}) => {
})
.then((res) => {
previewData.type = res.data.type;
if (res.data.type === "table") {
if (res.data.type === 'table') {
previewData.filterConfig = res.data.filter;
previewData.filterConfig.forEach((item) => {
if (item.type === 'number_range' && !previewData.filterData[item.name]) {
if (
item.type === 'number_range' &&
!previewData.filterData[item.name]
) {
previewData.filterData[item.name] = {
...item,
min: undefined,
max: undefined,
};
} else {
previewData.filterData[item.name] = previewData.filterData[item.name]
previewData.filterData[item.name] = previewData.filterData[
item.name
]
? previewData.filterData[item.name]
: undefined;
}
@ -413,7 +448,7 @@ const toPreview = ({filter}) => {
previewData.columnConfig = res.data.header;
previewData.dataList = res.data.data;
previewData.total = res.data.count;
previewData.isExport = res.data.is_export
previewData.isExport = res.data.is_export;
} else {
previewData.chartCfg = res.data.config;
previewData.filter = res.data.filter;
@ -431,7 +466,7 @@ const addViewName = () => {
const toSaveView = () => {
if (!previewName.value) {
message.error("请输入名称");
message.error('请输入名称');
return;
}
saveView({
@ -443,7 +478,7 @@ const toSaveView = () => {
yDataId: yDataId.value?.toString(),
isExport: isExport.value,
}).then(() => {
message.success("保存成功,可前往视图列表查看");
message.success('保存成功,可前往视图列表查看');
nameVisible.value = false;
});
};

View File

@ -1,9 +1,9 @@
import { get, post } from "@/utils/request";
import { get, post } from '@/utils/request';
// 项目-表-字段下拉
export function getProModularField() {
return get({
url: "/api/v1/field/get-project-modular-field-drop",
url: '/api/v1/field/get-project-modular-field-drop',
});
}
@ -17,7 +17,7 @@ export function getShowTypeSelect() {
// 字段列表
export function getFieldOpts({ modularId, showTypeId }) {
return get({
url: "/api/v1/preview/get-preview-field",
url: '/api/v1/preview/get-preview-field',
params: {
modular_id: modularId,
show_type_id: showTypeId,
@ -26,9 +26,19 @@ export function getFieldOpts({ modularId, showTypeId }) {
}
// 预览
export function preview({ modularId, fieldIds, page, perPage, filter, showTypeId, xDataId, yDataId, isExport }) {
export function preview({
modularId,
fieldIds,
page,
perPage,
filter,
showTypeId,
xDataId,
yDataId,
isExport,
}) {
return post({
url: "api/v1/preview/view",
url: 'api/v1/preview/view',
data: {
modular_id: modularId,
field_ids: fieldIds,
@ -44,9 +54,16 @@ export function preview({ modularId, fieldIds, page, perPage, filter, showTypeId
}
// 点击保存
export function saveView({ modularId, fieldIds, previewName, showTypeId, xDataId, yDataId }) {
export function saveView({
modularId,
fieldIds,
previewName,
showTypeId,
xDataId,
yDataId,
}) {
return post({
url: "api/v1/preview/save",
url: 'api/v1/preview/save',
data: {
modular_id: modularId,
field_ids: fieldIds,

View File

@ -83,7 +83,8 @@
@toFilt="
(params) => {
toGetViewInfo(params);
}"
}
"
/>
<y-chart
v-else-if="selectViewInfo.type === 'chart'"
@ -102,12 +103,12 @@
</template>
<script setup>
import { onMounted, ref, reactive } from "vue";
import { getProModular, getViewList, getViewInfo, deleteView } from "./service";
import { viewListCols } from "./config";
import yTable from "@/components/common/y-table.vue";
import { message } from "ant-design-vue";
import { BarChartOutlined } from "@ant-design/icons-vue";
import { onMounted, ref, reactive } from 'vue';
import { getProModular, getViewList, getViewInfo, deleteView } from './service';
import { viewListCols } from './config';
import yTable from '@/components/common/y-table.vue';
import { message } from 'ant-design-vue';
import { BarChartOutlined } from '@ant-design/icons-vue';
const projectSel = ref([]);
const modularSel = ref([]);
@ -116,13 +117,13 @@ const modularId = ref();
const dataList = ref([]);
const selectedRowId = ref();
const selectViewInfo = ref({
type: "",
type: '',
filter: [],
config: {
line: {
data: []
}
}
data: [],
},
},
});
const pageState = reactive({
@ -140,7 +141,7 @@ const toGetProModular = () => {
projectSel.value = res.data;
if (res.data.length) {
projectId.value = res.data[0].value;
onProjectChange(projectId.value)
onProjectChange(projectId.value);
}
});
};
@ -177,7 +178,7 @@ const onModularChange = () => {
const toDelete = (previewId) => {
deleteView({ previewId }).then(() => {
message.success("删除成功");
message.success('删除成功');
toGetList();
});
};

View File

@ -1,16 +1,16 @@
import { get, post } from "@/utils/request";
import { get, post } from '@/utils/request';
// 联动下拉
export function getProModular() {
return get({
url: "/api/v1/field/get-project-modular-drop",
url: '/api/v1/field/get-project-modular-drop',
});
}
// 视图列表
export function getViewList({ modularId, page = 1, perPage = 20 }) {
return get({
url: "/api/v1/preview/list",
url: '/api/v1/preview/list',
params: {
modular_id: modularId,
page,
@ -27,7 +27,7 @@ export function getViewInfo({
filter = [],
}) {
return post({
url: "/api/v1/preview/info",
url: '/api/v1/preview/info',
data: {
preview_id: previewId,
page,
@ -40,7 +40,7 @@ export function getViewInfo({
// 删除视图
export function deleteView({ previewId }) {
return post({
url: "/api/v1/preview/del",
url: '/api/v1/preview/del',
data: {
preview_id: previewId,
},

View File

@ -1,9 +1,9 @@
import { defineConfig } from "@farmfe/core";
import { defineConfig } from '@farmfe/core';
export default defineConfig({
compilation: {
input: {
index: "./src/index.ts",
index: './src/index.ts',
},
},
});

View File

@ -1,4 +1,4 @@
import { onLCP, onINP, onCLS } from "web-vitals";
import { onCLS, onINP, onLCP } from 'web-vitals';
// import { initializeApp } from "firebase/app";
// import { getAnalytics } from "firebase/analytics";