增加店铺相关操作

This commit is contained in:
zhl 2021-04-19 16:44:23 +08:00
parent 755204171e
commit fa9ffa66e3
11 changed files with 460 additions and 1 deletions

37
src/api/shop.ts Normal file
View File

@ -0,0 +1,37 @@
import request from '@/utils/request'
import { IShopData } from './types'
export const defaultShopData: IShopData = {
name: '',
id: ''
}
export const getShops = (params: any) =>
request({
url: '/shops',
method: 'post',
params
})
export const getShop = (id: string, params: any) =>
request({
url: `/shop/${id}`,
method: 'get',
params
})
export const saveShop = (data: any) =>
request({
url: `/shop/save`,
method: 'post',
data
})
export const deleteShop = (id: string) =>
request({
url: `/shop/${id}/delete`,
method: 'post'
})

6
src/api/types.d.ts vendored
View File

@ -40,3 +40,9 @@ export interface IQuestionData {
stars: number, stars: number,
status: number, status: number,
} }
export interface IShopData {
id: string,
name: string,
createdAt?: Date
}

View File

@ -42,6 +42,7 @@ import './question_list'
import './role' import './role'
import './search' import './search'
import './sell' import './sell'
import './shop_list'
import './shop' import './shop'
import './shopping' import './shopping'
import './size' import './size'

View File

