Commit 0d45399360777777d4666d35d50d5804fb3a6d4f
1 parent
13edea6e
feat: add work list page
Showing
9 changed files
with
324 additions
and
23 deletions
front-end/h5/src/components/core/editor/index.js
| @@ -42,7 +42,8 @@ export default { | @@ -42,7 +42,8 @@ export default { | ||
| 42 | ...mapState('editor', { | 42 | ...mapState('editor', { |
| 43 | editingElement: state => state.editingElement, | 43 | editingElement: state => state.editingElement, |
| 44 | elements: state => state.editingPage.elements, | 44 | elements: state => state.editingPage.elements, |
| 45 | - pages: state => state.work.pages | 45 | + pages: state => state.work.pages, |
| 46 | + work: state => state.work | ||
| 46 | }), | 47 | }), |
| 47 | ...mapState('loading', { | 48 | ...mapState('loading', { |
| 48 | saveWork_loading: state => state.saveWork_loading | 49 | saveWork_loading: state => state.saveWork_loading |
| @@ -98,7 +99,7 @@ export default { | @@ -98,7 +99,7 @@ export default { | ||
| 98 | }, | 99 | }, |
| 99 | render (h) { | 100 | render (h) { |
| 100 | return ( | 101 | return ( |
| 101 | - <a-layout id="luban-layout" style={{ height: '100vh' }}> | 102 | + <a-layout id="luban-editor-layout" style={{ height: '100vh' }}> |
| 102 | <a-layout-header class="header"> | 103 | <a-layout-header class="header"> |
| 103 | <div class="logo">鲁班 H5</div> | 104 | <div class="logo">鲁班 H5</div> |
| 104 | {/* TODO we can show the plugins shortcuts here */} | 105 | {/* TODO we can show the plugins shortcuts here */} |
| @@ -190,14 +191,16 @@ export default { | @@ -190,14 +191,16 @@ export default { | ||
| 190 | </a-tabs> | 191 | </a-tabs> |
| 191 | </a-layout-sider> | 192 | </a-layout-sider> |
| 192 | </a-layout> | 193 | </a-layout> |
| 193 | - <PreviewDialog visible={this.previewVisible} handleClose={() => { this.previewVisible = false }} /> | 194 | + { |
| 195 | + this.previewVisible && <PreviewDialog work={this.work} visible={this.previewVisible} handleClose={() => { this.previewVisible = false }} /> | ||
| 196 | + } | ||
| 194 | </a-layout> | 197 | </a-layout> |
| 195 | ) | 198 | ) |
| 196 | }, | 199 | }, |
| 197 | created () { | 200 | created () { |
| 198 | - let workId = this.$route.query.workId | 201 | + let workId = this.$route.params.workId |
| 202 | + console.log(workId) | ||
| 199 | if (workId) { | 203 | if (workId) { |
| 200 | - // this.$store.dispatch('getWorkById', workId) | ||
| 201 | this.fetchWork(workId) | 204 | this.fetchWork(workId) |
| 202 | } else { | 205 | } else { |
| 203 | this.createWork() | 206 | this.createWork() |
front-end/h5/src/components/core/editor/modals/preview.vue
| 1 | <script> | 1 | <script> |
| 2 | -import { mapActions, mapState } from 'vuex' | 2 | +import { mapActions } from 'vuex' |
| 3 | import QRCode from 'qrcode' | 3 | import QRCode from 'qrcode' |
| 4 | import { API_ORIGIN } from '../../../../constants/api.js' | 4 | import { API_ORIGIN } from '../../../../constants/api.js' |
| 5 | 5 | ||
| @@ -12,12 +12,16 @@ export default { | @@ -12,12 +12,16 @@ export default { | ||
| 12 | handleClose: { | 12 | handleClose: { |
| 13 | type: Function, | 13 | type: Function, |
| 14 | default: () => {} | 14 | default: () => {} |
| 15 | + }, | ||
| 16 | + work: { | ||
| 17 | + type: Object, | ||
| 18 | + default: () => {} | ||
| 15 | } | 19 | } |
| 16 | }, | 20 | }, |
| 17 | computed: { | 21 | computed: { |
| 18 | - ...mapState('editor', { | ||
| 19 | - work: state => state.work | ||
| 20 | - }), | 22 | + // ...mapState('editor', { |
| 23 | + // work: state => state.work | ||
| 24 | + // }), | ||
| 21 | releaseUrl () { | 25 | releaseUrl () { |
| 22 | return `${API_ORIGIN}/works/preview/${this.work.id}` | 26 | return `${API_ORIGIN}/works/preview/${this.work.id}` |
| 23 | } | 27 | } |
front-end/h5/src/components/core/styles/index.scss
| 1 | -#luban-layout { | 1 | +#luban-editor-layout, |
| 2 | +#luban-work-manager-layout { | ||
| 2 | .header { | 3 | .header { |
| 3 | padding: 0 10px; | 4 | padding: 0 10px; |
| 4 | 5 | ||
| @@ -92,4 +93,10 @@ | @@ -92,4 +93,10 @@ | ||
| 92 | 93 | ||
| 93 | .no-border { | 94 | .no-border { |
| 94 | border: none !important; | 95 | border: none !important; |
| 96 | +} | ||
| 97 | + | ||
| 98 | +.flex-center { | ||
| 99 | + display: flex !important; | ||
| 100 | + align-items: center; | ||
| 101 | + justify-content: center; | ||
| 95 | } | 102 | } |
| 96 | \ No newline at end of file | 103 | \ No newline at end of file |
front-end/h5/src/router.js
| 1 | import Vue from 'vue' | 1 | import Vue from 'vue' |
| 2 | import Router from 'vue-router' | 2 | import Router from 'vue-router' |
| 3 | // import Home from './views/Home.vue' | 3 | // import Home from './views/Home.vue' |
| 4 | +import Home from './views/work-manager/index.vue' | ||
| 4 | 5 | ||
| 5 | Vue.use(Router) | 6 | Vue.use(Router) |
| 6 | 7 | ||
| 7 | export default new Router({ | 8 | export default new Router({ |
| 9 | + // mode: 'history', | ||
| 8 | routes: [ | 10 | routes: [ |
| 9 | - // { | ||
| 10 | - // path: '/', | ||
| 11 | - // name: 'home', | ||
| 12 | - // component: Home | ||
| 13 | - // }, | 11 | + { |
| 12 | + path: '/work-manager', | ||
| 13 | + component: Home, | ||
| 14 | + name: 'work-manager', | ||
| 15 | + redirect: '/work-manager/list', | ||
| 16 | + alias: '/', | ||
| 17 | + children: [ | ||
| 18 | + { | ||
| 19 | + path: '/work-manager/list', | ||
| 20 | + name: 'work-manager-list', | ||
| 21 | + component: () => import('@/views/work-manager/list.vue') | ||
| 22 | + }, | ||
| 23 | + { | ||
| 24 | + path: '/work-manager/form-stat', | ||
| 25 | + name: 'form-stat', | ||
| 26 | + component: () => import('@/views/work-manager/form-stat.vue') | ||
| 27 | + } | ||
| 28 | + ] | ||
| 29 | + }, | ||
| 14 | { | 30 | { |
| 15 | path: '/about', | 31 | path: '/about', |
| 16 | name: 'about', | 32 | name: 'about', |
| 17 | component: () => import('./views/About.vue') | 33 | component: () => import('./views/About.vue') |
| 18 | }, | 34 | }, |
| 19 | { | 35 | { |
| 20 | - path: '/', // #!zh 编辑器页面,核心功能部分 | 36 | + path: '/editor/:workId', // #!zh 编辑器页面,核心功能部分 |
| 21 | name: 'editor', | 37 | name: 'editor', |
| 22 | component: () => import('./views/Editor.vue') | 38 | component: () => import('./views/Editor.vue') |
| 23 | - }, | ||
| 24 | - { | ||
| 25 | - path: '/form-stat', // #!zh 表单统计页面 | ||
| 26 | - name: 'form-stat', | ||
| 27 | - component: () => import('./views/About.vue') | ||
| 28 | } | 39 | } |
| 29 | ] | 40 | ] |
| 30 | }) | 41 | }) |
front-end/h5/src/store/modules/editor.js
| @@ -5,6 +5,7 @@ import { actions as elementActions, mutations as elementMutations } from './elem | @@ -5,6 +5,7 @@ import { actions as elementActions, mutations as elementMutations } from './elem | ||
| 5 | import { actions as workActions, mutations as workMutations } from './work' | 5 | import { actions as workActions, mutations as workMutations } from './work' |
| 6 | 6 | ||
| 7 | const state = { | 7 | const state = { |
| 8 | + works: [], | ||
| 8 | work: new Work(), | 9 | work: new Work(), |
| 9 | editingPage: { elements: [] }, | 10 | editingPage: { elements: [] }, |
| 10 | editingElement: null, | 11 | editingElement: null, |
front-end/h5/src/store/modules/work.js
| 1 | -// import Work from '../../components/core/models/work' | ||
| 2 | import Element from '../../components/core/models/element' | 1 | import Element from '../../components/core/models/element' |
| 3 | import strapi from '../../utils/strapi' | 2 | import strapi from '../../utils/strapi' |
| 4 | import Page from '../../components/core/models/page' | 3 | import Page from '../../components/core/models/page' |
| 5 | import { AxiosWrapper } from '../../utils/http.js' | 4 | import { AxiosWrapper } from '../../utils/http.js' |
| 5 | +import router from '@/router.js' | ||
| 6 | 6 | ||
| 7 | export const actions = { | 7 | export const actions = { |
| 8 | previewWork ({ commit }, payload = {}) { | 8 | previewWork ({ commit }, payload = {}) { |
| @@ -13,7 +13,8 @@ export const actions = { | @@ -13,7 +13,8 @@ export const actions = { | ||
| 13 | }, | 13 | }, |
| 14 | createWork ({ commit }, payload) { | 14 | createWork ({ commit }, payload) { |
| 15 | strapi.createEntry('works').then(entry => { | 15 | strapi.createEntry('works').then(entry => { |
| 16 | - window.location = `${window.location.origin}/#/?workId=${entry.id}` | 16 | + router.replace({ name: 'editor', params: { workId: entry.id } }) |
| 17 | + // window.location = `${window.location.origin}/#/editor/${entry.id}` | ||
| 17 | }) | 18 | }) |
| 18 | // commit('createWork') | 19 | // commit('createWork') |
| 19 | // commit('pageManager', { type: 'add' }) | 20 | // commit('pageManager', { type: 'add' }) |
| @@ -46,11 +47,19 @@ export const actions = { | @@ -46,11 +47,19 @@ export const actions = { | ||
| 46 | commit('setWork', entry) | 47 | commit('setWork', entry) |
| 47 | commit('setEditingPage') | 48 | commit('setEditingPage') |
| 48 | }) | 49 | }) |
| 50 | + }, | ||
| 51 | + fetchWorks ({ commit, state }, workId) { | ||
| 52 | + strapi.getEntries('works', {}).then(entries => { | ||
| 53 | + commit('setWorks', entries) | ||
| 54 | + }) | ||
| 49 | } | 55 | } |
| 50 | } | 56 | } |
| 51 | 57 | ||
| 52 | // mutations | 58 | // mutations |
| 53 | export const mutations = { | 59 | export const mutations = { |
| 60 | + setWorks (state, works) { | ||
| 61 | + state.works = works | ||
| 62 | + }, | ||
| 54 | setWork (state, work) { | 63 | setWork (state, work) { |
| 55 | work.pages = work.pages.map(page => { | 64 | work.pages = work.pages.map(page => { |
| 56 | page.elements = page.elements.map(element => new Element(element)) | 65 | page.elements = page.elements.map(element => new Element(element)) |
front-end/h5/src/views/work-manager/form-stat.vue
0 → 100644
front-end/h5/src/views/work-manager/index.vue
0 → 100644
| 1 | +<script> | ||
| 2 | +// import PreView from '@/pages/preview'; | ||
| 3 | +// import Sidebar from './components/sidebar.vue' | ||
| 4 | +import '@/components/core/styles/index.scss' | ||
| 5 | + | ||
| 6 | +const sidebarMenus = [ | ||
| 7 | + { | ||
| 8 | + label: '我的作品', | ||
| 9 | + value: 'workManager', | ||
| 10 | + antIcon: 'bars', | ||
| 11 | + key: '1' | ||
| 12 | + }, | ||
| 13 | + { | ||
| 14 | + label: '数据中心', | ||
| 15 | + value: 'dataCenter', | ||
| 16 | + antIcon: 'snippets', | ||
| 17 | + key: '2', | ||
| 18 | + children: [ | ||
| 19 | + { | ||
| 20 | + label: '基础数据', | ||
| 21 | + value: 'basicData', | ||
| 22 | + antIcon: 'snippets', | ||
| 23 | + key: '2-1' | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + label: '表单统计', | ||
| 27 | + value: 'formData', | ||
| 28 | + antIcon: 'snippets', | ||
| 29 | + key: '2-2' | ||
| 30 | + } | ||
| 31 | + ] | ||
| 32 | + }, | ||
| 33 | + { | ||
| 34 | + label: '模板中心', | ||
| 35 | + value: 'templateCenter', | ||
| 36 | + antIcon: 'snippets', | ||
| 37 | + key: '3', | ||
| 38 | + children: [ | ||
| 39 | + { | ||
| 40 | + label: '免费模板', | ||
| 41 | + value: 'freeTemplates', | ||
| 42 | + antIcon: 'snippets', | ||
| 43 | + key: '3-1' | ||
| 44 | + } | ||
| 45 | + ] | ||
| 46 | + }, | ||
| 47 | + { | ||
| 48 | + label: '账号中心', | ||
| 49 | + value: 'freeTemplate', | ||
| 50 | + antIcon: 'appstore', | ||
| 51 | + key: '4' | ||
| 52 | + } | ||
| 53 | +] | ||
| 54 | + | ||
| 55 | +export default { | ||
| 56 | + components: { | ||
| 57 | + // PreView, | ||
| 58 | + // Sidebar | ||
| 59 | + }, | ||
| 60 | + render (h) { | ||
| 61 | + return ( | ||
| 62 | + <a-layout id="luban-work-manager-layout" style={{ height: '100vh' }}> | ||
| 63 | + <a-layout-header class="header"> | ||
| 64 | + <div class="logo">鲁班 H5</div> | ||
| 65 | + {/* TODO we can show the plugins shortcuts here */} | ||
| 66 | + <a-dropdown style={{ float: 'right', background: 'transparent', margin: '16px 28px 16px 0' }}> | ||
| 67 | + <a-menu slot="overlay" onClick={() => {}}> | ||
| 68 | + <a-menu-item key="1"> | ||
| 69 | + <span>someone@luban</span> | ||
| 70 | + </a-menu-item> | ||
| 71 | + <a-menu-divider /> | ||
| 72 | + <a-menu-item key="2"><a-icon type="setting" />账号设置</a-menu-item> | ||
| 73 | + <a-menu-item key="3"><a-icon type="logout" />退出登录</a-menu-item> | ||
| 74 | + </a-menu> | ||
| 75 | + <a-avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" /> | ||
| 76 | + </a-dropdown> | ||
| 77 | + </a-layout-header> | ||
| 78 | + <a-layout> | ||
| 79 | + <a-layout-sider width="160" style="background: #fff"> | ||
| 80 | + <a-menu | ||
| 81 | + mode="inline" | ||
| 82 | + // defaultSelectedKeys={['1']} | ||
| 83 | + defaultOpenKeys={['1', '2', '3']} | ||
| 84 | + style="height: 100%" | ||
| 85 | + > | ||
| 86 | + { | ||
| 87 | + sidebarMenus.map(menu => ( | ||
| 88 | + menu.children | ||
| 89 | + ? <a-sub-menu key={menu.key}> | ||
| 90 | + <span slot="title"><a-icon type={menu.antIcon} />{menu.label}</span> | ||
| 91 | + { | ||
| 92 | + (menu.children).map(submenu => (<a-menu-item key={submenu.key}>{submenu.label}</a-menu-item>)) | ||
| 93 | + } | ||
| 94 | + </a-sub-menu> | ||
| 95 | + : <a-menu-item key={menu.key}> | ||
| 96 | + <a-icon type={menu.antIcon} /> | ||
| 97 | + <span>{menu.label}</span> | ||
| 98 | + </a-menu-item> | ||
| 99 | + )) | ||
| 100 | + } | ||
| 101 | + </a-menu> | ||
| 102 | + </a-layout-sider> | ||
| 103 | + <a-layout style="padding: 0 24px 24px"> | ||
| 104 | + <a-layout-content style={{ padding: '24px', margin: 0, minHeight: '280px' }}> | ||
| 105 | + <router-view /> | ||
| 106 | + </a-layout-content> | ||
| 107 | + </a-layout> | ||
| 108 | + </a-layout> | ||
| 109 | + {/** <PreviewDialog visible={this.previewVisible} handleClose={() => { this.previewVisible = false }} /> */} | ||
| 110 | + </a-layout>) | ||
| 111 | + } | ||
| 112 | +} | ||
| 113 | +</script> |
front-end/h5/src/views/work-manager/list.vue
0 → 100644
| 1 | +<script> | ||
| 2 | +import { mapState, mapActions } from 'vuex' | ||
| 3 | +import QRCode from 'qrcode' | ||
| 4 | + | ||
| 5 | +import { API_ORIGIN } from '@/constants/api.js' | ||
| 6 | +import PreviewDialog from '@/components/core/editor/modals/preview.vue' | ||
| 7 | + | ||
| 8 | +const ListItemCard = { | ||
| 9 | + props: { | ||
| 10 | + work: { | ||
| 11 | + type: Object, | ||
| 12 | + default: () => {} | ||
| 13 | + }, | ||
| 14 | + handleClickEdit: { | ||
| 15 | + type: Function, | ||
| 16 | + default: () => {} | ||
| 17 | + }, | ||
| 18 | + handleClickPreview: { | ||
| 19 | + type: Function, | ||
| 20 | + default: () => {} | ||
| 21 | + } | ||
| 22 | + }, | ||
| 23 | + data: () => ({ | ||
| 24 | + qrcodeUrl: '' | ||
| 25 | + }), | ||
| 26 | + methods: { | ||
| 27 | + timeFmt (date) { | ||
| 28 | + const dateTime = new Date(date) | ||
| 29 | + const displayTime = `${dateTime.getFullYear()}-${dateTime.getMonth() + | ||
| 30 | + 1}-${dateTime.getDate()}` | ||
| 31 | + return displayTime | ||
| 32 | + }, | ||
| 33 | + genQRCodeUrl (work) { | ||
| 34 | + const url = `${API_ORIGIN}/works/preview/${work.id}` | ||
| 35 | + QRCode.toDataURL(url, (err, url) => { | ||
| 36 | + if (err) console.log(err) | ||
| 37 | + this.qrcodeUrl = url | ||
| 38 | + }) | ||
| 39 | + } | ||
| 40 | + }, | ||
| 41 | + render (h) { | ||
| 42 | + return ( | ||
| 43 | + <a-card hoverable > | ||
| 44 | + <div slot="cover" class="flex-center" style="height: 200px;font-size: 24px;border: 1px dashed #eee;color: #aaa;background: #f7f5f557;" > | ||
| 45 | + { this.qrcodeUrl ? <img src={this.qrcodeUrl} /> : <span>Luban H5</span> } | ||
| 46 | + </div> | ||
| 47 | + <template class="ant-card-actions" slot="actions"> | ||
| 48 | + <a-tooltip effect="dark" placement="bottom" title="编辑"> | ||
| 49 | + <router-link to={{ name: 'editor', params: { workId: this.work.id }}} target="_blank"> | ||
| 50 | + <a-icon type="edit" title="编辑"/> | ||
| 51 | + </router-link> | ||
| 52 | + </a-tooltip> | ||
| 53 | + <a-tooltip effect="dark" placement="bottom" title="预览"> | ||
| 54 | + <a-icon type="eye" title="预览" onClick={this.handleClickPreview} /> | ||
| 55 | + </a-tooltip> | ||
| 56 | + { | ||
| 57 | + this.qrcodeUrl | ||
| 58 | + ? <a-icon type="close-circle" onClick={() => { this.qrcodeUrl = '' }} /> | ||
| 59 | + : <a-icon type="qrcode" onClick={() => this.genQRCodeUrl(this.work)} /> | ||
| 60 | + } | ||
| 61 | + {/** | ||
| 62 | + <a-icon type="setting" /> | ||
| 63 | + <a-icon type="ellipsis" /> | ||
| 64 | + */} | ||
| 65 | + </template> | ||
| 66 | + <a-card-meta | ||
| 67 | + > | ||
| 68 | + <div slot="title" class="ant-card-meta-title" style="font-size: 14px;"> | ||
| 69 | + {this.work.title}({this.work.id}) | ||
| 70 | + </div> | ||
| 71 | + <div slot="description" style="font-size: 12px;"> | ||
| 72 | + <div>描述:{this.work.description}</div> | ||
| 73 | + <div>时间:{this.timeFmt(this.work.created_at)}</div> | ||
| 74 | + </div> | ||
| 75 | + </a-card-meta> | ||
| 76 | + </a-card> | ||
| 77 | + ) | ||
| 78 | + } | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +const AddNewCard = { | ||
| 82 | + functional: true, | ||
| 83 | + render (h, { props }) { | ||
| 84 | + return ( | ||
| 85 | + <a-card hoverable> | ||
| 86 | + <div slot="cover" class="flex-center" style="height: 305px;background: #f7f5f557;" onClick={props.handleCreate}> | ||
| 87 | + <a-icon type="plus" /> | ||
| 88 | + </div> | ||
| 89 | + <template class="ant-card-actions" slot="actions"> | ||
| 90 | + <span onClick={props.handleCreate}>创建新作品</span> | ||
| 91 | + </template> | ||
| 92 | + </a-card> | ||
| 93 | + ) | ||
| 94 | + } | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +export default { | ||
| 98 | + components: { | ||
| 99 | + ListItemCard, | ||
| 100 | + AddNewCard | ||
| 101 | + }, | ||
| 102 | + data: () => ({ | ||
| 103 | + activeWork: null, | ||
| 104 | + previewVisible: false | ||
| 105 | + }), | ||
| 106 | + computed: { | ||
| 107 | + ...mapState('editor', ['works']) | ||
| 108 | + }, | ||
| 109 | + methods: { | ||
| 110 | + ...mapActions('editor', [ | ||
| 111 | + 'fetchWorks' | ||
| 112 | + ]), | ||
| 113 | + deleteWork (item) { | ||
| 114 | + // TODO delete work from work list | ||
| 115 | + }, | ||
| 116 | + createWork () { | ||
| 117 | + this.$router.push({ name: 'editor' }) | ||
| 118 | + } | ||
| 119 | + }, | ||
| 120 | + render (h) { | ||
| 121 | + return ( | ||
| 122 | + <div class="works-wrapper"> | ||
| 123 | + <a-row gutter={24}> | ||
| 124 | + <a-col span={6} style="margin-bottom: 10px;"> | ||
| 125 | + <AddNewCard handleCreate={this.createWork} /> | ||
| 126 | + </a-col> | ||
| 127 | + { | ||
| 128 | + this.works.map(work => ( | ||
| 129 | + <a-col span={6} key={work.id} style="margin-bottom: 20px;"> | ||
| 130 | + <ListItemCard work={work} handleClickPreview={e => { | ||
| 131 | + this.previewVisible = true | ||
| 132 | + this.activeWork = work | ||
| 133 | + }} /> | ||
| 134 | + </a-col> | ||
| 135 | + )) | ||
| 136 | + } | ||
| 137 | + </a-row> | ||
| 138 | + { | ||
| 139 | + this.previewVisible && | ||
| 140 | + <PreviewDialog | ||
| 141 | + work={this.activeWork} | ||
| 142 | + visible={this.previewVisible} | ||
| 143 | + handleClose={() => { this.previewVisible = false }} | ||
| 144 | + /> | ||
| 145 | + } | ||
| 146 | + </div> | ||
| 147 | + ) | ||
| 148 | + }, | ||
| 149 | + created () { | ||
| 150 | + this.fetchWorks() | ||
| 151 | + } | ||
| 152 | +} | ||
| 153 | +</script> |