Commit 9b4975b4 authored by 徐光星's avatar 徐光星

Merge branch 'feat/history' into 'master'

Feat/history

See merge request !96
parents c8f6f803 1a6accd9
import { Controller, Context } from 'egg'; import { Controller, Context } from 'egg';
import _, { omitBy } from 'lodash'; import _, { omitBy } from 'lodash';
import { v1 as uuidv1 } from 'uuid'; import { v1 as uuidv1 } from 'uuid';
import { PageHistory } from '../model/pageHistory';
export default class EditorController extends Controller { export default class EditorController extends Controller {
public async login(ctx: Context) { public async login(ctx: Context) {
...@@ -16,9 +17,11 @@ export default class EditorController extends Controller { ...@@ -16,9 +17,11 @@ export default class EditorController extends Controller {
const pageInfo = ctx.request.body; const pageInfo = ctx.request.body;
try { try {
const result = await ctx.model.PageInfo.create({ ...pageInfo, uuid: uuidv1().replace(/-/g, ''), tenantId: ctx.headers['qg-tenant-id']}); const result = await ctx.model.PageInfo.create({ ...pageInfo, uuid: uuidv1().replace(/-/g, ''), tenantId: ctx.headers['qg-tenant-id']});
const { uuid, id } = result.dataValues;
await ctx.model.PageHistory.create({ ...pageInfo, uuid, pageId: id, tenantId: ctx.headers['qg-tenant-id']});
ctx.body = ctx.helper.ok(result); ctx.body = ctx.helper.ok(result);
await ctx.service.redis.del(`pageInfo:${result.dataValues.uuid}`); await ctx.service.redis.del(`pageInfo:${uuid}`);
await ctx.service.redis.del(`page:${result.dataValues.uuid}`); await ctx.service.redis.del(`page:${uuid}`);
} catch (error) { } catch (error) {
if (error.message.indexOf('Data too long') > -1 && error.message.indexOf('page_data') > -1) { if (error.message.indexOf('Data too long') > -1 && error.message.indexOf('page_data') > -1) {
error.message = '配置组件过多,请对组件进行删减'; error.message = '配置组件过多,请对组件进行删减';
...@@ -35,10 +38,14 @@ export default class EditorController extends Controller { ...@@ -35,10 +38,14 @@ export default class EditorController extends Controller {
if (ctx.headers['qg-tenant-id']) { if (ctx.headers['qg-tenant-id']) {
pageInfo.tenantId = +ctx.headers['qg-tenant-id']; pageInfo.tenantId = +ctx.headers['qg-tenant-id'];
} }
const uuid = pageInfo.uuid;
try { try {
const result = await ctx.model.PageInfo.update(pageInfo, {where: { uuid: pageInfo.uuid, tenantId: ctx.headers['qg-tenant-id'] }}); const result = await ctx.model.PageInfo.update(pageInfo, {where: { uuid: pageInfo.uuid, tenantId: ctx.headers['qg-tenant-id'] }});
await ctx.service.redis.del(`pageInfo:${pageInfo.uuid}`); const pageData = await ctx.model.PageInfo.findOne({where: { uuid }});
await ctx.service.redis.del(`page:${pageInfo.uuid}`); const pageId = pageData.id;
await ctx.model.PageHistory.create({ ...pageInfo, uuid, pageId, tenantId: ctx.headers['qg-tenant-id']});
await ctx.service.redis.del(`pageInfo:${uuid}`);
await ctx.service.redis.del(`page:${uuid}`);
ctx.body = ctx.helper.ok(result); ctx.body = ctx.helper.ok(result);
} catch (error) { } catch (error) {
if (error.message.indexOf('Data too long') > -1 && error.message.indexOf('page_data') > -1) { if (error.message.indexOf('Data too long') > -1 && error.message.indexOf('page_data') > -1) {
...@@ -51,10 +58,9 @@ export default class EditorController extends Controller { ...@@ -51,10 +58,9 @@ export default class EditorController extends Controller {
public async get(ctx: Context) { public async get(ctx: Context) {
const { isEditor } = ctx.query; const { isEditor } = ctx.query;
let pageInfo = isEditor ? null : await ctx.service.redis.get(`pageInfo:${ctx.params.uuid}`); let pageInfo = isEditor ? null : await ctx.service.redis.get(`pageInfo:${ctx.params.uuid}`);
console.log('redis', pageInfo);
if (!pageInfo) { if (!pageInfo) {
pageInfo = await ctx.model.PageInfo.findOne({where: { uuid: ctx.params.uuid }}); pageInfo = await ctx.model.PageInfo.findOne({where: { uuid: ctx.params.uuid }});
console.log('sql', pageInfo); // console.log('sql', pageInfo);
if (ctx.query.lite && pageInfo) { if (ctx.query.lite && pageInfo) {
// 如果存在sheme移除掉 // 如果存在sheme移除掉
...@@ -92,7 +98,10 @@ export default class EditorController extends Controller { ...@@ -92,7 +98,10 @@ export default class EditorController extends Controller {
where = { ...where, isTemplate: 1 }; where = { ...where, isTemplate: 1 };
} }
const { count: total, rows: data } = await ctx.model.PageInfo.findAndCountAll({ where, limit: +pageSize || 10, const { count: total, rows: data } = await ctx.model.PageInfo.findAndCountAll({ where, limit: +pageSize || 10,
offset: (+pageNo - 1) * +pageSize || 0, order: [['updated_at', 'DESC']] }); offset: (+pageNo - 1) * +pageSize || 0, order: [['updated_at', 'DESC']], include: [PageHistory.scope('relative')]});
data.map(item => {
item.pageHistory && item.pageHistory.reverse();
})
ctx.body = ctx.helper.ok({ total, data }); ctx.body = ctx.helper.ok({ total, data });
} }
...@@ -125,9 +134,60 @@ export default class EditorController extends Controller { ...@@ -125,9 +134,60 @@ export default class EditorController extends Controller {
public async delete(ctx: Context) { public async delete(ctx: Context) {
const pageInfo = await ctx.model.PageInfo.update({ enable: 0 }, {where: { id: +ctx.params.pageId, tenantId: ctx.headers['qg-tenant-id'] }}); const pageInfo = await ctx.model.PageInfo.update({ enable: 0 }, {where: { id: +ctx.params.pageId, tenantId: ctx.headers['qg-tenant-id'] }});
const pageData = await ctx.model.PageInfo.find({where: { id: +ctx.params.pageId, tenantId: ctx.headers['qg-tenant-id'] }});
await ctx.model.PageHistory.destroy({where: { pageId: pageData.dataValues.id }});
ctx.body = ctx.helper.ok(pageInfo); ctx.body = ctx.helper.ok(pageInfo);
} }
public async getServerTime(ctx: Context) { public async getServerTime(ctx: Context) {
ctx.body = ctx.helper.ok(new Date().getTime()); ctx.body = ctx.helper.ok(new Date().getTime());
} }
// 恢复历史记录
public async recoverPageData(ctx: Context) {
const params = ctx.request.body;
const id = params.id;
const author = params.author;
console.log(params)
if (!id) {
ctx.body = {};
return;
}
const pageData = await ctx.model.PageHistory.findOne({where: {id: +id, tenantId: ctx.headers['qg-tenant-id']}})
const data = Object.assign({}, pageData.dataValues);
// 记录当前操作人
data.author = author;
delete data.updatedAt;
delete data.createdAt;
// 更新主表行数据
const uuid = pageData.uuid;
const updateData = Object.assign({}, data);
delete updateData.uuid;
delete updateData.pageId;
delete updateData.id;
await ctx.model.PageInfo.update(updateData, {where: { uuid: uuid, tenantId: ctx.headers['qg-tenant-id'] }});
await ctx.service.redis.del(`pageInfo:${uuid}`);
await ctx.service.redis.del(`page:${uuid}`);
// 创建新历史记录
const historyData = Object.assign({}, data);
delete historyData.id;
await ctx.model.PageHistory.create(historyData);
ctx.body = ctx.helper.ok({});
}
// 获取历史记录页面预览信息
public async getHistoryPreviewData(ctx: Context) {
const pageInfo = await ctx.model.PageHistory.findOne({where: { id: ctx.params.id }});
// console.log('sql', pageInfo);
if (ctx.query.lite && pageInfo) {
// 如果存在sheme移除掉
const page = JSON.parse(pageInfo.page || []);
for (let i = 0; i < page.elements.length; i++) {
delete page.elements[i].schame
}
if (page.scheme) {
delete page.scheme;
}
pageInfo.page = JSON.stringify(page);
}
ctx.body = ctx.helper.ok(pageInfo);
}
} }
\ No newline at end of file
/**
* @desc 页面历史记录表
*/
import { AutoIncrement, Column, DataType, Model, PrimaryKey, Table, AllowNull, ForeignKey, BelongsTo, DefaultScope, Scopes } from 'sequelize-typescript';
import { PageInfo } from './pageInfo';
@Scopes({
relative: {
attributes: ['id', 'author', 'updatedAt', 'createdAt']
}
})
@Table({
modelName: 'page_history',
freezeTableName: true
})
export class PageHistory extends Model<PageHistory> {
@PrimaryKey
@AutoIncrement
@Column({
type: DataType.INTEGER(11)
})
id: number;
@Column({
field: 'page_data',
type: DataType.TEXT
})
page: string;
@Column({
field: 'page_name',
type: DataType.STRING(32)
})
pageName: string;
@Column({
field: 'page_describe',
type: DataType.STRING(255)
})
pageDescribe: string;
@Column({
field: 'page_keywords',
type: DataType.STRING(255)
})
pageKeywords: string;
@Column({
field: 'uuid',
type: DataType.UUID
})
uuid: string;
// @BelongsTo(() => PageInfo)
// pageInfo: PageInfo;
@Column({
field: 'redirectUrl',
type: DataType.STRING(255),
get() {
const redirectUrl = this.getDataValue('redirectUrl');
return !redirectUrl ? '' : redirectUrl;
},
})
redirectUrl: string;
@Column({
field: 'validEndTime',
type: DataType.STRING(32),
get() {
const validEndTime = this.getDataValue('validEndTime');
return !validEndTime ? '' : validEndTime;
},
})
validEndTime: string;
@Column({
field: 'validStartTime',
type: DataType.STRING(32),
get() {
const validStartTime = this.getDataValue('validStartTime');
return !validStartTime ? '' : validStartTime;
},
})
validStartTime: string;
@Column({
type: DataType.INTEGER(1)
})
enable: number;
@AllowNull(false)
@Column({
type: DataType.STRING(32)
})
author: string;
@Column({
type: DataType.STRING(32),
field: 'cover_image'
})
coverImage: string;
@Column({
type: DataType.INTEGER(1),
field: 'is_template'
})
isTemplate: number;
@Column({
type: DataType.INTEGER(1),
field: 'is_publish'
})
isPublish: number;
@Column({
type: DataType.DATE,
field: 'updated_at',
get() {
const moment = require('moment');
const updatedAt = this.getDataValue('updatedAt');
return moment(updatedAt).utc().zone(-8).format('YYYY-MM-DD HH:mm:ss');
},
})
updatedAt: string;
@Column({
type: DataType.DATE,
field: 'created_at',
get() {
const moment = require('moment');
const createdAt = this.getDataValue('createdAt');
return moment(createdAt).format('YYYY-MM-DD HH:mm:ss');
},
})
createdAt: string;
@ForeignKey(() => PageInfo)
@Column({
field: 'page_id',
type: DataType.INTEGER(11)
})
pageId: number;
@Column({
type: DataType.INTEGER(11),
field: 'tenant_id',
})
tenantId: number;
}
export default () => PageHistory;
\ No newline at end of file
/** /**
* @desc 页面信息表 * @desc 页面信息表
*/ */
import { AutoIncrement, Column, DataType, Model, PrimaryKey, Table, AllowNull } from 'sequelize-typescript'; import { AutoIncrement, Column, DataType, Model, PrimaryKey, Table, AllowNull, HasMany } from 'sequelize-typescript';
import { PageHistory } from './pageHistory';
@Table({ @Table({
modelName: 'page_config_info', modelName: 'page_config_info',
freezeTableName: true freezeTableName: true
...@@ -122,6 +122,10 @@ export class PageInfo extends Model<PageInfo> { ...@@ -122,6 +122,10 @@ export class PageInfo extends Model<PageInfo> {
field: 'tenant_id', field: 'tenant_id',
}) })
tenantId: number; tenantId: number;
@HasMany(() => PageHistory)
pageHistory: PageHistory[];
} }
export default () => PageInfo; export default () => PageInfo;
\ No newline at end of file
...@@ -16,6 +16,8 @@ export default (application: Application) => { ...@@ -16,6 +16,8 @@ export default (application: Application) => {
router.get('/editor/get/template', controller.editor.getTemplateList); router.get('/editor/get/template', controller.editor.getTemplateList);
router.get('/editor/getServerTime', controller.editor.getServerTime); router.get('/editor/getServerTime', controller.editor.getServerTime);
router.get('/editor/get/:uuid', controller.editor.get); router.get('/editor/get/:uuid', controller.editor.get);
router.get('/editor/getHistoryPage/:id', controller.editor.getHistoryPreviewData);
router.post('/editor/recoverPageData', controller.editor.recoverPageData);
router.delete('/editor/:pageId', controller.editor.delete); router.delete('/editor/:pageId', controller.editor.delete);
router.get('/editor/login', controller.editor.login); router.get('/editor/login', controller.editor.login);
router.get('/editor', controller.editor.home); router.get('/editor', controller.editor.home);
......
import { Subscription } from 'egg';
class clearInvalidPageHistoryRecords extends Subscription {
// 通过 schedule 属性来设置定时任务的执行间隔等配置
static get schedule() {
return {
cronOptions: {
tz: 'Asia/Shanghai'
},
immediate: false,
cron: '0 0 2 L * *', // 每月的最后一天凌晨2点整执行
type: 'all', // 指定所有的 worker 都需要执行
};
}
async subscribe() {
this.ctx.service.records.clearInvalidPageHistoryRecords();
}
}
module.exports = clearInvalidPageHistoryRecords;
\ No newline at end of file
...@@ -46,7 +46,7 @@ function deleteUrlParams(url, ref) { ...@@ -46,7 +46,7 @@ function deleteUrlParams(url, ref) {
} }
} }
export default class ArticeService extends Service { export default class NavService extends Service {
private context: Context; private context: Context;
constructor(ctx: Context) { constructor(ctx: Context) {
super(ctx); super(ctx);
......
import { Context, Service } from 'egg';
export default class RecordsService extends Service {
private context: Context;
constructor(ctx: Context) {
super(ctx);
this.context = ctx;
}
async clearInvalidPageHistoryRecords() {
// 操作记录表直接删除一年以上的操作记录
const limitDate = new Date(new Date().getTime() - 365 * 24 * 3600 * 1000).toLocaleDateString().replace(/\//g, '-')
try {
this.context.logger.info(`开始删除页面历史记录,删除范围为日期在${limitDate}之前的所有记录`);
await this.context.model.PageHistory.destroy({
where: {
updated_at: {
$lte: new Date(limitDate)
}
}
});
this.context.logger.info(`页面历史记录删除成功,删除范围为日期在${limitDate}之前的所有记录`);
} catch (err) {
this.context.logger.info(`页面历史记录删除失败,失败原因: ${JSON.stringify(err)}`);
}
return;
}
}
...@@ -24,6 +24,9 @@ export default { ...@@ -24,6 +24,9 @@ export default {
getTemplateList() { getTemplateList() {
return http.get('editor/get/template'); return http.get('editor/get/template');
}, },
recoverPageData(params) {
return http.post(`editor/recoverPageData`, params);
},
getUpToken() { getUpToken() {
return axios.get(`${config.opapiHost}/upload/getToken`); return axios.get(`${config.opapiHost}/upload/getToken`);
}, },
......
<template>
<Modal width="700" v-model="show" title="历史记录" @on-visible-change="change">
<Table
:columns="columns"
:data="records"
class="tableStyle"
/>
<span slot="footer"></span>
</Modal>
</template>
<script>
import editorApi from '@api/editor.api';
import config from '@/config/index';
export default {
props: {
value: {
type: Boolean,
default: false
},
records: {
type: Array,
default() {
return []
}
}
},
watch: {
value(val) {
this.show = val;
}
},
data() {
return {
show: false,
columns: [
{
title: '版本',
align: 'center',
render: (h, params) => {
console.log(params);
return h('span', {
style: {
color: '#2d8cf0',
textDecoration: 'underline',
cursor: 'pointer'
},
on: {
'click': () => {
window.open(`${config.h5Host}/history/${params.row.id}?isPreview=1`)
},
},
}, `v${this.records.length - params.index}`)
}
},
{
title: '更新时间',
key: 'updatedAt',
align: 'center'
},
{
key: 'author',
title: '操作人',
align: 'center'
},
{
title: '操作',
align: 'center',
render: (h, params) => {
const props = {
type: 'primary',
};
const style = {
display: 'inline-block',
margin: '5px',
};
return h(
'Poptip',
{
props: {
confirm: true,
transfer: true,
title: '确认恢复到此版本?',
},
style: {
...style,
},
on: {
'on-ok': async () => {
await editorApi.recoverPageData({
id: params.row.id,
author: JSON.parse(localStorage.getItem('user')).account || ''
});
window.location.reload();
},
},
},
[
h(
'Button',
{
...props
},
'恢复'
),
]
)
}
}
]
}
},
created() {
this.show = this.value;
console.log(this.records, 123)
},
methods: {
change(val) {
if (!val) {
this.close();
}
},
close() {
this.$emit('close');
}
}
}
</script>
\ No newline at end of file
...@@ -82,15 +82,16 @@ ...@@ -82,15 +82,16 @@
/> />
</template> </template>
</div> </div>
<recordsModal v-model="showRecordsModal" :records="records" @close="showRecordsModal = false" />
</div> </div>
</template> </template>
<script> <script>
import Treeselect from '@riophae/vue-treeselect'; import Treeselect from '@riophae/vue-treeselect';
import '@riophae/vue-treeselect/dist/vue-treeselect.css'; import '@riophae/vue-treeselect/dist/vue-treeselect.css';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import recordsModal from './components/records.vue'
export default { export default {
components: { Treeselect }, components: { Treeselect, recordsModal },
props: { props: {
columns: { columns: {
type: Array, type: Array,
...@@ -114,7 +115,9 @@ export default { ...@@ -114,7 +115,9 @@ export default {
}, },
showSelected: false, // todo showSelected: false, // todo
searchCondition: [], searchCondition: [],
selectedTab: '1' selectedTab: '1',
records: [],
showRecordsModal: false
}; };
}, },
watch: { watch: {
...@@ -133,6 +136,11 @@ export default { ...@@ -133,6 +136,11 @@ export default {
} }
}, },
methods: { methods: {
showRecords(data) {
// 展示历史记录
this.records = data || [];
this.showRecordsModal = true;
},
changePageNo(page) { changePageNo(page) {
this.query(page); this.query(page);
this.$emit('on-page-change', page); this.$emit('on-page-change', page);
......
...@@ -122,7 +122,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -122,7 +122,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
// 清除新增数据时的缓存 // 清除新增数据时的缓存
if (!isCreate) { this.removeDefaultCache(); } if (!isCreate) { this.removeDefaultCache(); }
if (type === 'preview') { if (type === 'preview') {
window.open(`${config.h5Host}/activity/${this.uuid}?tenantId=${this.pageInfo.tenantId}`); window.open(`${config.h5Host}/activity/${this.uuid}?tenantId=${this.pageInfo.tenantId}&isPreview=1`);
} else { } else {
this.$Notice.success({ title: '保存成功!' }); this.$Notice.success({ title: '保存成功!' });
} }
......
...@@ -123,7 +123,7 @@ export default { ...@@ -123,7 +123,7 @@ export default {
}, },
on: { on: {
click: () => { click: () => {
window.open(`${config.h5Host}/activity/${params.row.uuid}?tenantId=${params.row.tenantId}`); window.open(`${config.h5Host}/activity/${params.row.uuid}?tenantId=${params.row.tenantId}&isPreview=1`);
}, },
}, },
}, },
...@@ -145,6 +145,22 @@ export default { ...@@ -145,6 +145,22 @@ export default {
}, },
'修改' '修改'
), ),
h(
'Button',
{
props,
style: {
...style,
display: params.row.pageHistory && params.row.pageHistory.length ? 'inline-block' : 'none'
},
on: {
click: () => {
this.$refs.qgTable.showRecords(params.row.pageHistory);
},
},
},
'历史记录'
),
h( h(
'Poptip', 'Poptip',
{ {
......
...@@ -64,6 +64,27 @@ export interface PageInfo { ...@@ -64,6 +64,27 @@ export interface PageInfo {
validEndTime?: string; validEndTime?: string;
} }
export interface PageHistory {
id?: number;
page?: Page | string;
enable?: number;
author?: string;
coverImage?: string;
shareCoverImage?: string; // 分享缩略图
shareOpenMethod?: string; // 分享后的打开方式
isTemplate?: number | boolean;
isPublish?: number | boolean;
pageName?: string;
pageDescribe?: string;
pageKeywords?: string;
uuid?: string;
tenantId?: number;
diversion?: string;
redirectUrl?: string;
validStartTime?: string;
validEndTime?: string;
}
export default interface EditorState { export default interface EditorState {
pageInfo: PageInfo; pageInfo: PageInfo;
curEleIndex: number | null; curEleIndex: number | null;
......
...@@ -2176,9 +2176,9 @@ ...@@ -2176,9 +2176,9 @@
} }
}, },
"@qg/citrus-ui": { "@qg/citrus-ui": {
"version": "0.3.57-beta1", "version": "0.3.59",
"resolved": "http://npmprivate.quantgroups.com/@qg%2fcitrus-ui/-/citrus-ui-0.3.57-beta1.tgz", "resolved": "http://npmprivate.quantgroups.com/@qg%2fcitrus-ui/-/citrus-ui-0.3.59.tgz",
"integrity": "sha512-LwDWiKca4AwcjC7YQlfB0gkOA1BwXS0OlLiDtvH6MUlcyRitIBReHjQV3S36LlSH6XfgMNoLUGi3FgMsX3SLrA==", "integrity": "sha512-+uMf1BPpyxgjKSkb2S2WShtU8+thLLfi3V7Zo/H5F/XWzNCKTCwTCb/Z1UmRrtHlITVEbr0tYYQ822On5eQX3g==",
"requires": { "requires": {
"@better-scroll/core": "^2.1.1", "@better-scroll/core": "^2.1.1",
"@qg/cherry-ui": "^2.23.9", "@qg/cherry-ui": "^2.23.9",
......
...@@ -3,12 +3,14 @@ ...@@ -3,12 +3,14 @@
import 'egg'; import 'egg';
import ExportNavigatorConfig from '../../../app/model/navigatorConfig'; import ExportNavigatorConfig from '../../../app/model/navigatorConfig';
import ExportPageHistory from '../../../app/model/pageHistory';
import ExportPageInfo from '../../../app/model/pageInfo'; import ExportPageInfo from '../../../app/model/pageInfo';
import ExportTenantAuth from '../../../app/model/tenantAuth'; import ExportTenantAuth from '../../../app/model/tenantAuth';
declare module 'egg' { declare module 'egg' {
interface IModel { interface IModel {
NavigatorConfig: ReturnType<typeof ExportNavigatorConfig>; NavigatorConfig: ReturnType<typeof ExportNavigatorConfig>;
PageHistory: ReturnType<typeof ExportPageHistory>;
PageInfo: ReturnType<typeof ExportPageInfo>; PageInfo: ReturnType<typeof ExportPageInfo>;
TenantAuth: ReturnType<typeof ExportTenantAuth>; TenantAuth: ReturnType<typeof ExportTenantAuth>;
} }
......
...@@ -7,11 +7,13 @@ type AnyFunc<T = any> = (...args: any[]) => T; ...@@ -7,11 +7,13 @@ type AnyFunc<T = any> = (...args: any[]) => T;
type CanExportFunc = AnyFunc<Promise<any>> | AnyFunc<IterableIterator<any>>; type CanExportFunc = AnyFunc<Promise<any>> | AnyFunc<IterableIterator<any>>;
type AutoInstanceType<T, U = T extends CanExportFunc ? T : T extends AnyFunc ? ReturnType<T> : T> = U extends AnyClass ? InstanceType<U> : U; type AutoInstanceType<T, U = T extends CanExportFunc ? T : T extends AnyFunc ? ReturnType<T> : T> = U extends AnyClass ? InstanceType<U> : U;
import ExportNavigator from '../../../app/service/navigator'; import ExportNavigator from '../../../app/service/navigator';
import ExportRecords from '../../../app/service/records';
import ExportRedis from '../../../app/service/redis'; import ExportRedis from '../../../app/service/redis';
declare module 'egg' { declare module 'egg' {
interface IService { interface IService {
navigator: AutoInstanceType<typeof ExportNavigator>; navigator: AutoInstanceType<typeof ExportNavigator>;
records: AutoInstanceType<typeof ExportRecords>;
redis: AutoInstanceType<typeof ExportRedis>; redis: AutoInstanceType<typeof ExportRedis>;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment