This commit is contained in:
caoqianming 2020-06-26 17:42:53 +08:00
parent 7f9a155d05
commit ee35e22b7e
22 changed files with 476 additions and 48 deletions

View File

@ -38,3 +38,30 @@ export function topArticle(id) {
method: 'put',
})
}
export function getMaterialList() {
return request({
url: '/cms/material/',
method: 'get',
})
}
export function createMaterial(data) {
return request({
url: '/cms/material/',
method: 'post',
data
})
}
export function updateMaterial(id, data) {
return request({
url: `/cms/material/${id}/`,
method: 'put',
data
})
}
export function deleteMaterial(id) {
return request({
url: `/cms/material/${id}/`,
method: 'delete',
})
}

View File

@ -1,7 +1,7 @@
import { getToken } from "@/utils/auth";
export function uploadUrl(){
export function upUrl(){
return process.env.VUE_APP_BASE_API + '/uploadfile/'
}
export function myHeaders(){
export function upHeaders(){
return { Authorization: "JWT " + getToken() }
}

View File

@ -12,7 +12,7 @@
import plugins from "./plugins";
import toolbar from "./toolbar";
import load from "./dynamicLoadScript";
import { uploadUrl, myHeaders } from "@/api/file";
import { upUrl, upHeaders } from "@/api/file";
import { getToken } from "@/utils/auth";
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN =
@ -171,7 +171,7 @@ export default {
var file = blobInfo.blob(); //file
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open("POST", uploadUrl());
xhr.open("POST", upUrl());
xhr.setRequestHeader('Authorization', 'JWT '+ getToken());
xhr.onload = function() {
var json;

View File

@ -68,7 +68,7 @@ export const asyncRoutes = [
component: Layout,
redirect: '/cms/',
name: 'CMS',
meta: { title: '资讯文章', icon: 'peoples', perms: ['news_view'] },
meta: { title: '资料文章', icon: 'documentation', perms: ['news_view'] },
children: [
{
path: 'news',
@ -76,6 +76,12 @@ export const asyncRoutes = [
component: () => import('@/views/news/news.vue'),
meta: { title: '文章列表', icon: 'documentation', perms: ['news_view'] }
},
{
path: 'materials',
name: 'MaterialList',
component: () => import('@/views/material/material.vue'),
meta: { title: '学习资料', icon: 'documentation', perms: ['material_view'] }
},
{
path: 'news/create',
name: 'NewsCreate',

View File

@ -73,10 +73,10 @@
type="primary"
>下载模板</el-link>
<el-upload
:action="uploadUrl"
:action="upUrl"
:on-success="handleUploadSuccess"
accept=".xlsx"
:headers="myHeaders"
:headers="upHeaders"
:show-file-list="false"
>
<el-button size="small" type="primary" @click="popovervisible = false">上传导入</el-button>
@ -246,7 +246,7 @@ import { getWorkScopeAll } from "@/api/examtest";
import { getCompanyList } from "@/api/crm";
import { genTree, deepClone } from "@/utils";
import checkPermission from "@/utils/permission";
import { uploadUrl } from "@/api/file";
import { upUrl } from "@/api/file";
import { getToken } from "@/utils/auth";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
@ -274,10 +274,10 @@ export default {
return {
tableKey: 0,
showCreate: false,
uploadUrl: uploadUrl(),
upUrl: upUrl(),
popovervisible: false,
consumer: defaultConsumer,
myHeaders: { Authorization: "JWT " + getToken() },
upHeaders: { Authorization: "JWT " + getToken() },
consumerList: [],
total: 0,
listLoading: true,

View File

@ -0,0 +1,246 @@
<template>
<div class="app-container">
<div>
<el-input
v-model="listQuery.search"
placeholder="名称/描述"
style="width: 300px;"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="handleFilter"
>搜索</el-button>
<el-button
class="filter-item"
style="margin-left: 10px;"
type="primary"
icon="el-icon-refresh-left"
@click="resetFilter"
>刷新重置</el-button>
</div>
<div style="margin-top:6px">
<el-button type="primary" icon="el-icon-plus" @click="handleCreate">新增</el-button>
</div>
<el-table
v-loading="listLoading"
:data="materialList.results"
style="width: 100%;margin-top:10px;"
border
fit
stripe
highlight-current-row
max-height="600"
>
<el-table-column type="index" width="50" />
<el-table-column label="名称">
<template slot-scope="scope">
<el-link type="primary" :href="scope.row.path" target="_blank" v-if="scope.row.path">{{ scope.row.name }}</el-link>
<span v-else>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column align="header-center" label="上传时间">
<template slot-scope="scope">{{ scope.row.create_time }}</template>
</el-table-column>
<el-table-column align="header-center" label="下载量">
<template slot-scope="scope">{{ scope.row.down_count }}</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
type="primary"
size="small"
icon="el-icon-edit"
:disabled="!checkPermission(['material_update'])"
@click="handleUpdate(scope)"
/>
<el-button
type="danger"
size="small"
icon="el-icon-delete"
:disabled="!checkPermission(['material_delete'])"
@click="handleDelete(scope)"
/>
</template>
</el-table-column>
</el-table>
<pagination
v-show="materialList.count>0"
:total="materialList.count"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="getList"
/>
<el-dialog :visible.sync="dialogVisible" :title="dialogType==='update'?'编辑':'新增'">
<el-form
ref="Form"
:model="material"
label-width="80px"
label-position="right"
:rules="rule1"
>
<el-form-item label="名称" prop="name">
<el-input v-model="material.name" placeholder="名称" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="material.description" placeholder="描述" />
</el-form-item>
<el-form-item label="文件" prop="path">
<el-upload
:on-preview="handlePreview"
:on-success="handleSuccess"
:action="upUrl"
:headers="upHeaders"
:limit="1"
:file-list="fileList"
>
<el-button size="small" type="primary" >点击上传</el-button>
</el-upload>
</el-form-item>
</el-form>
<div style="text-align:right;">
<el-button type="danger" @click="dialogVisible=false">取消</el-button>
<el-button type="primary" @click="confirm('Form')">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getMaterialList, createMaterial, updateMaterial, deleteMaterial } from "@/api/cms"
import { upUrl, upHeaders} from "@/api/file"
import Pagination from "@/components/Pagination"
import checkPermission from '@/utils/permission'
const defaultmaterial = {
id:null,
name:null,
description:null,
path:null
}
export default {
components: { Pagination },
data() {
return {
upHeaders: upHeaders(),
upUrl: upUrl(),
materialList: {count:0},
material:Object.assign({}, defaultmaterial),
listLoading: true,
listQuery: {
page: 1,
limit: 20
},
dialogVisible:false,
dialogType:'create',
rule1:{
name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
path: [{ required: true, message: "请上传文件", trigger: "blur" }]
},
fileList:[]
};
},
created() {
this.getList();
},
methods: {
checkPermission,
getList() {
this.listLoading = true;
getMaterialList(this.listQuery).then(response => {
if (response.data) {
this.materialList = response.data
}
this.listLoading = false;
});
},
resetFilter() {
this.listQuery = {
page: 1,
limit: 20
};
this.getList();
},
handleFilter() {
this.listQuery.page = 1;
this.getList();
},
handleDelete(scope) {
deleteMaterial(scope.row.id).then(res=>{
this.getList()
})
},
handleCreate() {
this.material = Object.assign({}, defaultmaterial);
this.fileList = []
this.dialogType = "create";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
handleUpdate(scope) {
this.material = Object.assign({}, scope.row) // copy obj
if(this.material.path){
this.fileList=[{
name:this.material.name + '.' + this.material.path.split('.')[this.material.path.split('.').length - 1],
url:this.material.path
}]
}
this.dialogType = 'update'
this.dialogVisible = true
this.$nextTick(() => {
this.$refs['Form'].clearValidate()
})
},
handlePreview(file) {
if ('url' in file){
window.open(file.url)
}else{
window.open(file.response.data.path)
}
},
handleSuccess(response, file, fileList) {
this.material.path = response.data.path
},
async confirm(form) {
this.$refs[form].validate(valid => {
if (valid) {
const isEdit = this.dialogType === "update";
if (isEdit) {
updateMaterial(this.material.id, this.material).then(res => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false
this.$notify({
title: "成功",
message: "编辑成功",
type: "success",
duration: 2000
});
}
});
} else {
createMaterial(this.material).then(res => {
this.getList();
this.dialogVisible = false
this.$notify({
title: "成功",
type: "success",
})
}).catch(error=>{})
}
} else {
return false;
}
});
}
}
};
</script>

View File

@ -54,10 +54,10 @@
<div style="text-align: left; margin: 0;">
<el-link href="/media/muban/question.xlsx" target="_blank" @click="popovervisible = false" type="primary">下载模板</el-link>
<el-upload
:action="uploadUrl"
:action="upUrl"
:on-success="handleUploadSuccess"
accept=".xlsx"
:headers="myHeaders"
:headers="upHeaders"
:show-file-list ="false"
>
<el-button size="small" type="primary" @click="popovervisible = false">上传导入</el-button>
@ -158,7 +158,7 @@ import {
import { genTree, deepClone } from "@/utils";
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination";
import { uploadUrl, myHeaders } from "@/api/file";
import { upUrl, upHeaders } from "@/api/file";
import { getToken } from "@/utils/auth";
const defaultObj = {
@ -175,8 +175,8 @@ export default {
data() {
return {
popovervisible:false,
uploadUrl: uploadUrl(),
myHeaders: myHeaders(),
upUrl: upUrl(),
upHeaders: upHeaders(),
questioncat: {
id: "",
name: ""

View File

@ -35,8 +35,8 @@
<el-form-item label="题干图片" prop="img" >
<el-upload
class="avatar-uploader"
:headers="myHeaders"
:action="uploadUrl"
:headers="upHeaders"
:action="upUrl"
accept="image/jpeg, image/gif, image/png, image/bmp"
:show-file-list="false"
:on-success="handleImgSuccess"
@ -106,13 +106,13 @@
<script>
import { createQuestion,getQuestioncatAll } from "@/api/question";
import { genTree, deepClone } from "@/utils";
import { uploadUrl } from "@/api/file";
import { upUrl } from "@/api/file";
import { getToken } from "@/utils/auth";
export default {
data() {
return {
myHeaders: { Authorization: "JWT " + getToken() },
uploadUrl: uploadUrl(),
upHeaders: { Authorization: "JWT " + getToken() },
upUrl: upUrl(),
Form: {
name: "",
type:"",

View File

@ -36,8 +36,8 @@
<el-form-item label="题干图片" prop="img" >
<el-upload
class="avatar-uploader"
:headers="myHeaders"
:action="uploadUrl"
:headers="upHeaders"
:action="upUrl"
accept="image/jpeg, image/gif, image/png, image/bmp"
:show-file-list="false"
:on-success="handleImgSuccess"
@ -105,13 +105,13 @@
<script>
import { createQuestion,getQuestioncatAll, getQuestion, getQuestioncatList, updateQuestion } from "@/api/question";
import { genTree, deepClone } from "@/utils";
import { uploadUrl } from "@/api/file";
import { upUrl } from "@/api/file";
import { getToken } from "@/utils/auth";
export default {
data() {
return {
myHeaders: { Authorization: "JWT " + getToken() },
uploadUrl: uploadUrl(),
upHeaders: { Authorization: "JWT " + getToken() },
upUrl: upUrl(),
Form: {
id:0,
name: "",

View File

@ -67,8 +67,8 @@
<el-form-item label="图片" prop="path" >
<el-upload
class="avatar-uploader"
:headers="myHeaders"
:action="uploadUrl"
:headers="upHeaders"
:action="upUrl"
accept="image/jpeg, image/gif, image/png, image/bmp"
:show-file-list="false"
:on-success="handleImgSuccess"
@ -91,7 +91,7 @@
import { getBannerAll, createBanner, deleteBanner, updateBanner } from "@/api/banner";
import { deepClone } from "@/utils";
import checkPermission from "@/utils/permission";
import { uploadUrl } from "@/api/file";
import { upUrl } from "@/api/file";
import { getToken } from "@/utils/auth";
@ -106,8 +106,8 @@ const defaultBanner = {
export default {
data() {
return {
myHeaders: { Authorization: "JWT " + getToken() },
uploadUrl: uploadUrl(),
upHeaders: { Authorization: "JWT " + getToken() },
upUrl: upUrl(),
banner: {
id: "",
name: "",

View File

@ -163,12 +163,12 @@
<el-form-item label="头像" prop="department">
<el-upload
class="avatar-uploader"
:action="uploadUrl"
:action="upUrl"
accept="image/jpeg, image/gif, image/png, image/bmp"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
:headers="myHeaders"
:headers="upHeaders"
>
<img v-if="user.avatar" :src="user.avatar" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
@ -213,7 +213,7 @@ import { getOrgList } from "@/api/org";
import { getRoles } from "@/api/role";
import { genTree, deepClone } from "@/utils";
import checkPermission from "@/utils/permission";
import { uploadUrl } from "@/api/file";
import { upUrl } from "@/api/file";
import { getToken } from "@/utils/auth";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
@ -240,8 +240,8 @@ export default {
department: null,
avatar:""
},
myHeaders: { Authorization: "JWT " + getToken() },
uploadUrl: uploadUrl(),
upHeaders: { Authorization: "JWT " + getToken() },
upUrl: upUrl(),
userList: [],
roles: [],
total: 0,

View File

@ -1,4 +1,5 @@
// pages/quota/quota.js
const api = require("../../utils/request.js");
Page({
/**
@ -12,7 +13,7 @@ Page({
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.getList()
},
/**
@ -47,7 +48,8 @@ Page({
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
this.getList()
wx.stopPullDownRefresh()
},
/**
@ -62,5 +64,22 @@ Page({
*/
onShareAppMessage: function () {
},
Baoming: function () {
wx.navigateToMiniProgram({
appId: 'wxdcf5bbfb50dbbac8',
path: '',
success(res) {
// 打开其他小程序成功同步触发
// wx.showToast({
// title: '跳转成功'
// })
}
})
},
getList:function(){
api.request('/analyse/quota/', 'GET').then(res => {
this.setData(res.data)
})
}
})

View File

@ -1,3 +1,5 @@
{
"usingComponents": {}
"usingComponents": {},
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
}

View File

@ -1,2 +1,35 @@
<!--pages/quota/quota.wxml-->
<text>pages/quota/quota.wxml</text>
<view class="head">
更新时间:{{update_time}}</view>
<a class="weui-btn weui-btn_primary" bindtap="Baoming" style="margin-top:4px">前往正式报名</a>
<view class="weui-cells weui-cells_after-title">
<block wx:for="{{tests}}" wx:key="unique">
<view class="weui-media-box weui-media-box_appmsg" hover-class="weui-cell_active">
<view class="weui-media-box__bd weui-media-box__bd_in-appmsg">
<view class="weui-media-box__title">{{item.cityName}} {{item.examDate}}</view>
<view class="weui-media-box__desc">
考试时间:
<span style="font-weight:bold;color:darkblue">{{item.examTime}}</span>
<span>-</span>
截止报名:
<span style="font-weight:bold;color:red;">{{item.endSignDate}}</span>
</view>
<view class="weui-media-box__desc" wx:if="{{item.linkContact}}">
联系电话:{{item.linkContact}}
</view>
<view class="weui-media-box__desc">
{{item.planName}}
</view>
</view>
<view class="weui-panel__ft weui-cell__ft_in-access">
<view class="weui-media-box__desc" style="color:green;font-weight:bold">
余{{item.validNum}}名
</view>
<view class="weui-media-box__desc" style="color:gray;font-weight:bold">
共{{item.pNum}}名
</view>
</view>
</view>
</block>
</view>

View File

@ -1 +1,6 @@
/* pages/quota/quota.wxss */
/* pages/quota/quota.wxss */
.head{
color:#fff;
background-color: cornflowerblue;
text-align: center;
}

View File

@ -0,0 +1,30 @@
# Generated by Django 3.0.5 on 2020-06-26 09:00
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cms', '0004_auto_20200429_1205'),
]
operations = [
migrations.CreateModel(
name='Material',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_delete', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=200, verbose_name='标题')),
('description', models.TextField(blank=True, null=True, verbose_name='描述')),
('path', models.CharField(max_length=1000, verbose_name='文件地址')),
('read_count', models.IntegerField(default=0, verbose_name='阅读量')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.0.5 on 2020-06-26 09:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cms', '0005_material'),
]
operations = [
migrations.RenameField(
model_name='material',
old_name='read_count',
new_name='down_count',
),
migrations.AlterField(
model_name='material',
name='name',
field=models.CharField(max_length=200, unique=True, verbose_name='标题'),
),
]

View File

@ -25,5 +25,7 @@ class Material(CommonModel):
"""
学习资料
"""
name = models.CharField(max_length=200, verbose_name='标题')
description = models.TextField('描述')
name = models.CharField(max_length=200, verbose_name='标题', unique=True)
description = models.TextField('描述', null=True, blank=True)
path = models.CharField(max_length=1000, verbose_name='文件地址')
down_count = models.IntegerField('阅读量', default=0)

View File

@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import Article
from .models import Article, Material
class ArticelSerializer(serializers.ModelSerializer):
@ -21,3 +21,10 @@ class ArticelListSerializer(serializers.ModelSerializer):
class Meta:
model = Article
exclude = ('content',)
class MaterialSerializer(serializers.ModelSerializer):
class Meta:
model = Material
fields = '__all__'

View File

@ -1,10 +1,11 @@
from django.urls import path,include
from .views import ArticleViewSet
from .views import ArticleViewSet, MaterialViewSet
from rest_framework import routers
router = routers.DefaultRouter()
router.register('article', ArticleViewSet, basename="article")
router.register('material', MaterialViewSet, basename="material")
urlpatterns = [
path('', include(router.urls)),

View File

@ -21,8 +21,8 @@ from rest_framework_jwt.serializers import (jwt_encode_handler,
from rest_framework_jwt.settings import api_settings
# Create your views here.
from .models import Article
from .serializers import ArticelSerializer, ArticelListSerializer
from .models import Article, Material
from .serializers import ArticelSerializer, ArticelListSerializer, MaterialSerializer
from utils.custom import CommonPagination
class ArticleViewSet(ModelViewSet):
"""
@ -35,8 +35,8 @@ class ArticleViewSet(ModelViewSet):
serializer_class = ArticelSerializer
pagination_class = CommonPagination
filter_backends = [DjangoFilterBackend,SearchFilter, OrderingFilter]
search_fields = ('^title','^content')
ordering_fields = ('title','update_time')
search_fields = ['title','content']
ordering_fields = ['title','update_time']
ordering = ['-is_top', '-update_time']
def get_serializer_class(self):
@ -53,4 +53,29 @@ class ArticleViewSet(ModelViewSet):
instance = self.get_object()
instance.is_top = False if instance.is_top else True
instance.save()
return Response(status=status.HTTP_200_OK)
class MaterialViewSet(ModelViewSet):
"""
资料增删改查
"""
perms_map = [
{'get': 'material_list'}, {'post': 'material_create'},
{'put': 'material_update'}, {'delete': 'material_delete'}]
queryset = Material.objects.filter(is_delete=0)
serializer_class = MaterialSerializer
pagination_class = CommonPagination
filter_backends = [DjangoFilterBackend,SearchFilter, OrderingFilter]
search_fields = ['name','description']
ordering_fields = ['update_time', 'down_count']
ordering = ['-update_time']
@action(methods=['get'], detail=True, url_name='down_material', perms_map=[{'*':'down_material'}])
def down(self, request, *args, **kwargs):
'''
下载资料
'''
instance = self.get_object()
instance.down_count = instance.down_count + 1
instance.save()
return Response(status=status.HTTP_200_OK)

View File

@ -155,6 +155,8 @@ REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
'UNAUTHENTICATED_USER': None,
'UNAUTHENTICATED_TOKEN': None,
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S',
'DATE_FORMAT': '%Y-%m-%d',
}
JWT_AUTH = {