create-meadminjs 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/meadmin/README.md +12 -1
- package/template/meadmin/packageTemplate.json +4 -4
- package/template/meadmin/src/app/admin/controller/userFile.controller.ts +84 -84
- package/template/meadmin/src/app/index/controller/file.controller.ts +2 -54
- package/template/meadmin/src/app/index/middleware/index.middleware.ts +1 -1
- package/template/meadmin/src/config/config.default.ts +154 -154
- package/template/meadmin/src/configuration.ts +107 -107
- package/template/meadmin/src/entities/abstract/base.entity.ts +40 -1
- package/template/meadmin/src/fileManage/storage/local.ts +93 -93
- package/template/meadmin/view/admin/src/components/meVxeTable/index.vue +2 -2
- package/template/meadmin/view/admin/src/config/login.ts +1 -1
- package/template/meadmin/view/admin/src/layout/components/header/components/topBar/components/right/components/user.vue +2 -7
- package/template/meadmin/view/index/src/app.ts +39 -39
- package/template/meadmin/view/index/src/components/meDialog/index.vue +3 -1
- package/template/meadmin/view/index/src/components/meVxeTable/index.vue +2 -2
- package/template/meadmin/view/index/src/components/service/meImageViewer.ts +6 -2
- package/template/meadmin/view/index/src/config/login.ts +1 -1
- package/template/meadmin/view/index/src/main.ts +1 -1
- package/template/meadmin/view/index/src/utils/cookies.ts +59 -59
- package/template/meadmin/view/index/src/utils/request.ts +2 -2
- package/template/meadmin/view/index/types/auto-imports.d.ts +0 -1
- package/template/meadmin/view/index/types/components.d.ts +2 -2
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
import { App, Configuration, ILogger, IMidwayApplication, IMidwayContainer, Init, Inject, Logger, MidwayDecoratorService } from '@midwayjs/core';
|
|
2
|
-
import * as info from '@midwayjs/info';
|
|
3
|
-
import * as koa from '@midwayjs/koa';
|
|
4
|
-
import * as validate from '@midwayjs/validate';
|
|
5
|
-
import './helper/dotenv.js';
|
|
6
|
-
// import { ReportMiddleware } from './middleware/report.middleware.js';
|
|
7
|
-
import DefaultConfig from '@/config/config.default.js';
|
|
8
|
-
import UnittestConfig from '@/config/config.unittest.js';
|
|
9
|
-
import * as meadmin from '@meadmin/core';
|
|
10
|
-
import * as viteView from '@meadmin/midway-vite-view'; //引入view组件
|
|
11
|
-
import * as busboy from '@midwayjs/busboy';
|
|
12
|
-
import * as cacheManager from '@midwayjs/cache-manager';
|
|
13
|
-
import * as captcha from '@midwayjs/captcha';
|
|
14
|
-
import * as i18n from '@midwayjs/i18n';
|
|
15
|
-
import * as redis from '@midwayjs/redis';
|
|
16
|
-
import * as staticFile from '@midwayjs/static-file';
|
|
17
|
-
import * as swagger from '@midwayjs/swagger';
|
|
18
|
-
import { RegistreDecorators } from './decorators/index.js';
|
|
19
|
-
import { filters } from './filter/index.js';
|
|
20
|
-
import { initLogger } from './logger.js';
|
|
21
|
-
|
|
22
|
-
const registreDecorators = new RegistreDecorators();
|
|
23
|
-
|
|
24
|
-
@Configuration({
|
|
25
|
-
imports: [
|
|
26
|
-
koa,
|
|
27
|
-
i18n,
|
|
28
|
-
meadmin, //必须放在swagger之前引入
|
|
29
|
-
validate,
|
|
30
|
-
{
|
|
31
|
-
component: info,
|
|
32
|
-
enabledEnvironment: ['local'],
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
component: swagger,
|
|
36
|
-
enabledEnvironment: ['local', 'dev'],
|
|
37
|
-
},
|
|
38
|
-
staticFile,
|
|
39
|
-
viteView,
|
|
40
|
-
redis,
|
|
41
|
-
cacheManager,
|
|
42
|
-
captcha,
|
|
43
|
-
busboy,
|
|
44
|
-
],
|
|
45
|
-
importConfigs: [
|
|
46
|
-
{
|
|
47
|
-
default: DefaultConfig,
|
|
48
|
-
unittest: UnittestConfig,
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
})
|
|
52
|
-
export class MainConfiguration {
|
|
53
|
-
@App('koa')
|
|
54
|
-
app: koa.Application;
|
|
55
|
-
|
|
56
|
-
@Inject()
|
|
57
|
-
decoratorService: MidwayDecoratorService;
|
|
58
|
-
|
|
59
|
-
@Logger()
|
|
60
|
-
appLogger: ILogger;
|
|
61
|
-
|
|
62
|
-
@Logger('coreLogger')
|
|
63
|
-
coreLogger: ILogger;
|
|
64
|
-
|
|
65
|
-
@Init()
|
|
66
|
-
async init() {
|
|
67
|
-
registreDecorators.decoratorService = this.decoratorService;
|
|
68
|
-
registreDecorators.init();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 在应用配置加载后执行
|
|
73
|
-
*/
|
|
74
|
-
async onConfigLoad?(container: IMidwayContainer, app: IMidwayApplication) {
|
|
75
|
-
registreDecorators.onConfigLoad(container, app);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 在依赖注入容器 ready 的时候执行
|
|
80
|
-
*/
|
|
81
|
-
async onReady?(container: IMidwayContainer, app: IMidwayApplication) {
|
|
82
|
-
initLogger(this.appLogger, this.coreLogger);
|
|
83
|
-
this.app.useFilter(filters);
|
|
84
|
-
registreDecorators.onReady(container, app);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 在应用服务启动后执行
|
|
89
|
-
*/
|
|
90
|
-
async onServerReady?(container: IMidwayContainer, app: IMidwayApplication) {
|
|
91
|
-
registreDecorators.onServerReady(container, app);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 在应用停止的时候执行
|
|
96
|
-
*/
|
|
97
|
-
async onStop?(container: IMidwayContainer, app: IMidwayApplication) {
|
|
98
|
-
registreDecorators.onStop(container, app);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* 在健康检查时执行
|
|
103
|
-
*/
|
|
104
|
-
async onHealthCheck?(container: IMidwayContainer) {
|
|
105
|
-
registreDecorators.onHealthCheck(container);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
1
|
+
import { App, Configuration, ILogger, IMidwayApplication, IMidwayContainer, Init, Inject, Logger, MidwayDecoratorService } from '@midwayjs/core';
|
|
2
|
+
import * as info from '@midwayjs/info';
|
|
3
|
+
import * as koa from '@midwayjs/koa';
|
|
4
|
+
import * as validate from '@midwayjs/validate';
|
|
5
|
+
import './helper/dotenv.js';
|
|
6
|
+
// import { ReportMiddleware } from './middleware/report.middleware.js';
|
|
7
|
+
import DefaultConfig from '@/config/config.default.js';
|
|
8
|
+
import UnittestConfig from '@/config/config.unittest.js';
|
|
9
|
+
import * as meadmin from '@meadmin/core';
|
|
10
|
+
import * as viteView from '@meadmin/midway-vite-view'; //引入view组件
|
|
11
|
+
import * as busboy from '@midwayjs/busboy';
|
|
12
|
+
import * as cacheManager from '@midwayjs/cache-manager';
|
|
13
|
+
import * as captcha from '@midwayjs/captcha';
|
|
14
|
+
import * as i18n from '@midwayjs/i18n';
|
|
15
|
+
import * as redis from '@midwayjs/redis';
|
|
16
|
+
import * as staticFile from '@midwayjs/static-file';
|
|
17
|
+
import * as swagger from '@midwayjs/swagger';
|
|
18
|
+
import { RegistreDecorators } from './decorators/index.js';
|
|
19
|
+
import { filters } from './filter/index.js';
|
|
20
|
+
import { initLogger } from './logger.js';
|
|
21
|
+
|
|
22
|
+
const registreDecorators = new RegistreDecorators();
|
|
23
|
+
|
|
24
|
+
@Configuration({
|
|
25
|
+
imports: [
|
|
26
|
+
koa,
|
|
27
|
+
i18n,
|
|
28
|
+
meadmin, //必须放在swagger之前引入
|
|
29
|
+
validate,
|
|
30
|
+
{
|
|
31
|
+
component: info,
|
|
32
|
+
enabledEnvironment: ['local'],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
component: swagger,
|
|
36
|
+
enabledEnvironment: ['local', 'dev'],
|
|
37
|
+
},
|
|
38
|
+
staticFile,
|
|
39
|
+
viteView,
|
|
40
|
+
redis,
|
|
41
|
+
cacheManager,
|
|
42
|
+
captcha,
|
|
43
|
+
busboy,
|
|
44
|
+
],
|
|
45
|
+
importConfigs: [
|
|
46
|
+
{
|
|
47
|
+
default: DefaultConfig,
|
|
48
|
+
unittest: UnittestConfig,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
export class MainConfiguration {
|
|
53
|
+
@App('koa')
|
|
54
|
+
app: koa.Application;
|
|
55
|
+
|
|
56
|
+
@Inject()
|
|
57
|
+
decoratorService: MidwayDecoratorService;
|
|
58
|
+
|
|
59
|
+
@Logger()
|
|
60
|
+
appLogger: ILogger;
|
|
61
|
+
|
|
62
|
+
@Logger('coreLogger')
|
|
63
|
+
coreLogger: ILogger;
|
|
64
|
+
|
|
65
|
+
@Init()
|
|
66
|
+
async init() {
|
|
67
|
+
registreDecorators.decoratorService = this.decoratorService;
|
|
68
|
+
registreDecorators.init();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 在应用配置加载后执行
|
|
73
|
+
*/
|
|
74
|
+
async onConfigLoad?(container: IMidwayContainer, app: IMidwayApplication) {
|
|
75
|
+
registreDecorators.onConfigLoad(container, app);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 在依赖注入容器 ready 的时候执行
|
|
80
|
+
*/
|
|
81
|
+
async onReady?(container: IMidwayContainer, app: IMidwayApplication) {
|
|
82
|
+
initLogger(this.appLogger, this.coreLogger);
|
|
83
|
+
this.app.useFilter(filters);
|
|
84
|
+
registreDecorators.onReady(container, app);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 在应用服务启动后执行
|
|
89
|
+
*/
|
|
90
|
+
async onServerReady?(container: IMidwayContainer, app: IMidwayApplication) {
|
|
91
|
+
registreDecorators.onServerReady(container, app);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 在应用停止的时候执行
|
|
96
|
+
*/
|
|
97
|
+
async onStop?(container: IMidwayContainer, app: IMidwayApplication) {
|
|
98
|
+
registreDecorators.onStop(container, app);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 在健康检查时执行
|
|
103
|
+
*/
|
|
104
|
+
async onHealthCheck?(container: IMidwayContainer) {
|
|
105
|
+
registreDecorators.onHealthCheck(container);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiPropertyRule } from '@/decorators/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { getContext } from '@meadmin/core';
|
|
3
3
|
import { BulkCreateOptions, InferAttributes, InferCreationAttributes, InstanceUpdateOptions, Model, ModelStatic, UpdateOptions } from '@sequelize/core';
|
|
4
4
|
import { AfterBulkUpdate, Attribute, BeforeBulkCreate, BeforeCreate, BeforeUpdate, CreatedAt, Table, UpdatedAt } from '@sequelize/core/decorators-legacy';
|
|
5
5
|
|
|
@@ -18,6 +18,7 @@ export class BaseModel<M extends Model<any, any>> extends Model<InferAttributes<
|
|
|
18
18
|
|
|
19
19
|
@BeforeCreate()
|
|
20
20
|
static async setCreatedId(info: BaseModel<any>, options: InstanceUpdateOptions<BaseModel<any>>) {
|
|
21
|
+
const ctx = getContext();
|
|
21
22
|
if (ctx?.adminInfo && info.modelDefinition.attributes.has('createdAdminId')) {
|
|
22
23
|
//设置创建管理员Id
|
|
23
24
|
(info as any).createdAdminId = ctx.adminInfo.id;
|
|
@@ -26,10 +27,19 @@ export class BaseModel<M extends Model<any, any>> extends Model<InferAttributes<
|
|
|
26
27
|
(info as any).updatedAdminId = ctx.adminInfo.id;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
if (ctx?.userInfo && info.modelDefinition.attributes.has('createdUserId')) {
|
|
31
|
+
//设置创建用户Id
|
|
32
|
+
(info as any).createdUserId = ctx.userInfo.id;
|
|
33
|
+
if (ctx?.userInfo && info.modelDefinition.attributes.has('updatedUserId')) {
|
|
34
|
+
//设置更新用户Id
|
|
35
|
+
(info as any).updatedUserId = ctx.userInfo.id;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
@BeforeBulkCreate()
|
|
32
41
|
static async setCreatedIdBulk(instances: BaseModel<any>[], options: BulkCreateOptions<BaseModel<any>> & { model: ModelStatic<BaseModel<any>> }) {
|
|
42
|
+
const ctx = getContext();
|
|
33
43
|
if (ctx?.adminInfo && options.model.modelDefinition.attributes.has('createdAdminId')) {
|
|
34
44
|
//设置创建管理员Id
|
|
35
45
|
instances.forEach((instance) => {
|
|
@@ -42,18 +52,36 @@ export class BaseModel<M extends Model<any, any>> extends Model<InferAttributes<
|
|
|
42
52
|
});
|
|
43
53
|
}
|
|
44
54
|
}
|
|
55
|
+
if (ctx?.userInfo && options.model.modelDefinition.attributes.has('createdUserId')) {
|
|
56
|
+
//设置创建用户Id
|
|
57
|
+
instances.forEach((instance) => {
|
|
58
|
+
(instance as any).createdUserId = ctx.userInfo.id;
|
|
59
|
+
});
|
|
60
|
+
if (options.model.modelDefinition.attributes.has('updatedUserId')) {
|
|
61
|
+
//设置更新用户Id
|
|
62
|
+
instances.forEach((instance) => {
|
|
63
|
+
(instance as any).updatedUserId = ctx.userInfo.id;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
45
67
|
}
|
|
46
68
|
|
|
47
69
|
@BeforeUpdate()
|
|
48
70
|
static async setUpdatedId(info: BaseModel<any>, options: InstanceUpdateOptions<BaseModel<any>>) {
|
|
71
|
+
const ctx = getContext();
|
|
49
72
|
if (ctx?.adminInfo && info.modelDefinition.attributes.has('updatedAdminId')) {
|
|
50
73
|
//设置更新管理员Id
|
|
51
74
|
(info as any).updatedAdminId = ctx.adminInfo.id;
|
|
52
75
|
}
|
|
76
|
+
if (ctx?.userInfo && info.modelDefinition.attributes.has('updatedUserId')) {
|
|
77
|
+
//设置更新用户Id
|
|
78
|
+
(info as any).updatedUserId = ctx.userInfo.id;
|
|
79
|
+
}
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
@AfterBulkUpdate()
|
|
56
83
|
static async setUpdatedIdBulk(options: UpdateOptions<BaseModel<any>> & { model: ModelStatic<BaseModel<any>> }) {
|
|
84
|
+
const ctx = getContext();
|
|
57
85
|
if (ctx?.adminInfo && options.model.modelDefinition.attributes.has('updatedAdminId')) {
|
|
58
86
|
//设置更新管理员Id
|
|
59
87
|
await options.model.update(
|
|
@@ -65,5 +93,16 @@ export class BaseModel<M extends Model<any, any>> extends Model<InferAttributes<
|
|
|
65
93
|
},
|
|
66
94
|
);
|
|
67
95
|
}
|
|
96
|
+
if (ctx?.userInfo && options.model.modelDefinition.attributes.has('updatedUserId')) {
|
|
97
|
+
//设置更新用户Id
|
|
98
|
+
await options.model.update(
|
|
99
|
+
{ updatedUserId: ctx.userInfo.id },
|
|
100
|
+
{
|
|
101
|
+
where: options.where,
|
|
102
|
+
transaction: options.transaction,
|
|
103
|
+
hooks: false,
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
}
|
|
68
107
|
}
|
|
69
108
|
}
|
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
import { UploadParam, UploadResult, UpStorageInterface } from '@/types/fileManage.js';
|
|
2
|
-
import { app } from '@meadmin/core';
|
|
3
|
-
import { UploadOptions, UploadStreamFileInfo } from '@midwayjs/busboy';
|
|
4
|
-
import { appendFileSync, createReadStream, createWriteStream, existsSync, mkdirSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
5
|
-
import { relative, resolve } from 'node:path';
|
|
6
|
-
import { BaseStorage } from './base.js';
|
|
7
|
-
export class LocalStorage extends BaseStorage implements UpStorageInterface {
|
|
8
|
-
get path() {
|
|
9
|
-
return '/' + this.model + '/';
|
|
10
|
-
}
|
|
11
|
-
//保存文件
|
|
12
|
-
async saveFie({ filename, data }: UploadStreamFileInfo, md5: string, savePath: string, tmpPath: string) {
|
|
13
|
-
const suffix = filename.substring(filename.lastIndexOf('.')); //后缀带着.
|
|
14
|
-
const tmpFilePath = resolve(tmpPath, '__tmp__' + process.pid + '__' + md5 + suffix); //临时文件带上进程id防止重复
|
|
15
|
-
const saveFilePath = resolve(savePath, md5 + suffix);
|
|
16
|
-
const saveStat = statSync(saveFilePath, { throwIfNoEntry: false });
|
|
17
|
-
if (saveStat) {
|
|
18
|
-
return { size: saveStat.size, path: saveFilePath }; //已存在无需上传
|
|
19
|
-
}
|
|
20
|
-
return await new Promise<{ size: number; path: string }>((reslove, reject) => {
|
|
21
|
-
const stream = createWriteStream(tmpFilePath);
|
|
22
|
-
stream.on('close', () => {
|
|
23
|
-
renameSync(tmpFilePath, saveFilePath);
|
|
24
|
-
reslove({ size: statSync(saveFilePath, { throwIfNoEntry: true }).size, path: saveFilePath });
|
|
25
|
-
});
|
|
26
|
-
stream.on('error', (e) => {
|
|
27
|
-
reject(e);
|
|
28
|
-
});
|
|
29
|
-
data.pipe(stream);
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
async getFilePath(path: string) {
|
|
33
|
-
return resolve((app.getConfig('busboy') as UploadOptions).upDir + this.path, path);
|
|
34
|
-
}
|
|
35
|
-
async getFileReadSteam(path: string) {
|
|
36
|
-
return createReadStream(await this.getFilePath(path));
|
|
37
|
-
}
|
|
38
|
-
async upload(file: UploadStreamFileInfo, params: UploadParam) {
|
|
39
|
-
const fileUpconfig = app.getConfig('busboy') as UploadOptions;
|
|
40
|
-
const uploadResult = { storage: 'local' } as UploadResult;
|
|
41
|
-
uploadResult.md5 = params.md5;
|
|
42
|
-
//只处理1个文件上传
|
|
43
|
-
const { filename, mimeType } = file;
|
|
44
|
-
if (params.chunk !== '1') {
|
|
45
|
-
//非分片上传
|
|
46
|
-
const res = await this.saveFie(file, params.md5, fileUpconfig.upDir + this.path, fileUpconfig.tmpdir);
|
|
47
|
-
uploadResult.size = res.size;
|
|
48
|
-
uploadResult.path = relative(fileUpconfig.upDir + this.path, res.path);
|
|
49
|
-
} else {
|
|
50
|
-
//分片上传
|
|
51
|
-
const suffix = filename.substring(filename.lastIndexOf('.')); //后缀带着.
|
|
52
|
-
const saveFilePath = resolve(fileUpconfig.upDir + this.path, params.md5 + suffix);
|
|
53
|
-
const saveStat = statSync(saveFilePath, { throwIfNoEntry: false });
|
|
54
|
-
if (saveStat) {
|
|
55
|
-
//已存在无需上传
|
|
56
|
-
uploadResult.size = saveStat.size;
|
|
57
|
-
uploadResult.path = relative(fileUpconfig.upDir + this.path, saveFilePath);
|
|
58
|
-
} else {
|
|
59
|
-
if (params.start === undefined || params.over === undefined || params.chunkMd5 === undefined) {
|
|
60
|
-
throw new Error('分片上传时,start、over、chunkMd5必须有值');
|
|
61
|
-
}
|
|
62
|
-
const path = resolve(fileUpconfig.tmpdir, params.md5 + '/');
|
|
63
|
-
if (!existsSync(path)) {
|
|
64
|
-
mkdirSync(path);
|
|
65
|
-
}
|
|
66
|
-
const res = await this.saveFie(file, params.chunkMd5, path, path);
|
|
67
|
-
const tmpFilePath = resolve(fileUpconfig.tmpdir, '__tmp__chunk_all__' + process.pid + '__' + params.md5 + suffix); //临时文件带上进程id防止重复
|
|
68
|
-
appendFileSync(tmpFilePath, ''); //追加空串确保文件存在
|
|
69
|
-
await new Promise<void>((resolve, reject) => {
|
|
70
|
-
const fsStream = createWriteStream(tmpFilePath, { flags: 'r+', start: +params.start, autoClose: true });
|
|
71
|
-
fsStream.on('close', () => {
|
|
72
|
-
resolve();
|
|
73
|
-
});
|
|
74
|
-
fsStream.on('error', (e) => {
|
|
75
|
-
reject(e);
|
|
76
|
-
});
|
|
77
|
-
createReadStream(res.path).pipe(fsStream);
|
|
78
|
-
});
|
|
79
|
-
if (params.over !== '1') {
|
|
80
|
-
//未结束直接返回
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
renameSync(tmpFilePath, saveFilePath);
|
|
84
|
-
rmSync(path, { force: true, recursive: true });
|
|
85
|
-
uploadResult.size = statSync(saveFilePath, { throwIfNoEntry: true }).size;
|
|
86
|
-
uploadResult.path = relative(fileUpconfig.upDir + this.path, saveFilePath);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
uploadResult.name = params.name || filename;
|
|
90
|
-
uploadResult.mimeType = mimeType;
|
|
91
|
-
return uploadResult;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
1
|
+
import { UploadParam, UploadResult, UpStorageInterface } from '@/types/fileManage.js';
|
|
2
|
+
import { app } from '@meadmin/core';
|
|
3
|
+
import { UploadOptions, UploadStreamFileInfo } from '@midwayjs/busboy';
|
|
4
|
+
import { appendFileSync, createReadStream, createWriteStream, existsSync, mkdirSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
5
|
+
import { relative, resolve } from 'node:path';
|
|
6
|
+
import { BaseStorage } from './base.js';
|
|
7
|
+
export class LocalStorage extends BaseStorage implements UpStorageInterface {
|
|
8
|
+
get path() {
|
|
9
|
+
return '/' + this.model + '/';
|
|
10
|
+
}
|
|
11
|
+
//保存文件
|
|
12
|
+
async saveFie({ filename, data }: UploadStreamFileInfo, md5: string, savePath: string, tmpPath: string) {
|
|
13
|
+
const suffix = filename.substring(filename.lastIndexOf('.')); //后缀带着.
|
|
14
|
+
const tmpFilePath = resolve(tmpPath, '__tmp__' + process.pid + '__' + md5 + suffix); //临时文件带上进程id防止重复
|
|
15
|
+
const saveFilePath = resolve(savePath, md5 + suffix);
|
|
16
|
+
const saveStat = statSync(saveFilePath, { throwIfNoEntry: false });
|
|
17
|
+
if (saveStat) {
|
|
18
|
+
return { size: saveStat.size, path: saveFilePath }; //已存在无需上传
|
|
19
|
+
}
|
|
20
|
+
return await new Promise<{ size: number; path: string }>((reslove, reject) => {
|
|
21
|
+
const stream = createWriteStream(tmpFilePath);
|
|
22
|
+
stream.on('close', () => {
|
|
23
|
+
renameSync(tmpFilePath, saveFilePath);
|
|
24
|
+
reslove({ size: statSync(saveFilePath, { throwIfNoEntry: true }).size, path: saveFilePath });
|
|
25
|
+
});
|
|
26
|
+
stream.on('error', (e) => {
|
|
27
|
+
reject(e);
|
|
28
|
+
});
|
|
29
|
+
data.pipe(stream);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async getFilePath(path: string) {
|
|
33
|
+
return resolve((app.getConfig('busboy') as UploadOptions).upDir + this.path, path);
|
|
34
|
+
}
|
|
35
|
+
async getFileReadSteam(path: string) {
|
|
36
|
+
return createReadStream(await this.getFilePath(path));
|
|
37
|
+
}
|
|
38
|
+
async upload(file: UploadStreamFileInfo, params: UploadParam) {
|
|
39
|
+
const fileUpconfig = app.getConfig('busboy') as UploadOptions;
|
|
40
|
+
const uploadResult = { storage: 'local' } as UploadResult;
|
|
41
|
+
uploadResult.md5 = params.md5;
|
|
42
|
+
//只处理1个文件上传
|
|
43
|
+
const { filename, mimeType } = file;
|
|
44
|
+
if (params.chunk !== '1') {
|
|
45
|
+
//非分片上传
|
|
46
|
+
const res = await this.saveFie(file, params.md5, fileUpconfig.upDir + this.path, fileUpconfig.tmpdir);
|
|
47
|
+
uploadResult.size = res.size;
|
|
48
|
+
uploadResult.path = relative(fileUpconfig.upDir + this.path, res.path);
|
|
49
|
+
} else {
|
|
50
|
+
//分片上传
|
|
51
|
+
const suffix = filename.substring(filename.lastIndexOf('.')); //后缀带着.
|
|
52
|
+
const saveFilePath = resolve(fileUpconfig.upDir + this.path, params.md5 + suffix);
|
|
53
|
+
const saveStat = statSync(saveFilePath, { throwIfNoEntry: false });
|
|
54
|
+
if (saveStat) {
|
|
55
|
+
//已存在无需上传
|
|
56
|
+
uploadResult.size = saveStat.size;
|
|
57
|
+
uploadResult.path = relative(fileUpconfig.upDir + this.path, saveFilePath);
|
|
58
|
+
} else {
|
|
59
|
+
if (params.start === undefined || params.over === undefined || params.chunkMd5 === undefined) {
|
|
60
|
+
throw new Error('分片上传时,start、over、chunkMd5必须有值');
|
|
61
|
+
}
|
|
62
|
+
const path = resolve(fileUpconfig.tmpdir, params.md5 + '/');
|
|
63
|
+
if (!existsSync(path)) {
|
|
64
|
+
mkdirSync(path);
|
|
65
|
+
}
|
|
66
|
+
const res = await this.saveFie(file, params.chunkMd5, path, path);
|
|
67
|
+
const tmpFilePath = resolve(fileUpconfig.tmpdir, '__tmp__chunk_all__' + process.pid + '__' + params.md5 + suffix); //临时文件带上进程id防止重复
|
|
68
|
+
appendFileSync(tmpFilePath, ''); //追加空串确保文件存在
|
|
69
|
+
await new Promise<void>((resolve, reject) => {
|
|
70
|
+
const fsStream = createWriteStream(tmpFilePath, { flags: 'r+', start: +params.start, autoClose: true });
|
|
71
|
+
fsStream.on('close', () => {
|
|
72
|
+
resolve();
|
|
73
|
+
});
|
|
74
|
+
fsStream.on('error', (e) => {
|
|
75
|
+
reject(e);
|
|
76
|
+
});
|
|
77
|
+
createReadStream(res.path).pipe(fsStream);
|
|
78
|
+
});
|
|
79
|
+
if (params.over !== '1') {
|
|
80
|
+
//未结束直接返回
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
renameSync(tmpFilePath, saveFilePath);
|
|
84
|
+
rmSync(path, { force: true, recursive: true });
|
|
85
|
+
uploadResult.size = statSync(saveFilePath, { throwIfNoEntry: true }).size;
|
|
86
|
+
uploadResult.path = relative(fileUpconfig.upDir + this.path, saveFilePath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
uploadResult.name = params.name || filename;
|
|
90
|
+
uploadResult.mimeType = mimeType;
|
|
91
|
+
return uploadResult;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<el-button v-if="vnodeProps?.onRefresh" @click="$emit('refresh')">
|
|
10
10
|
<mel-icon-refresh />
|
|
11
11
|
</el-button>
|
|
12
|
-
<el-button v-if="
|
|
12
|
+
<el-button v-if="onAdd" type="primary" @click="onAdd()">
|
|
13
13
|
<mel-icon-plus />
|
|
14
14
|
</el-button>
|
|
15
15
|
<slot name="buttons"></slot>
|
|
@@ -126,11 +126,11 @@ const props = {
|
|
|
126
126
|
type: Boolean,
|
|
127
127
|
default: true,
|
|
128
128
|
},
|
|
129
|
+
onAdd: Function as PropType<() => void>,
|
|
129
130
|
};
|
|
130
131
|
const emits = ['quickSearch', 'refresh', 'add', 'update:quickSearch'] as unknown as {
|
|
131
132
|
quickSearch: (searchText: string) => void;
|
|
132
133
|
refresh: () => void;
|
|
133
|
-
add: () => void;
|
|
134
134
|
['update:quickSearch']: (searchText: string) => void;
|
|
135
135
|
} & Required<VxeTableListeners>;
|
|
136
136
|
export default defineComponent({
|
|
@@ -11,17 +11,12 @@
|
|
|
11
11
|
{{ $t('首页') }}
|
|
12
12
|
</el-dropdown-item>
|
|
13
13
|
</router-link>
|
|
14
|
-
<a href="https://github.com/meadmin-cn/meadmin
|
|
14
|
+
<a target="_blank" href="https://github.com/meadmin-cn/meadmin">
|
|
15
15
|
<el-dropdown-item>
|
|
16
16
|
{{ $t('Github') }}
|
|
17
17
|
</el-dropdown-item>
|
|
18
18
|
</a>
|
|
19
|
-
<a href="https://
|
|
20
|
-
<el-dropdown-item>
|
|
21
|
-
{{ $t('Gitee') }}
|
|
22
|
-
</el-dropdown-item>
|
|
23
|
-
</a>
|
|
24
|
-
<a href="https://meadmin-cn.github.io/meadmin-template-doc/">
|
|
19
|
+
<a target="_blank" href="https://www.meadmin.cn/">
|
|
25
20
|
<el-dropdown-item>
|
|
26
21
|
{{ $t('文档') }}
|
|
27
22
|
</el-dropdown-item>
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import '@/styles/index.scss';
|
|
2
|
-
import nProgress from 'nprogress';
|
|
3
|
-
import { App } from 'vue';
|
|
4
|
-
import { initVxeTable } from './components/meVxeTable/install.js';
|
|
5
|
-
import { event, mitter } from './event';
|
|
6
|
-
import { installIcon } from './icons/index.js';
|
|
7
|
-
import { setupRouterGuard } from './router/guard/index.js';
|
|
8
|
-
import { installRoute } from './router/index.js';
|
|
9
|
-
import { installStore } from './store/index.js';
|
|
10
|
-
export const ssrVersionKey = Symbol('ssrVersionKey');
|
|
11
|
-
export async function bootscrapt(app: App, ssrVersion: string = '') {
|
|
12
|
-
app.config.globalProperties.$start = true;
|
|
13
|
-
if (import.meta.env.SSR) {
|
|
14
|
-
app.config.globalProperties.$ssrVersion = ssrVersion;
|
|
15
|
-
} else {
|
|
16
|
-
app.config.globalProperties.$ssrVersion = window.__ssrVersion ?? '';
|
|
17
|
-
}
|
|
18
|
-
app.provide(ssrVersionKey, app.config.globalProperties.$ssrVersion);
|
|
19
|
-
const router = installRoute();
|
|
20
|
-
app.config.globalProperties.$router = router;
|
|
21
|
-
const store = await installStore(app);
|
|
22
|
-
app.use(router);
|
|
23
|
-
setupRouterGuard(router, store);
|
|
24
|
-
initVxeTable(app);
|
|
25
|
-
installIcon(app);
|
|
26
|
-
if (!import.meta.env.SSR) {
|
|
27
|
-
window.addEventListener('resize', () => mitter.emit(event.RESIZE));
|
|
28
|
-
// 进度条配置
|
|
29
|
-
nProgress.configure({
|
|
30
|
-
showSpinner: false,
|
|
31
|
-
});
|
|
32
|
-
if (window.__pinia) {
|
|
33
|
-
store.state.value = window.__pinia;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
await Promise.allSettled(mitter.emit(event.START, app));
|
|
37
|
-
mitter.emit(event.READY, app);
|
|
38
|
-
return { router, store, app };
|
|
39
|
-
}
|
|
1
|
+
import '@/styles/index.scss';
|
|
2
|
+
import nProgress from 'nprogress';
|
|
3
|
+
import { App } from 'vue';
|
|
4
|
+
import { initVxeTable } from './components/meVxeTable/install.js';
|
|
5
|
+
import { event, mitter } from './event';
|
|
6
|
+
import { installIcon } from './icons/index.js';
|
|
7
|
+
import { setupRouterGuard } from './router/guard/index.js';
|
|
8
|
+
import { installRoute } from './router/index.js';
|
|
9
|
+
import { installStore } from './store/index.js';
|
|
10
|
+
export const ssrVersionKey = Symbol('ssrVersionKey');
|
|
11
|
+
export async function bootscrapt(app: App, ssrVersion: string = '') {
|
|
12
|
+
app.config.globalProperties.$start = true;
|
|
13
|
+
if (import.meta.env.SSR) {
|
|
14
|
+
app.config.globalProperties.$ssrVersion = ssrVersion;
|
|
15
|
+
} else {
|
|
16
|
+
app.config.globalProperties.$ssrVersion = window.__ssrVersion ?? '';
|
|
17
|
+
}
|
|
18
|
+
app.provide(ssrVersionKey, app.config.globalProperties.$ssrVersion);
|
|
19
|
+
const router = installRoute();
|
|
20
|
+
app.config.globalProperties.$router = router;
|
|
21
|
+
const store = await installStore(app);
|
|
22
|
+
app.use(router);
|
|
23
|
+
setupRouterGuard(router, store);
|
|
24
|
+
initVxeTable(app);
|
|
25
|
+
installIcon(app);
|
|
26
|
+
if (!import.meta.env.SSR) {
|
|
27
|
+
window.addEventListener('resize', () => mitter.emit(event.RESIZE));
|
|
28
|
+
// 进度条配置
|
|
29
|
+
nProgress.configure({
|
|
30
|
+
showSpinner: false,
|
|
31
|
+
});
|
|
32
|
+
if (window.__pinia) {
|
|
33
|
+
store.state.value = window.__pinia;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
await Promise.allSettled(mitter.emit(event.START, app));
|
|
37
|
+
mitter.emit(event.READY, app);
|
|
38
|
+
return { router, store, app };
|
|
39
|
+
}
|
|
@@ -34,7 +34,9 @@ export default defineComponent({
|
|
|
34
34
|
async () => {
|
|
35
35
|
if (elDialogRef.value?.dialogContentRef && props.full) {
|
|
36
36
|
await nextTick();
|
|
37
|
-
|
|
37
|
+
if (!import.meta.env.SSR) {
|
|
38
|
+
resetWH = minMax(elDialogRef.value!.dialogContentRef.$el)?.resetWH;
|
|
39
|
+
}
|
|
38
40
|
}
|
|
39
41
|
},
|
|
40
42
|
{ immediate: true },
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<el-button v-if="vnodeProps?.onRefresh" @click="$emit('refresh')">
|
|
10
10
|
<mel-icon-refresh />
|
|
11
11
|
</el-button>
|
|
12
|
-
<el-button v-if="
|
|
12
|
+
<el-button v-if="onAdd" type="primary" @click="onAdd()">
|
|
13
13
|
<mel-icon-plus />
|
|
14
14
|
</el-button>
|
|
15
15
|
<slot name="buttons"></slot>
|
|
@@ -125,11 +125,11 @@ const props = {
|
|
|
125
125
|
type: Boolean,
|
|
126
126
|
default: true,
|
|
127
127
|
},
|
|
128
|
+
onAdd: Function as PropType<() => void>,
|
|
128
129
|
};
|
|
129
130
|
const emits = ['quickSearch', 'refresh', 'add', 'update:quickSearch'] as unknown as {
|
|
130
131
|
quickSearch: (searchText: string) => void;
|
|
131
132
|
refresh: () => void;
|
|
132
|
-
add: () => void;
|
|
133
133
|
['update:quickSearch']: (searchText: string) => void;
|
|
134
134
|
} & Required<VxeTableListeners>;
|
|
135
135
|
export default defineComponent({
|