Commit 2170c951 authored by 郝聪敏's avatar 郝聪敏

feature: 项目拆分

parent 91acb5e9
# egg-vue-typescript-boilerplate
# quantum-blocks
基于 Egg + Vue + Webpack SSR 服务端渲染和 CSR 前端渲染工程骨架项目。
基于 Egg + Vue + Webpack SSR 服务端渲染和 CSR 前端渲染工程项目。
Single Page Application Isomorphic Example for Egg + Vue, Front-End and Node of The Application are Written in TypeScript.
......
import { Controller, Context } from 'egg';
export default class ActivityController extends Controller {
public async home(ctx: Context) {
await ctx.render('activity.js', { url: ctx.url });
}
}
\ No newline at end of file
......@@ -18,5 +18,4 @@ export default (application: Application) => {
router.get('/editor/login', controller.editor.login);
router.get('/editor', controller.editor.home);
router.get('/editor/*', controller.editor.home);
router.get('/activity/:id', controller.activity.home);
};
\ No newline at end of file
app/web/asset/images/favicon.ico

4.19 KB | W: | H:

app/web/asset/images/favicon.ico

2.3 KB | W: | H:

app/web/asset/images/favicon.ico
app/web/asset/images/favicon.ico
app/web/asset/images/favicon.ico
app/web/asset/images/favicon.ico
  • 2-up
  • Swipe
  • Onion skin
