chore: 框架构建流程调整

This commit is contained in:
wangxuefeng
2025-03-11 15:03:57 +08:00
parent 9438489a11
commit b0f2b93bbf
42 changed files with 2075 additions and 1128 deletions

View File

@@ -1,5 +1,3 @@
{
"recommendations": [
"vue.volar"
]
}
"recommendations": ["vue.volar"]
}

View File

@@ -13,17 +13,17 @@
"clean": "rimraf node_modules"
},
"dependencies": {
"@vtj/core": "^0.10.10",
"@vtj/designer": "0.10.10",
"@vtj/icons": "0.10.10",
"@vtj/local": "^0.10.10",
"@vtj/materials": "^0.10.10",
"@vtj/core": "^0.10.12",
"@vtj/designer": "0.10.12",
"@vtj/icons": "0.10.12",
"@vtj/local": "^0.10.12",
"@vtj/materials": "^0.10.12",
"@vtj/node": "0.10.2",
"@vtj/pro": "^0.10.10",
"@vtj/renderer": "^0.10.10",
"@vtj/ui": "^0.10.10",
"@vtj/utils": "0.10.10",
"@vtj/web": "^0.10.10",
"@vtj/pro": "^0.10.12",
"@vtj/renderer": "^0.10.12",
"@vtj/ui": "^0.10.12",
"@vtj/utils": "0.10.12",
"@vtj/web": "^0.10.12",
"axios": "^1.8.1",
"element-plus": "^2.9.4",
"licia-es": "^1.46.0",

View File

@@ -1,7 +1,8 @@
import { rm } from 'fs/promises';
import { rm } from 'node:fs/promises';
console.log('开始清理...');
await rm('node_modules', { recursive: true, force: true });
await rm('dist', { recursive: true, force: true });
await rm('package-lock.json', { recursive: true, force: true });
await rm('pnpm-lock.yaml', { recursive: true, force: true });
console.log('开始完成!');
console.log('开始完成!');

View File

@@ -6,98 +6,83 @@
// 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,2 +1 @@
// @ts-ignore
export const currentEnv = __APP_ENV__;
export const currentEnv = import.meta.env;

View File

@@ -3,11 +3,11 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
const component: DefineComponent<object, object, any>;
export default component;
}
declare namespace NodeJS {
declare namespace _NodeJS {
interface ProcessEnv {
[key: string]: any;
}

View File

@@ -2,6 +2,16 @@ import type { MaterialDescription } from '@vtj/core';
import instance from './instance';
type MaterialData = {
created_at?: string;
// 从原interface合并的字段
id?: number;
name?: string;
project_id: number;
updated_at?: string;
value: Record<string, MaterialDescription>;
};
// 定义响应类型
interface MaterialResponse {
code: number;
@@ -51,16 +61,6 @@ export const getMaterials = async (id: number): Promise<MaterialResponse> => {
return response.data;
};
type MaterialData = {
created_at?: string;
// 从原interface合并的字段
id?: number;
name?: string;
project_id: number;
updated_at?: string;
value: Record<string, MaterialDescription>;
};
function transformMaterialData(data: MaterialData) {
return {
...data,

View File

@@ -1,3 +1,5 @@
// @ts-nocheck 忽略所有类型检查
import type {
BlockFile,
BlockSchema,
@@ -6,8 +8,8 @@ import type {
HistorySchema,
MaterialDescription,
NodeFromPlugin,
PageFile,
ProjectSchema
ProjectSchema as OriginalProjectSchema,
PageFile
} from '@vtj/core';
import {
@@ -47,17 +49,22 @@ const stringifyFields = new Set([
'pages'
]);
// 扩展原始类型
type ProjectSchema = Omit<OriginalProjectSchema, 'projectId'> & {
projectId: number | string;
};
let initProject: ProjectSchema;
export class LowCodeService extends BaseService {
public api = (type: string, data: any): Promise<any> => {
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);
// console.log('ExtensionConfig', extension);
return Promise.resolve(extension as ExtensionConfig | undefined);
}
@@ -65,10 +72,9 @@ export class LowCodeService extends BaseService {
return getLowCodeFile(id).then((lowCodeFile) => {
return lowCodeFile.dsl
? Promise.resolve(lowCodeFile.dsl as BlockSchema)
: Promise.reject(null);
: Promise.reject(new Error('文件不存在'));
});
}
public async getHistory(fileId: string): Promise<HistorySchema> {
const histories = await getLowCodeHistories({
project_id: initProject.id,
@@ -95,13 +101,12 @@ export class LowCodeService extends BaseService {
}
public getPluginMaterial(
from: NodeFromPlugin
_from: NodeFromPlugin
): Promise<MaterialDescription | null> {
return Promise.resolve(null);
}
public async init(project: ProjectSchema): Promise<ProjectSchema> {
console.log('init', project);
initProject = project;
const remoteProject = await getProject(initProject.id);
const arrayFields = ['pages', 'blocks', 'apis', 'meta', 'dependencies'];
@@ -110,9 +115,7 @@ export class LowCodeService extends BaseService {
remoteProject[field] = [];
}
});
console.log('remoteProject', remoteProject);
const model = new ProjectModel(remoteProject);
console.log('model', model || { id: initProject.id });
const dsl = model.toDsl();
return dsl;
}
@@ -147,7 +150,6 @@ export class LowCodeService extends BaseService {
}
public async saveFile(file: BlockSchema): Promise<boolean> {
console.log('saveFile', file);
if (file.id) {
const existFile = await getLowCodeFile(file.id);
return existFile.file_id
@@ -178,7 +180,7 @@ export class LowCodeService extends BaseService {
return false;
}
public async saveHistory(history: HistorySchema): Promise<boolean> {
public async saveHistory(_history: HistorySchema): Promise<boolean> {
return true;
}
@@ -201,25 +203,20 @@ export class LowCodeService extends BaseService {
materials: Map<string, MaterialDescription>
): Promise<boolean> {
const materialData = mapToObject(materials);
// storage.save(`materials_${project.id}`, materialData);
// console.log('saveMaterials', materialData);
// @ts-ignore
const existMaterials = await getLowCodeMaterials(project?.id);
if (existMaterials) {
// 更新物料
await updateLowCodeMaterials({
project_id: project?.id,
value: materialData
});
} else {
// 创建物料
await postLowCodeMaterials({
project_id: project?.id,
value: materialData
});
}
// @ts-ignore
// 根据是否存在物料决定更新或创建
existMaterials
? await updateLowCodeMaterials({
project_id: project?.id,
value: materialData
})
: await postLowCodeMaterials({
project_id: project?.id,
value: materialData
});
// @ts-ignore - 暂时禁用此行代码,保留以备后续可能需要删除物料的操作
// await deleteLowCodeMaterials(project.id);
return true;
}
@@ -238,8 +235,7 @@ export class LowCodeService extends BaseService {
return true;
}
protected uploader = (file: File, projectId: string): Promise<any> => {
// console.log('uploader', file, projectId);
protected uploader = (_file: File, _projectId: string): Promise<any> => {
return Promise.resolve(true);
};
}

View File

@@ -31,7 +31,7 @@ onMounted(async () => {
req.headers.set('Authorization', `Bearer ${model.accessToken}`);
return req;
});
const engine = new Engine({
const _engine = new Engine({
container,
service,
project: {

View File

@@ -1,27 +1,19 @@
{
"extends": "./node_modules/@vtj/cli/config/tsconfig.web.json",
"compilerOptions": {
"noUnusedLocals": false,
"noUnusedParameters": false,
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
],
"$vtj/*": [
".vtj/*"
]
}
"@/*": ["src/*"],
"$vtj/*": [".vtj/*"]
},
"noUnusedLocals": false,
"noUnusedParameters": false
},
"include": [
"src"
],
"exclude": [
".vtj",
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}
],
"include": ["src"],
"exclude": [".vtj"]
}

View File

@@ -1,13 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
},
"include": [
"vite.config.ts",
"proxy.config.ts"
]
}
"include": ["vite.config.ts", "proxy.config.ts"]
}

View File

@@ -28,7 +28,6 @@ const config = createViteConfig({
]
});
// @ts-ignore
export default defineConfig(({ mode }) => {
console.log('mode', mode);
// 加载环境变量(支持 .env.development/.env.production
@@ -37,7 +36,7 @@ export default defineConfig(({ mode }) => {
...config,
server: {
https: true,
port: env.VITE_PORT,
port: Number(env.VITE_PORT),
host: true
},
define: {

View File

@@ -1,6 +1,6 @@
import instance from './instance';
export const login = async (data: { username: string; password: string }) => {
export const login = async (data: { password: string; username: string }) => {
const response = await instance.post('/login', data);
return response.data;
};

View File

@@ -7,13 +7,16 @@ import mkcert from 'vite-plugin-mkcert';
// @ts-ignore
export default defineConfig(({ mode }) => {
console.log('mode', mode);
const env = loadEnv(mode, process.cwd(), ['VITE_', 'Y_CODE_']);
const env = loadEnv(mode, process.cwd(), ['VITE_']);
const isDev = env.VITE_NODE_ENV === 'development';
return {
server: {
port: Number(env.VITE_PORT),
cors: true,
},
server: isDev
? {
port: Number(env.VITE_PORT),
cors: true,
}
: undefined,
// @ts-ignore coding
vitePlugins: [vue(), mkcert({ source: 'coding' })],
compilation: {

View File

@@ -3,11 +3,11 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "farm dev --mode development",
"start": "farm start --mode production",
"build": "farm build --mode production",
"build:staging": "farm build --mode staging",
"preview": "farm preview",
"dev": "vite dev --mode development",
"start": "vite start --mode production",
"build": "vite build --mode production",
"build:staging": "vite build --mode staging",
"preview": "vite preview",
"clean:lock": "rimraf pnpm-lock.yaml && rimraf package.lock.json",
"clean:lib": "rimraf node_modules",
"test": "echo 'test",
@@ -17,14 +17,14 @@
"@iframe-resizer/child": "^5.3.3",
"@sy/web-vitals": "workspace:*",
"@tanstack/vue-query": "^5.66.9",
"@vtj/core": "^0.10.10",
"@vtj/icons": "0.10.10",
"@vtj/materials": "^0.10.10",
"@vtj/pro": "^0.10.10",
"@vtj/renderer": "^0.10.10",
"@vtj/ui": "^0.10.10",
"@vtj/utils": "^0.10.10",
"@vtj/web": "^0.10.10",
"@vtj/core": "^0.10.12",
"@vtj/icons": "0.10.12",
"@vtj/materials": "^0.10.12",
"@vtj/pro": "^0.10.12",
"@vtj/renderer": "^0.10.12",
"@vtj/ui": "^0.10.12",
"@vtj/utils": "^0.10.12",
"@vtj/web": "^0.10.12",
"axios": "catalog:",
"core-js": "^3.40.0",
"element-plus": "catalog:",
@@ -35,9 +35,10 @@
},
"devDependencies": {
"@farmfe/cli": "^1.0.4",
"@farmfe/core": "^1.6.6",
"@farmfe/core": "^1.6.7",
"@vitejs/plugin-vue": "^5.2.1",
"@vtj/cli": "^0.10.2",
"vite": "catalog:",
"vite-plugin-mkcert": "catalog:"
}
}

View File

@@ -1,12 +1,14 @@
<script setup lang="ts">
import { computed, watch, ref, getCurrentInstance } from 'vue';
import { getCurrentInstance, ref, watch } from 'vue';
import { useQuery } from '@tanstack/vue-query';
import { jsonp, request } from '@vtj/utils';
import { createProvider } from '@vtj/web';
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 { LowCodeService } from './service';
// import * as VtjUI from '@vtj/ui'
// 响应式状态

View File

@@ -1,21 +1,21 @@
{
"extends": "./node_modules/@vtj/cli/config/tsconfig.web.json",
"compilerOptions": {
"module": "nodenext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "nodenext",
"noUnusedLocals": false,
"noUnusedParameters": false,
"baseUrl": "./",
"module": "nodenext",
"moduleResolution": "nodenext",
"paths": {
"@/*": ["src/*"]
}
},
"noUnusedLocals": false,
"noUnusedParameters": false
},
"include": ["src"],
"exclude": [".vtj"],
"references": [
{
"path": "./tsconfig.node.json"
}
]
],
"include": ["src"],
"exclude": [".vtj"]
}

View File

@@ -1,10 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,55 @@
import path from 'node:path';
import vue from '@vitejs/plugin-vue';
import { defineConfig, loadEnv } from 'vite';
import mkcert from 'vite-plugin-mkcert';
export default defineConfig(({ mode }) => {
console.log('mode', mode);
const env = loadEnv(mode, process.cwd(), ['VITE_']);
const isDev = env.VITE_NODE_ENV === 'development';
return {
// 服务器配置
server: isDev
? {
port: Number(env.VITE_PORT),
cors: true,
https: true, // mkcert 需要 https
}
: undefined,
// 插件配置
plugins: [vue(), mkcert({ source: 'coding' })],
// 解析配置
resolve: {
alias: {
'@': path.resolve(process.cwd(), 'src'),
$vtj: path.resolve(process.cwd(), '.vtj'),
},
},
// 定义全局变量
define: {
'process.env': JSON.stringify(env),
},
// 构建配置
build: {
// outDir: path.resolve(process.cwd(), "../../dist/renderer"),
// emptyOutDir: true,
target: 'es2022',
},
// 优化依赖
optimizeDeps: {
include: [],
},
// CSS 配置
css: {
// 预处理器配置
},
};
});

View File

@@ -3,7 +3,7 @@ require('@rushstack/eslint-patch/modern-module-resolution');
module.exports = {
root: true,
'extends': [
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
@@ -15,7 +15,9 @@ module.exports = {
semi: 0,
'vue/multi-word-component-names': 0,
indent: [
2, 2, {
2,
2,
{
SwitchCase: 1,
},
],

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

@@ -9,10 +9,9 @@ import Components from 'unplugin-vue-components/vite';
import mkcert from 'vite-plugin-mkcert';
import qiankun from 'vite-plugin-qiankun';
// @ts-ignore
export default defineConfig(({ mode }) => {
console.log('mode', mode);
const env = loadEnv(mode, process.cwd(), ['VITE_']);
const isDev = env.VITE_NODE_ENV === 'development';
return {
plugins: [less()],
vitePlugins: [
@@ -20,7 +19,7 @@ export default defineConfig(({ mode }) => {
vueJsx(),
mkcert(),
qiankun('y-code-app', {
useDevMode: env.VITE_NODE_ENV === 'development',
useDevMode: isDev,
}) as any,
Components({
resolvers: [
@@ -30,10 +29,12 @@ export default defineConfig(({ mode }) => {
],
}),
],
server: {
cors: true,
port: Number(env.VITE_PORT),
},
server: isDev
? {
cors: true,
port: Number(env.VITE_PORT),
}
: undefined,
compilation: {
resolve: {

View File

@@ -31,7 +31,7 @@
},
"devDependencies": {
"@farmfe/cli": "^1.0.4",
"@farmfe/core": "^1.6.6",
"@farmfe/core": "^1.6.7",
"@rushstack/eslint-patch": "^1.10.5",
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.17.17",

View File

@@ -1,50 +1,3 @@
<template>
<a-modal @ok="handleOk">
<a-form
:model="formData"
ref="formRef"
:rules="formRules"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
>
<a-form-item label="字段标题" name="field_title">
<a-input
placeholder="请输入字段标题"
v-model:value="formData.field_title"
/>
</a-form-item>
<a-form-item label="字段名称" name="field_name">
<a-input placeholder="请输入字段名称" v-model="formData.field_name" />
</a-form-item>
<a-form-item label="搜索状态" name="is_search">
<a-switch
v-model:checked="formData.is_search"
:checkedValue="1"
:unCheckedValue="0"
/>
</a-form-item>
<a-form-item label="字段类型" name="field_type_id">
<a-select
placeholder="请选择字段类型"
v-model:value="formData.field_type_id"
/>
</a-form-item>
<a-form-item label="所属表名称" name="belong_to_table">
<a-input
placeholder="请输入所属表名称"
v-model="formData.belong_to_table"
/>
</a-form-item>
<a-form-item label="sql数据源" name="original_sql">
<a-input
placeholder="请输入sql数据源"
v-model="formData.original_sql"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup>
import { ref, watch } from 'vue';
@@ -108,3 +61,50 @@ const handleOk = () => {
});
};
</script>
<template>
<a-modal @ok="handleOk">
<a-form
:model="formData"
ref="formRef"
:rules="formRules"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
>
<a-form-item label="字段标题" name="field_title">
<a-input
placeholder="请输入字段标题"
v-model:value="formData.field_title"
/>
</a-form-item>
<a-form-item label="字段名称" name="field_name">
<a-input placeholder="请输入字段名称" v-model="formData.field_name" />
</a-form-item>
<a-form-item label="搜索状态" name="is_search">
<a-switch
v-model:checked="formData.is_search"
:checked-value="1"
:un-checked-value="0"
/>
</a-form-item>
<a-form-item label="字段类型" name="field_type_id">
<a-select
placeholder="请选择字段类型"
v-model:value="formData.field_type_id"
/>
</a-form-item>
<a-form-item label="所属表名称" name="belong_to_table">
<a-input
placeholder="请输入所属表名称"
v-model="formData.belong_to_table"
/>
</a-form-item>
<a-form-item label="sql数据源" name="original_sql">
<a-input
placeholder="请输入sql数据源"
v-model="formData.original_sql"
/>
</a-form-item>
</a-form>
</a-modal>
</template>

View File

@@ -1,84 +1,16 @@
<template>
<div class="normal-container view-cfg-box">
<div class="header-box">
<a-space>
<a-input
v-model:value="fieldName"
placeholder="请输入字段名称"
allow-clear
style="width: 200px"
@change="search"
/>
<a-select
placeholder="请选择数据表id"
v-model:value="modularId"
allow-clear
style="width: 160px"
@change="search"
></a-select>
<a-button type="primary" @click="openCreateModal">新建</a-button>
</a-space>
</div>
<div class="content-box">
<a-table
:columns="viewCfgCols"
:data-source="dataList"
:pagination="false"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<a-space>
<a-button
type="link"
size="small"
@click="toGetDetail(record.field_id)"
>编辑</a-button
>
<a-popconfirm
title="确定删除?"
@confirm="toDelete(record.field_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="toGetFieldList"
/>
</div>
<CreateModal
:width="700"
:open="modalState.visible"
:title="modalState.title"
:type="modalState.type"
:data="modalState.data"
@cancel="modalState.visible = false"
@ok="toSave"
/>
</div>
</template>
<script setup>
import { onMounted, ref, reactive } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { message } from 'ant-design-vue';
import CreateModal from './components/create-modal.vue';
import { viewCfgCols } from './config';
import {
getFieldList,
deleteField,
saveField,
getFieldDetail,
getFieldList,
saveField,
} from './service';
import CreateModal from './components/create-modal.vue';
import { message } from 'ant-design-vue';
const listLoading = ref(false);
const saveLoading = ref(false);
@@ -166,6 +98,77 @@ const search = () => {
};
</script>
<template>
<div class="normal-container view-cfg-box">
<div class="header-box">
<a-space>
<a-input
v-model:value="fieldName"
placeholder="请输入字段名称"
allow-clear
style="width: 200px"
@change="search"
/>
<a-select
placeholder="请选择数据表id"
v-model:value="modularId"
allow-clear
style="width: 160px"
@change="search"
/>
<a-button type="primary" @click="openCreateModal">新建</a-button>
</a-space>
</div>
<div class="content-box">
<a-table
:columns="viewCfgCols"
:data-source="dataList"
:pagination="false"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<a-space>
<a-button
type="link"
size="small"
@click="toGetDetail(record.field_id)"
>
编辑
</a-button>
<a-popconfirm
title="确定删除?"
@confirm="toDelete(record.field_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="toGetFieldList"
/>
</div>
<CreateModal
:width="700"
:open="modalState.visible"
:title="modalState.title"
:type="modalState.type"
:data="modalState.data"
@cancel="modalState.visible = false"
@ok="toSave"
/>
</div>
</template>
<style lang="less" scoped>
.header-box {
margin-bottom: 10px;

View File

@@ -1,131 +1,19 @@
<template>
<div class="page-view-wrapp">
<div v-if="!isInQiankun" class="project">
<span>项目: </span>
<a-select
style="min-width: 160px"
placeholder="请选择项目"
v-model:value="projectVal"
:options="projectOptions"
@change="handleProjectChange"
></a-select>
</div>
<div>
<grid-layout
v-if="isDraggable"
:layout.sync="layoutList"
:col-num="2"
:is-draggable="true"
:is-resizable="false"
:is-mirrored="false"
:vertical-compact="true"
:use-css-transforms="true"
>
<grid-item
v-for="(item, index) in layoutList"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
drag-allow-from=".vue-draggable-handle"
drag-ignore-from=".no-drag"
>
<div class="view-box view-draggable">
<div class="vue-draggable-handle"><BarsOutlined /></div>
<div class="content no-drag">
<a-spin :spinning="ids[index].loading">
<div class="card-content">
<y-table
v-if="item.data.type === VIEW_TYPE.TABLE"
:preview-id="item.id"
:filter-config="item.data.filter"
:data-list="item.data.data"
:column-config="item.data.header"
:total="item.data.count"
:title="item.data.preview_name"
:is-export="item.data.is_export"
@toFilt="
(params?: object) => {
handleSingle(ids[index], params);
}
"
></y-table>
<y-chart
v-if="item.data.type === VIEW_TYPE.CHART"
:chartCfg="item.data.config"
:title="item.data.preview_name"
:filter-config="item.data.filter"
@toFilt="
(params?: object) => {
handleSingle(ids[index], params);
}
"
></y-chart>
</div>
</a-spin>
</div>
</div>
</grid-item>
</grid-layout>
<a-row v-else :gutter="[16, 16]">
<a-col v-for="(item, index) in layoutList" :span="24">
<a-spin :spinning="item.loading">
<div>
<div class="view-box">
<div class="content">
<div class="card-content">
<y-table
v-if="item.data.type === VIEW_TYPE.TABLE"
:preview-id="item.id"
:filter-config="item.data.filter"
:data-list="item.data.data"
:column-config="item.data.header"
:total="item.data.count"
:title="item.data.preview_name"
:is-export="item.data.is_export"
@toFilt="
(params?: object) => {
handleSingle(ids[index], params);
}
"
></y-table>
<y-chart
v-if="item.data.type === VIEW_TYPE.CHART"
:chartCfg="item.data.config"
:title="item.data.preview_name"
:filter-config="item.data.filter"
@toFilt="
(params?: object) => {
handleSingle(ids[index], params);
}
"
></y-chart>
</div>
</div>
</div>
</div>
</a-spin>
</a-col>
</a-row>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef, computed, onMounted, watch } from 'vue';
import type { SelectProps } from 'ant-design-vue';
import { computed, onMounted, ref, shallowRef, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getProjectDrop } from '@/api/common';
// api
import { searchInfo } from '@/api/preview/index';
import { BarsOutlined } from '@ant-design/icons-vue';
// utils
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 { getPageInfo } from './service';
interface ItemDetail {
id: number | string;
data: any;
@@ -204,7 +92,7 @@ const handleSingle = (info: ItemDetail, otherParams?: object) => {
getSinglePreview({ info, otherParams, type: SEARCH_TYPE.SEARCH });
};
const handleProjectChange = (value: string | number, option: Option) => {
const handleProjectChange = (value: number | string, option: Option) => {
projectTag.value = option.mark;
router.replace({
path: route.path,
@@ -263,27 +151,25 @@ const getPageInfoData = () => {
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) => {
ids.value = route.query.viewId
? 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,
};
})
: res.data?.map((item: any) => {
return {
id: item.preview_id,
data: item,
loading: false,
};
});
} else {
ids.value = res.data?.map((item: any) => {
return {
id: item.preview_id,
data: item,
loading: false,
};
});
}
getAllCardsData();
}
@@ -301,14 +187,129 @@ const fetchFn = (delay: number, info: ItemDetail) => {
};
const getAllCardsData = async () => {
let listDB = [];
for (let i in ids.value) {
const listDB = [];
for (const i in ids.value) {
listDB.push(pLimit(() => fetchFn(i === '0' ? 200 : 1000, ids.value[i])));
}
await Promise.all(listDB);
//此处的listDB就是最后整合的数据
// 此处的listDB就是最后整合的数据
};
</script>
<template>
<div class="page-view-wrapp">
<div v-if="!isInQiankun" class="project">
<span>项目: </span>
<a-select
style="min-width: 160px"
placeholder="请选择项目"
v-model:value="projectVal"
:options="projectOptions"
@change="handleProjectChange"
/>
</div>
<div>
<grid-layout
v-if="isDraggable"
v-model:layout="layoutList"
:col-num="2"
:is-draggable="true"
:is-resizable="false"
:is-mirrored="false"
:vertical-compact="true"
:use-css-transforms="true"
>
<grid-item
v-for="(item, index) in layoutList"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
drag-allow-from=".vue-draggable-handle"
drag-ignore-from=".no-drag"
>
<div class="view-box view-draggable">
<div class="vue-draggable-handle"><BarsOutlined /></div>
<div class="content no-drag">
<a-spin :spinning="ids[index].loading">
<div class="card-content">
<y-table
v-if="item.data.type === VIEW_TYPE.TABLE"
:preview-id="item.id"
:filter-config="item.data.filter"
:data-list="item.data.data"
:column-config="item.data.header"
:total="item.data.count"
:title="item.data.preview_name"
:is-export="item.data.is_export"
@to-filt="
(params?: object) => {
handleSingle(ids[index], params);
}
"
/>
<y-chart
v-if="item.data.type === VIEW_TYPE.CHART"
:chart-cfg="item.data.config"
:title="item.data.preview_name"
:filter-config="item.data.filter"
@to-filt="
(params?: object) => {
handleSingle(ids[index], params);
}
"
/>
</div>
</a-spin>
</div>
</div>
</grid-item>
</grid-layout>
<a-row v-else :gutter="[16, 16]">
<a-col v-for="(item, index) in layoutList" :span="24">
<a-spin :spinning="item.loading">
<div>
<div class="view-box">
<div class="content">
<div class="card-content">
<y-table
v-if="item.data.type === VIEW_TYPE.TABLE"
:preview-id="item.id"
:filter-config="item.data.filter"
:data-list="item.data.data"
:column-config="item.data.header"
:total="item.data.count"
:title="item.data.preview_name"
:is-export="item.data.is_export"
@to-filt="
(params?: object) => {
handleSingle(ids[index], params);
}
"
/>
<y-chart
v-if="item.data.type === VIEW_TYPE.CHART"
:chart-cfg="item.data.config"
:title="item.data.preview_name"
:filter-config="item.data.filter"
@to-filt="
(params?: object) => {
handleSingle(ids[index], params);
}
"
/>
</div>
</div>
</div>
</div>
</a-spin>
</a-col>
</a-row>
</div>
</div>
</template>
<style lang="less" scoped>
.view-box {
height: 100%;

View File

@@ -1,4 +1,5 @@
import { get, post } from '@/utils/request';
import { get } from '@/utils/request';
interface PageInfoParams {
mark: string;
page_id: number | string;

View File

@@ -1,235 +1,18 @@
<template>
<div class="normal-container">
<div class="view-create-box">
<div class="left-box">
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-form-item label="项目"
><a-select
placeholder="请选择项目"
:options="projectSel"
v-model:value="projectId"
@change="onProjectChange"
></a-select
></a-form-item>
<a-form-item label="数据来源">
<a-select
placeholder="请选择"
:options="modularSel"
v-model:value="modularId"
@change="onModularChange"
></a-select>
</a-form-item>
<a-form-item label="展示类型">
<a-select
placeholder="请选择展示类型"
:options="showTypeSel"
v-model:value="showTypeId"
@change="onShowTypeChange"
></a-select>
</a-form-item>
<a-form-item label="是否导出" v-if="showTypeId === 1">
<a-switch
v-model:checked="isExport"
:checkedValue="1"
:unCheckedValue="0"
>
</a-switch>
</a-form-item>
<a-form-item label="字段" v-if="fieldList.length">
<a-checkbox-group v-model:value="fieldIds">
<a-checkbox
v-for="(item, index) in fieldList"
:key="index"
:value="item.value"
>{{ item.label }}</a-checkbox
>
</a-checkbox-group>
</a-form-item>
<a-form-item label="x轴" v-if="xDataList.length">
<a-radio-group
:options="xDataList"
v-model:value="xDataId"
></a-radio-group>
</a-form-item>
<a-form-item label="y轴" v-if="yDataList.length">
<a-checkbox-group
:options="yDataList"
v-model:value="yDataId"
></a-checkbox-group>
</a-form-item>
</a-form>
<div class="footer">
<a-button
class="preview-btn"
:loading="previewLoading"
@click="
() => {
toPreview({});
}
"
>预览</a-button
>
<a-button type="primary" @click="addViewName">点击保存</a-button>
</div>
</div>
<div class="right-box">
<div class="y-table-container" v-if="previewData.type === 'table'">
<div class="y-table-name">
<div class="title">{{ previewData.preview_name }}</div>
</div>
<div class="y-table-filter">
<div
v-for="item in previewData.filterConfig"
:key="item.name"
class="filter-item"
>
<span>{{ item.label }}</span>
<a-select
v-if="item.type === 'select'"
class="input-item"
:options="item.options"
v-model:value="previewData.filterData[item.name]"
placeholder="请选择"
allow-clear
@change="toFilt"
></a-select>
<a-input
v-if="item.type === 'text'"
class="input-item"
placeholder="请输入"
allow-clear
v-model:value="previewData.filterData[item.name]"
@change="toFilt"
/>
<a-range-picker
v-if="item.type === 'time'"
class="date-item"
v-model:value="previewData.filterData[item.name]"
@change="toFilt"
/>
<a-range-picker
v-else-if="item.type === 'date_time'"
class="date-time-item"
v-model:value="previewData.filterData[item.name]"
show-time
format="YYYY-MM-DD HH:mm"
@change="toFilt"
/>
<!-- 数值区间 -->
<div
v-else-if="
item.type === 'number_range' &&
previewData.filterData[item.name]
"
class="number_range_box"
>
<a-input-number
placeholder="最小值"
style="width: 140px"
v-model:value="previewData.filterData[item.name].min"
@change="toFilt"
/>
<span class="divider"> - </span>
<a-input-number
placeholder="最大值"
style="width: 140px"
v-model:value="previewData.filterData[item.name].max"
@change="toFilt"
/>
</div>
</div>
<div v-if="previewData.isExport" class="filter-item">
<a-button type="primary">导出</a-button>
</div>
</div>
<div class="y-table-content">
<a-table
:columns="previewData.columnConfig"
:data-source="previewData.dataList"
:pagination="false"
:scroll="{ x: 1000, y: `calc(100vh - 260px)` }"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<a-image
v-if="column.show_type === 'img'"
:src="record[column.dataIndex]"
:width="160"
/>
<a
v-else-if="column.show_type === 'link'"
target="_blank"
:href="record[column.dataIndex]"
>{{ record[column.dataIndex] }}</a
>
<div
v-else-if="column.show_type === 'richText'"
v-html="record[column.dataIndex]"
></div>
<template v-else>{{ record[column.dataIndex] }}</template>
</template>
</a-table>
<a-pagination
v-model:current="previewData.page"
:total="previewData.total"
:page-size="previewData.perPage"
:hide-on-single-page="false"
size="small"
class="pagination-box"
@change="
() => {
toPreview({});
}
"
/>
</div>
</div>
<y-chart
v-else-if="previewData.type === 'chart'"
:chart-cfg="previewData.chartCfg"
:filter-config="previewData.filter"
@toFilt="
() => {
toPreview({});
}
"
></y-chart>
<div class="preview-area" v-else>
<div><BarChartOutlined /></div>
<div>预览区</div>
</div>
</div>
<a-modal
:open="nameVisible"
title="保存视图"
:bodyStyle="{ paddingTop: '20px' }"
@cancel="nameVisible = false"
@ok="toSaveView"
>
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="视图名称">
<a-input placeholder="请输入视图名称" v-model:value="previewName" />
</a-form-item>
</a-form>
</a-modal>
</div>
</div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import yChart from '@/components/common/y-chart.vue';
import { BarChartOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { cloneDeep } from 'lodash-es';
import {
getFieldOpts,
getProModularField,
getShowTypeSelect,
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';
const projectSel = ref([]); // 项目下拉
const modularSel = ref([]); // 数据来源下拉
@@ -289,7 +72,7 @@ const toGetFieldOpts = () => {
fieldList.value = res.data.list ? tranformList(res.data.list) : [];
xDataList.value = res.data.x_data ? tranformList(res.data.x_data) : [];
yDataList.value = res.data.y_data ? tranformList(res.data.y_data) : [];
if (!fieldList.value.length) {
if (fieldList.value.length === 0) {
fieldIds.value = [];
} else {
xDataId.value = undefined;
@@ -354,7 +137,9 @@ const resetPreviewData = () => {
const toPreview = ({ filter }) => {
previewLoading.value = true;
let filterData;
if (!filter) {
if (filter) {
filterData = filter;
} else {
const cloneFilter = cloneDeep(previewData.filterConfig);
filterData = cloneFilter
.filter((item) => {
@@ -381,12 +166,12 @@ const toPreview = ({ filter }) => {
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') {
// 数值区间
@@ -408,8 +193,6 @@ const toPreview = ({ filter }) => {
};
}
});
} else {
filterData = filter;
}
preview({
@@ -489,6 +272,219 @@ const toFilt = () => {
};
</script>
<template>
<div class="normal-container">
<div class="view-create-box">
<div class="left-box">
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-form-item label="项目">
<a-select
placeholder="请选择项目"
:options="projectSel"
v-model:value="projectId"
@change="onProjectChange"
/>
</a-form-item>
<a-form-item label="数据来源">
<a-select
placeholder="请选择"
:options="modularSel"
v-model:value="modularId"
@change="onModularChange"
/>
</a-form-item>
<a-form-item label="展示类型">
<a-select
placeholder="请选择展示类型"
:options="showTypeSel"
v-model:value="showTypeId"
@change="onShowTypeChange"
/>
</a-form-item>
<a-form-item label="是否导出" v-if="showTypeId === 1">
<a-switch
v-model:checked="isExport"
:checked-value="1"
:un-checked-value="0"
/>
</a-form-item>
<a-form-item label="字段" v-if="fieldList.length > 0">
<a-checkbox-group v-model:value="fieldIds">
<a-checkbox
v-for="(item, index) in fieldList"
:key="index"
:value="item.value"
>
{{ item.label }}
</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item label="x轴" v-if="xDataList.length > 0">
<a-radio-group :options="xDataList" v-model:value="xDataId" />
</a-form-item>
<a-form-item label="y轴" v-if="yDataList.length > 0">
<a-checkbox-group :options="yDataList" v-model:value="yDataId" />
</a-form-item>
</a-form>
<div class="footer">
<a-button
class="preview-btn"
:loading="previewLoading"
@click="
() => {
toPreview({});
}
"
>
预览
</a-button>
<a-button type="primary" @click="addViewName">点击保存</a-button>
</div>
</div>
<div class="right-box">
<div class="y-table-container" v-if="previewData.type === 'table'">
<div class="y-table-name">
<div class="title">{{ previewData.preview_name }}</div>
</div>
<div class="y-table-filter">
<div
v-for="item in previewData.filterConfig"
:key="item.name"
class="filter-item"
>
<span>{{ item.label }}</span>
<a-select
v-if="item.type === 'select'"
class="input-item"
:options="item.options"
v-model:value="previewData.filterData[item.name]"
placeholder="请选择"
allow-clear
@change="toFilt"
/>
<a-input
v-if="item.type === 'text'"
class="input-item"
placeholder="请输入"
allow-clear
v-model:value="previewData.filterData[item.name]"
@change="toFilt"
/>
<a-range-picker
v-if="item.type === 'time'"
class="date-item"
v-model:value="previewData.filterData[item.name]"
@change="toFilt"
/>
<a-range-picker
v-else-if="item.type === 'date_time'"
class="date-time-item"
v-model:value="previewData.filterData[item.name]"
show-time
format="YYYY-MM-DD HH:mm"
@change="toFilt"
/>
<!-- 数值区间 -->
<div
v-else-if="
item.type === 'number_range' &&
previewData.filterData[item.name]
"
class="number_range_box"
>
<a-input-number
placeholder="最小值"
style="width: 140px"
v-model:value="previewData.filterData[item.name].min"
@change="toFilt"
/>
<span class="divider"> - </span>
<a-input-number
placeholder="最大值"
style="width: 140px"
v-model:value="previewData.filterData[item.name].max"
@change="toFilt"
/>
</div>
</div>
<div v-if="previewData.isExport" class="filter-item">
<a-button type="primary">导出</a-button>
</div>
</div>
<div class="y-table-content">
<a-table
:columns="previewData.columnConfig"
:data-source="previewData.dataList"
:pagination="false"
:scroll="{ x: 1000, y: `calc(100vh - 260px)` }"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<a-image
v-if="column.show_type === 'img'"
:src="record[column.dataIndex]"
:width="160"
/>
<a
v-else-if="column.show_type === 'link'"
target="_blank"
:href="record[column.dataIndex]"
>{{ record[column.dataIndex] }}</a>
<div
v-else-if="column.show_type === 'richText'"
v-html="record[column.dataIndex]"
></div>
<template v-else>{{ record[column.dataIndex] }}</template>
</template>
</a-table>
<a-pagination
v-model:current="previewData.page"
:total="previewData.total"
:page-size="previewData.perPage"
:hide-on-single-page="false"
size="small"
class="pagination-box"
@change="
() => {
toPreview({});
}
"
/>
</div>
</div>
<y-chart
v-else-if="previewData.type === 'chart'"
:chart-cfg="previewData.chartCfg"
:filter-config="previewData.filter"
@to-filt="
() => {
toPreview({});
}
"
/>
<div class="preview-area" v-else>
<div><BarChartOutlined /></div>
<div>预览区</div>
</div>
</div>
<a-modal
:open="nameVisible"
title="保存视图"
:body-style="{ paddingTop: '20px' }"
@cancel="nameVisible = false"
@ok="toSaveView"
>
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="视图名称">
<a-input placeholder="请输入视图名称" v-model:value="previewName" />
</a-form-item>
</a-form>
</a-modal>
</div>
</div>
</template>
<style lang="less" scoped>
.normal-container {
height: calc(100vh - 120px);

View File

@@ -1,114 +1,12 @@
<template>
<div class="normal-container">
<div class="view-list-box">
<div class="left-box">
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-form-item label="项目">
<a-select
:options="projectSel"
v-model:value="projectId"
placeholder="请选择项目"
@change="onProjectChange"
></a-select>
</a-form-item>
<a-form-item label="数据来源">
<a-select
:options="modularSel"
v-model:value="modularId"
placeholder="请先选好项目再选择"
@change="onModularChange"
></a-select>
</a-form-item>
</a-form>
<a-table
:data-source="dataList"
:columns="viewListCols"
:pagination="false"
:row-class-name="
(record) =>
record.preview_id === selectedRowId ? 'selected-row' : ''
"
:custom-row="
(record, index) => {
return {
onClick: () => {
selectedRowId = record.preview_id;
toGetViewInfo();
},
};
}
"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<a-popconfirm
title="确定删除"
@confirm="toDelete(record.preview_id)"
>
<a-button
type="link"
@click="
(e) => {
e.stopPropagation();
}
"
>删除</a-button
>
</a-popconfirm>
</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="toGetList"
/>
</div>
<div class="right-box">
<y-table
v-if="selectViewInfo.type === 'table'"
:previewId="selectedRowId"
:filter-config="selectViewInfo.filter"
:data-list="selectViewInfo.data"
:column-config="selectViewInfo.header"
:total="selectViewInfo.count"
:title="selectViewInfo.preview_name"
:is-export="selectViewInfo.is_export"
@toFilt="
(params) => {
toGetViewInfo(params);
}
"
/>
<y-chart
v-else-if="selectViewInfo.type === 'chart'"
:chartCfg="selectViewInfo.config"
:title="selectViewInfo.preview_name"
:filter-config="selectViewInfo.filter"
@toFilt="toGetViewInfo"
/>
<div class="preview-area" v-else>
<div><BarChartOutlined /></div>
<div>展示区</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, reactive } from 'vue';
import { getProModular, getViewList, getViewInfo, deleteView } from './service';
import { viewListCols } from './config';
import { onMounted, reactive, ref } from 'vue';
import yTable from '@/components/common/y-table.vue';
import { message } from 'ant-design-vue';
import { BarChartOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { viewListCols } from './config';
import { deleteView, getProModular, getViewInfo, getViewList } from './service';
const projectSel = ref([]);
const modularSel = ref([]);
@@ -139,7 +37,7 @@ onMounted(() => {
const toGetProModular = () => {
getProModular().then((res) => {
projectSel.value = res.data;
if (res.data.length) {
if (res.data.length > 0) {
projectId.value = res.data[0].value;
onProjectChange(projectId.value);
}
@@ -184,6 +82,111 @@ const toDelete = (previewId) => {
};
</script>
<template>
<div class="normal-container">
<div class="view-list-box">
<div class="left-box">
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-form-item label="项目">
<a-select
:options="projectSel"
v-model:value="projectId"
placeholder="请选择项目"
@change="onProjectChange"
/>
</a-form-item>
<a-form-item label="数据来源">
<a-select
:options="modularSel"
v-model:value="modularId"
placeholder="请先选好项目再选择"
@change="onModularChange"
/>
</a-form-item>
</a-form>
<a-table
:data-source="dataList"
:columns="viewListCols"
:pagination="false"
:row-class-name="
(record) =>
record.preview_id === selectedRowId ? 'selected-row' : ''
"
:custom-row="
(record, index) => {
return {
onClick: () => {
selectedRowId = record.preview_id;
toGetViewInfo();
},
};
}
"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<a-popconfirm
title="确定删除"
@confirm="toDelete(record.preview_id)"
>
<a-button
type="link"
@click="
(e) => {
e.stopPropagation();
}
"
>
删除
</a-button>
</a-popconfirm>
</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="toGetList"
/>
</div>
<div class="right-box">
<y-table
v-if="selectViewInfo.type === 'table'"
:preview-id="selectedRowId"
:filter-config="selectViewInfo.filter"
:data-list="selectViewInfo.data"
:column-config="selectViewInfo.header"
:total="selectViewInfo.count"
:title="selectViewInfo.preview_name"
:is-export="selectViewInfo.is_export"
@to-filt="
(params) => {
toGetViewInfo(params);
}
"
/>
<y-chart
v-else-if="selectViewInfo.type === 'chart'"
:chart-cfg="selectViewInfo.config"
:title="selectViewInfo.preview_name"
:filter-config="selectViewInfo.filter"
@to-filt="toGetViewInfo"
/>
<div class="preview-area" v-else>
<div><BarChartOutlined /></div>
<div>展示区</div>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.normal-container {
height: calc(100vh - 120px);

View File

@@ -14,7 +14,7 @@ export function getViewList({ modularId, page = 1, perPage = 20 }) {
params: {
modular_id: modularId,
page,
perPage: perPage,
perPage,
},
});
}

View File

@@ -1,15 +1,22 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.tsx", "components.d.ts", "*.d.ts"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "esnext",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"target": "esnext"
}
}
},
"include": [
"env.d.ts",
"src/**/*",
"src/**/*.vue",
"src/**/*.tsx",
"components.d.ts",
"*.d.ts"
],
"exclude": ["src/**/__tests__/*"]
}

View File

@@ -1,5 +1,4 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
@@ -7,5 +6,6 @@
{
"path": "./tsconfig.app.json"
}
]
],
"files": []
}

View File

@@ -1,19 +1,19 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"],
"noEmit": true
},
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
]
}