mirror of https://github.com/openspug/spug
fix issue
parent
5e06e74612
commit
73232d9e66
|
@ -22,3 +22,21 @@ class Notice(models.Model, ModelMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'notices'
|
db_table = 'notices'
|
||||||
ordering = ('-sort_id',)
|
ordering = ('-sort_id',)
|
||||||
|
|
||||||
|
|
||||||
|
class Navigation(models.Model, ModelMixin):
|
||||||
|
title = models.CharField(max_length=64)
|
||||||
|
desc = models.CharField(max_length=128)
|
||||||
|
logo = models.TextField()
|
||||||
|
links = models.TextField()
|
||||||
|
sort_id = models.IntegerField(default=0, db_index=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def to_view(self):
|
||||||
|
tmp = self.to_dict()
|
||||||
|
tmp['links'] = json.loads(self.links)
|
||||||
|
return tmp
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'navigations'
|
||||||
|
ordering = ('-sort_id',)
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||||
|
# Copyright: (c) <spug.dev@gmail.com>
|
||||||
|
# Released under the AGPL-3.0 License.
|
||||||
|
from django.views.generic import View
|
||||||
|
from libs import json_response, JsonParser, Argument
|
||||||
|
from apps.home.models import Navigation
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class NavView(View):
|
||||||
|
def get(self, request):
|
||||||
|
navs = Navigation.objects.all()
|
||||||
|
return json_response([x.to_view() for x in navs])
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
form, error = JsonParser(
|
||||||
|
Argument('id', type=int, required=False),
|
||||||
|
Argument('title', help='请输入导航标题'),
|
||||||
|
Argument('desc', help='请输入导航描述'),
|
||||||
|
Argument('logo', help='请上传导航logo'),
|
||||||
|
Argument('links', type=list, filter=lambda x: len(x), help='请设置导航链接'),
|
||||||
|
).parse(request.body)
|
||||||
|
if error is None:
|
||||||
|
form.links = json.dumps(form.links)
|
||||||
|
if form.id:
|
||||||
|
Navigation.objects.filter(pk=form.id).update(**form)
|
||||||
|
else:
|
||||||
|
nav = Navigation.objects.create(**form)
|
||||||
|
nav.sort_id = nav.id
|
||||||
|
nav.save()
|
||||||
|
return json_response(error=error)
|
||||||
|
|
||||||
|
def patch(self, request):
|
||||||
|
form, error = JsonParser(
|
||||||
|
Argument('id', type=int, help='参数错误'),
|
||||||
|
Argument('sort', filter=lambda x: x in ('up', 'down'), required=False),
|
||||||
|
).parse(request.body)
|
||||||
|
if error is None:
|
||||||
|
nav = Navigation.objects.filter(pk=form.id).first()
|
||||||
|
if not nav:
|
||||||
|
return json_response(error='未找到指定记录')
|
||||||
|
if form.sort:
|
||||||
|
if form.sort == 'up':
|
||||||
|
tmp = Navigation.objects.filter(sort_id__gt=nav.sort_id).last()
|
||||||
|
else:
|
||||||
|
tmp = Navigation.objects.filter(sort_id__lt=nav.sort_id).first()
|
||||||
|
if tmp:
|
||||||
|
tmp.sort_id, nav.sort_id = nav.sort_id, tmp.sort_id
|
||||||
|
tmp.save()
|
||||||
|
nav.save()
|
||||||
|
return json_response(error=error)
|
||||||
|
|
||||||
|
def delete(self, request):
|
||||||
|
form, error = JsonParser(
|
||||||
|
Argument('id', type=int, help='参数错误')
|
||||||
|
).parse(request.GET)
|
||||||
|
if error is None:
|
||||||
|
Navigation.objects.filter(pk=form.id).delete()
|
||||||
|
return json_response(error=error)
|
|
@ -5,6 +5,7 @@ from django.urls import path
|
||||||
|
|
||||||
from .views import *
|
from .views import *
|
||||||
from apps.home.notice import NoticeView
|
from apps.home.notice import NoticeView
|
||||||
|
from apps.home.navigation import NavView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('statistic/', get_statistic),
|
path('statistic/', get_statistic),
|
||||||
|
@ -12,4 +13,5 @@ urlpatterns = [
|
||||||
path('deploy/', get_deploy),
|
path('deploy/', get_deploy),
|
||||||
path('request/', get_request),
|
path('request/', get_request),
|
||||||
path('notice/', NoticeView.as_view()),
|
path('notice/', NoticeView.as_view()),
|
||||||
|
path('navigation/', NavView.as_view()),
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,54 +7,79 @@ import React, { useState, useEffect } from 'react';
|
||||||
import { Avatar, Button, Card, Col, Row } from 'antd';
|
import { Avatar, Button, Card, Col, Row } from 'antd';
|
||||||
import { LeftSquareOutlined, RightSquareOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
import { LeftSquareOutlined, RightSquareOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import NavForm from './NavForm';
|
import NavForm from './NavForm';
|
||||||
|
import { http } from 'libs';
|
||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
|
|
||||||
function NavIndex(props) {
|
function NavIndex(props) {
|
||||||
const [isEdit, setIsEdit] = useState(true);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
|
const [records, setRecords] = useState([]);
|
||||||
const [record, setRecord] = useState();
|
const [record, setRecord] = useState();
|
||||||
|
|
||||||
function handleSubmit() {
|
useEffect(() => {
|
||||||
|
fetchRecords()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
function fetchRecords() {
|
||||||
|
http.get('/api/home/navigation/')
|
||||||
|
.then(res => setRecords(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
fetchRecords();
|
||||||
|
setRecord(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSort(info, sort) {
|
||||||
|
http.patch('/api/home/navigation/', {id: info.id, sort})
|
||||||
|
.then(() => fetchRecords())
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title="便捷导航" className={styles.nav} extra={<Button type="link">编辑</Button>}>
|
<Card
|
||||||
|
title="便捷导航"
|
||||||
|
className={styles.nav}
|
||||||
|
extra={<Button type="link" onClick={() => setIsEdit(!isEdit)}>{isEdit ? '完成' : '编辑'}</Button>}>
|
||||||
{isEdit ? (
|
{isEdit ? (
|
||||||
<Row gutter={24}>
|
<Row gutter={24}>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<div className={styles.add} onClick={() => setRecord({links: [{}]})}><PlusOutlined/></div>
|
<div
|
||||||
|
className={styles.add}
|
||||||
|
onClick={() => setRecord({links: [{}]})}>
|
||||||
|
<PlusOutlined/>
|
||||||
|
<span>新建</span>
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
{records.map(item => (
|
||||||
|
<Col key={item.id} span={6}>
|
||||||
|
<Card actions={[
|
||||||
|
<LeftSquareOutlined onClick={() => handleSort(item, 'up')}/>,
|
||||||
|
<RightSquareOutlined onClick={() => handleSort(item, 'down')}/>,
|
||||||
|
<EditOutlined onClick={() => setRecord(item)}/>
|
||||||
|
]}>
|
||||||
|
<Card.Meta
|
||||||
|
avatar={<Avatar src={item.logo}/>}
|
||||||
|
title={item.title}
|
||||||
|
description={item.desc}/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
) : (
|
) : (
|
||||||
<Row gutter={24}>
|
<Row gutter={24}>
|
||||||
<Col span={6}>
|
{records.map(item => (
|
||||||
<div className={styles.add}><PlusOutlined/></div>
|
<Col key={item.id} span={6}>
|
||||||
</Col>
|
<Card actions={item.links.map(x => <span>{x.name}</span>)}>
|
||||||
<Col span={6}>
|
<Card.Meta
|
||||||
<Card actions={[
|
avatar={<Avatar src={item.logo}/>}
|
||||||
<span>演示地址</span>
|
title={item.title}
|
||||||
]}>
|
description={item.desc}/>
|
||||||
<Card.Meta
|
</Card>
|
||||||
avatar={<Avatar src="https://gitee.com/openspug/index/raw/master/img/gitlab.png"/>}
|
</Col>
|
||||||
title="Gitlab"
|
))}
|
||||||
description="Gitlab 内部代码仓库,请使用公司LDAP账户登录"/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<Card actions={[
|
|
||||||
<LeftSquareOutlined/>,
|
|
||||||
<RightSquareOutlined/>,
|
|
||||||
<EditOutlined/>
|
|
||||||
]}>
|
|
||||||
<Card.Meta
|
|
||||||
avatar={<Avatar src="https://gitee.com/openspug/index/raw/master/img/wiki.png"/>}
|
|
||||||
title="Wiki系统"
|
|
||||||
description="文档系统,技术架构及技术文档"/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{record ? <NavForm record={record} onCancel={() => setRecord(null)}/> : null}
|
{record ? <NavForm record={record} onCancel={() => setRecord(null)} onOk={handleSubmit}/> : null}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
* Copyright (c) <spug.dev@gmail.com>
|
* Copyright (c) <spug.dev@gmail.com>
|
||||||
* Released under the AGPL-3.0 License.
|
* Released under the AGPL-3.0 License.
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Form, Input, Modal, Button } from 'antd';
|
import { Form, Input, Modal, Button, Upload, message } from 'antd';
|
||||||
import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
|
import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { http } from 'libs';
|
||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
import lds from 'lodash';
|
import lds from 'lodash';
|
||||||
|
|
||||||
|
@ -13,10 +14,27 @@ function NavForm(props) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [record, setRecord] = useState(props.record);
|
const [record, setRecord] = useState(props.record);
|
||||||
|
const [fileList, setFileList] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.record.logo) {
|
||||||
|
setFileList([{uid: 0, thumbUrl: props.record.logo}])
|
||||||
|
}
|
||||||
|
}, [props.record])
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
const formData = form.getFieldsValue();
|
const formData = form.getFieldsValue();
|
||||||
console.log(formData)
|
const links = record.links.filter(x => x.name && x.url);
|
||||||
|
if (links.length === 0) return message.error('请设置至少一条导航链接');
|
||||||
|
if (fileList.length === 0) return message.error('请上传导航logo');
|
||||||
|
formData.id = record.id;
|
||||||
|
formData.links = links;
|
||||||
|
formData.logo = fileList[0].thumbUrl;
|
||||||
|
setLoading(true);
|
||||||
|
http.post('/api/home/navigation/', formData)
|
||||||
|
.then(() => {
|
||||||
|
props.onOk();
|
||||||
|
}, () => setLoading(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
function add() {
|
function add() {
|
||||||
|
@ -34,15 +52,38 @@ function NavForm(props) {
|
||||||
setRecord(lds.cloneDeep(record))
|
setRecord(lds.cloneDeep(record))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function beforeUpload(file) {
|
||||||
|
if (file.size / 1024 > 100) {
|
||||||
|
message.error('图片将直接存储至数据库,请上传小于100KB的图片');
|
||||||
|
setTimeout(() => setFileList([]))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible
|
visible
|
||||||
title={`${record.id ? '编辑' : '新建'}导航`}
|
title={`${record.id ? '编辑' : '新建'}链接`}
|
||||||
afterClose={() => console.log('after close')}
|
afterClose={() => console.log('after close')}
|
||||||
onCancel={props.onCancel}
|
onCancel={props.onCancel}
|
||||||
confirmLoading={loading}
|
confirmLoading={loading}
|
||||||
onOk={handleSubmit}>
|
onOk={handleSubmit}>
|
||||||
<Form form={form} initialValues={record} labelCol={{span: 5}} wrapperCol={{span: 18}}>
|
<Form form={form} initialValues={record} labelCol={{span: 5}} wrapperCol={{span: 18}}>
|
||||||
|
<Form.Item required label="导航图标">
|
||||||
|
<Upload
|
||||||
|
listType="picture-card"
|
||||||
|
fileList={fileList}
|
||||||
|
beforeUpload={beforeUpload}
|
||||||
|
showUploadList={{showPreviewIcon: false}}
|
||||||
|
onChange={({fileList}) => setFileList(fileList)}>
|
||||||
|
{fileList.length === 0 && (
|
||||||
|
<div>
|
||||||
|
<PlusOutlined/>
|
||||||
|
<div style={{marginTop: 8}}>点击上传</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Upload>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item required name="title" label="导航标题">
|
<Form.Item required name="title" label="导航标题">
|
||||||
<Input placeholder="请输入"/>
|
<Input placeholder="请输入"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
|
@ -20,10 +20,11 @@ function NoticeIndex(props) {
|
||||||
const [notice, setNotice] = useState();
|
const [notice, setNotice] = useState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch()
|
fetchRecords()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
function fetch() {
|
function fetchRecords() {
|
||||||
setFetching(true);
|
setFetching(true);
|
||||||
http.get('/api/home/notice/')
|
http.get('/api/home/notice/')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
@ -43,7 +44,7 @@ function NoticeIndex(props) {
|
||||||
formData['id'] = record.id;
|
formData['id'] = record.id;
|
||||||
http.post('/api/home/notice/', formData)
|
http.post('/api/home/notice/', formData)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
fetch()
|
fetchRecords()
|
||||||
setRecord(null)
|
setRecord(null)
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
|
@ -57,14 +58,14 @@ function NoticeIndex(props) {
|
||||||
function handleSort(e, info, sort) {
|
function handleSort(e, info, sort) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
http.patch('/api/home/notice/', {id: info.id, sort})
|
http.patch('/api/home/notice/', {id: info.id, sort})
|
||||||
.then(() => fetch())
|
.then(() => fetchRecords())
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRead() {
|
function handleRead() {
|
||||||
if (!notice.read_ids.includes(id)) {
|
if (!notice.read_ids.includes(id)) {
|
||||||
const formData = {id: notice.id, read: 1};
|
const formData = {id: notice.id, read: 1};
|
||||||
http.patch('/api/home/notice/', formData)
|
http.patch('/api/home/notice/', formData)
|
||||||
.then(() => fetch())
|
.then(() => fetchRecords())
|
||||||
}
|
}
|
||||||
setNotice(null);
|
setNotice(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,13 @@
|
||||||
* Released under the AGPL-3.0 License.
|
* Released under the AGPL-3.0 License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, Card, List } from 'antd';
|
import { Card, List } from 'antd';
|
||||||
|
|
||||||
function TodoIndex(props) {
|
function TodoIndex(props) {
|
||||||
return (
|
return (
|
||||||
<Card title="待办事项" bodyStyle={{height: 234, padding: '0 24px'}}>
|
<Card title="待办事项" bodyStyle={{height: 234, padding: '0 24px'}}>
|
||||||
<List>
|
<List>
|
||||||
<List.Item extra={<Button type="link">已完成</Button>}>发布申请 测试未附件 需要你审核。</List.Item>
|
<List.Item>工单 ECS购买 需要你审核。</List.Item>
|
||||||
<List.Item>工单 资源添加 需要你审核。</List.Item>
|
|
||||||
</List>
|
</List>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
height: 166px;
|
height: 166px;
|
||||||
border: 1px dashed #d9d9d9;
|
border: 1px dashed #d9d9d9;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
@ -79,13 +80,14 @@
|
||||||
|
|
||||||
:global(.ant-card) {
|
:global(.ant-card) {
|
||||||
height: 166px;
|
height: 166px;
|
||||||
background-color: #fafafa;
|
background-color: #fdfdfd;
|
||||||
|
|
||||||
:global(.ant-card-actions) {
|
:global(.ant-card-actions) {
|
||||||
background-color: #f0f0f0;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.ant-card-meta-description) {
|
:global(.ant-card-meta-description) {
|
||||||
|
height: 44px;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
Loading…
Reference in New Issue