This commit is contained in:
caoqianming 2020-04-28 17:23:00 +08:00
parent 9c7f4023eb
commit 48c4edb7ea
23 changed files with 524 additions and 127 deletions

View File

@ -875,6 +875,75 @@
"to-fast-properties": "^2.0.0"
}
},
"@ckeditor/ckeditor5-build-classic": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-build-classic/-/ckeditor5-build-classic-18.0.0.tgz",
"integrity": "sha512-7nyaDU5sUSl+7wXPwr0d5bOlO2e0LRQh2iJJCJfAjjuUESwtiBGoFC+Ql5dEht0xlfARoSWQMlkUvGuHOVja7A=="
},
"@ckeditor/ckeditor5-core": {
"version": "18.0.0",
"resolved": "https://registry.npm.taobao.org/@ckeditor/ckeditor5-core/download/@ckeditor/ckeditor5-core-18.0.0.tgz",
"integrity": "sha1-71NcvrPEgY1GPkXnhEY/k3vy1js=",
"requires": {
"@ckeditor/ckeditor5-engine": "^18.0.0",
"@ckeditor/ckeditor5-utils": "^18.0.0",
"lodash-es": "^4.17.10"
}
},
"@ckeditor/ckeditor5-editor-classic": {
"version": "18.0.0",
"resolved": "https://registry.npm.taobao.org/@ckeditor/ckeditor5-editor-classic/download/@ckeditor/ckeditor5-editor-classic-18.0.0.tgz",
"integrity": "sha1-YquzsVFShohUzoJJ/8MKRT8k/ek=",
"requires": {
"@ckeditor/ckeditor5-core": "^18.0.0",
"@ckeditor/ckeditor5-engine": "^18.0.0",
"@ckeditor/ckeditor5-ui": "^18.0.0",
"@ckeditor/ckeditor5-utils": "^18.0.0",
"lodash-es": "^4.17.10"
}
},
"@ckeditor/ckeditor5-engine": {
"version": "18.0.0",
"resolved": "https://registry.npm.taobao.org/@ckeditor/ckeditor5-engine/download/@ckeditor/ckeditor5-engine-18.0.0.tgz",
"integrity": "sha1-iKtK8/zggGuQ/drZjJDRYkFlst4=",
"requires": {
"@ckeditor/ckeditor5-utils": "^18.0.0",
"lodash-es": "^4.17.10"
}
},
"@ckeditor/ckeditor5-ui": {
"version": "18.0.0",
"resolved": "https://registry.npm.taobao.org/@ckeditor/ckeditor5-ui/download/@ckeditor/ckeditor5-ui-18.0.0.tgz",
"integrity": "sha1-GSlSk6IwAR3tU/5OYgQFppwMsRU=",
"requires": {
"@ckeditor/ckeditor5-core": "^18.0.0",
"@ckeditor/ckeditor5-utils": "^18.0.0",
"lodash-es": "^4.17.10"
}
},
"@ckeditor/ckeditor5-upload": {
"version": "18.0.0",
"resolved": "https://registry.npm.taobao.org/@ckeditor/ckeditor5-upload/download/@ckeditor/ckeditor5-upload-18.0.0.tgz",
"integrity": "sha1-ay1FTh3+rcKQQW7UrkJNGGQktfo=",
"requires": {
"@ckeditor/ckeditor5-core": "^18.0.0",
"@ckeditor/ckeditor5-ui": "^18.0.0",
"@ckeditor/ckeditor5-utils": "^18.0.0"
}
},
"@ckeditor/ckeditor5-utils": {
"version": "18.0.0",
"resolved": "https://registry.npm.taobao.org/@ckeditor/ckeditor5-utils/download/@ckeditor/ckeditor5-utils-18.0.0.tgz",
"integrity": "sha1-lc1mYcAA6RV+u3BSoUKp2qNLU4g=",
"requires": {
"lodash-es": "^4.17.10"
}
},
"@ckeditor/ckeditor5-vue": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-vue/-/ckeditor5-vue-1.0.1.tgz",
"integrity": "sha512-4MaQwZ04cWwqYW0732sg2aqx9ILeHIP0LSLKUuLCLR21qYONZPvxY/V/czh1DH99toaL/iwPvEoJtO2ldriPaA=="
},
"@hapi/address": {
"version": "2.1.4",
"resolved": "https://registry.npm.taobao.org/@hapi/address/download/@hapi/address-2.1.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40hapi%2Faddress%2Fdownload%2F%40hapi%2Faddress-2.1.4.tgz",
@ -9793,7 +9862,7 @@
},
"load-script": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/load-script/download/load-script-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
"integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ="
},
"loader-fs-cache": {
@ -9897,6 +9966,11 @@
"integrity": "sha1-tEf2ZwoEVbv+7dETku/zMOoJdUg=",
"dev": true
},
"lodash-es": {
"version": "4.17.15",
"resolved": "https://registry.npm.taobao.org/lodash-es/download/lodash-es-4.17.15.tgz",
"integrity": "sha1-Ib2Wg5NUQS8j16EDQOXqxu5FXXg="
},
"lodash.defaultsdeep": {
"version": "4.6.1",
"resolved": "https://registry.npm.taobao.org/lodash.defaultsdeep/download/lodash.defaultsdeep-4.6.1.tgz?cache=0&sync_timestamp=1562718178896&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash.defaultsdeep%2Fdownload%2Flodash.defaultsdeep-4.6.1.tgz",

View File

@ -15,6 +15,10 @@
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
},
"dependencies": {
"@ckeditor/ckeditor5-build-classic": "^18.0.0",
"@ckeditor/ckeditor5-editor-classic": "^18.0.0",
"@ckeditor/ckeditor5-upload": "^18.0.0",
"@ckeditor/ckeditor5-vue": "^1.0.1",
"axios": "0.18.1",
"ckeditor4-vue": "^0.2.0",
"echarts": "^4.7.0",

View File

@ -0,0 +1,34 @@
import request from '@/utils/request'
export function getArticleList() {
return request({
url: '/cms/article/',
method: 'get',
})
}
export function createArticle(data) {
return request({
url: '/cms/article/',
method: 'post',
data
})
}
export function updateArticle(id, data) {
return request({
url: `/cms/article/${id}/`,
method: 'put',
data
})
}
export function deleteArticle(id) {
return request({
url: `/cms/article/${id}/`,
method: 'delete',
})
}
export function getArticle(id) {
return request({
url: `/cms/article/${id}/`,
method: 'get',
})
}

View File

@ -1,9 +1,6 @@
<template>
<div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
</div>
</div>
</template>
@ -12,38 +9,42 @@
* docs:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
*/
import editorImage from './components/EditorImage'
import plugins from './plugins'
import toolbar from './toolbar'
import load from './dynamicLoadScript'
import plugins from "./plugins";
import toolbar from "./toolbar";
import load from "./dynamicLoadScript";
import { uploadUrl, myHeaders } from "@/api/file";
import { getToken } from "@/utils/auth";
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
const tinymceCDN =
"https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js";
export default {
name: 'Tinymce',
components: { editorImage },
name: "Tinymce",
props: {
id: {
type: String,
default: function() {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
return (
"vue-tinymce-" +
+new Date() +
((Math.random() * 1000).toFixed(0) + "")
);
}
},
value: {
type: String,
default: ''
default: ""
},
toolbar: {
type: Array,
required: false,
default() {
return []
return [];
}
},
menubar: {
type: String,
default: 'file edit insert view format table'
default: "file edit insert view format table"
},
height: {
type: [Number, String],
@ -53,7 +54,7 @@ export default {
width: {
type: [Number, String],
required: false,
default: 'auto'
default: "auto"
}
},
data() {
@ -63,91 +64,93 @@ export default {
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
'en': 'en',
'zh': 'zh_CN',
'es': 'es_MX',
'ja': 'ja'
en: "en",
zh: "zh_CN",
es: "es_MX",
ja: "ja"
}
}
};
},
computed: {
containerWidth() {
const width = this.width
if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
return `${width}px`
const width = this.width;
if (/^[\d]+(\.[\d]+)?$/.test(width)) {
// matches `100`, `'100'`
return `${width}px`;
}
return width
return width;
}
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || ''))
window.tinymce.get(this.tinymceId).setContent(val || "")
);
}
}
},
mounted() {
this.init()
this.init();
},
activated() {
if (window.tinymce) {
this.initTinymce()
this.initTinymce();
}
},
deactivated() {
this.destroyTinymce()
this.destroyTinymce();
},
destroyed() {
this.destroyTinymce()
this.destroyTinymce();
},
methods: {
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, (err) => {
load(tinymceCDN, err => {
if (err) {
this.$message.error(err.message)
return
this.$message.error(err.message);
return;
}
this.initTinymce()
})
this.initTinymce();
});
},
initTinymce() {
const _this = this
const _this = this;
window.tinymce.init({
selector: `#${this.tinymceId}`,
language: this.languageTypeList['en'],
language: this.languageTypeList["zh"],
height: this.height,
body_class: 'panel-body ',
body_class: "panel-body ",
object_resizing: false,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
powerpaste_word_import: "clean",
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
default_link_target: '_blank',
advlist_bullet_styles: "square",
advlist_number_styles: "default",
imagetools_cors_hosts: ["www.tinymce.com", "codepen.io"],
default_link_target: "_blank",
link_title: false,
nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
editor.setContent(_this.value);
}
_this.hasInit = true
editor.on('NodeChange Change KeyUp SetContent', () => {
this.hasChange = true
this.$emit('input', editor.getContent())
})
_this.hasInit = true;
editor.on("NodeChange Change KeyUp SetContent", () => {
this.hasChange = true;
this.$emit("input", editor.getContent());
});
},
setup(editor) {
editor.on('FullscreenStateChanged', (e) => {
_this.fullscreen = e.state
})
}
editor.on("FullscreenStateChanged", e => {
_this.fullscreen = e.state;
});
},
//
// images_dataimg_filter(img) {
// setTimeout(() => {
@ -163,50 +166,54 @@ export default {
// }, 0);
// return img
// },
// images_upload_handler(blobInfo, success, failure, progress) {
// progress(0);
// const token = _this.$store.getters.token;
// getToken(token).then(response => {
// const url = response.data.qiniu_url;
// const formData = new FormData();
// formData.append('token', response.data.qiniu_token);
// formData.append('key', response.data.qiniu_key);
// formData.append('file', blobInfo.blob(), url);
// upload(formData).then(() => {
// success(url);
// progress(100);
// })
// }).catch(err => {
// failure('')
// console.log(err);
// });
// },
})
images_upload_handler: function(blobInfo, succFun, failFun) {
var xhr, formData;
var file = blobInfo.blob(); //file
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open("POST", uploadUrl());
xhr.setRequestHeader('Authorization', 'JWT '+ getToken());
xhr.onload = function() {
var json;
if (xhr.status != 200) {
failFun("HTTP Error: " + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
succFun(json.data.path);
};
formData = new FormData();
formData.append("file", file, file.name); //
xhr.send(formData);
}
});
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId)
const tinymce = window.tinymce.get(this.tinymceId);
if (this.fullscreen) {
tinymce.execCommand('mceFullScreen')
tinymce.execCommand("mceFullScreen");
}
if (tinymce) {
tinymce.destroy()
tinymce.destroy();
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value)
window.tinymce.get(this.tinymceId).setContent(value);
},
getContent() {
window.tinymce.get(this.tinymceId).getContent()
window.tinymce.get(this.tinymceId).getContent();
},
imageSuccessCBK(arr) {
const _this = this
const _this = this;
arr.forEach(v => {
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
})
window.tinymce
.get(_this.tinymceId)
.insertContent(`<img class="wscnph" src="${v.url}" >`);
});
}
}
}
};
</script>
<style scoped>
@ -214,7 +221,7 @@ export default {
position: relative;
line-height: normal;
}
.tinymce-container>>>.mce-fullscreen {
.tinymce-container >>> .mce-fullscreen {
z-index: 10000;
}
.tinymce-textarea {

View File

@ -24,7 +24,7 @@ export default {
},
data() {
return {
title: 'Vue Admin Template',
title: '辐射学堂后台管理',
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
}
}

View File

@ -3,6 +3,7 @@ import Vue from 'vue'
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import ElementUI from 'element-ui'
import CKEditor from '@ckeditor/ckeditor5-vue';
import 'element-ui/lib/theme-chalk/index.css'
// import locale from 'element-ui/lib/locale/lang/en' // lang i18n
@ -32,7 +33,7 @@ if (process.env.NODE_ENV === 'production') {
// Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui按如下方式声明
Vue.use(ElementUI)
Vue.use(CKEditor)
Vue.config.productionTip = false
new Vue({

View File

@ -64,25 +64,32 @@ export const constantRoutes = [
*/
export const asyncRoutes = [
{
path: '/news',
path: '/cms',
component: Layout,
redirect: '/news/',
name: 'News',
redirect: '/cms/',
name: 'CMS',
meta: { title: '资讯文章', icon: 'peoples', perms: ['news_view'] },
children: [
{
path: 'news',
name: 'News',
name: 'NewsList',
component: () => import('@/views/news/news.vue'),
meta: { title: '文章列表', icon: 'documentation', perms: ['news_view'] }
},
{
path: 'create',
path: 'news/create',
name: 'NewsCreate',
component: () => import('@/views/news/newscreate.vue'),
meta: { title: '新建文章', noCache: true, icon: '', perms: ['news_create']},
hidden: true
},
{
path: 'news/update',
name: 'NewsUpdate',
component: () => import('@/views/news/newsupdate.vue'),
meta: { title: '编辑文章', noCache: true, icon: '', perms: ['news_update']},
hidden: true
},
]
},
{

View File

@ -12,5 +12,5 @@ module.exports = {
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: false
sidebarLogo: true
}

View File

@ -3,7 +3,7 @@
<div style="margin-top:10px">
<el-input
v-model="listQuery.search"
placeholder="名称"
placeholder="搜索"
style="width: 200px;"
class="filter-item"
@keyup.enter.native="handleSearch"
@ -30,20 +30,14 @@
max-height="600"
>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column align="left" label="名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
<el-table-column align="left" label="标题">
<template slot-scope="scope">{{ scope.row.title }}</template>
</el-table-column>
<el-table-column align="left" label="工作类别">
<template slot-scope="scope">{{ scope.row.workscope_name }}</template>
<el-table-column align="left" label="新建时间">
<template slot-scope="scope">{{ scope.row.create_time }}</template>
</el-table-column>
<el-table-column align="left" label="总分">
<template slot-scope="scope">{{ scope.row.total_score }}</template>
</el-table-column>
<el-table-column align="left" label="通过分">
<template slot-scope="scope">{{ scope.row.pass_score }}</template>
</el-table-column>
<el-table-column align="left" label="限时(分钟)">
<template slot-scope="scope">{{ scope.row.limit }}</template>
<el-table-column align="left" label="更新时间">
<template slot-scope="scope">{{ scope.row.update_time }}</template>
</el-table-column>
<el-table-column align="center" label="操作">
<template slot-scope="scope">
@ -52,14 +46,14 @@
size="small"
@click="handleEdit(scope)"
icon="el-icon-edit"
:disabled="!checkPermission(['paper_update'])"
:disabled="!checkPermission(['article_update'])"
></el-button>
<el-button
type="danger"
size="small"
@click="handleDelete(scope)"
icon="el-icon-delete"
:disabled="!checkPermission(['paper_delete'])"
:disabled="!checkPermission(['article_delete'])"
></el-button>
</template>
</el-table-column>
@ -75,7 +69,7 @@
</template>
<script>
import { getPaperList, deletePaper } from "@/api/examtest";
import { getArticleList, deleteArticle } from "@/api/cms";
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination";
@ -104,7 +98,7 @@ export default {
checkPermission,
getList(query = this.listQuery) {
this.listLoading = true;
getPaperList(query).then(response => {
getArticleList(query).then(response => {
if(response.data.results){
this.tableData = response.data;
}
@ -124,10 +118,10 @@ export default {
this.getList();
},
handleAdd() {
this.$router.push({path:"/news/create"})
this.$router.push({path:"/cms/news/create"})
},
handleEdit(scope) {
this.$router.push({path:"/sjmanage/paper/update",query:{id:scope.row.id}})
this.$router.push({path:"/cms/news/update",query:{id:scope.row.id}})
},
handleDelete(scope) {
this.$confirm('确认删除?', '提示', {
@ -135,7 +129,7 @@ export default {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deletePaper(scope.row.id).then(response => {
deleteArticle(scope.row.id).then(response => {
this.$message({
type: 'success',
message: '删除成功!'

View File

@ -1,36 +1,88 @@
<template>
<div class="app-container">
<aside>
Rich text is a core feature of the management backend, but at the same time it is a place with lots of pits. In the process of selecting rich texts, I also took a lot of detours. The common rich texts on the market have been basically used, and I finally chose Tinymce. See the more detailed rich text comparison and introduction.
<a target="_blank" class="link-type" href="https://panjiachen.github.io/vue-element-admin-site/component/rich-editor.html">Documentation</a>
</aside>
<div>
<tinymce v-model="content" :height="300" />
<el-form
:model="Form"
:rules="rules"
ref="Form"
label-width="100px"
status-icon
>
<el-form-item label="标题" prop="title">
<el-input v-model="Form.title" style="width: 500"></el-input>
</el-form-item>
<el-form-item label="来源" prop="ifrom">
<el-input v-model="Form.ifrom" style="width: 500"></el-input>
</el-form-item>
<el-form-item label="内容" prop="content">
<tinymce v-model="Form.content" :height="400" :width="600"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('Form')" :loading="submitLoding">立即创建</el-button>
<el-button type="warning" @click="goBack()">返回</el-button>
</el-form-item>
</el-form>
</div>
<div class="editor-content" v-html="content" />
</div>
</template>
<script>
import Tinymce from '@/components/Tinymce'
import {createArticle} from "@/api/cms";
export default {
name: 'TinymceDemo',
components: { Tinymce },
data() {
return {
content:
`<h1 style="text-align: center;">Welcome to the TinyMCE demo!</h1><p style="text-align: center; font-size: 15px;"><img title="TinyMCE Logo" src="//www.tinymce.com/images/glyph-tinymce@2x.png" alt="TinyMCE Logo" width="110" height="97" /><ul>
<li>Our <a href="//www.tinymce.com/docs/">documentation</a> is a great resource for learning how to configure TinyMCE.</li><li>Have a specific question? Visit the <a href="https://community.tinymce.com/forum/">Community Forum</a>.</li><li>We also offer enterprise grade support as part of <a href="https://tinymce.com/pricing">TinyMCE premium subscriptions</a>.</li>
</ul>`
Form:{
title:'',
ifrom:'',
content:''
},
submitLoding: false,
rules: {
title: [
{ required: true, message: "请输入", trigger: "blur" }
],
ifrom: [
{ required: true, message: "请输入", trigger: "blur" }
],
content: [
{ required: true, message: "请输入", trigger: "blur" }
],
},
}
},
methods:{
goBack() {
this.$router.go(-1)
},
submitForm(formName) {
this.$refs[formName].validate(valid => {
if (valid) {
// this.submitLoding = true
createArticle(this.Form).then(response => {
this.submitLoding = false
if(response.code >= 200){
this.$message({
type: "success",
message: "新建成功!"
});
this.goBack()
}
});
} else {
return false;
}
});
},
}
}
</script>
<style scoped>
.editor-content{
margin-top: 20px;
}
</style>

View File

@ -0,0 +1,97 @@
<template>
<div class="app-container">
<div>
<el-form
:model="Form"
:rules="rules"
ref="Form"
label-width="100px"
status-icon
>
<el-form-item label="标题" prop="title">
<el-input v-model="Form.title" style="width: 500"></el-input>
</el-form-item>
<el-form-item label="来源" prop="ifrom">
<el-input v-model="Form.ifrom" style="width: 500"></el-input>
</el-form-item>
<el-form-item label="内容" prop="content">
<tinymce v-model="Form.content" :height="400" :width="600"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('Form')" :loading="submitLoding">保存</el-button>
<el-button type="warning" @click="goBack()">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import Tinymce from '@/components/Tinymce'
import {getArticle, updateArticle} from "@/api/cms";
export default {
name: 'TinymceDemo',
components: { Tinymce },
data() {
return {
Form:{
title:'',
ifrom:'',
content:''
},
submitLoding: false,
rules: {
title: [
{ required: true, message: "请输入", trigger: "blur" }
],
ifrom: [
{ required: true, message: "请输入", trigger: "blur" }
],
content: [
{ required: true, message: "请输入", trigger: "blur" }
],
},
}
},
created() {
this.Form.id = this.$route.query.id //
this.getArticle();
},
methods:{
goBack() {
this.$router.go(-1)
},
submitForm(formName) {
this.$refs[formName].validate(valid => {
if (valid) {
// this.submitLoding = true
updateArticle(this.Form.id, this.Form).then(response => {
this.submitLoding = false
if(response.code >= 200){
this.$message({
type: "success",
message: "修改成功!"
});
this.goBack()
}
});
} else {
return false;
}
});
},
getArticle() {
getArticle(this.Form.id).then(response => {
this.Form = response.data ;
});
},
}
}
</script>

View File

3
test_server/cms/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
test_server/cms/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class CmsConfig(AppConfig):
name = 'cms'

View File

@ -0,0 +1,31 @@
# Generated by Django 3.0.4 on 2020-04-28 08:26
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Article',
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='删除标记')),
('title', models.CharField(max_length=60, verbose_name='标题')),
('content', models.TextField(verbose_name='内容')),
('ifrom', models.CharField(max_length=60, verbose_name='来源')),
],
options={
'verbose_name': '文章',
'verbose_name_plural': '文章',
},
),
]

View File

19
test_server/cms/models.py Normal file
View File

@ -0,0 +1,19 @@
from django.db import models
import django.utils.timezone as timezone
from rbac.models import SoftCommonModel, CommonModel
# Create your models here.
class Article(CommonModel):
'''
文章
'''
title = models.CharField(max_length=60, verbose_name='标题')
content = models.TextField(verbose_name='内容')
ifrom = models.CharField(max_length=60, verbose_name='来源')
class Meta:
verbose_name = '文章'
verbose_name_plural = verbose_name
def __str__(self):
return self.title

View File

@ -0,0 +1,13 @@
from rest_framework import serializers
from .models import Article
class ArticelSerializer(serializers.ModelSerializer):
"""
文章序列化
"""
create_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True)
update_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True)
class Meta:
model = Article
fields = '__all__'

3
test_server/cms/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

11
test_server/cms/urls.py Normal file
View File

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

40
test_server/cms/views.py Normal file
View File

@ -0,0 +1,40 @@
import json
import random
import warnings
from calendar import timegm
from datetime import datetime
import requests
from django.db.models import Q
from django_filters.rest_framework import DjangoFilterBackend
from openpyxl import Workbook, load_workbook
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.serializers import (jwt_encode_handler,
jwt_payload_handler)
from rest_framework_jwt.settings import api_settings
# Create your views here.
from .models import Article
from .serializers import ArticelSerializer
from utils.custom import CommonPagination
class ArticleViewSet(ModelViewSet):
"""
文章增删改查
"""
perms_map = [
{'get': 'article_list'}, {'post': 'article_create'},
{'put': 'article_update'}, {'delete': 'article_delete'}]
queryset = Article.objects.filter(is_delete=0).all()
serializer_class = ArticelSerializer
pagination_class = CommonPagination
filter_backends = [DjangoFilterBackend,SearchFilter, OrderingFilter]
search_fields = ('^title','^content')
ordering_fields = ('id',)
ordering = ['-id']

View File

@ -46,7 +46,8 @@ INSTALLED_APPS = [
'crm',
'question',
'examtest',
'analyse'
'analyse',
'cms'
]
MIDDLEWARE = [

View File

@ -28,6 +28,7 @@ urlpatterns = [
path('crm/', include('crm.urls')),
path('question/', include('question.urls')),
path('examtest/', include('examtest.urls')),
path('cms/', include('cms.urls')),
path('analyse/', include('analyse.urls')),
path('token/', obtain_jwt_token),
path('token/refresh/', refresh_jwt_token),