Commit f1e76057 authored by Zhang Xiaojie's avatar Zhang Xiaojie

活动管理接口完成

parent 9e9fe440
<template>
<!-- 上传概要图片 -->
<div class="border-dotted border-2 p-4 mb-5">
<div class="border-dotted border-2 p-4 mb-5" :path="path">
<a-upload
name="file"
name="uploadFile"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
action="/api/upload"
action="/proxyApi/api/v1/image/upload"
:accept="ACCEPT_IMAGE_TYPE"
:before-upload="beforeUpload"
@change="handleChange"
>
<img v-if="imageUrl" :src="imageUrl" alt="avatar" />
<img v-if="imageUrl" :src="imageUrl" alt="图片不存在" />
<div v-else>
<a-icon :type="loading ? 'loading' : 'plus'" />
<div class="ant-upload-text">
......@@ -24,6 +25,14 @@
<script lang="ts">
import Vue from 'vue'
import { message } from 'ant-design-vue'
import {
ACCEPT_IMAGE_TYPE,
isAcceptImageType,
TOAST_TEXT,
TOAST_TEXT2,
MAX_IMAGE_SIZE,
} from '@/const/config/upload'
function getBase64(img: Blob, callback: (any: any) => void) {
const reader = new FileReader();
......@@ -32,10 +41,23 @@ function getBase64(img: Blob, callback: (any: any) => void) {
}
export default Vue.extend({
props:{
path:{
type:String,
required:true
}
},
watch:{
path(newV,oldV){
this.imageUrl = newV
}
},
data(){
return{
imageUrl: "",
loading: false
loading: false,
ACCEPT_IMAGE_TYPE,
file_name:'',
}
},
methods:{
......@@ -45,24 +67,26 @@ export default Vue.extend({
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
// get file_hash
this.file_name = info.file.response.data
this.$emit('getFileHash',this.file_name)
getBase64(info.file.originFileObj, imageUrl => {
this.imageUrl = imageUrl;
this.loading = false;
});
}
},
beforeUpload(file:File) {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('You can only upload JPG file!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJpgOrPng && isLt2M;
},
beforeUpload(file: File) {
const isAccpet = isAcceptImageType(file)
if (!isAccpet) {
message.error(TOAST_TEXT)
}
const isLt4M = file.size < MAX_IMAGE_SIZE
if (!isLt4M) {
message.error(TOAST_TEXT2)
}
return isAccpet && isLt4M
},
}
})
</script>
......
<template>
<div class=" border border-gray-300 p-3 rounded">
<EditorMenuBar :editor="editor"/>
<div class="border border-gray-300 p-3 rounded" :text="text">
<EditorMenuBar :editor="editor" />
<editor-content :editor="editor" class="editor__content" />
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import { Editor, EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
import {Image as Timage} from '@tiptap/extension-image'
import EditorMenuBar from './editorMenuBar.vue'
// async function handleUpload(file) {
// const ret = await upload(file);
// if (ret && ret.code === 200) {
// return ret.data.url;
// } else {
// return "";
// }
// }
import Vue from "vue";
import { Editor, EditorContent } from "@tiptap/vue-2";
import StarterKit from "@tiptap/starter-kit";
import { Image as Timage } from "@tiptap/extension-image";
import EditorMenuBar from "./editorMenuBar.vue";
export default Vue.extend({
components: {
EditorContent,
EditorMenuBar,
},
props:{
visible:{
type:Boolean,
default:true
props: {
visible: {
type: Boolean,
default: true,
},
text: {
type: String,
required: true,
default:''
},
},
watch:{
text(newV,oldV){
this.editor.commands.insertContent(newV)
}
},
data(){
let editor:any = undefined
data() {
let editor: any = undefined;
return {
editor,
content:''
}
content: "",
};
},
mounted(){
this.editor = new Editor({
mounted() {
this.editor = new Editor({
onUpdate: () => {
this.content = this.editor.getHTML()
this.$emit('getContent',this.content)
this.content = this.editor.getHTML();
this.$emit("getContent", this.content);
},
extensions: [
StarterKit,
Timage,
],
autofocus: 'start',
})
extensions: [StarterKit, Timage],
autofocus: "start",
});
},
beforeDestroy() {
this.editor.destroy()
this.editor.destroy();
},
})
});
</script>
<style lang="scss">
.editor__content {
min-height: 400px;
img{
img {
display: block;
margin: 0 auto;
}
......@@ -70,12 +67,11 @@ export default Vue.extend({
/* Placeholder (at the top) */
.ProseMirror p.is-editor-empty:first-child::before {
content: '请输入文章正文';
content: "请输入文章正文";
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
font-style: italic;
}
</style>
\ No newline at end of file
......@@ -3,7 +3,8 @@ const columns:Array<column>=[
{
title: '发布时间',
align:'center',
dataIndex: 'time',
dataIndex: 'created_at',
scopedSlots: { customRender: 'created_at' },
},
{
title: '活动标题',
......@@ -19,12 +20,12 @@ const columns:Array<column>=[
{
title: '状态',
align:'center',
dataIndex: 'state',
dataIndex: 'activity_status',
scopedSlots: { customRender: 'activity_status' },
},
{
title: '操作',
align:'center',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
}
]
......
import baseAxios from '../index'
const prefix = '/activity/admin'
import { eActivityItem } from "./type"
export default class ActivityService {
static instance: ActivityService
static getInstance() {
if (!ActivityService.instance) {
ActivityService.instance = new ActivityService()
}
return ActivityService.instance
}
addActivity(
data: {
content: string
file_name: string
title: string
}
) {
return baseAxios({
url: prefix + '/add',
method: 'POST',
data,
})
}
queryActivityList(
data: {
activity_status: number
end_time?: number
limit?: number
offset?: number
start_time?: number
}
) {
return baseAxios<{
count: number,
items: eActivityItem[],
total: number
}>({
url: prefix + '/list',
method: 'POST',
data,
})
}
queryActivityInfo(uuid: string) {
return baseAxios<{
count: number,
items: eActivityItem[],
total: number
}>({
url: '/activity/query/info?uuid='+uuid,
method: 'GET',
})
}
modifyActivity(data: {
content: string
file_name: string
title: string
uuid:string
}) {
return baseAxios({
url: prefix + '/modify',
method: 'POST',
data,
})
}
modifyActivityStatus(data: {
activity_status: number,
uuid: string,
}) {
return baseAxios({
url: prefix + '/modify/status',
method: 'POST',
data,
})
}
deleteActivity(uuid: string) {
return baseAxios({
url: prefix + '/delete?uuid=' + uuid,
method: 'DELETE',
})
}
}
\ No newline at end of file
export enum activityStatus{
all=0,
editable=1, //未发布
published=2 //已发布
}
export interface eActivityItem{
activity_status:activityStatus,
content:string,
created_at:number,
file_name:string,
title:string,
update_at:number,
uuid:string
}
<template>
<div>
<p class=" text-2xl font-bold mb-5 ">{{title}}</p>
<p class=" text-gray-400">发布时间:{{time}} <span class=" ml-2">作者:{{name}}</span></p>
<p class="text-left p-5">{{content}}</p>
<p class="text-2xl font-bold mb-5">{{ title }}</p>
<p class="text-gray-400">发布时间:{{ time | formatDate }}</p>
<img v-if="imageUrl" :src="imageUrl" class=" mx-auto my-5" />
<p class="text-left p-5" v-html="content">{{ content }}</p>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import Vue from "vue";
import ActivityService from "@/service/ActivityService/index";
import { eActivityItem } from "@/service/ActivityService/type";
import FileService from "@/service/FileService/index";
export default Vue.extend({
data(){
return{
title:'银保监会、人民银行联合出台现金管理类理财产品监管规则',
time:'2021-07-01',
name:'丁洁',
content:'“中国冰雪大篷车”是国家体育总局深入推进冰雪运动“南展西扩东进”战略,落实“带动三亿人参与冰雪运动”目标的重要群众性活动之一。“中国冰雪大篷车”将在为期半年的时间里,深入全国30余个城市,举办100场线下活动,同时结合持续的线上互动,以创新的体验模式、丰富的活动内容和众多的展示渠道,普及冰雪运动常识,推广冰雪运动理念,让普通大众在家门口就能感受冰雪运动的乐趣。'
data() {
return {
uuid: "",
title: "",
file_name: undefined as undefined | string,
time: 0,
content: "",
imageUrl: "",
};
},
async mounted() {
this.uuid = this.$route.params.uuid;
const ret = await ActivityService.getInstance().queryActivityInfo(
this.uuid
);
if (ret.code == 200) {
const item: eActivityItem = ret.data as unknown as eActivityItem;
this.title = item.title;
this.time = item.created_at;
this.content = item.content;
this.file_name = item.file_name;
}
}
})
if (this.file_name) {
this.imageUrl = FileService.getInstance().getImageSrc(this.file_name)
}
},
});
</script>
\ No newline at end of file
<template>
<div>
<p class=" text-2xl font-bold mb-5">活动管理</p>
<p class="text-2xl font-bold mb-5">活动管理</p>
<!-- 搜索框 -->
<a-input placeholder="活动名称模糊搜索" v-model="searchPageReqParams.name" style="width: 150px; margin-right:10px;"/>
<!-- timepicker -->
<span class=" font-semibold">注册时间:</span>
<timerange class=" mr-3"
@getNewTime="getNewTime"/>
<a-input
placeholder="活动名称模糊搜索"
v-model="searchPageReqParams.name"
style="width: 150px; margin-right: 10px"
/>
<!-- timepicker -->
<span class="font-semibold">注册时间:</span>
<timerange
class="mr-3"
:startTime="searchPageReqParams.startTime"
:endTime="searchPageReqParams.endTime"
@getNewTime="getNewTime"
/>
<!-- 操作 -->
<a-button type="primary" style="margin-right: 10px" @click="query"
>查询</a-button
>
<a-button type="primary" style="margin-right: 10px" @click="reset"
>重置</a-button
>
<a-button type="primary" @click="release">发布</a-button>
<!-- 资讯列表 -->
<a-table
:columns="columns"
:data-source="list"
style="text-align: center; margin-top: 40px"
bordered
:loading="tableLoading"
:pagination="false"
>
<template #created_at="text">
{{ text | formatDate }}
</template>
<template #activity_status="text">
{{ text | filterActivitytatus }}
</template>
<!-- 新闻内容 -->
<span slot="content" slot-scope="text, record">
<a @click="toDetail(record)">查看</a>
</span>
<!-- 操作 -->
<a-button type="primary" style=" margin-right:10px;" @click="query">查询</a-button>
<a-button type="primary" style=" margin-right:10px;" @click="reset">重置</a-button>
<a-button type="primary" @click="release">发布</a-button>
<!-- 资讯列表 -->
<a-table :columns="columns" :data-source="list" style=" text-align: center; margin-top:40px;" bordered >
<!-- 新闻内容 -->
<span slot="content" slot-scope="text,record">
<a @click="toDetail(record.key)">查看</a>
</span>
<!-- 操作 -->
<span slot="action" slot-scope="text,record">
<a v-if="record.state=='已下架'"
@click="add(record.key)">
上架
</a>
<a v-else
@click="remove(record.key)">
下架
</a>
<span slot="action" slot-scope="text, record">
<span v-if="record.activity_status == activityStatus.editable">
<a @click="add(record)"> 上架 </a>
<a-divider type="vertical" />
<a @click="edit(record)"> 编辑 </a>
<a-divider type="vertical" />
<a @click="onDelete(record.key)">
删除
</a>
<a @click="onDelete(record)"> 删除 </a>
</span>
</a-table>
<!-- 操作Modal -->
<a-modal
:title="title"
v-model="show"
:centered="true"
:closable="false"
@ok="handleOk"
@cancle="show = false"
>
<p class=" text-center">{{text}}</p>
</a-modal>
<a v-else @click="remove(record)"> 下架 </a>
</span>
</a-table>
<a-pagination
class="text-right mt-4"
:current="current"
:total="total"
:pageSize="PAGE_SIZE"
@change="handlePaginationChange"
></a-pagination>
<!-- 操作Modal -->
<a-modal
:title="title"
v-model="show"
:centered="true"
:closable="false"
@ok="handleOk"
:confirmLoading="modalLoading"
@cancle="show = false"
>
<p class="text-center">{{ text }}</p>
</a-modal>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import { activityList } from '@/mock/index'
import { columns } from '@/const/columns/activityColumn'
import { modal } from './const'
import timerange from '@/components/TimePicker/index.vue'
import Vue from "vue";
import { columns } from "@/const/columns/activityColumn";
import { modal } from "./const";
import timerange from "@/components/TimePicker/index.vue";
import ActivityService from "@/service/ActivityService/index";
import { eActivityItem, activityStatus } from "@/service/ActivityService/type";
import { PAGE_SIZE } from "@/const/config/page";
export default Vue.extend({
components:{ timerange },
computed:{
columns(){
return columns
components: { timerange },
computed: {
columns() {
return columns;
},
current(): number {
return this.searchPageReqParams.offset / PAGE_SIZE + 1;
},
list(){
return activityList
}
},
data(){
return{
data() {
return {
searchPageReqParams: {
name: '',
name: "",
startTime: undefined as undefined | number,
endTime: undefined as undefined | number,
offset: 0,
},
show:false,
title:'',
type:modal.on,
text:'',
}
show: false,
title: "",
type: modal.on,
text: "",
activityStatus,
uuid: "",
modalLoading: false,
list: [] as eActivityItem[],
total: 0,
tableLoading:false,
PAGE_SIZE
};
},
methods:{
query(){
console.log(this.searchPageReqParams);
mounted() {
this.fetchList();
},
methods: {
handlePaginationChange(current: number) {
this.searchPageReqParams.offset = (current - 1) * PAGE_SIZE;
console.log(current,this.searchPageReqParams.offset );
this.fetchList();
},
getNewTime(startTime:number,endTime:number){
this.searchPageReqParams.startTime = startTime
this.searchPageReqParams.endTime = endTime
fetchList() {
this.tableLoading = false
ActivityService.getInstance()
.queryActivityList({
activity_status: activityStatus.all,
end_time: this.searchPageReqParams.endTime,
limit: PAGE_SIZE,
offset: this.searchPageReqParams.offset,
start_time: this.searchPageReqParams.startTime,
})
.then((ret) => {
if (ret.code === 200) {
this.list = ret.data.items;
this.total = ret.data.total;
this.tableLoading = false
}
});
},
reset(){
this.searchPageReqParams.name =''
this.searchPageReqParams.startTime = undefined
this.searchPageReqParams.endTime = undefined
query() {
this.fetchList();
},
release(){
this.$router.push({name:'publishActivity'})
getNewTime(startTime: number, endTime: number) {
this.searchPageReqParams.startTime = startTime;
this.searchPageReqParams.endTime = endTime;
},
edit(key:string){
this.$router.push({name:'publishActivity',query:{key:key}})
reset() {
this.searchPageReqParams.name = "";
this.searchPageReqParams.startTime = undefined;
this.searchPageReqParams.endTime = undefined;
},
add(record:any){
this.type = modal.on
this.show = true
this.title = '上架资讯'
this.text = '确定上架该资讯吗?'
release() {
this.$router.push({ name: "publishActivity" });
},
remove(record:any){
this.type = modal.off
this.show = true
this.title = '下架活动'
this.text = '确定下架该活动吗?'
edit(record: any) {
this.$router.push({
name: "editActivity",
params: { uuid: record.uuid },
});
},
onDelete(record:any){
this.type = modal.delete
this.show = true
this.title = '删除活动'
this.text = '确定删除该活动吗?'
add(record: any) {
this.uuid = record.uuid;
this.type = modal.on;
this.show = true;
this.title = "上架资讯";
this.text = "确定上架该资讯吗?";
},
handleOk(record:any){
if( this.type == modal.on){
} else if( this.type == modal.off){
} else if( this.type == modal.delete){
}
this.show = false
remove(record: any) {
this.uuid = record.uuid;
this.type = modal.off;
this.show = true;
this.title = "下架活动";
this.text = "确定下架该活动吗?";
},
toDetail(key:string){
this.$router.push({name:'activityDetail',query:{key:key}})
}
}
})
onDelete(record: any) {
this.uuid = record.uuid;
this.type = modal.delete;
this.show = true;
this.title = "删除活动";
this.text = "确定删除该活动吗?";
},
handleOk(record: any) {
this.modalLoading = true;
try {
if (this.type == modal.on) {
ActivityService.getInstance().modifyActivityStatus({
uuid: this.uuid,
activity_status: activityStatus.published,
});
} else if (this.type == modal.off) {
ActivityService.getInstance().modifyActivityStatus({
uuid: this.uuid,
activity_status: activityStatus.editable,
});
} else if (this.type == modal.delete) {
ActivityService.getInstance().deleteActivity(this.uuid);
}
this.fetchList();
this.show = false;
} catch (err) {}
this.modalLoading = false;
},
toDetail(record: any) {
const uuid = record.uuid;
this.$router.push({ name: "activityDetail", params: { uuid: uuid } });
},
},
});
</script>
<template>
<div class=" editor">
<p class=" text-2xl font-bold mb-5">发布活动</p>
<input
v-model="title"
placeholder="请输入标题"
class="border-gray-300 border-2 px-3 py-2 w-full focus:outline-none focus:ring rounded"
/>
<a-form-model :model="form" :rules="rules" ref="activityPublishForm">
<a-form-model-item prop="title">
<a-input
v-model="form.title"
placeholder="请输入标题"
/>
</a-form-model-item>
<!-- 上传概要图片 -->
<editor-upload/>
<a-form-model-item prop="file_hash" ref="timeUploader" :autoLink="false">
<editor-upload @getFileHash="getFileHash" :path="file_src"/>
</a-form-model-item>
<!-- 编辑器 -->
<editor class="edit-news text-left" @getContent="getContent"/>
<a-form-model-item prop="content" ref="editor" :autoLink="false">
<editor class="edit-news text-left" @getContent="getContent" :text="form.editableContent"/>
</a-form-model-item>
<!-- 发布操作 -->
<a-button type="primary" @click="onSubmit" class=" mt-5">确定发布</a-button>
</a-form-model>
</div>
</template>
......@@ -19,20 +26,102 @@
import Vue from 'vue'
import Editor from '@/components/Editor/index.vue'
import EditorUpload from '@/components/Editor/editorUpload.vue'
import ActivityService from "@/service/ActivityService/index"
import { message,FormModel } from 'ant-design-vue'
import { eActivityItem } from "@/service/ActivityService/type"
import FileService from "@/service/FileService/index"
export default Vue.extend({
components:{ Editor,EditorUpload },
data(){
return{
title:'',
content:''
form:{
title:'',
content:'',
file_hash:'',
editableContent:''
},
rules: {
title: [ { required: true, message: '请输入文章标题', trigger: 'blur' }],
content: [ { required: true, message: '请输入文章内容', trigger: 'blur' }],
file_hash: [ { required: true, message: '请上传概要图片', trigger: 'blur' }],
},
uuid:"",
isEditable:false,
file_src:"",
}
},
async mounted(){
this.isEditable = this.$route.name == "editActivity" ? true : false;
if(this.isEditable){
this.uuid = this.$route.params.uuid
ActivityService.getInstance().queryActivityInfo(this.uuid).then((ret)=>{
const items:eActivityItem = ret.data as unknown as eActivityItem
if(ret.code === 200){
this.form.title = items.title
this.form.editableContent = items.content
this.form.file_hash = items.file_name
if(this.form.file_hash){
this.file_src = FileService.getInstance().getImageSrc(this.form.file_hash)
}
} else{
return
}
})
}
},
methods:{
getContent(value:string){
this.content = value
this.form.content = value;
(this.$refs.editor as any).onFieldBlur()
},
getFileHash(value:string){
this.form.file_hash = value;
(this.$refs.timeUploader as any).onFieldBlur()
},
isValid():boolean{
let isValid = false;
(this.$refs.activityPublishForm as FormModel).validate((valid) => {
if (valid) {
isValid = true;
} else {
isValid = false;
}
});
return isValid
},
onSubmit() {
async onSubmit() {
if(this.isValid()){
if(this.isEditable){
const ret = await ActivityService.getInstance().modifyActivity({
title:this.form.title,
content:this.form.content,
file_name:this.form.file_hash,
uuid:this.uuid
})
if(ret.code == 200){
message.success('编辑成功')
this.$router.push({name:'activity'})
}else{
return
}
}else{
const ret = await ActivityService.getInstance().addActivity({
title:this.form.title,
content:this.form.content,
file_name:this.form.file_hash
})
console.log(ret);
if(ret.code == 200){
message.success('发布成功')
this.$router.push({name:'activity'})
}else{
return
}
}
} else{
return
}
},
}
})
......
......@@ -347,7 +347,7 @@ export const menuList: iMenuConfigItem[] = [
roles: [eRole.superManager],
},
{
getName: () => '发布新闻内容',
getName: () => '发布活动内容',
routeName: 'publishActivity',
hiddeInMenu: true,
belongToMenuName: 'activity',
......@@ -357,6 +357,16 @@ export const menuList: iMenuConfigItem[] = [
onClick: (e: Event) => {},
},
{
getName: () => '编辑活动内容',
routeName: 'editActivity',
hiddeInMenu: true,
belongToMenuName: 'activity',
path: '/backend/activity/edit',
component: () => import('@/views/Root/Activity/publish.vue'),
roles: [eRole.superManager],
onClick: (e: Event) => {},
},
{
getName: () => '查看活动内容',
routeName: 'activityDetail',
hiddeInMenu: true,
......
......@@ -74,7 +74,7 @@ export default Vue.extend({
};
},
mounted() {
console.log(this.menuList, "show menulist");
// console.log(this.menuList, "show menulist");
},
computed: {
role(): eRole | undefined {
......@@ -84,7 +84,7 @@ export default Vue.extend({
| undefined;
},
menuList(): iMenuList {
console.log(this.role, "show this.role");
// console.log(this.role, "show this.role");
return (this.role !== undefined && getMenuList(this.role)) || [];
},
filteredMenuList(): iMenuList {
......
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