Commit 91ca76b1 authored by 徐光星's avatar 徐光星

Merge branch 'master' of git.quantgroup.cn:ui/quantum-blocks into feat/multiActivity

parents 676d4f0d d3661513
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';
import * as path from 'path';
const { api } = require(path.resolve('./config/apollo.json'));
export default class EditorController extends Controller { export default class EditorController extends Controller {
public async login(ctx: Context) { public async login(ctx: Context) {
...@@ -16,9 +19,11 @@ export default class EditorController extends Controller { ...@@ -16,9 +19,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 +40,14 @@ export default class EditorController extends Controller { ...@@ -35,10 +40,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 +60,9 @@ export default class EditorController extends Controller { ...@@ -51,10 +60,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移除掉
...@@ -73,13 +81,13 @@ export default class EditorController extends Controller { ...@@ -73,13 +81,13 @@ export default class EditorController extends Controller {
} }
public async getList(ctx: Context) { public async getList(ctx: Context) {
const { type, pageSize, pageNo, author, pageName, isPublish, pageDescribe } = ctx.query; const { type, pageSize, pageNo, author, pageName, isPublish, pageDescribe, uuid } = ctx.query;
const { like } = ctx.model.Sequelize.Op;
let where = omitBy({ let where = omitBy({
author: author && { like: `%${author}%`}, author: author && { like: `%${author}%`},
pageName: pageName && { like: `%${pageName}%`}, pageName: pageName && { like: `%${pageName}%`},
pageDescribe: pageDescribe && { like: `%${pageDescribe}%`}, pageDescribe: pageDescribe && { like: `%${pageDescribe}%`},
isPublish, isPublish,
uuid,
tenantId: ctx.headers['qg-tenant-id'], tenantId: ctx.headers['qg-tenant-id'],
enable: 1 enable: 1
}, v => !v); }, v => !v);
...@@ -90,8 +98,17 @@ export default class EditorController extends Controller { ...@@ -90,8 +98,17 @@ export default class EditorController extends Controller {
} else if (type === 'template') { } else if (type === 'template') {
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({
offset: (+pageNo - 1) * +pageSize || 0, order: [['updated_at', 'DESC']] }); where,
limit: +pageSize || 10,
offset: (+pageNo - 1) * +pageSize || 0,
order: [['updated_at', 'DESC']],
attributes: ['id', 'uuid', 'author', 'updated_at', 'isTemplate', 'coverImage', 'pageDescribe', 'pageName', 'tenantId'],
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 });
} }
...@@ -124,6 +141,100 @@ export default class EditorController extends Controller { ...@@ -124,6 +141,100 @@ 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);
}
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;
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); ctx.body = ctx.helper.ok(pageInfo);
} }
public async getListForActivityCalendar(ctx: Context) {
let { exposureTime } = ctx.request.body;
exposureTime = exposureTime.replace(/-/g, '/').split(' ')[0]
console.log(exposureTime);
// let where:any = omitBy({
// // validStartTime: {
// // [ctx.model.Sequelize.Op.lte]: exposureTime
// // },
// // validEndTime: {
// // [ctx.model.Sequelize.Op.gte]: exposureTime
// // },
// enable: 1,
// isPublish: 1,
// tenantId: '560761',
// }, v => !v);
// where[ctx.model.Sequelize.fn('validStartTime', '%Y/%m/%d')] = {
// [ctx.model.Sequelize.Op.lte]: exposureTime
// }
// where[ctx.model.Sequelize.fn('validEndTime', '%Y/%m/%d')] = {
// [ctx.model.Sequelize.Op.gte]: exposureTime
// }
// const { rows: data } = await ctx.model.PageInfo.findAndCountAll({
// where,
// order: [['updated_at', 'DESC']],
// attributes: ['id', 'uuid', 'pageName', 'tenantId', 'validStartTime', 'validEndTime']
// });
const data = await ctx.model.query(
"SELECT `id`, `uuid`, `page_name` AS `pageName`, `tenant_id` AS `tenantId`, `validStartTime`, `validEndTime` FROM page_config_info WHERE DATE_FORMAT(`validStartTime`, '%Y/%m/%d') <= '"+ exposureTime +"' AND DATE_FORMAT(`validEndTime`, '%Y/%m/%d') >= '" + exposureTime + "' AND `page_config_info`.`enable` = 1 AND `page_config_info`.`is_publish` = 1 AND `page_config_info`.`tenant_id` = '560761'", {
type: ctx.model.QueryTypes.SELECT
}
)
const res = data.map(item => {
item.url = `${api.h5Host}/activity/${item.uuid}?tenantId=${item.tenantId}&vccToken={token}&appChannel={appChannel}`;
item.uuid && delete item.uuid;
item.tenantId && delete item.tenantId;
return item;
})
ctx.body = ctx.helper.ok(res);
}
} }
\ 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).utcOffset('+08:00').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
...@@ -47,6 +47,36 @@ export class PageInfo extends Model<PageInfo> { ...@@ -47,6 +47,36 @@ export class PageInfo extends Model<PageInfo> {
}) })
uuid: string; uuid: string;
@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({ @Column({
type: DataType.INTEGER(1) type: DataType.INTEGER(1)
}) })
...@@ -81,7 +111,7 @@ export class PageInfo extends Model<PageInfo> { ...@@ -81,7 +111,7 @@ export class PageInfo extends Model<PageInfo> {
field: 'updated_at', field: 'updated_at',
get() { get() {
const moment = require('moment'); const moment = require('moment');
const updatedAt = this.getDataValue('updatedAt'); const updatedAt = this.getDataValue('updated_at');
return moment(updatedAt).format('YYYY-MM-DD HH:mm:ss'); return moment(updatedAt).format('YYYY-MM-DD HH:mm:ss');
}, },
}) })
...@@ -92,6 +122,10 @@ export class PageInfo extends Model<PageInfo> { ...@@ -92,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
...@@ -13,8 +13,12 @@ export default (application: Application) => { ...@@ -13,8 +13,12 @@ export default (application: Application) => {
router.post('/editor/update', controller.editor.update); router.post('/editor/update', controller.editor.update);
router.post('/editor/clearcache', controller.editor.clearCache); router.post('/editor/clearcache', controller.editor.clearCache);
router.get('/editor/get/list', controller.editor.getList); router.get('/editor/get/list', controller.editor.getList);
router.post('/editor/getList/activityCalendar', controller.editor.getListForActivityCalendar);
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/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`);
}, },
......
...@@ -105,4 +105,9 @@ export default { ...@@ -105,4 +105,9 @@ export default {
accessToken: true accessToken: true
}); });
}, },
getCouponModalList(params) {
return http.post(`${config.opapiHost}/kdspOp/api/kdsp/app/app-config-push/listAll`, params, {
accessToken: true
});
}
}; };
\ No newline at end of file
...@@ -134,6 +134,7 @@ export default { ...@@ -134,6 +134,7 @@ export default {
border-radius: 0; border-radius: 0;
background: #f5f7f9; background: #f5f7f9;
width: 100%; width: 100%;
z-index: 1;
.layout-container { .layout-container {
height: 100%; height: 100%;
flex-flow: row; flex-flow: row;
......
...@@ -14,7 +14,7 @@ export default class DynamicForm extends Vue { ...@@ -14,7 +14,7 @@ export default class DynamicForm extends Vue {
@Getter('pageData') pageData; @Getter('pageData') pageData;
@State(state => state.tenant.cartAndShareBtn) cartAndShareBtn; @State(state => state.tenant.cartAndShareBtn) cartAndShareBtn;
@Prop(Boolean) value; @Prop(Boolean) value;
validTime: array = [];
showPopup: boolean = false; showPopup: boolean = false;
loadingSave: boolean = false; loadingSave: boolean = false;
loadingPreview: boolean = false; loadingPreview: boolean = false;
...@@ -34,9 +34,11 @@ export default class DynamicForm extends Vue { ...@@ -34,9 +34,11 @@ export default class DynamicForm extends Vue {
@Watch('pageData', { immediate: true }) @Watch('pageData', { immediate: true })
onPageDataChange(newVal) { onPageDataChange(newVal) {
const { pageName, pageDescribe, pageKeywords, coverImage, isPublish, isTemplate, page } = this.pageInfo; const { pageName, pageDescribe, pageKeywords, coverImage, isPublish, isTemplate, page, validStartTime, validEndTime, redirectUrl } = this.pageInfo;
const { shareOpenMethod, shareCoverImage, diversion } = page.props; const { shareOpenMethod, shareCoverImage, diversion } = page.props;
this.formCustom = { pageName, pageDescribe, pageKeywords, coverImage, isPublish: !!isPublish, isTemplate: !!isTemplate, shareCoverImage, shareOpenMethod, diversion }; this.formCustom = { pageName, pageDescribe, pageKeywords, coverImage, isPublish: !!isPublish, isTemplate: !!isTemplate, shareCoverImage, shareOpenMethod, diversion, validStartTime, validEndTime, redirectUrl };
this.validTime = validStartTime && validEndTime ? [validStartTime, validEndTime] : [];
// `${validStartTime} - ${validEndTime}` : '';
} }
@Watch('value') @Watch('value')
...@@ -65,6 +67,17 @@ export default class DynamicForm extends Vue { ...@@ -65,6 +67,17 @@ export default class DynamicForm extends Vue {
this.$emit('input', val); this.$emit('input', val);
} }
validTimeChange(val) {
if (val.length && val[0] && val[1]) {
if (val[1].slice(-8) === '00:00:00') {
val[1] = val[1].replace('00:00:00', '23:59:59');
}
}
this.validTime = val;
this.formCustom.validStartTime = val[0] || '';
this.formCustom.validEndTime = val[1] || '';
}
preview() { preview() {
this.formCustom.pageName = this.formCustom.pageName || '未命名'; this.formCustom.pageName = this.formCustom.pageName || '未命名';
this.handleSubmit('preview'); this.handleSubmit('preview');
......
<template> <template>
<Modal v-model="showPopup" width="380" @on-visible-change="change" class-name='basic-form'> <Modal v-model="showPopup" width="380" @on-visible-change="change" class-name='basic-form'>
<!-- {{formCustom}} -->
<Form @submit.native.prevent ref="formCustom" :model="formCustom" :rules="ruleCustom" :label-width="80" label-position="left"> <Form @submit.native.prevent ref="formCustom" :model="formCustom" :rules="ruleCustom" :label-width="80" label-position="left">
<FormItem label="页面名称" prop="pageName"> <FormItem label="页面名称" prop="pageName">
<Input v-model="formCustom.pageName" placeholder="请输入页面名称"></Input> <Input v-model="formCustom.pageName" placeholder="请输入页面名称"></Input>
...@@ -24,8 +25,14 @@ ...@@ -24,8 +25,14 @@
<FormItem label="是否发布" prop="isPublish"> <FormItem label="是否发布" prop="isPublish">
<i-switch v-model="formCustom.isPublish"></i-switch> <i-switch v-model="formCustom.isPublish"></i-switch>
</FormItem> </FormItem>
<FormItem label="活动有效期" prop="redirectUrl">
<DatePicker v-model="validTime" @on-change="validTimeChange" format="yyyy/MM/dd HH:mm:ss" type="datetimerange" placeholder="选填,留空默认长期有效" style="width: 100%"></DatePicker>
</FormItem>
<FormItem label="活动结束url" prop="redirectUrl">
<Input v-model="formCustom.redirectUrl" placeholder="选填,留空默认首页" :rows="3"></Input>
</FormItem>
<FormItem label="导流url" prop="diversion"> <FormItem label="导流url" prop="diversion">
<Input v-model="formCustom.diversion" placeholder="可选,具体url请联系对应导流项目的产品或开发" :rows="3"></Input> <Input v-model="formCustom.diversion" placeholder="选填,具体url请联系对应导流项目的产品或开发" :rows="3"></Input>
</FormItem> </FormItem>
<FormItem label="设为模板" prop="isTemplate" v-if="formCustom.isPublish"> <FormItem label="设为模板" prop="isTemplate" v-if="formCustom.isPublish">
<i-switch v-model="formCustom.isTemplate"></i-switch> <i-switch v-model="formCustom.isTemplate"></i-switch>
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
<p slot="title">{{ config.title }}</p> <p slot="title">{{ config.title }}</p>
<div class="dynamic-row-card"> <div class="dynamic-row-card">
<img <img
data-html2canvas-ignore="true"
class="ignore-html2canvas-loop"
:src="config.coverImage || 'http://activitystatic.q-gp.com/low_code.jpg'" :src="config.coverImage || 'http://activitystatic.q-gp.com/low_code.jpg'"
/> />
</div> </div>
...@@ -26,6 +28,8 @@ ...@@ -26,6 +28,8 @@
<p slot="title">{{ config.title }}</p> <p slot="title">{{ config.title }}</p>
<div class="dynamic-row-card"> <div class="dynamic-row-card">
<img <img
data-html2canvas-ignore="true"
class="ignore-html2canvas-loop"
:src="config.coverImage || 'http://activitystatic.q-gp.com/low_code.jpg'" :src="config.coverImage || 'http://activitystatic.q-gp.com/low_code.jpg'"
/> />
</div> </div>
......
<template>
<div class="couponModalSeletor">
<Select v-model="modalId" filterable clearable multiple>
<Option v-for="item in list" :value="item.appConfigPushNo" :key="item.appConfigPushNo">{{ `id ${item.id} - ${item.name}` }}</Option>
</Select>
<!-- <Button type="primary" @click="save">保存</Button> -->
</div>
</template>
<script>
import operationApi from '@api/operation.api';
export default {
props: {
value: {
type: Array,
default: []
}
},
watch: {
value(val) {
this.modalId = val;
},
modalId(val) {
this.$emit('input', val);
}
},
components: {
},
data() {
return {
list: [],
modalId: []
}
},
created() {
this.modalId = this.value;
this.getCouponModalList();
},
methods: {
async getCouponModalList() {
const res = await operationApi.getCouponModalList({
elementType: 4, // 4为优惠券弹窗类型
enable: 'VALID'
});
const list = res.list.map(item => {
let obj = {};
obj.id = item.id.toString();
obj.appConfigPushNo = item.appConfigPushNo.toString();
obj.name = item.componentName;
return obj;
})
// console.log(list)
this.list = list;
}
}
}
</script>
<style scoped lang="less">
</style>
<template>
<div class="fontWeightSelectorContainer">
<span class="option" @click="select(item.id)" v-for="(item, index) in options" :key="index" :class="{selected: item.id == selected}">
{{item.label}}
</span>
<!-- <Button type="primary" @click="save">保存</Button> -->
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: "normal"
}
},
watch: {
value(val) {
this.selected = val;
},
selected(val) {
this.$emit('input', val);
}
},
methods: {
select(id) {
this.selected = id;
}
},
data() {
return {
options: [
{id: 'normal', label: '标准'},
{id: 'medium', label: '中粗'},
{id: 'bold', label: '加粗'}
],
selected: ''
}
},
created() {
this.selected = this.value;
}
}
</script>
<style scoped lang="less">
.option{
cursor: pointer;
border-radius: 4px;
line-height:normal;
display: inline-block;
padding: 3px 5px;
border: 1px solid #dddee1;
margin: 0 2px;
user-select: none;
&.selected, &:hover{
border-color: #57a3f3;
box-shadow: 0px 0px 0px 2px rgb(45 140 240 / 20%)
}
}
</style>
<template>
<div class="VerticalAdGoodsSelectorContainer">
<Input
v-model="data.ids"
type="textarea"
:autosize="{minRows: 3,maxRows: 10}"
placeholder="请输入skuNo,每个skuNo使用英文逗号隔开"
@on-blur="changeIds"
/>
<div class="goodsItem" v-for="(item, index) in data.data" :key="index">
<div class="title">
商品{{index + 1}}
</div>
<Form @submit.native.prevent ref="formCustom" :model="item" :label-width="90" label-position="left">
<FormItem label="sku_no" prop="skuNo">
<Input v-model="item.skuNo" disabled />
</FormItem>
<FormItem label="商品名称" prop="skuName">
<Input v-model="item.skuName" />
</FormItem>
<FormItem label="商品图片" prop="skuUrl">
<Upload v-model="item.skuUrl"></Upload>
</FormItem>
<FormItem label="商品横幅文案" prop="subTitle">
<Input v-model="item.subTitle" />
</FormItem>
</Form>
<Button type="error" long @click="del(index, item.skuNo)" class="deleteGood">删除</Button>
</div>
</div>
</template>
<script>
import operationApi from '@api/operation.api';
import Upload from '@editor/component/DynamicForm/component/Upload/index.vue';
export default {
props: {
value: {
type: Object,
default: () => {}
}
},
watch: {
value: {
handler(val) {
this.data = val;
}
},
data: {
deep: true,
handler(val) {
this.$emit('input', val);
}
}
},
components: {
Upload
},
data() {
return {
data: {}
}
},
created() {
this.data = JSON.parse(JSON.stringify(this.value));
},
methods: {
init() {
const data = this.value;
if (!data.ids) data.ids = '';
if (!data.data) data.data = [];
this.data = data;
},
async getGoods(params) {
let prevDataArr = this.data && this.data.data ? JSON.parse(JSON.stringify(this.data.data)) : [];
let skuToInfoMaps = {};
for (let i = 0; i < prevDataArr.length; i++) {
skuToInfoMaps[prevDataArr[i].skuNo] = prevDataArr[i];
}
const res = await operationApi.getGoods(params);
try {
let goodsList = res.skus || [];
let arr = [];
if (goodsList.length) {
for (let i = 0; i < goodsList.length; i++) {
let item = {};
item.skuName = goodsList[i].skuName;
item.salePrice = goodsList[i].salePrice;
item.skuNo = goodsList[i].skuNo;
item.skuUrl = goodsList[i].skuUrl;
item.status = goodsList[i].status;
if (skuToInfoMaps[goodsList[i].skuNo]) {
item = Object.assign({}, skuToInfoMaps[goodsList[i].skuNo])
};
arr.push(item);
}
this.$set(this.data, 'data', arr);
} else {
this.$set(this.data, 'data', []);
}
} catch (err) {
console.log(err);
this.$set(this.data, 'data', []);
}
// setTimeout(() => {
// this.$emit('adjust')
// }, 1000)
},
changeIds() {
const ids = this.data && this.data.ids ? this.data.ids.trim() : '';
this.data.ids = ids;
const skus = ids ? ids.split(',') : [];
if (skus.length) {
if (skus.length > 30) {
this.$toast('最多填入30个商品');
return;
}
this.getGoods({
skus: Array.from(new Set(skus))
});
} else {
this.$set(this.data, 'data', []);
}
},
del(index, skuNo) {
this.data.data.splice(index, 1)
const ids = this.data.ids ? this.data.ids.split(',') : [];
const idx = ids.indexOf(skuNo);
if (idx > -1 && ids.length) ids.splice(idx, 1);
this.data.ids = ids.join();
if (this.data.data.length == 0) this.data.ids = '';
}
}
}
</script>
<style scoped lang="less">
.goodsItem{
border: 1px solid #dddee1;
border-radius: 4px;
margin-top: 10px;
padding: 5px;
.title{
width: 100%;
height: 30px;
font-weight: bold;
}
}
</style>
<template>
<div class="goodsResourceSelectorContainer">
<span class="option" @click="select(item.id)" v-for="(item, index) in options" :key="index" :class="{selected: item.id == dataValue.type}">
{{item.label}}
</span>
<div v-if="dataValue.type == 'category'">
<Button type="primary" @click="add">选择品类</Button>
</div>
<div v-if="dataValue.type == 'brand'">
<Button type="primary" @click="add">选择品牌</Button>
</div>
<Modal v-model="showModal" :mask-closable="false" :title="modalTitle" width="600">
<div v-if="dataValue.type == 'category'">
<div class="search">
<Form class="searchFormStyle" inline>
<FormItem label="类目名称" prop="name">
<Input v-model="searchParams.name" placeholder="" />
</FormItem>
<FormItem label="类目编码" prop="name">
<Input v-model="searchParams.sceneId" placeholder="" />
</FormItem>
<Button type="primary" @click="searchList">查询</Button>
</Form>
</div>
<Table
@on-select="selectItem"
@on-select-cancel="cancelSelectItem"
:columns="tableColumn"
:data="tableData"
class="tableStyle"
height="600"
:loading="loading"
/>
<Page class="page" :total="total" @on-change="changePageNo" show-total></Page>
</div>
<div v-if="dataValue.type == 'brand'">
<div class="search">
<Form class="searchFormStyle" inline>
<FormItem label="品牌名称" prop="name">
<Input v-model="searchParams.name" placeholder="" />
</FormItem>
<FormItem label="品牌编码" prop="name">
<Input v-model="searchParams.sceneId" placeholder="" />
</FormItem>
<Button type="primary" @click="searchList">查询</Button>
</Form>
</div>
<Table
@on-select="selectItem"
@on-select-cancel="cancelSelectItem"
:columns="tableColumn"
:data="tableData"
class="tableStyle"
height="600"
:loading="loading"
/>
<Page class="page" :total="total" @on-change="changePageNo" show-total></Page>
</div>
<div slot="footer">
<Button @click="showModal = false">取消</Button>
<Button type="primary" @click="submitSelect">确认</Button>
</div>
</Modal>
</div>
</template>
<script>
import operationApi from '@api/operation.api';
const categoryCol = function() {
return [
{
type: 'selection',
width: 60,
align: 'center'
},
{
align: 'center',
title: '类目名称',
key: 'categoryName',
},
{
align: 'center',
title: '类目编码',
key: 'sceneId',
width: 180
},
]
}
const brandCol = function() {
return [
{
type: 'selection',
width: 60,
align: 'center'
},
{
align: 'center',
title: '品牌名称',
key: 'brandName',
},
{
align: 'center',
title: '品牌编码',
key: 'sceneId',
width: 120
},
{
align: 'center',
title: 'logo',
render: (h, params) => {
const row = params.row;
return h('div', {
style: {
padding: '5px 0'
}
}, [
h('img', {
attrs: {
src: row.imgUrl,
width: 80,
height: 80
},
})
]
)
},
width: 200
}
]
}
export default {
props: {
value: {
type: Object,
default() {
return {}
}
}
},
computed: {
modalTitle() {
return this.dataValue.type === 'category' ? '选择品类' : '选择品牌'
}
},
watch: {
value(val) {
console.log(val, '122')
this.dataValue = val;
},
dataValue: {
deep: true,
handler(val) {
this.$emit('input', val);
}
}
},
methods: {
submitSelect() {
// 提交选择
const targetKey = this.dataValue.type === 'category' ? 'categoryId' : 'brandId';
this.dataValue[targetKey] = this.dataValue.type === 'category' ? this.selectCategoryCollect.join() : this.selectBrandCollect.join();
this.dataValue.type === 'category' ? this.dataValue.brandId = '' : this.dataValue.categoryId = ''; // 清空非当前选项的id数据
this.showModal = false;
},
selectItem(arr, item) {
// 选择某一项
if (this.dataValue.type == 'category') {
this.selectCategoryCollect.push(String(item.sceneId));
console.log(this.selectCategoryCollect);
}
if (this.dataValue.type == 'brand') {
this.selectBrandCollect.push(String(item.sceneId));
console.log(this.selectBrandCollect);
}
},
cancelSelectItem(arr, item) {
// 取消选择某一项
if (this.dataValue.type == 'category') {
this.selectCategoryCollect.splice(this.selectCategoryCollect.indexOf(item.sceneId), 1)
console.log(this.selectCategoryCollect);
}
if (this.dataValue.type == 'brand') {
this.selectBrandCollect.splice(this.selectBrandCollect.indexOf(item.sceneId), 1)
console.log(this.selectBrandCollect);
}
},
add() {
this.showModal = true;
this.getData();
},
select(type) {
if (type == this.dataValue.type) return;
this.dataValue = {
type,
brandId: '',
categoryId: '',
}
this.selectCategoryCollect = [];
this.selectBrandCollect = [];
},
changePageNo(val) {
this.searchParams.pageNo = val;
this.getData();
},
searchList() {
this.searchParams.pageNo = 1;
this.searchParams.pageSize = 10;
this.getData();
},
async getData() {
let res;
this.loading = true;
this.tableData = [];
this.tableColumn = this.dataValue.type == 'category' ? categoryCol.bind(this)() : brandCol.bind(this)();
try {
if (this.dataValue.type == 'category') {
// 品类
res = await operationApi.getCategoryAndPriority({...this.searchParams, categoryLevel: 1});
} else {
// 品牌
res = await operationApi.getBrandAndPriority(this.searchParams);
}
this.$nextTick(() => {
this.loading = false
this.total = res.total || 0;
const list = res.records || [];
list.map(item => {
if (this.dataValue.type == 'category') {
if (this.selectCategoryCollect.indexOf(String(item.sceneId)) > -1) {
item._checked = true;
}
}
if (this.dataValue.type == 'brand') {
if (this.selectBrandCollect.indexOf(String(item.sceneId)) > -1) {
item._checked = true;
}
}
return item;
})
this.tableData = list;
})
} catch (e) {
this.loading = false;
}
},
},
data() {
return {
options: [
{id: 'all', label: '全部商品'},
{id: 'category', label: '按照品类'},
{id: 'brand', label: '按照品牌'}
],
dataValue: {},
showModal: false,
categoryData: [],
tableData: [],
tableColumn: [],
total: 0,
searchParams: {
pageSize: 10,
pageNo: 1,
name: '',
sceneId: ''
},
loading: false,
selectCategoryCollect: [], // 选择品类集合
selectBrandCollect: [] // 选择品牌集合
}
},
created() {
if (!Object.keys(this.value).length) {
// 默认值
this.dataValue = {
type: 'all',
brandId: '',
categoryId: '',
}
} else {
this.dataValue = this.value;
if (this.dataValue.type == 'category') {
this.selectCategoryCollect = this.dataValue.categoryId ? this.dataValue.categoryId.split(',') : [];
}
if (this.dataValue.type == 'brand') {
this.selectBrandCollect = this.dataValue.brandId ? this.dataValue.brandId.split(',') : [];
}
console.log(this.selectCategoryCollect, this.selectBrandCollect)
}
}
}
</script>
<style lang="less" scoped>
.option{
cursor: pointer;
border-radius: 4px;
line-height:normal;
display: inline-block;
padding: 3px 5px;
border: 1px solid #dddee1;
margin: 0 2px;
user-select: none;
&.selected, &:hover{
border-color: #57a3f3;
box-shadow: 0px 0px 0px 2px rgb(45 140 240 / 20%)
}
}
.searchFormStyle {
text-align: left;
clear: both;
background-color: #fff;
padding: 4px;
min-height: 70px;
font-size: 0;
display: flex;
align-items: center;
.btnGroupStyle{
button{
margin-right: 6px;
}
}
/deep/ .ivu-form-item{
margin-bottom: 0;
}
/deep/ .ivu-form-item-label {
font-weight: bold;
display: inline-block;
}
/deep/.ivu-form-item-content {
display: inline-block;
}
}
.page{
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
/deep/ th.ivu-table-column-center{
.ivu-checkbox-wrapper{
display: none;
}
}
</style>
\ No newline at end of file
<template>
<div class="seckillActivityContainer">
<Select v-model="activityInfoId" filterable>
<Option v-for="item in list" :value="item.id" :key="item.id">{{ `id ${item.id} - ${item.title}` }}</Option>
</Select>
<!-- <Button type="primary" @click="save">保存</Button> -->
</div>
</template>
<script>
import operationApi from '@api/operation.api';
export default {
props: {
value: {
type: String,
default: ''
}
},
watch: {
activityInfoId(val) {
this.$emit('input', val);
}
},
components: {
},
data() {
return {
list: [],
activityInfoId: ''
}
},
created() {
this.activityInfoId = this.value;
this.getActivityList();
console.log(this.value)
},
methods: {
async getActivityList() {
const res = await operationApi.getActivityList({
templateType: 1,
crowdType: [1],
userType:[]
});
for (let i = 0; i < res.length; i++) {
res[i].id = res[i].id.toString();
}
this.list = res;
}
}
}
</script>
<style scoped lang="less">
</style>
<template> <template>
<div class="snapUpActivityContainer"> <div class="swiperStyleSeletorContainer">
<span class="option" @click="select(item.id)" v-for="(item, index) in options" :key="index" :class="{selected: item.id == selected}"> <span class="option" @click="select(item.id)" v-for="(item, index) in options" :key="index" :class="{selected: item.id == selected}">
{{item.label}} {{item.label}}
</span> </span>
......
...@@ -7,6 +7,7 @@ import Upload from './component/Upload/index.vue'; ...@@ -7,6 +7,7 @@ import Upload from './component/Upload/index.vue';
import ColorSelector from './component/ColorSelector/index.vue'; import ColorSelector from './component/ColorSelector/index.vue';
import DiscountGoodsSelector from './component/DiscountGoodsSelector/index.vue'; import DiscountGoodsSelector from './component/DiscountGoodsSelector/index.vue';
import SnapUpActivitySelector from './component/SnapUpActivitySelector/index.vue'; import SnapUpActivitySelector from './component/SnapUpActivitySelector/index.vue';
import SeckillSelector from './component/SeckillSelector/index.vue';
import VerticalAdStyleSelector from './component/VerticalAdStyleSelector/index.vue'; import VerticalAdStyleSelector from './component/VerticalAdStyleSelector/index.vue';
import CategoryTypeSelector from './component/CategoryTypeSelector/index.vue'; import CategoryTypeSelector from './component/CategoryTypeSelector/index.vue';
import CategorySelector from './component/CategorySelector/index.vue'; import CategorySelector from './component/CategorySelector/index.vue';
...@@ -17,6 +18,8 @@ import GoodsSwiperSelector from './component/GoodsSwiperSelector/index.vue'; ...@@ -17,6 +18,8 @@ import GoodsSwiperSelector from './component/GoodsSwiperSelector/index.vue';
import DiscountGoodsSelectorV2 from './component/DiscountGoodsSelectorV2/index.vue'; import DiscountGoodsSelectorV2 from './component/DiscountGoodsSelectorV2/index.vue';
import GoodsChannelTypeSelector from './component/GoodsChannelTypeSelector/index.vue'; import GoodsChannelTypeSelector from './component/GoodsChannelTypeSelector/index.vue';
import GoodsPromotionTypeSelector from './component/GoodsPromotionTypeSelector/index.vue'; import GoodsPromotionTypeSelector from './component/GoodsPromotionTypeSelector/index.vue';
import GoodsResourceSelector from './component/GoodsResourceSelector/index.vue';
import GoodsCubeSelector from './component/GoodsCubeSelector/index.vue';
import BaseSelect from './component/BaseSelect/index.vue'; import BaseSelect from './component/BaseSelect/index.vue';
import ComponentSelect from './component/ComponentSelect/index.vue'; import ComponentSelect from './component/ComponentSelect/index.vue';
import FormList from './component/FormList/index.vue'; import FormList from './component/FormList/index.vue';
...@@ -25,6 +28,7 @@ import CouponTableModal from './component/CouponTableModal/index.vue'; ...@@ -25,6 +28,7 @@ import CouponTableModal from './component/CouponTableModal/index.vue';
import Textarea from './component/Textarea/index.vue'; import Textarea from './component/Textarea/index.vue';
import Number from './component/Number/index.vue'; import Number from './component/Number/index.vue';
import ColumnSelector from './component/ColumnSelector/index.vue'; import ColumnSelector from './component/ColumnSelector/index.vue';
import FontWeightSelector from './component/FontWeightSelector/index.vue';
import { resizeDiv, getStyle } from '@/service/utils.service'; import { resizeDiv, getStyle } from '@/service/utils.service';
import { getAllScheme } from '../../../store/modules/editor/scheme'; import { getAllScheme } from '../../../store/modules/editor/scheme';
import EventBus from '@service/eventBus.service'; import EventBus from '@service/eventBus.service';
...@@ -52,7 +56,11 @@ const allComponentsMap = getAllScheme(); ...@@ -52,7 +56,11 @@ const allComponentsMap = getAllScheme();
SwiperListSelector, SwiperListSelector,
GoodsSwiperSelector, GoodsSwiperSelector,
GoodsChannelTypeSelector, GoodsChannelTypeSelector,
GoodsPromotionTypeSelector GoodsPromotionTypeSelector,
FontWeightSelector,
GoodsResourceSelector,
GoodsCubeSelector,
SeckillSelector
}, name: 'DynamicForm' }) }, name: 'DynamicForm' })
export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMixin) { export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMixin) {
@State(state => state.editor.curEleIndex) curEleIndex; @State(state => state.editor.curEleIndex) curEleIndex;
......
...@@ -10,7 +10,10 @@ ...@@ -10,7 +10,10 @@
<div v-if="item.title"> <div v-if="item.title">
<h3>{{ item.title }}</h3> <h3>{{ item.title }}</h3>
<FormItem :label="child.name" :key="curElement.id + child.key" v-for="child in item.children"> <FormItem :label="child.name" :key="curElement.id + child.key" v-for="child in item.children">
<component :is="getComponent(child.type)" :options="child.options" :formControl="child.formControl" :limit-name="child.type === 'FormList' && curElement.name === 'cs-goods-tabs' ? 'cs-goods' : ''" v-model="form[child.key]" /> <!-- {{curElement.name}}
{{child.key}} -->
<InputNumber v-if="curElement.name == 'cs-guess-you-like' && child.key == 'rowNumber'" :max="10" :min="1" v-model="form[child.key]"></InputNumber>
<component v-else :is="getComponent(child.type)" :options="child.options" :formControl="child.formControl" :limit-name="child.type === 'FormList' && curElement.name === 'cs-goods-tabs' ? 'cs-goods' : ''" v-model="form[child.key]" />
</FormItem> </FormItem>
</div> </div>
<FormItem class="Df-component-formitem" v-else :label="item.name" :title="item.desc || ''"> <FormItem class="Df-component-formitem" v-else :label="item.name" :title="item.desc || ''">
......
...@@ -4,6 +4,7 @@ import { reduce, ceil, subtract, divide, cloneDeep, assign } from 'lodash'; ...@@ -4,6 +4,7 @@ import { reduce, ceil, subtract, divide, cloneDeep, assign } from 'lodash';
import ContextMenuMixin from '@editor/mixins/contextMenu.mixin'; import ContextMenuMixin from '@editor/mixins/contextMenu.mixin';
import Upload from '../DynamicForm/component/Upload/index.vue'; import Upload from '../DynamicForm/component/Upload/index.vue';
import ColorSelector from '../DynamicForm/component/ColorSelector/index.vue'; import ColorSelector from '../DynamicForm/component/ColorSelector/index.vue';
import CouponModalSelector from '../DynamicForm/component/CouponModalSelector/index.vue'; // 用于选择优惠券弹窗
import BaseSelect from '../DynamicForm/component/BaseSelect/index.vue'; import BaseSelect from '../DynamicForm/component/BaseSelect/index.vue';
import Textarea from '../DynamicForm/component/Textarea/index.vue'; import Textarea from '../DynamicForm/component/Textarea/index.vue';
import Number from '../DynamicForm/component/Number/index.vue'; import Number from '../DynamicForm/component/Number/index.vue';
...@@ -12,7 +13,7 @@ import BackTopPicker from '../DynamicForm/component/BackTopPicker/index.vue'; ...@@ -12,7 +13,7 @@ import BackTopPicker from '../DynamicForm/component/BackTopPicker/index.vue';
import SwitchBtn from '../DynamicForm/component/SwitchBtn/index.vue'; import SwitchBtn from '../DynamicForm/component/SwitchBtn/index.vue';
import { SHOP_CART_CONFIG, DEFAULT_CONFIG } from '@service/staticData.service'; import { SHOP_CART_CONFIG, DEFAULT_CONFIG } from '@service/staticData.service';
@Component({ components: { Upload, ColorSelector, BaseSelect, Textarea, Number, FormList, BackTopPicker, SwitchBtn }, name: 'DynamicPageForm' }) @Component({ components: { Upload, ColorSelector, BaseSelect, Textarea, Number, FormList, BackTopPicker, SwitchBtn, CouponModalSelector }, name: 'DynamicPageForm' })
export default class DynamicPageForm extends Mixins(ContextMenuMixin) { export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
@Getter('pageData') pageData; @Getter('pageData') pageData;
@State(state => state.tenant.cartAndShareBtn) cartAndShareBtn; @State(state => state.tenant.cartAndShareBtn) cartAndShareBtn;
...@@ -32,6 +33,13 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) { ...@@ -32,6 +33,13 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
type: 'ColorSelector' type: 'ColorSelector'
} }
]; ];
baseConfigSchema: object[] = [
{
key: 'pageNeedLogin',
name: '是否登录',
type: 'SwitchBtn'
},
]; ,
titleSchema: object[] = [ titleSchema: object[] = [
{ {
key: 'titleBgColor', key: 'titleBgColor',
...@@ -158,6 +166,13 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) { ...@@ -158,6 +166,13 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
formDefault: {} formDefault: {}
} }
]; ];
floatModal: object[] = [
{
key: 'couponModal',
name: '优惠券弹窗',
type: 'CouponModalSelector'
},
];
// 这块用于让被污染的pageInfo的page.props返回shareOpenMethod // 这块用于让被污染的pageInfo的page.props返回shareOpenMethod
otherProps: object[] = [ otherProps: object[] = [
{ {
...@@ -171,7 +186,7 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) { ...@@ -171,7 +186,7 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
}, },
]; ];
get propsSchema() { get propsSchema() {
return [...this.titleSchema, ...this.bottomSchema, ...this.floatSchema, ...this.otherProps]; return [...this.titleSchema, ...this.bottomSchema, ...this.floatSchema, ...this.otherProps, ...this.floatModal, ...this.baseConfigSchema];
} }
@Watch('cartAndShareBtn', { immediate: true }) @Watch('cartAndShareBtn', { immediate: true })
...@@ -189,6 +204,7 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) { ...@@ -189,6 +204,7 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
this.propsSchema.forEach(schame => { this.propsSchema.forEach(schame => {
this.$set(this.propsForm, schame.key, this.pageData?.props?.[schame.key]); this.$set(this.propsForm, schame.key, this.pageData?.props?.[schame.key]);
}); });
// console.log(this.propsForm);
} }
@Watch('commonStyleForm', { immediate: true, deep: true }) @Watch('commonStyleForm', { immediate: true, deep: true })
......
...@@ -3,6 +3,12 @@ ...@@ -3,6 +3,12 @@
<h2>{{title}}</h2> <h2>{{title}}</h2>
<Form class="dynamic-form-component" :label-width="80" :model="propsForm" @submit.native.prevent> <Form class="dynamic-form-component" :label-width="80" :model="propsForm" @submit.native.prevent>
<h3>基础属性</h3> <h3>基础属性</h3>
<h4>基础配置</h4>
<template v-for="(item, index) in baseConfigSchema">
<FormItem class="Df-component-formitem" :label="item.name" :key="'baseConfigSchema_' + index">
<component :is="item.type" :options="item.options" v-bind="item.props" v-model="propsForm[item.key]" :formControl="item.formControl" />
</FormItem>
</template>
<h4>标题栏</h4> <h4>标题栏</h4>
<template v-for="(item, index) in titleSchema"> <template v-for="(item, index) in titleSchema">
<FormItem class="Df-component-formitem" :label="item.name" :key="'titleSchema_' + index"> <FormItem class="Df-component-formitem" :label="item.name" :key="'titleSchema_' + index">
...@@ -21,6 +27,12 @@ ...@@ -21,6 +27,12 @@
<component :is="item.type" :options="item.options" v-bind="item.props" v-model="propsForm[item.key]" :formDefault="item.formDefault" :formControl="item.formControl" /> <component :is="item.type" :options="item.options" v-bind="item.props" v-model="propsForm[item.key]" :formDefault="item.formDefault" :formControl="item.formControl" />
</FormItem> </FormItem>
</template> </template>
<h4>弹窗</h4>
<template v-for="(item, index) in floatModal">
<FormItem class="Df-component-formitem" :label="item.name" :key="'floatModal_' + index">
<component :is="item.type" :options="item.options" v-bind="item.props" v-model="propsForm[item.key]" :formDefault="item.formDefault" :formControl="item.formControl" />
</FormItem>
</template>
</Form> </Form>
<Form class="dynamic-form-component" :label-width="80" :model="commonStyleForm" @submit.native.prevent> <Form class="dynamic-form-component" :label-width="80" :model="commonStyleForm" @submit.native.prevent>
<h3>基础样式</h3> <h3>基础样式</h3>
...@@ -57,7 +69,7 @@ ...@@ -57,7 +69,7 @@
} }
} }
/deep/ .ivu-form-item-label { /deep/ .ivu-form-item-label {
font-size: 14px; font-size: 12px;
} }
/deep/ .ivu-input-number { /deep/ .ivu-input-number {
......
<template> <template>
<div class="freedom" :ref="`freedomContainer${containerIndex}`" @click.stop="handleElementClick(containerIndex)"> <div class="freedom" :ref="`freedomContainer${containerIndex}`" @click.stop="handleElementClick(containerIndex)">
<div class="freedom-body" :data-index="containerIndex"> <div class="freedom-body" :data-index="containerIndex">
<video width="100%" @loadeddata="videoOnload" ref="videoBackground" height="auto" style="z-index: -10; osition: absolute; top: 0; left: 0" v-if="childItem.props.freedomVideo" :src="childItem.props.freedomVideo" autoplay loop muted />
<div v-for="(item, index) in childItem.child" :style="transformStyle(item.commonStyle, 'container')" :class="['freedom-body-item', { 'Fb-item_selected': curChildIndex === index }]" :key="index" @click.stop="handleElementClick(containerIndex, index)" @mousedown.stop.prevent="mousedown(index, $event)" @contextmenu.prevent.stop="show($event, containerIndex, index)"> <div v-for="(item, index) in childItem.child" :style="transformStyle(item.commonStyle, 'container')" :class="['freedom-body-item', { 'Fb-item_selected': curChildIndex === index }]" :key="index" @click.stop="handleElementClick(containerIndex, index)" @mousedown.stop.prevent="mousedown(index, $event)" @contextmenu.prevent.stop="show($event, containerIndex, index)">
<component ref="childComponent" :style="transformStyle(item.commonStyle, 'component')" :is="item.name" v-bind="item.props"></component> <component ref="childComponent" :style="transformStyle(item.commonStyle, 'component')" :is="item.name" v-bind="item.props"></component>
<div class="freedom-body-dot" <div class="freedom-body-dot"
......
<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);
......
...@@ -16,6 +16,9 @@ export default class GoodsTabsMixin extends Vue { ...@@ -16,6 +16,9 @@ export default class GoodsTabsMixin extends Vue {
get hasSnapUpCom() { get hasSnapUpCom() {
return this.pageData.elements.some(item => item.name === 'cs-snap-up'); return this.pageData.elements.some(item => item.name === 'cs-snap-up');
} }
get hasSeckillCom() {
return this.pageData.elements.some(item => item.name === 'cs-seckill');
}
get hasGoodsNavCom() { get hasGoodsNavCom() {
return this.pageData.elements.some(item => item.name === 'cs-goods-tabs'); return this.pageData.elements.some(item => item.name === 'cs-goods-tabs');
} }
......
...@@ -89,7 +89,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -89,7 +89,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
pageData = this.handleComAchorScrollEnable(); pageData = this.handleComAchorScrollEnable();
pageData = this.handleGoodsTabs(); pageData = this.handleGoodsTabs();
pageData = this.handleCouponIds(pageData); pageData = this.handleCouponIds(pageData);
const { pageName, pageDescribe, pageKeywords, coverImage, isPublish, isTemplate, shareCoverImage, shareOpenMethod, diversion } = pageConfig; const { pageName, pageDescribe, pageKeywords, coverImage, isPublish, isTemplate, shareCoverImage, shareOpenMethod, diversion, validStartTime, validEndTime, redirectUrl } = pageConfig;
// !diversion shareCoverImage shareOpenMethod没有作为单独的sql字段存储下来,只是单纯的存储到的redis中 // !diversion shareCoverImage shareOpenMethod没有作为单独的sql字段存储下来,只是单纯的存储到的redis中
// 目前对此打了补丁,存放到page的props中 // 目前对此打了补丁,存放到page的props中
pageData.props.diversion = diversion; pageData.props.diversion = diversion;
...@@ -99,15 +99,30 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -99,15 +99,30 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
// 如果存在sheme, 移除页面中的sacheme // 如果存在sheme, 移除页面中的sacheme
delete pageData.scheme; delete pageData.scheme;
separateAllScheme(pageData.elements); separateAllScheme(pageData.elements);
const pageInfo = { diversion, page: JSON.stringify(pageData), author: user?.account, isPublish, pageName, pageDescribe, pageKeywords, coverImage, isTemplate, shareCoverImage, shareOpenMethod } as pageInfo; const pageInfo = { diversion, page: JSON.stringify(pageData), author: user?.account, isPublish, pageName, pageDescribe, pageKeywords, coverImage, isTemplate, shareCoverImage, shareOpenMethod, validStartTime, validEndTime, redirectUrl } as pageInfo;
if (this.uuid) { pageInfo.uuid = this.uuid; } if (this.uuid) { pageInfo.uuid = this.uuid; }
// 兼容老数据保存时redirectUrl为null的情况
pageInfo.redirectUrl = pageInfo.redirectUrl && typeof pageInfo.redirectUrl == 'string' ? pageInfo.redirectUrl.trim() : '';
if (pageInfo.redirectUrl.length) {
if (pageInfo.redirectUrl.indexOf('https://') != 0 && pageInfo.redirectUrl.indexOf('xyqb://') != 0) {
// 校验活动结束地址链接配置是否满足https和xyqb协议
this.$Notice.error({
title: '链接地址错误或域名不支持,请重新输入',
desc: '',
});
this.showSubmitPopup = false;
return;
}
}
// console.log({ pageInfo, pageData: this.pageData }, 0);
// return;
await this.savePageData({ pageInfo, pageData: this.pageData }); await this.savePageData({ pageInfo, pageData: this.pageData });
if (this.uuid) { await this.getPageDate({ pageId: this.uuid }); } if (this.uuid) { await this.getPageDate({ pageId: this.uuid }); }
this.showSubmitPopup = false; this.showSubmitPopup = false;
// 清除新增数据时的缓存 // 清除新增数据时的缓存
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: '保存成功!' });
} }
...@@ -258,6 +273,14 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -258,6 +273,14 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
return; return;
} }
} }
if (el.data.name === 'cs-seckill') {
if (this.hasSeckillCom) {
this.$Notice.warning({
title: '秒杀组件目前只支持添加一个'
});
return;
}
}
// 添加多个goods-tabs会取消锚点滚动 // 添加多个goods-tabs会取消锚点滚动
if (el.data.name === 'cs-goods-tabs') { if (el.data.name === 'cs-goods-tabs') {
if (this.hasGoodsNavCom) { if (this.hasGoodsNavCom) {
......
...@@ -35,6 +35,12 @@ export default { ...@@ -35,6 +35,12 @@ export default {
title: '描述', title: '描述',
formType: 'input', formType: 'input',
}, },
{
key: 'uuid',
title: 'uuid',
formType: 'input',
hideTable: true,
},
{ {
key: 'author', key: 'author',
title: '作者', title: '作者',
...@@ -76,7 +82,7 @@ export default { ...@@ -76,7 +82,7 @@ export default {
title: '链接', title: '链接',
hideSearch: true, hideSearch: true,
render: (h, params) => { render: (h, params) => {
return h('span', `${config.h5Host}/activity/${params.row.uuid}?tenantId=${params.row.tenantId}&vccToken={token}`) return h('span', `${config.h5Host}/activity/${params.row.uuid}?tenantId=${params.row.tenantId}&vccToken={token}&appChannel={appChannel}`)
} }
}, },
{ {
...@@ -117,7 +123,7 @@ export default { ...@@ -117,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`);
}, },
}, },
}, },
...@@ -139,6 +145,22 @@ export default { ...@@ -139,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',
{ {
......
...@@ -59,6 +59,30 @@ export interface PageInfo { ...@@ -59,6 +59,30 @@ export interface PageInfo {
uuid?: string; uuid?: string;
tenantId?: number; tenantId?: number;
diversion?: string; diversion?: string;
redirectUrl?: string;
validStartTime?: 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 {
...@@ -83,17 +107,22 @@ export const defaultState = { ...@@ -83,17 +107,22 @@ export const defaultState = {
isPublish: false, isPublish: false,
isTemplate: false, isTemplate: false,
uuid: '', uuid: '',
validEndTime: '',
validStartTime: '',
redirectUrl: '',
page: { page: {
commonStyle: { commonStyle: {
backgroundColor: '#f7f8fa', backgroundColor: '#f7f8fa',
backgroundImage: '', backgroundImage: '',
}, },
props: { props: {
pageNeedLogin: false,
titleBgColor: '#fff', titleBgColor: '#fff',
titleUseUrl: false, titleUseUrl: false,
showPageBottomTip: true, showPageBottomTip: true,
pageBottomTxt: '没有更多啦~', pageBottomTxt: '没有更多啦~',
pageBottomColor: '#333', pageBottomColor: '#333',
couponModal: [],
showBackTop: true, showBackTop: true,
btAttachVal: [ btAttachVal: [
{ {
......
This diff is collapsed.
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
"test": "cross-env NODE_ENV=production EGG_SERVER_ENV=sit egg-scripts start --port 80 --workers 1", "test": "cross-env NODE_ENV=production EGG_SERVER_ENV=sit egg-scripts start --port 80 --workers 1",
"stop": "egg-scripts stop", "stop": "egg-scripts stop",
"backend": "nohup egg-scripts start --port 7001 --workers 4", "backend": "nohup egg-scripts start --port 7001 --workers 4",
"dev": "cross-env NODE_ENV=test APOLLO_CLUSTER=k8s NAMESPACE=yxm npm run apollo && egg-bin dev -r egg-ts-helper/register --port 7002", "dev": "cross-env NODE_ENV=test APOLLO_CLUSTER=k8s NAMESPACE=ds npm run apollo && egg-bin dev -r egg-ts-helper/register --port 7002",
"debug": "egg-bin debug -r egg-ts-helper/register", "debug": "egg-bin debug -r egg-ts-helper/register",
"apollo": "node bin/apollo.js", "apollo": "node bin/apollo.js",
"build": "npm run tsc && cross-env NODE_ENV=production APOLLO_CLUSTER=3C npm run apollo && cross-env COS_ENV=production easy build --devtool", "build": "npm run tsc && cross-env NODE_ENV=production APOLLO_CLUSTER=3C npm run apollo && cross-env COS_ENV=production easy build --devtool",
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
"@hubcarl/json-typescript-mapper": "^2.0.0", "@hubcarl/json-typescript-mapper": "^2.0.0",
"@qg/apollo-nodejs": "^2.1.2", "@qg/apollo-nodejs": "^2.1.2",
"@qg/cherry-ui": "2.23.10", "@qg/cherry-ui": "2.23.10",
"@qg/citrus-ui": "0.3.54", "@qg/citrus-ui": "0.3.64",
"@riophae/vue-treeselect": "^0.4.0", "@riophae/vue-treeselect": "^0.4.0",
"@types/lodash": "^4.14.117", "@types/lodash": "^4.14.117",
"@types/node": "^10.12.0", "@types/node": "^10.12.0",
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
"egg-webpack": "^4.4.7", "egg-webpack": "^4.4.7",
"egg-webpack-vue": "^2.0.0", "egg-webpack-vue": "^2.0.0",
"extend": "~3.0.0", "extend": "~3.0.0",
"html2canvas": "^1.0.0-rc.7", "html2canvas": "1.4.1",
"iview": "^2.9.0", "iview": "^2.9.0",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
"less": "^3.12.2", "less": "^3.12.2",
......
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
"only-arrow-functions": false, "only-arrow-functions": false,
"interface-over-type-literal": false, "interface-over-type-literal": false,
"ter-indent": [ true, 2], "ter-indent": [ true, 2],
"no-var-requires": false "no-var-requires": false,
"triple-equals": false
}, },
"rulesDirectory": ["app"] "rulesDirectory": ["app"]
} }
\ No newline at end of file
...@@ -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