Commit 85f2a80f authored by 郭志伟's avatar 郭志伟

feat(tenant): 添加租户概念追加相关配置

parent 7b231575
import { Controller, Context } from 'egg';
import { trim, omitBy } from 'lodash';
import { omitBy } from 'lodash';
import { v1 as uuidv1 } from 'uuid';
import * as path from 'path';
export default class EditorController extends Controller {
public async login(ctx: Context) {
......@@ -29,7 +28,7 @@ export default class EditorController extends Controller {
if (!pageInfo.uuid) {
pageInfo.uuid = uuidv1().replace(/-/g, '');
}
const result = await ctx.model.PageInfo.update(pageInfo, {where: { uuid: pageInfo.uuid }});
const result = await ctx.model.PageInfo.update(pageInfo, {where: { uuid: pageInfo.uuid, tenantId: ctx.headers['qg-tenant-id'] }});
await ctx.service.redis.set(`pageInfo:${pageInfo.uuid}`, pageInfo);
await ctx.service.redis.del(`page:${pageInfo.uuid}`);
ctx.body = ctx.helper.ok(result);
......@@ -38,7 +37,14 @@ export default class EditorController extends Controller {
public async get(ctx: Context) {
let pageInfo = await ctx.service.redis.get(`pageInfo:${ctx.params.uuid}`);
if (!pageInfo) {
pageInfo = await ctx.model.PageInfo.findOne({where: { uuid: ctx.params.uuid }});
pageInfo = await ctx.model.PageInfo.findOne({where: { uuid: ctx.params.uuid, tenantId: ctx.headers['qg-tenant-id'] }});
if (ctx.query.lite && pageInfo.elements.length) {
const page = JSON.parse(pageInfo.page || []);
for (let i = 0; i < page.elements.length; i++) {
delete page.elements[i].schame
}
pageInfo.page = JSON.stringify(page);
}
await ctx.service.redis.set(`pageInfo:${ctx.params.uuid}`, pageInfo);
}
ctx.body = ctx.helper.ok(pageInfo);
......@@ -52,6 +58,7 @@ export default class EditorController extends Controller {
pageName: pageName && { like: `%${pageName}%`},
pageDescribe: pageDescribe && { like: `%${pageDescribe}%`},
isPublish,
tenantId: ctx.headers['qg-tenant-id'],
enable: 1
}, v => !v);
if (type === 'list') {
......@@ -67,7 +74,7 @@ export default class EditorController extends Controller {
}
public async getTemplateList(ctx: Context) {
const list = await ctx.model.PageInfo.findAll({ where: { isTemplate: 1 } });
const list = await ctx.model.PageInfo.findAll({ where: { isTemplate: 1, tenantId: ctx.headers['qg-tenant-id'] } });
ctx.body = ctx.helper.ok(list);
}
......@@ -77,6 +84,7 @@ export default class EditorController extends Controller {
[ctx.model.Sequelize.Op.lt]: Date.now(),
[ctx.model.Sequelize.Op.gt]: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)
},
tenantId: ctx.headers['qg-tenant-id'],
enable: 1,
isPublish: 1
};
......@@ -91,7 +99,7 @@ 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 }});
const pageInfo = await ctx.model.PageInfo.update({ enable: 0 }, {where: { id: +ctx.params.pageId, tenantId: ctx.headers['qg-tenant-id'] }});
ctx.body = ctx.helper.ok(pageInfo);
}
}
\ No newline at end of file
import { Controller, Context } from 'egg';
import { omitBy } from 'lodash';
type GetWhere = {
id?: number | unknown;
tenantId?: number | unknown;
};
export default class TenantAuthController extends Controller {
public async add(ctx: Context) {
const authInfo = ctx.request.body;
await ctx.model.TenantAuth.create({ ...authInfo });
ctx.body = ctx.helper.ok();
}
public async update(ctx: Context) {
const authInfo = ctx.request.body;
if (!authInfo.tenantId) {
ctx.body = ctx.helper.fail({ message: '无租户id', code: '0001', businessCode: '0002' });
return;
}
await ctx.model.TenantAuth.update(authInfo, {where: { id: authInfo.id }});
ctx.body = ctx.helper.ok();
}
public async get(ctx: Context) {
const where: GetWhere = {};
if (ctx.headers['qg-tenant-id']) where.tenantId = ctx.headers['qg-tenant-id'];
if (ctx.query.id) where.tenantId = ctx.query.id;
try {
const result = await ctx.model.TenantAuth.findOne({ where });
ctx.body = ctx.helper.ok(result);
} catch (error) {
console.log(error);
ctx.body = ctx.helper.fail({ message: error, code: '0001', businessCode: '0001' });
}
}
public async getList(ctx: Context) {
const { pageSize, pageNo, tenantName, tenantId } = ctx.query;
const { like } = ctx.model.Sequelize.Op;
let where = omitBy({
tenantName: tenantName && { like: `%${tenantName}%`},
tenantId: tenantId && { like: `%${tenantId}%`},
enable: 1
}, v => !v);
const { count: total, rows: data } = await ctx.model.PageInfo.findAndCountAll({ where, limit: +pageSize || 10,
offset: (+pageNo - 1) * +pageSize || 0, order: [['updated_at', 'DESC']] });
ctx.body = ctx.helper.ok({ total, data });
}
public async delete(ctx: Context) {
const authInfo = await ctx.model.PageInfo.update({ enable: 0 }, {where: { id: +ctx.params.id }});
ctx.body = ctx.helper.ok(authInfo);
}
}
\ No newline at end of file
......@@ -86,6 +86,12 @@ export class PageInfo extends Model<PageInfo> {
},
})
updatedAt: string;
@Column({
type: DataType.INTEGER(11),
field: 'tenant_id',
})
tenantId: number;
}
export default () => PageInfo;
\ No newline at end of file
/**
* @desc 页面信息表
*/
import { AutoIncrement, Column, DataType, Model, PrimaryKey, Table, AllowNull } from 'sequelize-typescript';
@Table({
modelName: 'tenant_auth_info',
freezeTableName: true
})
export class TenantAuth extends Model<TenantAuth> {
@PrimaryKey
@AutoIncrement
@Column({
type: DataType.INTEGER(11)
})
id: number;
@AllowNull(false)
@Column({
type: DataType.INTEGER(11),
field: 'tenant_id',
})
tenantId: number;
@Column({
field: 'tenant_name',
type: DataType.STRING(32)
})
tenantName: string;
@Column({
field: 'auth_config',
type: DataType.TEXT
})
authConfig: string;
@Column({
field: 'config_desc',
type: DataType.STRING(255)
})
configDesc: string;
@Column({
type: DataType.INTEGER(1)
})
enable: number;
@Column({
type: DataType.DATE,
field: 'updated_at',
get() {
const moment = require('moment');
const updatedAt = this.getDataValue('updatedAt');
return moment(updatedAt).format('YYYY-MM-DD HH:mm:ss');
},
})
updatedAt: string;
}
export default () => TenantAuth;
\ No newline at end of file
......@@ -19,5 +19,7 @@ export default (application: Application) => {
router.get('/editor/login', controller.editor.login);
router.get('/editor', controller.editor.home);
router.get('/editor/*', controller.editor.home);
router.post('/tenant/add', controller.tenantAuth.add);
router.get('/tenant/get', controller.tenantAuth.get);
router.get('/*', controller.editor.home);
};
\ No newline at end of file
......@@ -20,6 +20,10 @@ export default {
getTemplateList() {
return http.get('editor/get/template');
},
// 获取权限
getTenantAuthInfo() {
return http.get('tenant/get');
},
// 商品列表-查询
skuInfo(params) {
return http.post(`${config.opapiHost}/kdspOp/api/kdsp/activity/activity-goods/sku-info/list`, params, {
......
......@@ -37,6 +37,7 @@
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
data() {
return {
......@@ -65,6 +66,9 @@
}
},
methods: {
...mapActions([
'fetchTenantAuthData'
]),
select(name) {
console.log('select', name);
const current = this.page[name];
......@@ -79,6 +83,7 @@
},
},
created() {
this.fetchTenantAuthData();
console.log('isDashboard', this.$route);
},
computed: {
......
import { Action, State } from 'vuex-class';
import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
import iView from 'iview';
import Raven from 'raven-js';
......@@ -27,6 +28,8 @@ if (process.env.SENTRY_ENV !== 'test' && process.env.NODE_ENV === 'production')
export default class Layout extends Vue {
activeName: string = 'list';
username: string = localStorage.get('user')?.name || '陌生人';
@Action('fetchTenantAuthData') fetchTenantAuthData;
@State(state => state) state;
get isDashboard() {
return this.activeName === 'detail';
......@@ -48,7 +51,8 @@ export default class Layout extends Vue {
window.location.href = '/editor/login';
}
created() {
async created() {
await this.fetchTenantAuthData();
console.log('>>EASY_ENV_IS_NODE create', EASY_ENV_IS_NODE);
}
}
\ No newline at end of file
......@@ -10,5 +10,6 @@ export default {
opapiHost: `https://opapi-bfe.liangkebang.net`,
qiniuUpHost: `${protocol}//up-z0.qiniup.com`,
// kdspHost: 'https://kdsp-api-bfe.liangkebang.net',
kdspHost: 'https://talos-bfe.liangkebang.net'
kdspHost: 'https://talos-bfe.liangkebang.net',
yxmTenantId: 560761
};
\ No newline at end of file
......@@ -7,7 +7,8 @@ const hostMap = {
shenceUrl: `${protocol}//bn.xyqb.com/sa?project=production`,
opapiHost: `${protocol}//opapi.q-gp.com`,
qiniuUpHost: `${protocol}//up-z0.qiniup.com`,
kdspHost: `${protocol}//talos.q-gp.com`
kdspHost: `${protocol}//talos.q-gp.com`,
yxmTenantId: 560761
};
if (EASY_ENV_IS_BROWSER) {
......
......@@ -7,6 +7,7 @@ const hostMap = {
qiniuHost: `${protocol}//appsync.lkbang.net`,
shenceUrl: `${protocol}//bn.xyqb.com/sa?project=default`,
qiniuUpHost: `${protocol}//up-z0.qiniup.com`,
yxmTenantId: 560761
};
if (EASY_ENV_IS_BROWSER) {
......
import Vue from 'vue';
import { sync } from 'vuex-router-sync';
export default class App {
config: any;
constructor(config) {
......
......@@ -12,6 +12,7 @@ import { getStyle } from '@service/utils.service';
export default class DynamicForm extends Vue {
@Getter('pageInfo') pageInfo;
@Getter('pageData') pageData;
@State(state => state.tenant.hideShareBtn) hideShareBtn;
@Prop(Boolean) value;
showPopup: boolean = false;
......
......@@ -14,7 +14,7 @@
<Upload v-model="formCustom.shareCoverImage" :show-input="false" />
<p class="basic-form__tip">可选,默认使用页面缩略图,建议图片比例1:1,图片格式jpg、png</p>
</FormItem>
<FormItem label="分享打开方式" prop="shareOpenMethod">
<FormItem label="分享打开方式" prop="shareOpenMethod" v-if="!hideShareBtn">
<RadioGroup v-model="formCustom.shareOpenMethod">
<Radio :label="1" :disabled="!enableShare">小程序</Radio>
<Radio :label="2" :disabled="!enableShare">APP</Radio>
......
......@@ -7,6 +7,7 @@ import { v4 as uuid } from 'uuid';
@Component({ name: 'DynamicComponent' })
export default class DynamicComponent extends Vue {
@State(state => state.editor.gridLayout.colNum) colNum;
@State(state => state.tenant.hideFinanceTab) hideFinanceTab;
@Prop({ default: () => ([]), type: Array }) data;
@Prop(String) type;
......@@ -14,6 +15,12 @@ export default class DynamicComponent extends Vue {
eleConfigMap: object = {};
activeName: string = '';
@Watch('hideFinanceTab', { immediate: true })
onHideFinanceTabChange(val) {
if (val && this.eleConfigMap.fs) {
this.$delete(this.eleConfigMap, 'fs');
}
}
@Watch('data', { immediate: true })
onDataChange(newVal) {
if (this.type === 'business') {
......
......@@ -27,6 +27,7 @@ const CONFIG_MAP = {
['自定义']: DEFAULT_CONFIG
};
export default {
name: 'BackTopPicker',
mixins: [FormList],
data() {
return {
......
......@@ -5,7 +5,7 @@ import DynamicPageForm from '@editor/component/DynamicPageForm/index.vue';
import DynamicComponentSort from '@editor/component/DynamicComponentSort/index.vue';
// import EventBus from '@service/eventBus.service';
@Component({ components: { DynamicPageForm, DynamicForm, DynamicComponentSort }, name: 'RecordModal' })
@Component({ components: { DynamicPageForm, DynamicForm, DynamicComponentSort }, name: 'DynamicFormTabs' })
export default class DynamicFormTabs extends Vue {
@Getter('curRightTabName') curRightTabName!: string | null;
......
......@@ -15,6 +15,7 @@ import { SHOP_CART_CONFIG } from '@service/staticData.service';
@Component({ components: { Upload, ColorSelector, BaseSelect, Textarea, Number, FormList, BackTopPicker, SwitchBtn }, name: 'DynamicPageForm' })
export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
@Getter('pageData') pageData;
@State(state => state.tenant.hideShareBtn) hideShareBtn;
title: string = '页面';
commonStyleForm: object = {};
......@@ -154,6 +155,14 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
get propsSchema() {
return [...this.titleSchema, ...this.bottomSchema, ...this.floatSchema];
}
@Watch('hideShareBtn', { immediate: true })
onAuthStateChange(val) {
const options = val ? ['购物车', '自定义'] : ['购物车', '分享', '自定义'];
this.$set(this.floatSchema[1].formControl[0], 'options', options);
console.log(this.floatSchema[1].formControl[0].options);
}
@Watch('pageData', { immediate: true, deep: true })
onElementChange() {
this.commonStyleSchame.forEach(schame => {
......
......@@ -3,7 +3,7 @@ import DynamicComponent from '@editor/component/DynamicComponent/index.vue';
import { basicComponents, businessComponents } from '@lib/config';
import { State } from 'vuex-class';
@Component({ components: { DynamicComponent }, name: 'RecordModal' })
@Component({ components: { DynamicComponent }, name: 'DynamicForm' })
export default class DynamicForm extends Vue {
@State(state => state.editor.templateList) templateList!: any[];
......
......@@ -59,7 +59,7 @@ export default {
[h(
'img',{
attrs: {
src: params.row.coverImage,
src: params.row.coverImage + '?imageMogr2/format/jpg/thumbnail/!5p',
},
style: {
width: '37.5px',
......
......@@ -3,6 +3,7 @@ import Vue from 'vue';
import Vuex from 'vuex';
import RootState from './state';
import Editor from './modules/editor';
import Tenant from './modules/tenant';
Vue.use(Vuex);
......@@ -13,7 +14,8 @@ export default function createStore(initState: any = {}) {
return new Vuex.Store<RootState>({
state,
modules: {
editor: new Editor(editor)
tenant: new Tenant(),
editor: new Editor(editor),
}
});
}
\ No newline at end of file
......@@ -57,6 +57,7 @@ export interface PageInfo {
pageDescribe?: string;
pageKeywords?: string;
uuid?: string;
tenantId?: number;
}
export default interface EditorState {
......
import operationApi from '@api/operation.api';
import { Module, GetterTree, ActionTree, MutationTree } from 'vuex';
import {
FETCH_AUTH_INFO
} from './type';
import RootState from '../../state';
import EditorAuthInfo from './state';
import Vue from 'vue';
export default class TenantModule implements Module<EditorAuthInfo, RootState> {
state: EditorAuthInfo;
getters: GetterTree<EditorAuthInfo, RootState> = {
authData(state: EditorAuthInfo) {
return state;
}
};
actions: ActionTree<EditorAuthInfo, RootState> = {
async fetchTenantAuthData({ commit }) {
const res: Record<any, any> = await operationApi.getTenantAuthInfo();
commit(FETCH_AUTH_INFO, JSON.parse(res.authConfig as string));
}
};
mutations: MutationTree<EditorAuthInfo> = {
[FETCH_AUTH_INFO](state, data) {
// ! 此处存取存在问题
Vue.set(this.state, 'tenant', data);
}
};
constructor() {
this.state = {} as EditorAuthInfo;
}
}
\ No newline at end of file
export default interface EditorAuthInfo {
hideFinanceTab?: boolean;
hideShareBtn?: boolean;
}
\ No newline at end of file
'use strict';
export const FETCH_AUTH_INFO = 'FETCH_AUTH_INFO';
......@@ -96,7 +96,9 @@ instance.interceptors.request.use(
config.headers['X-Auth-Token'] = token;
if (config.accessToken) { config.headers['Access-Token'] = token; }
}
// TODO 需要确定后台字段是什么
const tenantId = localStorage.get('tenantId') || '';
config.headers['qg-tenant-id'] = tenantId || '';
return config;
},
error => {
......
......@@ -3,9 +3,11 @@
import 'egg';
import ExportEditor from '../../../app/controller/editor';
import ExportTenantAuth from '../../../app/controller/tenantAuth';
declare module 'egg' {
interface IController {
editor: ExportEditor;
tenantAuth: ExportTenantAuth;
}
}
......@@ -3,9 +3,11 @@
import 'egg';
import ExportPageInfo from '../../../app/model/pageInfo';
import ExportTenantAuth from '../../../app/model/tenantAuth';
declare module 'egg' {
interface IModel {
PageInfo: ReturnType<typeof ExportPageInfo>;
TenantAuth: ReturnType<typeof ExportTenantAuth>;
}
}
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