@ -0,0 +1,12 @@
/* eslint-disable */
/* tslint:disable */
// @ts-ignore
import icon from 'vue-svgicon'
icon.register({
'shop_list': {
width: 64,
height: 64,
viewBox: '0 0 1024 1024',
data: '<defs/><path pid="0" d="M85.705 0h329.143a73.143 73.143 0 0173.143 73.143v329.143a73.143 73.143 0 01-73.143 73.143H85.705a73.143 73.143 0 01-73.143-73.143V73.143A73.143 73.143 0 0185.705 0zm0 548.571h329.143a73.143 73.143 0 0173.143 73.143v329.143A73.143 73.143 0 01414.848 1024H85.705a73.143 73.143 0 01-73.143-73.143V621.714a73.143 73.143 0 0173.143-73.143zm563.42-414.866h322.304a40.01 40.01 0 0140.009 40.01 40.01 40.01 0 01-40.01 40.008H649.126a39.973 39.973 0 01-39.973-40.009 39.973 39.973 0 0139.973-40.009zm0 158.866h322.304a40.01 40.01 0 0140.009 39.973 40.01 40.01 0 01-40.01 40.01H649.126a39.973 39.973 0 01-39.973-40.01 39.973 39.973 0 0139.973-39.973zm0 378.295h322.304a39.973 39.973 0 0140.009 39.973 39.973 39.973 0 01-40.01 39.972H649.126a39.973 39.973 0 01-39.973-39.972 39.973 39.973 0 0139.973-39.973zm0 194.268h322.304a40.01 40.01 0 0140.009 40.009 40.01 40.01 0 01-40.01 40.009H649.126a39.973 39.973 0 01-39.973-40.01 39.973 39.973 0 0139.973-40.008z" _fill="#333"/><path pid="1" d="M85.705 73.143h329.143v329.143H85.705zM85.705 621.714h329.143v329.143H85.705z" _fill="#FFF"/>'
}
})

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1618811796115" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17726" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M85.705143 0h329.142857a73.142857 73.142857 0 0 1 73.142857 73.142857v329.142857a73.142857 73.142857 0 0 1-73.142857 73.142857H85.705143a73.142857 73.142857 0 0 1-73.142857-73.142857V73.142857a73.142857 73.142857 0 0 1 73.142857-73.142857z m0 548.571429h329.142857a73.142857 73.142857 0 0 1 73.142857 73.142857v329.142857a73.142857 73.142857 0 0 1-73.142857 73.142857H85.705143a73.142857 73.142857 0 0 1-73.142857-73.142857v-329.142857a73.142857 73.142857 0 0 1 73.142857-73.142857zM649.124571 133.705143h322.304a40.009143 40.009143 0 0 1 40.009143 40.009143 40.009143 40.009143 0 0 1-40.009143 40.009143h-322.304a39.972571 39.972571 0 0 1-39.972571-40.009143 39.972571 39.972571 0 0 1 39.972571-40.009143z m0 158.866286h322.304a40.009143 40.009143 0 0 1 40.009143 39.972571 40.009143 40.009143 0 0 1-40.009143 40.009143h-322.304a39.972571 39.972571 0 0 1-39.972571-40.009143A39.972571 39.972571 0 0 1 649.124571 292.571429z m0 378.294857h322.304a39.972571 39.972571 0 0 1 40.009143 39.972571 39.972571 39.972571 0 0 1-40.009143 39.972572h-322.304a39.972571 39.972571 0 0 1-39.972571-39.972572 39.972571 39.972571 0 0 1 39.972571-39.972571z m0 194.267428h322.304a40.009143 40.009143 0 0 1 40.009143 40.009143 40.009143 40.009143 0 0 1-40.009143 40.009143h-322.304a39.972571 39.972571 0 0 1-39.972571-40.009143 39.972571 39.972571 0 0 1 39.972571-40.009143z" fill="#333333" p-id="17727"></path><path d="M85.705143 73.142857h329.142857v329.142857H85.705143z" fill="#FFFFFF" p-id="17728"></path><path d="M85.705143 621.714286h329.142857v329.142857H85.705143z" fill="#FFFFFF" p-id="17729"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -75,6 +75,9 @@ export default {
question_prepare: 'Question Edit', question_prepare: 'Question Edit',
createQuestion: 'Create Question', createQuestion: 'Create Question',
shop: 'Shop Setting', shop: 'Shop Setting',
shop_list: 'Shop List',
create_shop: 'Create Shop',
shop_edit: 'Shop Editor',
game_setting: 'Game Setting' game_setting: 'Game Setting'
}, },
navbar: { navbar: {

View File

@ -75,6 +75,9 @@ export default {
question_prepare: '题目编辑', question_prepare: '题目编辑',
createQuestion: '创建题目', createQuestion: '创建题目',
shop: '店铺设置', shop: '店铺设置',
shop_list: '店铺列表',
create_shop: '创建店铺',
shop_edit: '编辑店铺',
game_setting: '游戏设置' game_setting: '游戏设置'
}, },
navbar: { navbar: {

View File

@ -10,6 +10,37 @@ const shopRoutes: RouteConfig = {
alwaysShow: true alwaysShow: true
}, },
children: [ children: [
{
path: 'list',
component: () => import('@/views/shop/list.vue'),
name: 'ShopList',
meta: {
title: 'shop_list',
permissions: ['shop:read'],
icon: 'shop_list'
}
},
{
path: 'create',
component: () => import('@/views/shop/edit.vue'),
name: 'CreateShop',
meta: {
title: 'create_shop',
icon: 'edit',
hidden: true
}
},
{
path: 'edit/:id',
component: () => import('@/views/shop/edit.vue'),
name: 'ShopEditor',
meta: {
title: 'shop_edit',
permissions: ['shop:read'],
elicon: 'el-icon-arrow-right',
hidden: true
}
},
{ {
path: 'setting', path: 'setting',
component: () => import('@/views/shop/game_setting.vue'), component: () => import('@/views/shop/game_setting.vue'),

View File

@ -18,7 +18,7 @@ export const parseTime = (
} else { } else {
// support safari // support safari
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
time = time.replace(new RegExp(/-/gm), '/') // time = time.replace(new RegExp(/-/gm), '/')
} }
} }
if (typeof time === 'number' && time.toString().length === 10) { if (typeof time === 'number' && time.toString().length === 10) {

210
src/views/shop/edit.vue Normal file
View File

@ -0,0 +1,210 @@
<template>
<div class="createPost-container">
<el-form
ref="postForm"
:model="postForm"
:rules="rules"
class="form-container"
>
<sticky
:z-index="10"
:class-name="'sub-navbar '+postForm.status"
>
<el-button
v-loading="loading"
style="margin-left: 10px;"
type="success"
@click="submitForm"
>
保存
</el-button>
<el-button
v-loading="loading"
type="warning"
@click="draftForm"
>
Draft
</el-button>
</sticky>
<div class="createPost-main-container">
<el-row>
<el-col :span="24">
<el-form-item
style="margin-bottom: 40px;"
prop="name"
>
<material-input
v-model="postForm.name"
:maxlength="100"
name="name"
required
>
店铺名
</material-input>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/modules/app'
import { TagsViewModule, ITagView } from '@/store/modules/tags-view'
import MaterialInput from '@/components/MaterialInput/index.vue'
import Sticky from '@/components/Sticky/index.vue'
import UploadImage from '@/components/UploadImage/index.vue'
import { Form } from 'element-ui'
import { defaultShopData, getShop, saveShop } from '@/api/shop'
@Component({
name: 'ShopEditor',
components: {
MaterialInput,
Sticky,
UploadImage
}
})
export default class extends Vue {
private validateRequire = (rule: any, value: string, callback: Function) => {
if (value === '') {
if (rule.field === 'imageURL') {
this.$message({
message: 'Upload cover image is required',
type: 'error'
})
} else {
this.$message({
message: rule.field + ' 是必填的',
type: 'error'
})
}
callback(new Error(rule.field + ' 是必填的'))
} else {
callback()
}
}
private postForm = Object.assign({}, defaultShopData)
private loading = false
private rules = {
title: [{ validator: this.validateRequire }],
}
private tempTagView?: ITagView
get lang() {
return AppModule.language
}
created() {
const id = this.$route.params?.id
if (id) {
this.fetchData(id)
}
this.tempTagView = Object.assign({}, this.$route)
}
deactivated() {
}
activated() {
}
private async fetchData(id: string) {
try {
const { data } = await getShop(id, { /* Your params here */ })
console.log(data)
this.postForm = data
// Just for test
this.postForm.title += ` 店铺 Id:${this.postForm._id}`
const title = this.lang === 'zh' ? '编辑店铺' : 'Edit Shop'
// Set tagsview title
this.setTagsViewTitle(title)
// Set page title
this.setPageTitle(title)
} catch (err) {
console.error(err)
}
}
private setTagsViewTitle(title: string) {
const tagView = this.tempTagView
if (tagView) {
tagView.title = `${title}-${this.postForm._id}`
TagsViewModule.updateVisitedView(tagView)
}
}
private setPageTitle(title: string) {
document.title = `${title} - ${this.postForm._id}`
}
private submitForm() {
(this.$refs.postForm as Form).validate(valid => {
if (valid) {
this.loading = true
saveShop(this.postForm)
.then(() => {
this.loading = false
this.$notify({
title: 'Success',
message: 'The post published successfully',
type: 'success',
duration: 2000
})
})
} else {
console.error('Submit Error!')
return false
}
})
}
private draftForm() {
this.$message({
message: 'The draft saved successfully',
type: 'success',
showClose: true,
duration: 1000
})
}
}
</script>
<style lang="scss" scoped>
.createPost-container {
position: relative;
.createPost-main-container {
padding: 40px 45px 20px 50px;
.postInfo-container {
position: relative;
@include clearfix;
margin-bottom: 10px;
.postInfo-container-item {
float: left;
}
}
}
.word-counter {
width: 40px;
position: absolute;
right: 10px;
top: 0px;
}
}
</style>

155
src/views/shop/list.vue Normal file
View File

@ -0,0 +1,155 @@
<template>
<div class="app-container">
<router-link to="/shop/create">
<el-button
type="primary"
icon="el-icon-edit"
>
添加
</el-button>
</router-link>
<el-table
v-loading="listLoading"
:data="list"
border
fit
highlight-current-row
style="width: 100%;margin-top:30px;"
>
<el-table-column
width="180px"
align="center"
label="添加时间"
>
<template slot-scope="{row}">
<span>{{ row.createdAt | parseTime }}</span>
</template>
</el-table-column>
<el-table-column
min-width="300px"
label="店铺名"
>
<template slot-scope="{row}">
<router-link
:to="'/shop/edit/'+row._id"
class="link-type"
>
<span>{{ row.name }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
>
<template slot-scope="scope">
<router-link :to="'/shop/edit/'+scope.row._id">
<el-button
type="primary"
size="small"
icon="el-icon-edit"
>
编辑
</el-button>
</router-link>
<el-button
type="danger"
size="small"
style="margin-left: 10px"
@click="handleDelete(scope)"
>
{{ $t('permission.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="getList"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { IShopData } from '@/api/types'
import Pagination from '@/components/Pagination/index.vue'
import { deleteShop, getShops } from '@/api/shop'
import { parseTime } from '@/utils'
import { deleteRole } from '@/api/roles'
@Component({
name: 'ShopList',
components: {
Pagination
},
filters: {
parseTime: (timestamp: string) => {
return parseTime(timestamp)
}
}
})
export default class extends Vue {
private total = 0
private list: IShopData[] = []
private listLoading = true
private listQuery = {
page: 1,
limit: 20
}
created() {
this.getList()
}
private async getList() {
this.listLoading = true
const { data } = await getShops(this.listQuery)
this.list = data.records
this.total = data.total
// Just to simulate the time of the request
setTimeout(() => {
this.listLoading = false
}, 0.5 * 1000)
}
private handleDelete(scope: any) {
const { $index, row } = scope
console.log($index, scope)
this.$confirm('确认删除该店铺?', 'Warning', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async() => {
await deleteShop(row._id)
this.list.splice($index, 1)
this.$message({
type: 'success',
message: '删除成功!'
})
})
.catch(err => { console.error(err) })
}
}
</script>
<style lang="scss" scoped>
.edit-input {
padding-right: 100px;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
</style>