Commit 3d455d7d75e40acd988af7fc2ae775c6b50c8951
1 parent
4aac184e
feat: support work template
Showing
12 changed files
with
350 additions
and
23 deletions
back-end/h5-api/api/work/config/routes.json
| @@ -71,6 +71,22 @@ | @@ -71,6 +71,22 @@ | ||
| 71 | "config": { | 71 | "config": { |
| 72 | "policies": [] | 72 | "policies": [] |
| 73 | } | 73 | } |
| 74 | + }, | ||
| 75 | + { | ||
| 76 | + "method": "POST", | ||
| 77 | + "path": "/works/set-as-template/:id", | ||
| 78 | + "handler": "Work.setAsTemplate", | ||
| 79 | + "config": { | ||
| 80 | + "policies": [] | ||
| 81 | + } | ||
| 82 | + }, | ||
| 83 | + { | ||
| 84 | + "method": "POST", | ||
| 85 | + "path": "/works/use-template/:id", | ||
| 86 | + "handler": "Work.useTemplate", | ||
| 87 | + "config": { | ||
| 88 | + "policies": [] | ||
| 89 | + } | ||
| 74 | } | 90 | } |
| 75 | ] | 91 | ] |
| 76 | } | 92 | } |
back-end/h5-api/api/work/controllers/Work.js
| @@ -49,4 +49,20 @@ module.exports = { | @@ -49,4 +49,20 @@ module.exports = { | ||
| 49 | // eslint-disable-next-line require-atomic-updates | 49 | // eslint-disable-next-line require-atomic-updates |
| 50 | return ctx.body = { uuidMap2Name, formDetails }; | 50 | return ctx.body = { uuidMap2Name, formDetails }; |
| 51 | }, | 51 | }, |
| 52 | + setAsTemplate: async (ctx) => { | ||
| 53 | + let work = await strapi.services.work.findOne(ctx.params); | ||
| 54 | + work = work.toJSON(); | ||
| 55 | + | ||
| 56 | + // eslint-disable-next-line no-unused-vars | ||
| 57 | + const templateWork = await strapi.services.work.create(); | ||
| 58 | + return strapi.services.work.update({id: templateWork.id}, { pages: work.pages, is_template: true }); | ||
| 59 | + }, | ||
| 60 | + useTemplate: async (ctx) => { | ||
| 61 | + let templateWork = await strapi.services.work.findOne(ctx.params); | ||
| 62 | + templateWork = templateWork.toJSON(); | ||
| 63 | + | ||
| 64 | + // eslint-disable-next-line no-unused-vars | ||
| 65 | + const work = await strapi.services.work.create(); | ||
| 66 | + return strapi.services.work.update({id: work.id}, { pages: templateWork.pages, is_template: false }); | ||
| 67 | + }, | ||
| 52 | }; | 68 | }; |
back-end/h5-api/api/work/models/Work.js
| @@ -7,19 +7,19 @@ | @@ -7,19 +7,19 @@ | ||
| 7 | module.exports = { | 7 | module.exports = { |
| 8 | // Before saving a value. | 8 | // Before saving a value. |
| 9 | // Fired before an `insert` or `update` query. | 9 | // Fired before an `insert` or `update` query. |
| 10 | - beforeSave: async (model, attrs, options) => { | ||
| 11 | - // https://github.com/strapi/strapi/issues/2882 | ||
| 12 | - // need to remove this after this pr will be merged(https://github.com/strapi/strapi/pull/3664) | ||
| 13 | - Object.keys(model.constructor.attributes).forEach(k => { | ||
| 14 | - if (model.constructor.attributes[k].type === 'json') { | ||
| 15 | - const value = model.get(k); | 10 | + // beforeSave: async (model, attrs, options) => { |
| 11 | + // // https://github.com/strapi/strapi/issues/2882 | ||
| 12 | + // // need to remove this after this pr will be merged(https://github.com/strapi/strapi/pull/3664) | ||
| 13 | + // Object.keys(model.constructor.attributes).forEach(k => { | ||
| 14 | + // if (model.constructor.attributes[k].type === 'json') { | ||
| 15 | + // const value = model.get(k); | ||
| 16 | 16 | ||
| 17 | - if (Array.isArray(value)) { | ||
| 18 | - model.set(k, JSON.stringify(value)); | ||
| 19 | - } | ||
| 20 | - } | ||
| 21 | - }); | ||
| 22 | - }, | 17 | + // if (Array.isArray(value)) { |
| 18 | + // model.set(k, JSON.stringify(value)); | ||
| 19 | + // } | ||
| 20 | + // } | ||
| 21 | + // }); | ||
| 22 | + // }, | ||
| 23 | 23 | ||
| 24 | // After saving a value. | 24 | // After saving a value. |
| 25 | // Fired after an `insert` or `update` query. | 25 | // Fired after an `insert` or `update` query. |
| @@ -48,6 +48,7 @@ module.exports = { | @@ -48,6 +48,7 @@ module.exports = { | ||
| 48 | elements: [] | 48 | elements: [] |
| 49 | }]; | 49 | }]; |
| 50 | model.set('pages', JSON.stringify(defaultPages)); | 50 | model.set('pages', JSON.stringify(defaultPages)); |
| 51 | + model.set('is_template', false); | ||
| 51 | }, | 52 | }, |
| 52 | 53 | ||
| 53 | // After creating a value. | 54 | // After creating a value. |
| @@ -56,7 +57,17 @@ module.exports = { | @@ -56,7 +57,17 @@ module.exports = { | ||
| 56 | 57 | ||
| 57 | // Before updating a value. | 58 | // Before updating a value. |
| 58 | // Fired before an `update` query. | 59 | // Fired before an `update` query. |
| 59 | - // beforeUpdate: async (model, attrs, options) => {}, | 60 | + // beforeUpdate: async (model, attrs, options) => { |
| 61 | + // Object.keys(model.constructor.attributes).forEach(k => { | ||
| 62 | + // if (model.constructor.attributes[k].type === 'json') { | ||
| 63 | + // const value = model.get(k); | ||
| 64 | + | ||
| 65 | + // if (Array.isArray(value)) { | ||
| 66 | + // model.set(k, JSON.stringify(value)); | ||
| 67 | + // } | ||
| 68 | + // } | ||
| 69 | + // }); | ||
| 70 | + // }, | ||
| 60 | 71 | ||
| 61 | // After updating a value. | 72 | // After updating a value. |
| 62 | // Fired after an `update` query. | 73 | // Fired after an `update` query. |
back-end/h5-api/api/work/models/Work.settings.json
| @@ -35,10 +35,13 @@ | @@ -35,10 +35,13 @@ | ||
| 35 | "update_time": { | 35 | "update_time": { |
| 36 | "type": "date" | 36 | "type": "date" |
| 37 | }, | 37 | }, |
| 38 | - "pages": { | 38 | + "formData": { |
| 39 | "type": "json" | 39 | "type": "json" |
| 40 | }, | 40 | }, |
| 41 | - "formData": { | 41 | + "is_template": { |
| 42 | + "type": "boolean" | ||
| 43 | + }, | ||
| 44 | + "pages": { | ||
| 42 | "type": "json" | 45 | "type": "json" |
| 43 | } | 46 | } |
| 44 | } | 47 | } |
front-end/h5/src/components/core/editor/index.js
| @@ -100,7 +100,8 @@ export default { | @@ -100,7 +100,8 @@ export default { | ||
| 100 | work: state => state.work | 100 | work: state => state.work |
| 101 | }), | 101 | }), |
| 102 | ...mapState('loading', { | 102 | ...mapState('loading', { |
| 103 | - saveWork_loading: state => state.saveWork_loading | 103 | + saveWork_loading: state => state.saveWork_loading, |
| 104 | + setWorkAsTemplate_loading: state => state.setWorkAsTemplate_loading | ||
| 104 | }) | 105 | }) |
| 105 | }, | 106 | }, |
| 106 | methods: { | 107 | methods: { |
| @@ -109,8 +110,12 @@ export default { | @@ -109,8 +110,12 @@ export default { | ||
| 109 | 'pageManager', | 110 | 'pageManager', |
| 110 | 'saveWork', | 111 | 'saveWork', |
| 111 | 'createWork', | 112 | 'createWork', |
| 112 | - 'fetchWork' | 113 | + 'fetchWork', |
| 114 | + 'setWorkAsTemplate' | ||
| 113 | ]), | 115 | ]), |
| 116 | + ...mapActions('loading', { | ||
| 117 | + updateLoading: 'update' | ||
| 118 | + }), | ||
| 114 | /** | 119 | /** |
| 115 | * !#zh 点击插件,copy 其基础数据到组件树(中间画布) | 120 | * !#zh 点击插件,copy 其基础数据到组件树(中间画布) |
| 116 | * #!en click the plugin shortcut, create new Element with the plugin's meta data | 121 | * #!en click the plugin shortcut, create new Element with the plugin's meta data |
| @@ -165,7 +170,29 @@ export default { | @@ -165,7 +170,29 @@ export default { | ||
| 165 | > | 170 | > |
| 166 | <a-menu-item key="1" class="transparent-bg"><a-button type="primary" size="small" onClick={() => { this.previewVisible = true }}>预览</a-button></a-menu-item> | 171 | <a-menu-item key="1" class="transparent-bg"><a-button type="primary" size="small" onClick={() => { this.previewVisible = true }}>预览</a-button></a-menu-item> |
| 167 | <a-menu-item key="2" class="transparent-bg"><a-button size="small" onClick={() => this.saveWork()} loading={this.saveWork_loading}>保存</a-button></a-menu-item> | 172 | <a-menu-item key="2" class="transparent-bg"><a-button size="small" onClick={() => this.saveWork()} loading={this.saveWork_loading}>保存</a-button></a-menu-item> |
| 168 | - <a-menu-item key="3" class="transparent-bg"><a-button size="small">发布</a-button></a-menu-item> | 173 | + {/* <a-menu-item key="3" class="transparent-bg"><a-button size="small">发布</a-button></a-menu-item> */} |
| 174 | + <a-menu-item key="3" class="transparent-bg"> | ||
| 175 | + <a-dropdown-button onClick={() => {}} size="small"> | ||
| 176 | + 发布 | ||
| 177 | + <a-menu slot="overlay" onClick={({ key }) => { | ||
| 178 | + switch (key) { | ||
| 179 | + case 'setAsTemplate': | ||
| 180 | + this.updateLoading({ type: 'setWorkAsTemplate_loading', value: true }) | ||
| 181 | + this.saveWork().then(() => { | ||
| 182 | + this.setWorkAsTemplate() | ||
| 183 | + }) | ||
| 184 | + } | ||
| 185 | + }}> | ||
| 186 | + <a-menu-item key="setAsTemplate"> | ||
| 187 | + <a-spin spinning={this.setWorkAsTemplate_loading} size="small"> | ||
| 188 | + <a-icon type="cloud-upload" />设置为模板 | ||
| 189 | + </a-spin> | ||
| 190 | + </a-menu-item> | ||
| 191 | + {/* <a-menu-item key="2"><a-icon type="user" />2nd menu item</a-menu-item> */} | ||
| 192 | + {/* <a-menu-item key="3"><a-icon type="user" />3rd item</a-menu-item> */} | ||
| 193 | + </a-menu> | ||
| 194 | + </a-dropdown-button> | ||
| 195 | + </a-menu-item> | ||
| 169 | </a-menu> | 196 | </a-menu> |
| 170 | <ExternalLinksOfHeader /> | 197 | <ExternalLinksOfHeader /> |
| 171 | </a-layout-header> | 198 | </a-layout-header> |
front-end/h5/src/router.js
| @@ -21,6 +21,11 @@ export default new Router({ | @@ -21,6 +21,11 @@ export default new Router({ | ||
| 21 | component: () => import('@/views/work-manager/list.vue') | 21 | component: () => import('@/views/work-manager/list.vue') |
| 22 | }, | 22 | }, |
| 23 | { | 23 | { |
| 24 | + path: '/work-manager/templates', | ||
| 25 | + name: 'work-manager-templates', | ||
| 26 | + component: () => import('@/views/work-manager/templates.vue') | ||
| 27 | + }, | ||
| 28 | + { | ||
| 24 | path: '/work-manager/form-stat', | 29 | path: '/work-manager/form-stat', |
| 25 | name: 'form-stat', | 30 | name: 'form-stat', |
| 26 | component: () => import('@/views/work-manager/form-stat/index.vue') | 31 | component: () => import('@/views/work-manager/form-stat/index.vue') |
front-end/h5/src/store/modules/editor.js
front-end/h5/src/store/modules/loading.js
| 1 | // initial state | 1 | // initial state |
| 2 | const state = { | 2 | const state = { |
| 3 | saveWork_loading: false, | 3 | saveWork_loading: false, |
| 4 | - fetchWorks_loading: false | 4 | + fetchWorks_loading: false, |
| 5 | + setWorkAsTemplate_loading: false, | ||
| 6 | + fetchWorkTemplates_loading: false, | ||
| 7 | + useTemplate_loading: false | ||
| 5 | } | 8 | } |
| 6 | 9 | ||
| 7 | // getters | 10 | // getters |
front-end/h5/src/store/modules/work.js
| @@ -13,6 +13,7 @@ export const actions = { | @@ -13,6 +13,7 @@ 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 | + commit('setWork', entry) | ||
| 16 | router.replace({ name: 'editor', params: { workId: entry.id } }) | 17 | router.replace({ name: 'editor', params: { workId: entry.id } }) |
| 17 | // window.location = `${window.location.origin}/#/editor/${entry.id}` | 18 | // window.location = `${window.location.origin}/#/editor/${entry.id}` |
| 18 | }) | 19 | }) |
| @@ -35,7 +36,7 @@ export const actions = { | @@ -35,7 +36,7 @@ export const actions = { | ||
| 35 | ...payload | 36 | ...payload |
| 36 | } | 37 | } |
| 37 | 38 | ||
| 38 | - new AxiosWrapper({ | 39 | + return new AxiosWrapper({ |
| 39 | dispatch, | 40 | dispatch, |
| 40 | commit, | 41 | commit, |
| 41 | loading_name: 'saveWork_loading', | 42 | loading_name: 'saveWork_loading', |
| @@ -57,7 +58,17 @@ export const actions = { | @@ -57,7 +58,17 @@ export const actions = { | ||
| 57 | loading_name: 'fetchWorks_loading', | 58 | loading_name: 'fetchWorks_loading', |
| 58 | successMsg: '获取作品列表成功', | 59 | successMsg: '获取作品列表成功', |
| 59 | customRequest: strapi.getEntries.bind(strapi) | 60 | customRequest: strapi.getEntries.bind(strapi) |
| 60 | - }).get('works', {}) | 61 | + }).get('works', { is_template: false }) |
| 62 | + }, | ||
| 63 | + fetchWorkTemplates ({ commit, dispatch, state }, workId) { | ||
| 64 | + new AxiosWrapper({ | ||
| 65 | + dispatch, | ||
| 66 | + commit, | ||
| 67 | + name: 'editor/setWorkTemplates', | ||
| 68 | + loading_name: 'fetchWorkTemplates_loading', | ||
| 69 | + successMsg: '获取模板列表成功', | ||
| 70 | + customRequest: strapi.getEntries.bind(strapi) | ||
| 71 | + }).get('works', { is_template: true }) | ||
| 61 | }, | 72 | }, |
| 62 | /** | 73 | /** |
| 63 | * | 74 | * |
| @@ -128,6 +139,24 @@ export const actions = { | @@ -128,6 +139,24 @@ export const actions = { | ||
| 128 | loading_name: 'queryFormsOfWork_loading', | 139 | loading_name: 'queryFormsOfWork_loading', |
| 129 | successMsg: '表单查询完毕' | 140 | successMsg: '表单查询完毕' |
| 130 | }).get(`/works/form/query/${workId}`) | 141 | }).get(`/works/form/query/${workId}`) |
| 142 | + }, | ||
| 143 | + setWorkAsTemplate ({ commit, state, dispatch }, workId) { | ||
| 144 | + new AxiosWrapper({ | ||
| 145 | + dispatch, | ||
| 146 | + commit, | ||
| 147 | + // name: 'editor/formDetailOfWork', | ||
| 148 | + loading_name: 'setWorkAsTemplate_loading', | ||
| 149 | + successMsg: '设置为模板成功' | ||
| 150 | + }).post(`/works/set-as-template/${workId || state.work.id}`) | ||
| 151 | + }, | ||
| 152 | + useTemplate ({ commit, state, dispatch }, workId) { | ||
| 153 | + return new AxiosWrapper({ | ||
| 154 | + dispatch, | ||
| 155 | + commit, | ||
| 156 | + // name: 'editor/formDetailOfWork', | ||
| 157 | + loading_name: 'useTemplate_loading', | ||
| 158 | + successMsg: '使用模板成功' | ||
| 159 | + }).post(`/works/use-template/${workId}`) | ||
| 131 | } | 160 | } |
| 132 | } | 161 | } |
| 133 | 162 | ||
| @@ -143,6 +172,16 @@ export const mutations = { | @@ -143,6 +172,16 @@ export const mutations = { | ||
| 143 | value.sort((a, b) => b.id - a.id) | 172 | value.sort((a, b) => b.id - a.id) |
| 144 | state.works = value | 173 | state.works = value |
| 145 | }, | 174 | }, |
| 175 | + /** | ||
| 176 | + * payload: { | ||
| 177 | + * type: @params {String} "editor/setWorks", | ||
| 178 | + * value: @params {Array} work list | ||
| 179 | + * } | ||
| 180 | + */ | ||
| 181 | + setWorkTemplates (state, { type, value }) { | ||
| 182 | + value.sort((a, b) => b.id - a.id) | ||
| 183 | + state.workTemplates = value | ||
| 184 | + }, | ||
| 146 | setWork (state, work) { | 185 | setWork (state, work) { |
| 147 | window.__work = work | 186 | window.__work = work |
| 148 | work.pages = work.pages.map(page => { | 187 | work.pages = work.pages.map(page => { |
front-end/h5/src/utils/http.js
| @@ -43,6 +43,14 @@ export class AxiosWrapper { | @@ -43,6 +43,14 @@ export class AxiosWrapper { | ||
| 43 | this.setDefaultLoadingName(args) | 43 | this.setDefaultLoadingName(args) |
| 44 | 44 | ||
| 45 | this.setLoadingValue(true) | 45 | this.setLoadingValue(true) |
| 46 | + if (this.customRequest) { | ||
| 47 | + return this.customRequest(...args) | ||
| 48 | + .then(data => { | ||
| 49 | + const handler = this.getCommonResponseHandler({ failMsg: 'Save Failed.' }) | ||
| 50 | + handler.call(this, { status: 200, data }) | ||
| 51 | + }) | ||
| 52 | + .finally(() => this.setLoadingValue(false)) | ||
| 53 | + } | ||
| 46 | return this.instance.get(...args).then(response => { | 54 | return this.instance.get(...args).then(response => { |
| 47 | const handler = this.getCommonResponseHandler({ failMsg: 'Query Failed.' }) | 55 | const handler = this.getCommonResponseHandler({ failMsg: 'Query Failed.' }) |
| 48 | handler.call(this, response) | 56 | handler.call(this, response) |
| @@ -59,6 +67,7 @@ export class AxiosWrapper { | @@ -59,6 +67,7 @@ export class AxiosWrapper { | ||
| 59 | return this.instance.post(...args).then(response => { | 67 | return this.instance.post(...args).then(response => { |
| 60 | const handler = this.getCommonResponseHandler({ failMsg: 'Save Failed.' }) | 68 | const handler = this.getCommonResponseHandler({ failMsg: 'Save Failed.' }) |
| 61 | handler.call(this, response) | 69 | handler.call(this, response) |
| 70 | + return response.data | ||
| 62 | }).catch(error => { | 71 | }).catch(error => { |
| 63 | // handle error | 72 | // handle error |
| 64 | myMessage.error(error.message) | 73 | myMessage.error(error.message) |
front-end/h5/src/views/work-manager/index.vue
| @@ -38,7 +38,8 @@ const sidebarMenus = [ | @@ -38,7 +38,8 @@ const sidebarMenus = [ | ||
| 38 | label: '免费模板', | 38 | label: '免费模板', |
| 39 | value: 'freeTemplates', | 39 | value: 'freeTemplates', |
| 40 | antIcon: 'snippets', | 40 | antIcon: 'snippets', |
| 41 | - key: '3-1' | 41 | + key: '3-1', |
| 42 | + routerName: 'work-manager-templates' | ||
| 42 | } | 43 | } |
| 43 | ] | 44 | ] |
| 44 | }, | 45 | }, |
front-end/h5/src/views/work-manager/templates.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 | + handleUseTemplate: { | ||
| 23 | + type: Function, | ||
| 24 | + default: () => {} | ||
| 25 | + } | ||
| 26 | + }, | ||
| 27 | + data: () => ({ | ||
| 28 | + qrcodeUrl: '' | ||
| 29 | + }), | ||
| 30 | + methods: { | ||
| 31 | + timeFmt (date) { | ||
| 32 | + const dateTime = new Date(date) | ||
| 33 | + const displayTime = `${dateTime.getFullYear()}-${dateTime.getMonth() + | ||
| 34 | + 1}-${dateTime.getDate()}` | ||
| 35 | + return displayTime | ||
| 36 | + }, | ||
| 37 | + genQRCodeUrl (work) { | ||
| 38 | + const url = `${API_ORIGIN}/works/preview/${work.id}` | ||
| 39 | + QRCode.toDataURL(url, (err, url) => { | ||
| 40 | + if (err) console.log(err) | ||
| 41 | + this.qrcodeUrl = url | ||
| 42 | + }) | ||
| 43 | + } | ||
| 44 | + }, | ||
| 45 | + render (h) { | ||
| 46 | + return ( | ||
| 47 | + <a-card hoverable > | ||
| 48 | + <div slot="cover" class="flex-center" style="height: 200px;font-size: 24px;border: 1px dashed #eee;color: #aaa;background: #f7f5f557;" > | ||
| 49 | + { this.qrcodeUrl ? <img src={this.qrcodeUrl} /> : <span>Luban H5</span> } | ||
| 50 | + </div> | ||
| 51 | + <template class="ant-card-actions" slot="actions"> | ||
| 52 | + {/** | ||
| 53 | + <router-link to={{ name: 'editor', params: { workId: this.work.id } }} target="_blank"> | ||
| 54 | + <a-tooltip effect="dark" placement="bottom" title="立即使用"> | ||
| 55 | + <a-icon type="plus-square" title="立即使用"/> | ||
| 56 | + </a-tooltip> | ||
| 57 | + </router-link> | ||
| 58 | + */} | ||
| 59 | + <a-tooltip effect="dark" placement="bottom" title="立即使用"> | ||
| 60 | + <a-icon type="plus-square" title="立即使用" onClick={() => { | ||
| 61 | + this.handleUseTemplate(this.work) | ||
| 62 | + }} /> | ||
| 63 | + </a-tooltip> | ||
| 64 | + <a-tooltip effect="dark" placement="bottom" title="预览"> | ||
| 65 | + <a-icon type="eye" title="预览" onClick={this.handleClickPreview} /> | ||
| 66 | + </a-tooltip> | ||
| 67 | + { | ||
| 68 | + this.qrcodeUrl | ||
| 69 | + ? <a-icon type="close-circle" onClick={() => { this.qrcodeUrl = '' }} /> | ||
| 70 | + : <a-icon type="qrcode" onClick={() => this.genQRCodeUrl(this.work)} /> | ||
| 71 | + } | ||
| 72 | + {/** | ||
| 73 | + <a-icon type="setting" /> | ||
| 74 | + <a-icon type="ellipsis" /> | ||
| 75 | + */} | ||
| 76 | + </template> | ||
| 77 | + <a-card-meta | ||
| 78 | + > | ||
| 79 | + <div slot="title" class="ant-card-meta-title" style="font-size: 14px;"> | ||
| 80 | + {this.work.title}({this.work.id}) | ||
| 81 | + </div> | ||
| 82 | + <div slot="description" style="font-size: 12px;"> | ||
| 83 | + <div>描述:{this.work.description}</div> | ||
| 84 | + <div>时间:{this.timeFmt(this.work.created_at)}</div> | ||
| 85 | + </div> | ||
| 86 | + </a-card-meta> | ||
| 87 | + </a-card> | ||
| 88 | + ) | ||
| 89 | + } | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +export default { | ||
| 93 | + components: { | ||
| 94 | + ListItemCard | ||
| 95 | + }, | ||
| 96 | + data: () => ({ | ||
| 97 | + activeWork: null, | ||
| 98 | + previewVisible: false, | ||
| 99 | + useTemplateDialogVisible: false, | ||
| 100 | + clonedWorkFromTemplate: null // 从某个模板复制出来的作品 | ||
| 101 | + }), | ||
| 102 | + computed: { | ||
| 103 | + ...mapState('editor', ['works', 'workTemplates']), | ||
| 104 | + ...mapState('loading', ['fetchWorks_loading']) | ||
| 105 | + }, | ||
| 106 | + methods: { | ||
| 107 | + ...mapActions('editor', [ | ||
| 108 | + 'fetchWorks', | ||
| 109 | + 'fetchWorkTemplates', | ||
| 110 | + 'useTemplate' | ||
| 111 | + ]), | ||
| 112 | + deleteWork (item) { | ||
| 113 | + // TODO delete work from work list | ||
| 114 | + }, | ||
| 115 | + createWork () { | ||
| 116 | + this.$router.push({ name: 'editor' }) | ||
| 117 | + // window.open('#/editor', '_blank') | ||
| 118 | + } | ||
| 119 | + }, | ||
| 120 | + render (h) { | ||
| 121 | + return ( | ||
| 122 | + <div class="works-wrapper"> | ||
| 123 | + <a-row gutter={24}> | ||
| 124 | + { | ||
| 125 | + this.fetchWorkTemplates_loading | ||
| 126 | + ? <a-col span={24} style="margin-bottom: 10px;text-align: center;height: 355px;line-height: 355px;border-bottom: 1px solid #ebedf0;background: rgba(255, 255, 255, 0.5);"> | ||
| 127 | + <a-spin tip="作品列表获取中..."/> | ||
| 128 | + </a-col> | ||
| 129 | + : this.workTemplates.map(work => ( | ||
| 130 | + <a-col span={6} key={work.id} style="margin-bottom: 20px;"> | ||
| 131 | + <ListItemCard work={work} | ||
| 132 | + handleClickPreview={e => { | ||
| 133 | + this.previewVisible = true | ||
| 134 | + this.activeWork = work | ||
| 135 | + }} | ||
| 136 | + handleUseTemplate={templateWork => { | ||
| 137 | + this.useTemplateDialogVisible = true | ||
| 138 | + this.useTemplate(templateWork.id).then((clonedWork) => { | ||
| 139 | + this.clonedWorkFromTemplate = clonedWork | ||
| 140 | + }) | ||
| 141 | + }} | ||
| 142 | + /> | ||
| 143 | + </a-col> | ||
| 144 | + )) | ||
| 145 | + } | ||
| 146 | + </a-row> | ||
| 147 | + { | ||
| 148 | + this.previewVisible && | ||
| 149 | + <PreviewDialog | ||
| 150 | + work={this.activeWork} | ||
| 151 | + visible={this.previewVisible} | ||
| 152 | + handleClose={() => { this.previewVisible = false }} | ||
| 153 | + /> | ||
| 154 | + } | ||
| 155 | + { | ||
| 156 | + this.useTemplateDialogVisible && | ||
| 157 | + <a-modal | ||
| 158 | + visible={true} | ||
| 159 | + // onOk={() => { this.useTemplateDialogVisible = true }} | ||
| 160 | + // onCancel={() => { this.useTemplateDialogVisible = false }} | ||
| 161 | + width="240px" | ||
| 162 | + okText="保存" | ||
| 163 | + footer={null} | ||
| 164 | + closable={false} | ||
| 165 | + centered | ||
| 166 | + > | ||
| 167 | + <div style="text-align: center;"> | ||
| 168 | + { | ||
| 169 | + this.clonedWorkFromTemplate | ||
| 170 | + ? <div> | ||
| 171 | + <div style={{ margin: '12px' }}><a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" /> 模板已保存至"我的作品"中</div> | ||
| 172 | + <a-button onClick={() => { | ||
| 173 | + this.useTemplateDialogVisible = false | ||
| 174 | + this.clonedWorkFromTemplate = null | ||
| 175 | + }}>我再逛逛</a-button> | ||
| 176 | + <a-button type="primary" | ||
| 177 | + onClick={() => { | ||
| 178 | + const routeData = this.$router.resolve({ name: 'editor', params: { workId: this.clonedWorkFromTemplate.id } }) | ||
| 179 | + window.open(routeData.href, '_blank') | ||
| 180 | + }} | ||
| 181 | + style={{ marginLeft: '12px' }} | ||
| 182 | + >立即使用</a-button> | ||
| 183 | + </div> | ||
| 184 | + : <a-spin tip="复制中" /> | ||
| 185 | + } | ||
| 186 | + </div> | ||
| 187 | + </a-modal> | ||
| 188 | + } | ||
| 189 | + </div> | ||
| 190 | + ) | ||
| 191 | + }, | ||
| 192 | + created () { | ||
| 193 | + this.fetchWorkTemplates() | ||
| 194 | + } | ||
| 195 | +} | ||
| 196 | +</script> |