管理后台增加举报信息

publicsignpoll
youngS 3 years ago
parent 479abff102
commit 819c9a7017
  1. 25
      fir_admin/src/api/report.js
  2. 19
      fir_admin/src/router/index.js
  3. 157
      fir_admin/src/views/report/Detail.vue
  4. 227
      fir_admin/src/views/report/list.vue
  5. 2
      fir_ser/admin/urls.py
  6. 76
      fir_ser/admin/views/report.py
  7. 38
      fir_ser/api/migrations/0021_appreportinfo.py
  8. 3
      fir_ser/api/models.py
  9. 6
      fir_ser/api/utils/modelutils.py
  10. 18
      fir_ser/api/utils/serializer.py

@ -0,0 +1,25 @@
import request from '@/utils/request'
export function getAppReportIfo(query) {
return request({
url: '/report/info',
method: 'get',
params: query
})
}
export function updateAppReportIfo(data) {
return request({
url: '/report/info',
method: 'put',
data
})
}
export function deleteAppReportIfo(data) {
return request({
url: '/report/info',
method: 'delete',
data
})
}

@ -196,6 +196,25 @@ export const constantRoutes = [
}
]
},
{
path: '/report',
component: Layout,
children: [
{
path: 'list',
name: 'report_info_list',
component: () => import('@/views/report/list'),
meta: { title: '应用举报信息', icon: 'form' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/report/Detail'),
name: 'report_info_edit',
meta: { title: '编辑信息', noCache: true, activeMenu: '/report/list' },
hidden: true
}
]
},
{
path: '/wxbind',
component: Layout,

@ -0,0 +1,157 @@
<template>
<div class="app-container">
<el-form ref="postForm" :model="postForm" label-width="100px" :disabled="!is_edit">
<el-row>
<el-col :span="12">
<el-form-item label="应用ID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.app_id" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="应用名称">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.app_name" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="举报类型">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.report_type" class="filter-item" disabled>
<el-option v-for="item in postForm.report_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.created_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="举报原因">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.report_reason" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" disabled />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="举报者姓名" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.username" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="举报联系方式" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.email" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="举报者IP地址" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.remote_addr" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="处理状态" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.status" class="filter-item">
<el-option v-for="item in postForm.status_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="处理备注" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.description" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" />
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-col :span="9" style="float: right">
<el-button v-if="!is_edit" type="primary" :disabled="postForm.id === postForm.used_id" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
<el-button type="primary" @click="updateData">保存修改</el-button>
</div>
</el-col>
</div>
</template>
<script>
import { getAppReportIfo, updateAppReportIfo } from '@/api/report'
const defaultForm = {
app_id: undefined,
app_name: undefined,
bundle_id: undefined,
created_time: undefined,
description: undefined,
email: undefined,
remote_addr: undefined,
report_reason: undefined,
status: undefined,
report_type: undefined,
username: undefined
}
export default {
name: 'ReportDetail',
components: { }, filters: {},
data() {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false
}
},
computed: {},
created() {
const id = this.$route.params && this.$route.params.id
this.fetchData(id)
},
methods: {
fetchData(id) {
getAppReportIfo({ id: id }).then(response => {
if (response.data.length === 1) {
this.postForm = response.data[0]
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateAppReportIfo(this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style scoped>
.editor-container {
position: relative;
height: 100%;
}
</style>

@ -0,0 +1,227 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.app_id" placeholder="应用ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.app_name" placeholder="应用名称" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.bundle_id" placeholder="Bundle_Id" style="width: 300px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.remote_addr" placeholder="远端地址" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.email" placeholder="联系信息" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.report_type" placeholder="举报类型" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in report_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.status" placeholder="处理状态" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in status_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
Search
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
stripe
>
<el-table-column align="center" label="ID" width="90">
<template slot-scope="scope">
{{ scope.row.id }}
</template>
</el-table-column>
<el-table-column label="应用ID" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'app_info_edit',params:{id:scope.row.app_id}}">
<el-link type="primary"> {{ scope.row.app_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="应用名称" width="80px">
<template slot-scope="scope">
{{ scope.row.app_name }}
</template>
</el-table-column>
<el-table-column label="Bundle_Id" align="center">
<template slot-scope="scope">
<span>{{ scope.row.bundle_id }}</span>
</template>
</el-table-column>
<el-table-column label="联系信息" align="center">
<template slot-scope="scope">
<span>{{ scope.row.email }}</span>
</template>
</el-table-column>
<el-table-column label="联系姓名" align="center">
<template slot-scope="scope">
<span>{{ scope.row.username }}</span>
</template>
</el-table-column>
<el-table-column label="举报者IP" align="center">
<template slot-scope="scope">
<span>{{ scope.row.remote_addr }}</span>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="举报类型" width="95" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.report_type | payStatusFilter">{{ scope.row| payLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="处理状态" width="95" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status | certStatusFilter">{{ scope.row| statusLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="提交时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.created_time">
<span>{{ scope.row.created_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'report_info_edit',params:{id:scope.row.id}}">
<el-button type="primary" size="mini">
查看编辑
</el-button>
</router-link>
<el-button type="danger" size="mini" @click="remove_order_info(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
</div>
</template>
<script>
import { getAppReportIfo, deleteAppReportIfo } from '@/api/report'
import { baseFilter } from '@/utils'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' }
]
export default {
name: 'OrderInfo',
components: { Pagination },
directives: { waves },
filters: {
formatTime(time) {
if (time) {
return time.split('T')[0]
}
},
payStatusFilter(status) {
const statusMap = {
'0': 'gray',
'1': 'success' }
return statusMap[status]
},
certStatusFilter(status) {
const statusMap = {
'0': 'success',
'1': 'info',
'2': 'gray' }
return statusMap[status]
},
payLableFilter(row) {
return baseFilter(row.report_type, row.report_type_choices)
},
statusLableFilter(row) {
return baseFilter(row.status, row.status_choices)
},
statusFilter(status) {
const statusMap = {
true: 'success',
false: 'danger'
}
return statusMap[status]
},
appStatusNameFilter(row) {
for (const r of row.status_choices) {
if (r.id === row.status) {
return r.name
}
}
},
appStatusFilter(status) {
const statusMap = {
'0': 'danger',
'1': 'success',
'2': 'gray'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
page: 1,
limit: 10,
app_id: undefined,
sort: '-created_time',
report_type: undefined,
app_name: undefined,
bundle_id: undefined,
email: undefined,
status: undefined,
remote_addr: undefined,
username: undefined
},
sortOptions,
report_type_choices: [],
status_choices: []
}
},
created() {
this.fetchData()
}, mounted() {
if (this.$route.params.user_id) {
this.listQuery.user_id = this.$route.params.user_id
}
},
methods: {
remove_order_info(order_info) {
deleteAppReportIfo({ id: order_info.id }).then(response => {
this.list = response.data
if (response.code === 1000) {
this.fetchData()
} else {
this.$message.error('操作失败了 ' + response.msg)
}
})
},
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getAppReportIfo(this.listQuery).then(response => {
this.list = response.data
if (this.list && this.list.length > 0) {
this.report_type_choices = this.list[0].report_type_choices
this.status_choices = this.list[0].status_choices
}
this.total = response.total
this.listLoading = false
})
}
}
}
</script>

@ -19,6 +19,7 @@ from admin.views.app import AppInfoView, AppReleaseInfoView
from admin.views.domain import DomainNameInfoView
from admin.views.login import LoginView, LoginUserView
from admin.views.order import OrderInfoView
from admin.views.report import AdminReportView
from admin.views.storage import StorageInfoView, StorageChangeView
from admin.views.supersign import DeveloperInfoView, DevicesInfoView
from admin.views.user import UserInfoView, UserCertificationInfoView, ThirdWxAccountView
@ -40,5 +41,6 @@ urlpatterns = [
re_path("^devices/info", DevicesInfoView.as_view()),
re_path("^domain/info", DomainNameInfoView.as_view()),
re_path("^wxbind/info", ThirdWxAccountView.as_view()),
re_path("^report/info", AdminReportView.as_view()),
]

@ -0,0 +1,76 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: 4月
# author: liuyu
# date: 2021/4/11
import logging
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from rest_framework.views import APIView
from api.models import AppReportInfo
from api.utils.auth import AdminTokenAuthentication
from api.utils.baseutils import get_dict_from_filter_fields
from api.utils.response import BaseResponse
from api.utils.serializer import AdminAppReportSerializer
logger = logging.getLogger(__name__)
class AppsPageNumber(PageNumberPagination):
page_size = 20 # 每页显示多少条
page_size_query_param = 'limit' # URL中每页显示条数的参数
page_query_param = 'page' # URL中页码的参数
max_page_size = None # 最大页码数限制
class AdminReportView(APIView):
authentication_classes = [AdminTokenAuthentication, ]
def get(self, request):
res = BaseResponse()
filter_fields = ["id", "app_name", "bundle_id", "remote_addr", "report_type", "email", "status", "app_id"]
filter_data = get_dict_from_filter_fields(filter_fields, request.query_params)
sort = request.query_params.get("sort", "-created_time")
page_obj = AppsPageNumber()
obj_list = AppReportInfo.objects.filter(**filter_data).order_by(sort)
page_serializer = page_obj.paginate_queryset(queryset=obj_list, request=request,
view=self)
serializer = AdminAppReportSerializer(page_serializer, many=True)
res.data = serializer.data
res.total = obj_list.count()
return Response(res.dict)
def put(self, request):
res = BaseResponse()
data = request.data
pk = data.get("id", None)
if not pk:
res.code = 1003
res.msg = "参数错误"
return Response(res.dict)
obj = AppReportInfo.objects.filter(id=pk).first()
if obj:
data['pk'] = pk
serializer = AdminAppReportSerializer(obj, data=data, partial=True)
if serializer.is_valid():
serializer.save()
res.data = serializer.data
return Response(res.dict)
res.code = 1004
res.msg = "数据校验失败"
return Response(res.dict)
def delete(self, request):
res = BaseResponse()
data = request.data
pk = data.get("id", None)
if not pk:
res.code = 1003
res.msg = "参数错误"
else:
AppReportInfo.objects.filter(pk=pk).delete()
return self.get(request)
return Response(res.dict)

@ -0,0 +1,38 @@
# Generated by Django 3.2.3 on 2021-11-05 17:12
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0020_auto_20211104_1118'),
]
operations = [
migrations.CreateModel(
name='AppReportInfo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('app_name', models.CharField(blank=True, max_length=32, null=True, verbose_name='应用名称')),
('bundle_id', models.CharField(blank=True, max_length=64, verbose_name='bundle id')),
('remote_addr', models.GenericIPAddressField(verbose_name='远程IP地址')),
('report_type',
models.SmallIntegerField(choices=[(0, '侵权'), (1, '色情'), (2, '赌博'), (3, '欺诈'), (4, '暴力'), (5, '其他')],
default=5, verbose_name='举报类型')),
('report_reason', models.CharField(max_length=512, verbose_name='举报详情')),
('email', models.CharField(max_length=64, verbose_name='联系方式')),
('username', models.CharField(max_length=64, verbose_name='姓名')),
('status', models.SmallIntegerField(choices=[(1, '处理中'), (2, '已经处理')], default=1, verbose_name='处理状态')),
('description', models.CharField(blank=True, default='', max_length=256, verbose_name='备注')),
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='访问时间')),
('app_id',
models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='api.apps',
verbose_name='应用信息')),
],
options={
'verbose_name': '应用举报信息',
'verbose_name_plural': '应用举报信息',
},
),
]

@ -584,6 +584,9 @@ class AppReportInfo(models.Model):
report_reason = models.CharField(max_length=512, verbose_name="举报详情")
email = models.CharField(max_length=64, verbose_name="联系方式")
username = models.CharField(max_length=64, verbose_name="姓名")
status_choices = ((1, '处理中'), (2, '已经处理'))
status = models.SmallIntegerField(choices=status_choices, default=1, verbose_name="处理状态")
description = models.CharField(verbose_name="备注", max_length=256, default='', blank=True)
created_time = models.DateTimeField(auto_now_add=True, verbose_name="访问时间")
class Meta:

@ -80,11 +80,11 @@ def add_remote_info_from_request(request, description):
if request.user and request.user.id is not None:
description = f"{request.user.uid}_{description}"
remote_info = {
'user_agent': meta_info.get('HTTP_USER_AGENT'),
'user_agent': meta_info.get('HTTP_USER_AGENT')[0:510],
'remote_addr': get_real_ip_address(request),
'method': meta_info.get('REQUEST_METHOD'),
'uri_info': urljoin(meta_info.get('PATH_INFO'), meta_info.get('QUERY_STRING')),
'a_domain': get_origin_domain_name(request),
'uri_info': urljoin(meta_info.get('PATH_INFO'), meta_info.get('QUERY_STRING'))[0:255],
'a_domain': get_origin_domain_name(request)[0:127],
'description': description[0:255]
}
RemoteClientInfo.objects.create(**remote_info)

@ -777,3 +777,21 @@ class AppReportSerializer(serializers.ModelSerializer):
class Meta:
model = models.AppReportInfo
exclude = ["id"]
class AdminAppReportSerializer(AppReportSerializer):
class Meta:
model = models.AppReportInfo
fields = '__all__'
read_only_fields = ["id", "app_id", "username", "created_time", "email", "report_reason", "report_type",
"remote_addr", "bundle_id", "app_name"]
report_type_choices = serializers.SerializerMethodField()
def get_report_type_choices(self, obj):
return get_choices_dict(obj.report_type_choices)
status_choices = serializers.SerializerMethodField()
def get_status_choices(self, obj):
return get_choices_dict(obj.status_choices)

Loading…
Cancel
Save