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 _, { omitBy } from 'lodash';
import { v1 as uuidv1 } from 'uuid';
import { PageHistory } from '../model/pageHistory';
export default class EditorController extends Controller {
public async login(ctx: Context) {
......@@ -16,9 +17,11 @@ export default class EditorController extends Controller {
const pageInfo = ctx.request.body;
try {
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);
await ctx.service.redis.del(`pageInfo:${result.dataValues.uuid}`);
await ctx.service.redis.del(`page:${result.dataValues.uuid}`);
await ctx.service.redis.del(`pageInfo:${uuid}`);
await ctx.service.redis.del(`page:${uuid}`);
} catch (error) {
if (error.message.indexOf('Data too long') > -1 && error.message.indexOf('page_data') > -1) {
error.message = '配置组件过多,请对组件进行删减';
......@@ -35,10 +38,14 @@ export default class EditorController extends Controller {
if (ctx.headers['qg-tenant-id']) {
pageInfo.tenantId = +ctx.headers['qg-tenant-id'];
}
const uuid = pageInfo.uuid;
try {
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}`);
await ctx.service.redis.del(`page:${pageInfo.uuid}`);
const pageData = await ctx.model.PageInfo.findOne({where: { 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);
} catch (error) {
if (error.message.indexOf('Data too long') > -1 && error.message.indexOf('page_data') > -1) {
......@@ -51,10 +58,9 @@ export default class EditorController extends Controller {
public async get(ctx: Context) {
const { isEditor } = ctx.query;
let pageInfo = isEditor ? null : await ctx.service.redis.get(`pageInfo:${ctx.params.uuid}`);
console.log('redis', pageInfo);
if (!pageInfo) {
pageInfo = await ctx.model.PageInfo.findOne({where: { uuid: ctx.params.uuid }});
console.log('sql', pageInfo);
// console.log('sql', pageInfo);
if (ctx.query.lite && pageInfo) {
// 如果存在sheme移除掉
......@@ -92,7 +98,10 @@ export default class EditorController extends Controller {
where = { ...where, isTemplate: 1 };
}
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 });
}
......@@ -125,9 +134,60 @@ export default class EditorController extends Controller {
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 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);
}
public async getServerTime(ctx: Context) {
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 页面信息表
*/
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({
modelName: 'page_config_info',
freezeTableName: true
......@@ -122,6 +122,10 @@ export class PageInfo extends Model<PageInfo> {
field: 'tenant_id',
})
tenantId: number;
@HasMany(() => PageHistory)
pageHistory: PageHistory[];
}
export default () => PageInfo;
\ No newline at end of file
......@@ -16,6 +16,8 @@ export default (application: Application) => {
router.get('/editor/get/template', controller.editor.getTemplateList);
router.get('/editor/getServerTime', controller.editor.getServerTime);
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.get('/editor/login', controller.editor.login);
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) {
}
}
export default class ArticeService extends Service {
export default class NavService extends Service {
private context: Context;
constructor(ctx: Context) {
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 {
getTemplateList() {
return http.get('editor/get/template');
},
recoverPageData(params) {
return http.post(`editor/recoverPageData`, params);
},
getUpToken() {
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 @@
/>
</template>
</div>
<recordsModal v-model="showRecordsModal" :records="records" @close="showRecordsModal = false" />
</div>
</template>
<script>
import Treeselect from '@riophae/vue-treeselect';
import '@riophae/vue-treeselect/dist/vue-treeselect.css';
import { cloneDeep } from 'lodash';
import recordsModal from './components/records.vue'
export default {
components: { Treeselect },
components: { Treeselect, recordsModal },
props: {
columns: {
type: Array,
......@@ -114,7 +115,9 @@ export default {
},
showSelected: false, // todo
searchCondition: [],
selectedTab: '1'
selectedTab: '1',
records: [],
showRecordsModal: false
};
},
watch: {
......@@ -133,6 +136,11 @@ export default {
}
},
methods: {
showRecords(data) {
// 展示历史记录
this.records = data || [];
this.showRecordsModal = true;
},
changePageNo(page) {
this.query(page);
this.$emit('on-page-change', page);
......
......@@ -122,7 +122,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
// 清除新增数据时的缓存
if (!isCreate) { this.removeDefaultCache(); }
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 {
this.$Notice.success({ title: '保存成功!' });
}
......
......@@ -123,7 +123,7 @@ export default {
},
on: {
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 {
},
'修改'
),
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(
'Poptip',
{
......
......@@ -64,6 +64,27 @@ export interface PageInfo {
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 {
pageInfo: PageInfo;
curEleIndex: number | null;
......
......@@ -2176,9 +2176,9 @@
}
},
"@qg/citrus-ui": {
"version": "0.3.57-beta1",
"resolved": "http://npmprivate.quantgroups.com/@qg%2fcitrus-ui/-/citrus-ui-0.3.57-beta1.tgz",
"integrity": "sha512-LwDWiKca4AwcjC7YQlfB0gkOA1BwXS0OlLiDtvH6MUlcyRitIBReHjQV3S36LlSH6XfgMNoLUGi3FgMsX3SLrA==",
"version": "0.3.59",
"resolved": "http://npmprivate.quantgroups.com/@qg%2fcitrus-ui/-/citrus-ui-0.3.59.tgz",
"integrity": "sha512-+uMf1BPpyxgjKSkb2S2WShtU8+thLLfi3V7Zo/H5F/XWzNCKTCwTCb/Z1UmRrtHlITVEbr0tYYQ822On5eQX3g==",
"requires": {
"@better-scroll/core": "^2.1.1",
"@qg/cherry-ui": "^2.23.9",
......
......@@ -3,12 +3,14 @@
import 'egg';
import ExportNavigatorConfig from '../../../app/model/navigatorConfig';
import ExportPageHistory from '../../../app/model/pageHistory';
import ExportPageInfo from '../../../app/model/pageInfo';
import ExportTenantAuth from '../../../app/model/tenantAuth';
declare module 'egg' {
interface IModel {
NavigatorConfig: ReturnType<typeof ExportNavigatorConfig>;
PageHistory: ReturnType<typeof ExportPageHistory>;
PageInfo: ReturnType<typeof ExportPageInfo>;
TenantAuth: ReturnType<typeof ExportTenantAuth>;
}
......
......@@ -7,11 +7,13 @@ type AnyFunc<T = any> = (...args: any[]) => T;
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;
import ExportNavigator from '../../../app/service/navigator';
import ExportRecords from '../../../app/service/records';
import ExportRedis from '../../../app/service/redis';
declare module 'egg' {
interface IService {
navigator: AutoInstanceType<typeof ExportNavigator>;
records: AutoInstanceType<typeof ExportRecords>;
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