import { Vue, Component, Prop } from 'vue-property-decorator';
import Raven from 'raven-js';
import RavenVue from 'raven-js/plugins/vue';
import { release } from '@/.sentryclirc';
import '@/service/qg.service';
// 初始化sentry
if (process.env.SENTRY_ENV !== 'test' && process.env.NODE_ENV === 'production') {
Raven.config('http://0785298052fd46128e201f30ca649102@sentry.q-gp.com/64', {
release,
environment: 'production'
})
.addPlugin(RavenVue, Vue)
.install();
}
@Component({
name: 'Layout'
})
export default class Layout extends Vue {
@Prop({ type: String, default: 'egg' }) title?: string;
@Prop({ type: String, default: 'Vue TypeScript Framework, Server Side Render' }) description?: string;
@Prop({ type: String, default: 'Vue,TypeScript,Isomorphic' }) keywords?: string;
isNode: boolean = EASY_ENV_IS_NODE;
created() {
console.log('>>EASY_ENV_IS_NODE create', EASY_ENV_IS_NODE);
}
}
\ No newline at end of file
<template>
<html v-if="isNode">
<head>
<title>{{title}}</title>
<meta name="keywords" :content="keywords">
<meta name="description" :content="description">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="/public/asset/css/reset.css">
</head>
<body>
<div id="app"><slot></slot></div>
</body>
</html>
<div v-else-if="!isNode" id="app"><slot></slot></div>
</template>
<style>
html {
font-size: 10vw !important;
}
@media screen and (min-width: 768Px) {
html {
font-size: 37.5Px !important;
}
body {
max-width: 375Px;
max-height: 667Px;
margin: 0 auto !important;
}
}
</style>
<script lang="ts" src="./index.ts"></script>
\ No newline at end of file
const protocol = EASY_ENV_IS_BROWSER ? window.location.protocol : 'http';
export default {
apiHost: `http://localhost:7001/`,
apiHost: `http://localhost:7002/`,
// apiHost: `http://192.168.28.199:7001/`,
// apiHost: 'https://quantum-vcc2.liangkebang.net/',
h5Host: 'https://quantum-h5-vcc2.liangkebang.net/',
qiniuHost: `https://appsync.lkbang.net/`,
shenceUrl: `${protocol}//bn.xyqb.com/sa?project=default`,
opapiHost: `https://opapi-vcc2.liangkebang.net`,
......
......@@ -2,6 +2,7 @@ const protocol = EASY_ENV_IS_BROWSER ? window.location.protocol : 'https';
export default {
apiHost: `https://quantum-blocks.q-gp.com/`,
h5Host: 'https://quantum-h5-vcc2.liangkebang.net/',
qiniuHost: `https://appsync.lkbang.net/`,
shenceUrl: `${protocol}//bn.xyqb.com/sa?project=production`,
opapiHost: `${protocol}//opapi.q-gp.com`,
......
......@@ -2,6 +2,7 @@ const protocol = EASY_ENV_IS_BROWSER ? window.location.protocol : 'https';
export default {
apiHost: `https://quantum-vcc2.liangkebang.net/`,
h5Host: 'https://quantum-h5-vcc2.liangkebang.net/',
opapiHost: 'https://opapi-vcc2.liangkebang.net',
qiniuHost: `https://appsync.lkbang.net/`,
shenceUrl: `${protocol}//bn.xyqb.com/sa?project=production`,
......
......@@ -29,7 +29,6 @@
type: Number,
default: 1
},
loop: Boolean,
autoplay: Boolean,
animation: Boolean
},
......@@ -40,9 +39,8 @@
let lastProgress = 0;
return {
showSwiper: true,
// style: {},
swiperOptions: {
loop: this.loop,
loop: this.slidesPerColumn === 1 ? true : false,
slidesPerView: 4,
slidesPerColumn: this.slidesPerColumn,
spaceBetween: 8,
......@@ -117,8 +115,10 @@
return new Array(length).fill(0).map((v, i) => [0, 3].includes(i % baseNumber) ? 1 : 0);
},
style() {
return {
return this.animation ? {
transition: 'all .2s cubic-bezier(.4, 0, .2, 1)'
} : {
transform: 'none'
};
}
},
......
......@@ -550,11 +550,6 @@ export const businessComponents = [
name: '行数',
type: 'InputNumber'
},
{
key: 'loop',
name: '循环',
type: 'checkbox'
},
{
key: 'autoplay',
name: '自动播放',
......@@ -622,7 +617,6 @@ export const businessComponents = [
link: 'http://activitystatic.q-gp.com'
}],
slidesPerColumn: 1,
loop: false,
autoplay: false,
animation: false
}
......
import { cloneDeep } from 'lodash';
import { Component, Prop, Vue, Mixins } from 'vue-property-decorator';
import { Action, Mutation, State, Getter } from 'vuex-class';
import TransformStyleMixin from '@/page/mixins/transformStyle.mixin';
import CustomMarquee from '@/lib/Marquee/index.vue';
import { resizeDiv } from '@/service/utils.service';
@Component({ components: { CustomMarquee }, name: 'FreedomContainer' })
export default class FreedomContainer extends Mixins(TransformStyleMixin) {
@Getter('pageData') pageData;
@State(state => state.editor.curChildIndex) curChildIndex;
@Mutation('UPDATE_PAGE_INFO') updatePageInfo;
@Prop({type: Object, default: () => ({ child: [] })}) childItem;
@Prop(String) backgroundImage;
mounted() {
// 根据背景图设置元素高度
const index = this.pageData?.elements?.findIndex(v => v.point?.responsive);
const { props: { backgroundImage }, point } = this.pageData?.elements[index] || { props: {}};
if (backgroundImage) {
resizeDiv(backgroundImage, null, null, (height) => {
this.updatePageInfo({ containerIndex: index, data: { ...this.pageData?.elements[index], point: { ...point, h: height ?? point.h } } });
});
}
}
}
\ No newline at end of file
<template>
<div class="freedom">
<div class="freedom-body">
<component :class="['freedom-body-item', { 'Fb-item_selected': curChildIndex === index }]" v-for="(item, index) in childItem.child" :style="transformStyle(item.commonStyle, 'container')" :is="item.name" :key="index" v-bind="item.props"></component>
</div>
</div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less" scoped>
.freedom {
height: 100%;
width: 100%;
header {
width: 100%;
height: 48px;
line-height: 48px;
text-align: center;
border-bottom: 1px solid #f0f0f0;
}
&-body {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
&_full {
height: calc(100% - 48px);
}
}
}
</style>
\ No newline at end of file
let currentDir = 'auto';
// let currentDir = "auto";
function hasDocument() {
return (typeof document !== 'undefined');
}
function hasWindow() {
return (typeof window !== 'undefined');
}
export function getDocumentDir() {
if (!hasDocument()) {
return currentDir;
}
const direction = (typeof document.dir !== 'undefined') ?
document.dir :
document.getElementsByTagName('html')[0].getAttribute('dir');
return direction;
}
export function setDocumentDir(dir) {
// export function setDocumentDir(dir){
if (!hasDocument) {
currentDir = dir;
return;
}
const html = document.getElementsByTagName('html')[0];
html.setAttribute('dir', dir);
}
export function addWindowEventListener(event, callback) {
if (!hasWindow) {
callback();
return;
}
window.addEventListener(event, callback);
}
export function removeWindowEventListener(event, callback) {
if (!hasWindow) {
return;
}
window.removeEventListener(event, callback);
}
// Get {x, y} positions from event.
export function getControlPosition(e) {
return offsetXYFromParentOf(e);
}
// Get from offsetParent
export function offsetXYFromParentOf(evt) {
const offsetParent = evt.target.offsetParent || document.body;
const offsetParentRect = evt.offsetParent === document.body ? {left: 0, top: 0} : offsetParent.getBoundingClientRect();
const x = evt.clientX + offsetParent.scrollLeft - offsetParentRect.left;
const y = evt.clientY + offsetParent.scrollTop - offsetParentRect.top;
/*const x = Math.round(evt.clientX + offsetParent.scrollLeft - offsetParentRect.left);
const y = Math.round(evt.clientY + offsetParent.scrollTop - offsetParentRect.top);*/
return {x, y};
}
// Create an data object exposed by <DraggableCore>'s events
export function createCoreData(lastX, lastY, x, y) {
// State changes are often (but not always!) async. We want the latest value.
const isStart = !isNum(lastX);
if (isStart) {
// If this is our first move, use the x and y as last coords.
return {
deltaX: 0, deltaY: 0,
lastX: x, lastY: y,
x, y
};
} else {
// Otherwise calculate proper values.
return {
deltaX: x - lastX, deltaY: y - lastY,
lastX, lastY,
x, y
};
}
}
function isNum(num) {
return typeof num === 'number' && !isNaN(num);
}
// @flow
import {cloneLayout, compact, correctBounds} from './utils';
// import type {Layout} from './utils';
// export type ResponsiveLayout = {lg, md, sm, xs, xxs};
// type Breakpoint = string;
// type Breakpoints = {lg, md, sm, xs, xxs};
/**
* Given a width, find the highest breakpoint that matches is valid for it (width > breakpoint).
*
* @param {Object} breakpoints Breakpoints object (e.g. {lg: 1200, md: 960, ...})
* @param {Number} width Screen width.
* @return {String} Highest breakpoint that is less than width.
*/
export function getBreakpointFromWidth(breakpoints, width) {
const sorted = sortBreakpoints(breakpoints);
let matching = sorted[0];
for (let i = 1, len = sorted.length; i < len; i++) {
const breakpointName = sorted[i];
if (width > breakpoints[breakpointName]) { matching = breakpointName; }
}
return matching;
}
/**
* Given a breakpoint, get the # of cols set for it.
* @param {String} breakpoint Breakpoint name.
* @param {Object} cols Map of breakpoints to cols.
* @return {Number} Number of cols.
*/
export function getColsFromBreakpoint(breakpoint, cols) {
if (!cols[breakpoint]) {
throw new Error('ResponsiveGridLayout: `cols` entry for breakpoint ' + breakpoint + ' is missing!');
}
return cols[breakpoint];
}
/**
* Given existing layouts and a new breakpoint, find or generate a new layout.
*
* This finds the layout above the new one and generates from it, if it exists.
*
* @param {Array} orgLayout Original layout.
* @param {Object} layouts Existing layouts.
* @param {Array} breakpoints All breakpoints.
* @param {String} breakpoint New breakpoint.
* @param {String} breakpoint Last breakpoint (for fallback).
* @param {Number} cols Column count at new breakpoint.
* @param {Boolean} verticalCompact Whether or not to compact the layout
* vertically.
* @return {Array} New layout.
*/
export function findOrGenerateResponsiveLayout(orgLayout, layouts, breakpoints,
breakpoint, lastBreakpoint,
cols, verticalCompact) {
// If it already exists, just return it.
if (layouts[breakpoint]) { return cloneLayout(layouts[breakpoint]); }
// Find or generate the next layout
let layout = orgLayout;
const breakpointsSorted = sortBreakpoints(breakpoints);
const breakpointsAbove = breakpointsSorted.slice(breakpointsSorted.indexOf(breakpoint));
for (let i = 0, len = breakpointsAbove.length; i < len; i++) {
const b = breakpointsAbove[i];
if (layouts[b]) {
layout = layouts[b];
break;
}
}
layout = cloneLayout(layout || []); // clone layout so we don't modify existing items
return compact(correctBounds(layout, {cols}), verticalCompact);
}
export function generateResponsiveLayout(layout, breakpoints,
breakpoint, lastBreakpoint,
cols, verticalCompact) {
// If it already exists, just return it.
/*if (layouts[breakpoint]) return cloneLayout(layouts[breakpoint]);
// Find or generate the next layout
let layout = layouts[lastBreakpoint];*/
/*const breakpointsSorted = sortBreakpoints(breakpoints);
const breakpointsAbove = breakpointsSorted.slice(breakpointsSorted.indexOf(breakpoint));
for (let i = 0, len = breakpointsAbove.length; i < len; i++) {
const b = breakpointsAbove[i];
if (layouts[b]) {
layout = layouts[b];
break;
}
}*/
layout = cloneLayout(layout || []); // clone layout so we don't modify existing items
return compact(correctBounds(layout, {cols}), verticalCompact);
}
/**
* Given breakpoints, return an array of breakpoints sorted by width. This is usually
* e.g. ['xxs', 'xs', 'sm', ...]
*
* @param {Object} breakpoints Key/value pair of breakpoint names to widths.
* @return {Array} Sorted breakpoints.
*/
export function sortBreakpoints(breakpoints) {
const keys = Object.keys(breakpoints);
return keys.sort(function(a, b) {
return breakpoints[a] - breakpoints[b];
});
}
'use strict';
import App from '../../framework/app';
import createStore from '../store/index';
import createRouter from './router/index';
import entry from './view/home/index.vue';
export default new App({ entry, createStore, createRouter }).bootstrap();
\ No newline at end of file
import Vue from 'vue';
import VueRouter from 'vue-router';
import Activity from '../view/activity/index.vue';
Vue.use(VueRouter);
export default function createRouter() {
return new VueRouter({
mode: 'history',
routes: [
{
path: '/activity/:pageId',
component: Activity
}
]
});
}
import { kebabCase } from 'lodash';
import { Vue, Component, Watch, Provide, Mixins } from 'vue-property-decorator';
import eleConfig from '../../../editor/utils/config';
import FreedomContainer from '../../component/FreedomContainer/index.vue';
import GridLayout from '../../component/VueGridLayout/GridLayout.vue';
import GridItem from '../../component/VueGridLayout/GridItem.vue';
// import LoginForm from '@/lib/Form/index.vue';
import DownloadGuide from '@/lib/DownloadGuide/index.vue';
import { Getter, State, Mutation } from 'vuex-class';
import GuideCube from '@/lib/GuideCube/index.vue';
import GoodsTabs from '@/lib/GoodsTabs/index.vue';
import Coupon from '@/lib/Coupon/index.vue';
import Advertisement from '@/lib/Advertisement/index.vue';
import TransformStyleMixin from '@/page/mixins/transformStyle.mixin';
import { getStyle } from '@/service/utils.service';
@Component({ components: { FreedomContainer, GridLayout, GridItem, DownloadGuide, GoodsTabs, GuideCube, Advertisement, Coupon }, name: 'Activity'})
export default class Activity extends Mixins(TransformStyleMixin) {
@Getter('pageData') pageData;
@State(state => state.editor.pageInfo.pageName) pageName;
@State(state => state.editor.gridLayout.rowHeight) rowHeight;
@Provide('editor');
isLayoutComReady = false;
showBackTop = false;
targetEle: HTMLElement | null = null;
get layout() {
return this.pageData && this.pageData.elements.map(v => v.point) || [];
}
@Watch('pageName', { immediate: true })
onPageNameChange(newVal) {
if (EASY_ENV_IS_BROWSER && newVal) {
document.title = newVal;
// 如果是 iOS 设备,则使用如下 hack 的写法实现页面标题的更新
if (navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) {
const hackIframe = document.createElement('iframe');
hackIframe.style.display = 'none';
hackIframe.src = '/public/fixIosTitle.html?r=' + Math.random();
document.body.appendChild(hackIframe);
setTimeout(() => {
document.body.removeChild(hackIframe);
}, 300);
}
}
}
mounted() {
this.targetEle = document.querySelector('body');
this.showBackTop = true;
setTimeout(() => {
this.modfiTabsStyle();
}, 300);
}
fetchApi(options) {
const { store, route } = options;
const { pageId } = route.params;
console.log('fetchApi', route);
return store.dispatch('getPageDate', { pageId });
}
createStyle({h}) {
return EASY_ENV_IS_NODE ? {
height: `${h * this.rowHeight}px`,
} : {};
}
modfiTabsStyle() {
const tabsEle = document.querySelector('.tabs');
if (tabsEle) {
const gridItemEle = tabsEle?.parentNode;
if (gridItemEle?.classList.contains('vue-grid-item')) {
// 处理transform
const transform = getStyle(gridItemEle, 'transform');
const transformY = transform.split('(')[1].split(')')[0].split(',')[5];
gridItemEle.style.transform = 'none';
gridItemEle.style.top = `${transformY}px`;
// 处理backgroundColor
const backgroundColor = getStyle(tabsEle, 'backgroundColor');
const crTabs = tabsEle.childNodes[0];
crTabs.style.backgroundColor = backgroundColor;
const stickyEle = crTabs?.childNodes[0];
if (stickyEle?.classList.contains('cr-sticky') && stickyEle?.childNodes) {
stickyEle.childNodes[0]?.style.backgroundColor = backgroundColor;
}
}
}
}
}
\ No newline at end of file
<template>
<div class="activity" :style="transformStyle(pageData.commonStyle)">
<grid-layout
:layout.sync="layout"
:isDraggable="false"
:isResizable="false"
:col-num="12"
:row-height="rowHeight"
:margin="[0, 0]"
:is-draggable="true"
:is-resizable="true"
:is-mirrored="false"
:vertical-compact="true"
:use-css-transforms="true"
@layout-ready="modfiTabsStyle"
>
<grid-item :style="createStyle(item.point)" v-for="(item, index) in pageData.elements"
:x="item.point.x"
:y="item.point.y"
:w="item.point.w"
:h="item.point.h"
:i="item.point.i"
:key="item.point.i">
<component :style="transformStyle(item.commonStyle)" :data-index="index" :containerIndex="index" :childItem="item" :is="item.name" :key="index" v-bind="item.props"></component>
</grid-item>
</grid-layout>
<cr-back-top v-if="showBackTop && pageData.props.showBackTop" />
</div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less" scoped>
html,
body,
#app {
height: 100%;
/deep/ .cr-popup {
.cr-notify {
font-size: 14px;
}
}
}
.activity {
width: 100%;
height: 100%;
min-height: 100%;
background-color: rgb(244, 244, 244);
box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.2);
/deep/ .vue-grid-layout {
min-height: 667px;
// transform: translateY(-10px);
transition-property: none;
.vue-grid-item {
transition-property: none;
display: flex;
justify-content: center;
align-items: center;
&>*:first-child {
height: 100%;
}
}
}
}
</style>
import { Vue, Component, Emit } from 'vue-property-decorator';
import Layout from 'component/layout/activity/index.vue';
@Component({
components: {
Layout
}
})
export default class Home extends Vue {}
\ No newline at end of file
<template>
<Layout>
<router-view></router-view>
</Layout>
</template>
<script lang="ts" src="./index.ts"></script>
......@@ -6,7 +6,7 @@
<Tooltip placement="top" content="两列">
<Button type="ghost" icon="ios-pause" @click="select(2)"></Button>
</Tooltip>
<Tooltip placement="top" content="列">
<Tooltip placement="top" content="列">
<Button type="ghost" icon="navicon-round" @click="select(3)" ></Button>
</Tooltip>
</div>
......
......@@ -93,7 +93,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
await this.savePageData({ pageInfo, pageData: this.pageData });
this.showSubmitPopup = false;
if (type === 'preview') {
window.open(`${config.apiHost}activity/${this.pageId}`);
window.open(`${config.h5Host}activity/${this.pageId}`);
}
}
}
......
import axios from 'axios';
import basicConfig from '../config';
import localStorage from './localStorage.service';
import { Notify } from '@qg/cherry-ui';
import Notify from '@qg/cherry-ui/src/notify';
const ERR_MESSAGE_MAP = {
status: {
......
import Vue from 'vue';
import {
Button,
Image,
Icon,
Cell,
CellGroup,
Row,
Col,
Dialog,
Popup,
Overlay,
Divider,
Loading,
Picker,
NavBar,
Field,
Checkbox,
CardList,
List,
Form,
Sticky,
Tab,
Tabs,
Notify,
Swipe,
SwipeItem,
Toast,
BackTop
} from '@qg/cherry-ui';
// import {
// Button,
// Image,
// Icon,
// Cell,
// CellGroup,
// Row,
// Col,
// Dialog,
// Popup,
// Overlay,
// Divider,
// Loading,
// Picker,
// NavBar,
// Field,
// Checkbox,
// CardList,
// List,
// Form,
// Sticky,
// Tab,
// Tabs,
// Notify,
// Swipe,
// SwipeItem,
// Toast,
// BackTop
// } from '@qg/cherry-ui';
import Button from '@qg/cherry-ui/src/button';
import Image from '@qg/cherry-ui/src/image';
import Field from '@qg/cherry-ui/src/field';
import Form from '@qg/cherry-ui/src/form';
import Tab from '@qg/cherry-ui/src/tab';
import Tabs from '@qg/cherry-ui/src/tabs';
import BackTop from '@qg/cherry-ui/src/back-top';
import Notify from '@qg/cherry-ui/src/notify';
import Toast from '@qg/cherry-ui/src/toast';
import { KaLoginForm } from '@qg/citrus-ui';
Vue.use(Button);
......
......@@ -36,5 +36,10 @@ export default (appInfo: EggAppConfig) => {
'access'
];
config.cors = {
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
};
return config;
};
......@@ -2,12 +2,10 @@
// Do not modify this file!!!!!!!!!
import 'egg';
import ExportActivity from '../../../app/controller/activity';
import ExportEditor from '../../../app/controller/editor';
declare module 'egg' {
interface IController {
activity: ExportActivity;
editor: ExportEditor;
}
}
......@@ -8,7 +8,6 @@ module.exports = {
entry: {
'login': 'app/web/page/login/index.vue',
'editor': 'app/web/page/editor/index.ts',
'activity': 'app/web/page/activity/index.ts',
},
resolve: {
alias:{
......
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