增加应用展示页面

pull/16/head
youngS 4 years ago
parent c8f3126a85
commit 23a215d980
  1. 17
      fir_admin/src/api/app.js
  2. 8
      fir_admin/src/api/user.js
  3. 21
      fir_admin/src/router/index.js
  4. 305
      fir_admin/src/views/appinfos/AppDetail.vue
  5. 41
      fir_admin/src/views/appinfos/Dropdown/Comment.vue
  6. 46
      fir_admin/src/views/appinfos/Dropdown/Platform.vue
  7. 38
      fir_admin/src/views/appinfos/Dropdown/SourceUrl.vue
  8. 3
      fir_admin/src/views/appinfos/Dropdown/index.js
  9. 12
      fir_admin/src/views/appinfos/edit.vue
  10. 201
      fir_admin/src/views/appinfos/list.vue
  11. 265
      fir_admin/src/views/userinfos/UserDetail.vue
  12. 14
      fir_admin/src/views/userinfos/list.vue
  13. 6
      fir_client/src/components/apps/FirAppInfossecurity.vue
  14. 2
      fir_ser/admin/urls.py
  15. 117
      fir_ser/admin/views/app.py
  16. 49
      fir_ser/admin/views/user.py
  17. 38
      fir_ser/api/migrations/0037_auto_20210412_1559.py
  18. 18
      fir_ser/api/migrations/0038_auto_20210412_1601.py
  19. 18
      fir_ser/api/migrations/0039_auto_20210412_1759.py
  20. 12
      fir_ser/api/models.py
  21. 79
      fir_ser/api/utils/serializer.py
  22. 2
      fir_ser/api/utils/storage/storage.py
  23. 6
      fir_ser/api/utils/utils.py
  24. 2
      fir_ser/api/views/login.py
  25. 2
      fir_ser/api/views/storage.py

@ -0,0 +1,17 @@
import request from '@/utils/request'
export function getAppInfos(query) {
return request({
url: '/appinfo',
method: 'get',
params: query
})
}
export function updateAppInfo(data) {
return request({
url: '/appinfo',
method: 'put',
data
})
}

@ -30,3 +30,11 @@ export function getUserInfos(query) {
params: query params: query
}) })
} }
export function updateUserInfo(data) {
return request({
url: '/userinfo',
method: 'put',
data
})
}

@ -83,12 +83,25 @@ export const constantRoutes = [
{ {
path: '/apps', path: '/apps',
component: Layout, component: Layout,
redirect: '/apps/list',
name: 'apps',
meta: {
title: '应用管理',
icon: 'el-icon-s-help'
},
children: [ children: [
{ {
path: 'index', path: 'list',
name: 'index', name: 'app_info_list',
component: () => import('@/views/form/index'), component: () => import('@/views/appinfos/list'),
meta: { title: '应用管理', icon: 'form' } meta: { title: '应用列表', icon: 'form' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/appinfos/edit'),
name: 'app_info_edit',
meta: { title: '编辑信息', noCache: true, activeMenu: '/apps/list' },
hidden: true
} }
] ]
}, },

@ -0,0 +1,305 @@
<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="APP_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="Bundle_Id">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.bundle_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.name" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="短连接" label-width="100px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.short" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="应用状态" label-width="100px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.status" class="filter-item" placeholder="Please select">
<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="应用类型">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.type" class="filter-item" placeholder="Please select" disabled>
<el-option v-for="item in postForm.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="更新时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.updated_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="下载次数" prop="download_times">
<el-row :gutter="12">
<el-col :span="8">
<el-input :value="postForm.count_hits" 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.description" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" placeholder="Please input" />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="应用图标" label-width="200px">
<el-row :gutter="12">
<el-col :span="16">
<el-image v-if="postForm.master_release" :src="postForm.master_release.icon_url" :preview-src-list="[postForm.master_release.icon_url]" fit="contain" style="width: 100px; height: 100px" />
</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.password" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="应用所属用户ID" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.user_id" disabled />
</el-col>
</el-row>
</el-form-item>
<div v-if="postForm.type===1">
<el-form-item label="是否开启超级签" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-tooltip :content="postForm.issupersign|statusFilter" placement="top">
<el-switch
v-model="postForm.issupersign"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="true"
:inactive-value="false"
/>
</el-tooltip>
</el-col>
</el-row>
</el-form-item>
<div v-if="postForm.issupersign===true">
<el-form-item label="超级签名签名类型" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.supersign_type" class="filter-item" placeholder="Please select">
<el-option v-for="item in postForm.supersign_type_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.supersign_limit_number" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="超级签名新Bundle_Id" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.new_bundle_id" />
</el-col>
</el-row>
</el-form-item>
</div>
</div>
<el-form-item label="应用专属访问域名" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.domain_name" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="下载页对所有人可见" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-tooltip :content="postForm.isshow|statusFilter" placement="top">
<el-switch
v-model="postForm.isshow"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="true"
:inactive-value="false"
/>
</el-tooltip>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="微信内简易访问模式" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-tooltip :content="postForm.wxeasytype|statusFilter" placement="top">
<el-switch
v-model="postForm.wxeasytype"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="true"
:inactive-value="false"
/>
</el-tooltip>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="微信内第三方自动跳转" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-tooltip :content="postForm.wxredirect|userStatusFilter" placement="top">
<el-switch
v-model="postForm.wxredirect"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="true"
:inactive-value="false"
/>
</el-tooltip>
</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" @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 { getAppInfos, updateAppInfo } from '@/api/app'
const defaultForm = {
email: undefined,
job: undefined,
qq: undefined,
id: undefined,
company: undefined,
gender: undefined,
username: undefined,
first_name: undefined,
role: undefined,
history_release_limit: undefined,
domain_name: undefined,
download_times: undefined,
is_active: undefined,
head_img: '',
memo: undefined,
date_joined: undefined,
storage_active: undefined,
supersign_active: undefined,
role_choices: [],
gender_choices: [],
storage_choices: []
}
export default {
name: 'AppDetail',
components: { }, filters: {
userStatusFilter(status) {
const statusMap = {
true: '激活,允许登录',
false: '禁用,禁止登录'
}
return statusMap[status]
},
statusFilter(status) {
const statusMap = {
true: '启用,允许配置',
false: '禁用,禁止配置'
}
return statusMap[status]
}
},
props: {
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false
}
},
computed: {
},
created() {
if (this.isEdit) {
const id = this.$route.params && this.$route.params.id
this.fetchData(id)
}
},
methods: {
fetchData(id) {
getAppInfos({ id: id }).then(response => {
if (response.data.length === 1) {
this.postForm = response.data[0]
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateAppInfo(this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style lang="scss" scoped>
.el-form-item__label {
width: 200px;
}
</style>

@ -0,0 +1,41 @@
<template>
<el-dropdown :show-timeout="100" trigger="click">
<el-button plain>
{{ !comment_disabled?'Comment: opened':'Comment: closed' }}
<i class="el-icon-caret-bottom el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown" class="no-padding">
<el-dropdown-item>
<el-radio-group v-model="comment_disabled" style="padding: 10px;">
<el-radio :label="true">
Close comment
</el-radio>
<el-radio :label="false">
Open comment
</el-radio>
</el-radio-group>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
default: false
}
},
computed: {
comment_disabled: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
}
}
</script>

@ -0,0 +1,46 @@
<template>
<el-dropdown :hide-on-click="false" :show-timeout="100" trigger="click">
<el-button plain>
Platfroms({{ platforms.length }})
<i class="el-icon-caret-bottom el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown" class="no-border">
<el-checkbox-group v-model="platforms" style="padding: 5px 15px;">
<el-checkbox v-for="item in platformsOptions" :key="item.key" :label="item.key">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
props: {
value: {
required: true,
default: () => [],
type: Array
}
},
data() {
return {
platformsOptions: [
{ key: 'a-platform', name: 'a-platform' },
{ key: 'b-platform', name: 'b-platform' },
{ key: 'c-platform', name: 'c-platform' }
]
}
},
computed: {
platforms: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
}
}
</script>

@ -0,0 +1,38 @@
v<template>
<el-dropdown :show-timeout="100" trigger="click">
<el-button plain>
Link
<i class="el-icon-caret-bottom el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown" class="no-padding no-border" style="width:400px">
<el-form-item label-width="0px" style="margin-bottom: 0px" prop="source_uri">
<el-input v-model="source_uri" placeholder="Please enter the content">
<template slot="prepend">
URL
</template>
</el-input>
</el-form-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
computed: {
source_uri: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
}
}
</script>

@ -0,0 +1,3 @@
export { default as CommentDropdown } from './Comment'
export { default as PlatformDropdown } from './Platform'
export { default as SourceUrlDropdown } from './SourceUrl'

@ -0,0 +1,12 @@
<template>
<app-detail :is-edit="true" />
</template>
<script>
import AppDetail from './AppDetail'
export default {
name: 'EditForm',
components: { AppDetail }
}
</script>

@ -0,0 +1,201 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.bundle_id" placeholder="Bundle_Id" style="width: 250px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.name" placeholder="应用名称" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.short" placeholder="短连接" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.domain_name" placeholder="应用专属访问域名" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-if="type_choices" v-model="listQuery.type" placeholder="应用类型" clearable class="filter-item" style="width: 120px" @change="handleFilter">
<el-option v-for="item in type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-if="status_choices" v-model="listQuery.status" placeholder="应用状态" clearable class="filter-item" style="width: 120px" @change="handleFilter">
<el-option v-for="item in status_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-input v-model="listQuery.user_id" placeholder="用户ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<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="图标" align="center" width="130">
<template slot-scope="scope">
<el-image :src="scope.row.master_release.icon_url" :preview-src-list="[scope.row.master_release.icon_url]" fit="contain" style="width: 80px; height: 80px" />
</template>
</el-table-column>
<el-table-column label="应用名称">
<template slot-scope="scope">
{{ scope.row.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" width="130">
<template slot-scope="scope">
<el-image v-if="scope.row.has_combo" :src="scope.row.has_combo.master_release.icon_url" :preview-src-list="[scope.row.has_combo.master_release.icon_url]" fit="contain" style="width: 80px; height: 80px" />
<el-link v-else>无关联应用</el-link>
</template>
</el-table-column>
<el-table-column label="应用专属访问域名" align="center">
<template slot-scope="scope">
{{ scope.row.domain_name }}
</template>
</el-table-column>
<el-table-column label="用户ID" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'user_info_edit',params:{id:scope.row.user_id}}">
<el-link type="primary"> {{ scope.row.user_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="短连接" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.short }}
</template>
</el-table-column>
<el-table-column label="下载次数" width="100" align="center">
<template slot-scope="scope">
{{ scope.row.count_hits }}
</template>
</el-table-column>
<el-table-column class-name="status-col" label="应用状态" width="80" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status | appStatusFilter">{{ scope.row |appStatusNameFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="下载页显示" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.isshow | statusFilter">{{ scope.row.isshow }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="created_at" label="更新时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.updated_time">
<span>{{ scope.row.updated_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'app_info_edit',params:{id:scope.row.id}}">
<el-button type="primary" size="mini" icon="el-icon-edit">
编辑
</el-button>
</router-link>
</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 { getAppInfos } from '@/api/app'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '更新时间 Ascending', key: 'updated_time' },
{ label: '更新时间 Descending', key: '-updated_time' },
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' },
{ label: '下载次数 Ascending', key: 'count_hits' },
{ label: '下载次数 Descending', key: '-count_hits' }
]
export default {
name: 'AppInfo',
components: { Pagination },
directives: { waves },
filters: {
formatTime(time) {
return time.split('T')[0]
},
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,
name: undefined,
bundle_id: undefined,
sort: '-count_hits',
type: undefined,
domain_name: undefined,
user_id: undefined
},
sortOptions,
type_choices: [],
status_choices: []
}
},
created() {
this.fetchData()
},
methods: {
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getAppInfos(this.listQuery).then(response => {
this.list = response.data
if (this.list && this.list.length > 0) {
this.type_choices = this.list[0].type_choices
this.status_choices = this.list[0].status_choices
}
this.total = response.total
this.listLoading = false
})
}
}
}
</script>

@ -1,19 +1,22 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form ref="postForm" :model="postForm" :rules="rules"> <el-form ref="postForm" :model="postForm" :rules="rules" label-width="80px" :disabled="!is_edit">
<el-row> <el-row>
<el-col :span="12">
<el-form-item label="UID"> <el-form-item label="UID">
<el-row :gutter="20"> <el-row :gutter="12">
<el-col :span="9"> <el-col :span="16">
<el-input :value="postForm.uid" disabled /> <el-input :value="postForm.uid" disabled />
</el-col> </el-col>
<el-col :span="9"> </el-row>
<el-button type="primary">保存本次修改</el-button> </el-form-item>
<el-form-item label="账号" prop="username">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.username" />
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-col :span="12">
<el-form-item label="昵称"> <el-form-item label="昵称">
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <el-col :span="16">
@ -25,7 +28,7 @@
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <el-col :span="16">
<el-select v-model="postForm.gender" class="filter-item" placeholder="Please select"> <el-select v-model="postForm.gender" class="filter-item" placeholder="Please select">
<el-option v-for="item in gender_choices" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in postForm.gender_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-col> </el-col>
</el-row> </el-row>
@ -34,7 +37,7 @@
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <el-col :span="16">
<el-select v-model="postForm.role" class="filter-item" placeholder="Please select"> <el-select v-model="postForm.role" class="filter-item" placeholder="Please select">
<el-option v-for="item in role_choices" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in postForm.role_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-col> </el-col>
</el-row> </el-row>
@ -54,6 +57,13 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-form-item label="Q Q" prop="number">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.qq" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="职位"> <el-form-item label="职位">
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <el-col :span="16">
@ -68,21 +78,95 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-form-item label="备注">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.memo" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" placeholder="Please input" />
</el-col>
</el-row>
</el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="头像"> <el-form-item label="用户头像">
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <el-col :span="16">
<el-image :src="postForm.head_img" :preview-src-list="[postForm.head_img]" fit="contain" style="width: 100px; height: 100px" /> <el-image :src="postForm.head_img" :preview-src-list="[postForm.head_img]" fit="contain" style="width: 100px; height: 100px" />
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-form-item label="用户名称" prop="username"> <el-form-item label="账户状态">
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <el-col :span="16">
<el-input v-model="postForm.username" /> <el-tooltip :content="postForm.is_active|userStatusFilter" placement="top">
<el-switch
v-model="postForm.is_active"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="true"
:inactive-value="false"
/>
</el-tooltip>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="私有存储">
<el-row :gutter="12">
<el-col :span="16">
<el-tooltip :content="postForm.storage_active|statusFilter" placement="top">
<el-switch
v-model="postForm.storage_active"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="true"
:inactive-value="false"
/>
</el-tooltip>
</el-col>
</el-row>
</el-form-item>
<!-- <el-form-item v-if="postForm.storage_active===true" label="私有存储">-->
<!-- <el-row :gutter="12">-->
<!-- <el-col :span="16">-->
<!-- <el-select v-model="postForm.use_storage_id" filterable :placeholder="selectlabel" @change="select_storage">-->
<!-- <el-option-group-->
<!-- v-for="storage_group in fstorage_lists"-->
<!-- :key="storage_group.group_name"-->
<!-- :label="storage_group.group_name">-->
<!-- <el-option-->
<!-- v-for="storage in storage_group.storages"-->
<!-- :key="storage.id"-->
<!-- :label="storage.name"-->
<!-- :value="storage.id">-->
<!-- </el-option>-->
<!-- </el-option-group>-->
<!-- </el-select>-->
<!-- <el-button v-if="use_storage_id!==org_storage_id" style="margin-left: 10px" round type="info"-->
<!-- icon="el-icon-thumb"-->
<!-- @click="change_storage_info">-->
<!-- 迁移数据并保存-->
<!-- </el-button>-->
<!-- <el-select v-model="postForm.storage" class="filter-item" placeholder="Please select">-->
<!-- <el-option v-for="item in postForm.storage_choices" :key="item.id" :label="item.name" :value="item.id" />-->
<!-- </el-select>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </el-form-item>-->
<el-form-item label="超级签名">
<el-row :gutter="12">
<el-col :span="16">
<el-tooltip :content="postForm.supersign_active|statusFilter" placement="top">
<el-switch
v-model="postForm.supersign_active"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="true"
:inactive-value="false"
/>
</el-tooltip>
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
@ -110,7 +194,6 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-form-item label="应用版本历史记录"> <el-form-item label="应用版本历史记录">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="8"> <el-col :span="8">
@ -120,44 +203,64 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<!-- <el-form-item label="Status">-->
<!-- <el-select v-model="postForm.status" class="filter-item" placeholder="Please select">-->
<!-- <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item label="备注">
<el-input v-model="postForm.memo" :autosize="{ minRows: 2, maxRows: 6}" type="textarea" placeholder="Please input" />
</el-form-item>
</el-form> </el-form>
<!-- <div slot="footer" class="dialog-footer">--> <el-col :span="9" style="float: right">
<!-- <el-button @click="dialogFormVisible = false">--> <el-button v-if="!is_edit" type="primary" @click="is_edit=true">修改</el-button>
<!-- Cancel--> <div v-else>
<!-- </el-button>--> <el-button type="primary" @click="is_edit=false">取消</el-button>
<!-- <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">--> <el-button type="primary" @click="updateData">保存修改</el-button>
<!-- Confirm--> </div>
<!-- </el-button>--> </el-col>
<!-- </div>-->
<!-- </el-form>-->
</div> </div>
</template> </template>
<script> <script>
import { validURL } from '@/utils/validate' import { validURL } from '@/utils/validate'
import { getUserInfos } from '@/api/user' import { getUserInfos, updateUserInfo } from '@/api/user'
import { CommentDropdown, PlatformDropdown, SourceUrlDropdown } from './Dropdown' import { CommentDropdown, PlatformDropdown, SourceUrlDropdown } from './Dropdown'
const defaultForm = { const defaultForm = {
email: undefined,
job: undefined,
qq: undefined,
id: undefined, id: undefined,
company: undefined,
gender: undefined,
username: undefined,
first_name: undefined,
role: undefined,
history_release_limit: undefined,
domain_name: undefined,
download_times: undefined,
is_active: undefined,
head_img: '',
memo: undefined,
date_joined: undefined,
storage_active: undefined,
supersign_active: undefined,
role_choices: [], role_choices: [],
gender_choices: [] gender_choices: [],
storage_choices: [],
} }
export default { export default {
name: 'UserDetail', name: 'UserDetail',
components: { CommentDropdown, PlatformDropdown, SourceUrlDropdown }, components: { CommentDropdown, PlatformDropdown, SourceUrlDropdown }, filters: {
userStatusFilter(status) {
const statusMap = {
true: '激活,允许登录',
false: '禁用,禁止登录'
}
return statusMap[status]
},
statusFilter(status) {
const statusMap = {
true: '启用,允许配置',
false: '禁用,禁止配置'
}
return statusMap[status]
}
},
props: { props: {
isEdit: { isEdit: {
type: Boolean, type: Boolean,
@ -201,105 +304,33 @@ export default {
content: [{ validator: validateRequire }], content: [{ validator: validateRequire }],
source_uri: [{ validator: validateSourceUri, trigger: 'blur' }] source_uri: [{ validator: validateSourceUri, trigger: 'blur' }]
}, },
postFormRoute: {} is_edit: false,
} }
}, },
computed: { computed: {
displayTime: {
// set and get is useful when the data
// returned by the back end api is different from the front end
// back end return => "2013-06-25 06:59:25"
// front end need timestamp => 1372114765000
get() {
return (+new Date(this.postForm.display_time))
},
set(val) {
this.postForm.display_time = new Date(val)
}
}
}, },
created() { created() {
if (this.isEdit) { if (this.isEdit) {
const id = this.$route.params && this.$route.params.id const id = this.$route.params && this.$route.params.id
this.fetchData(id) this.fetchData(id)
} }
// Why need to make a copy of this.$route here?
// Because if you enter this page and quickly switch tag, may be in the execution of the setTagsViewTitle function, this.$route is no longer pointing to the current page
// https://github.com/PanJiaChen/vue-element-admin/issues/1221
this.postFormRoute = Object.assign({}, this.$route)
}, },
methods: { methods: {
fetchData(id) { fetchData(id) {
getUserInfos({ id: id }).then(response => { getUserInfos({ id: id }).then(response => {
if (response.data.length === 1) { if (response.data.length === 1) {
this.postForm = response.data[0] this.postForm = response.data[0]
this.gender_choices = response.gender_choices
this.role_choices = response.role_choices
} }
// just for test
this.postForm.title += ` Article Id:${this.postForm.id}`
this.postForm.content_short += ` Article Id:${this.postForm.id}`
// set tagsview title
// this.setTagsViewTitle()
// set page title
// this.setPageTitle()
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)
}) })
}, },
setTagsViewTitle() { updateData() {
const title = 'Edit Article' updateUserInfo(this.postForm).then(response => {
const route = Object.assign({}, this.postFormRoute, { title: `${title}-${this.postForm.id}` }) this.$message.success('更新成功')
this.$store.dispatch('tagsView/updateVisitedView', route) this.postForm = response.data
}, }).catch(err => {
setPageTitle() { console.log(err)
const title = 'Edit Article'
document.title = `${title} - ${this.postForm.id}`
},
submitForm() {
console.log(this.postForm)
this.$refs.postForm.validate(valid => {
if (valid) {
this.loading = true
this.$notify({
title: '成功',
message: '发布文章成功',
type: 'success',
duration: 2000
})
this.postForm.status = 'published'
this.loading = false
} else {
console.log('error submit!!')
return false
}
})
},
draftForm() {
if (this.postForm.content.length === 0 || this.postForm.title.length === 0) {
this.$message({
message: '请填写必要的标题和内容',
type: 'warning'
})
return
}
this.$message({
message: '保存成功',
type: 'success',
showClose: true,
duration: 1000
})
this.postForm.status = 'draft'
},
getRemoteUserList(query) {
searchUser(query).then(response => {
if (!response.data.items) return
this.userListOptions = response.data.items.map(v => v.name)
}) })
} }
} }

@ -29,7 +29,7 @@
{{ scope.row.id }} {{ scope.row.id }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="头像"> <el-table-column label="头像" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-image :src="scope.row.head_img" :preview-src-list="[scope.row.head_img]" fit="contain" style="width: 80px; height: 80px" /> <el-image :src="scope.row.head_img" :preview-src-list="[scope.row.head_img]" fit="contain" style="width: 80px; height: 80px" />
</template> </template>
@ -91,16 +91,13 @@
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="180" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="100" class-name="small-padding fixed-width">
<template slot-scope="scope"> <template slot-scope="scope">
<router-link :to="{name: 'user_info_edit',params:{id:scope.row.id}}"> <router-link :to="{name: 'user_info_edit',params:{id:scope.row.id}}">
<el-button type="info" size="mini" icon="el-icon-edit"> <el-button type="primary" size="mini" icon="el-icon-edit">
详情
</el-button>
</router-link>
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑 编辑
</el-button> </el-button>
</router-link>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -167,7 +164,7 @@ export default {
total: 0, total: 0,
listQuery: { listQuery: {
page: 1, page: 1,
limit: 3, limit: 10,
first_name: undefined, first_name: undefined,
certification: undefined, certification: undefined,
sort: '-date_joined', sort: '-date_joined',
@ -176,7 +173,6 @@ export default {
}, },
sortOptions, sortOptions,
searchCertficationOptions searchCertficationOptions
} }
}, },
created() { created() {

@ -147,7 +147,7 @@
this.passwordflag = true; this.passwordflag = true;
}, },
setbuttondefaltshow(currentapp) { setbuttondefaltshow(currentapp) {
if (currentapp.isshow === 1) { if (currentapp.isshow === true) {
this.showdownloadevent("on"); this.showdownloadevent("on");
this.downtip.val = 'on'; this.downtip.val = 'on';
} else { } else {
@ -157,7 +157,7 @@
this.showdownloadflag = true; this.showdownloadflag = true;
}, },
setxeasytypeshow(currentapp) { setxeasytypeshow(currentapp) {
if (currentapp.wxeasytype === 1) { if (currentapp.wxeasytype === true) {
this.wxeasytypeevent("on"); this.wxeasytypeevent("on");
this.wxeasytypetip.val = 'on'; this.wxeasytypetip.val = 'on';
} else { } else {
@ -169,7 +169,7 @@
this.wxeasy_disable = !this.$store.state.userinfo.domain_name && !this.currentapp.domain_name; this.wxeasy_disable = !this.$store.state.userinfo.domain_name && !this.currentapp.domain_name;
}, },
setwxredirectshow(currentapp) { setwxredirectshow(currentapp) {
if (currentapp.wxredirect === 1) { if (currentapp.wxredirect === true) {
this.wxredirectevent("on"); this.wxredirectevent("on");
this.wxredirecttip.val = 'on'; this.wxredirecttip.val = 'on';
} else { } else {

@ -16,6 +16,7 @@ Including another URLconf
from django.urls import re_path from django.urls import re_path
from admin.views.login import LoginView, LoginUserView from admin.views.login import LoginView, LoginUserView
from admin.views.user import UserInfoView from admin.views.user import UserInfoView
from admin.views.app import AppInfoView
urlpatterns = [ urlpatterns = [
# path("",include(router.urls)), # path("",include(router.urls)),
@ -24,5 +25,6 @@ urlpatterns = [
re_path("^login", LoginView.as_view()), re_path("^login", LoginView.as_view()),
re_path("^user/info", LoginUserView.as_view()), re_path("^user/info", LoginUserView.as_view()),
re_path("^userinfo", UserInfoView.as_view()), re_path("^userinfo", UserInfoView.as_view()),
re_path("^appinfo", AppInfoView.as_view()),
] ]

@ -0,0 +1,117 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: 4月
# author: liuyu
# date: 2021/4/11
from django.contrib import auth
from api.models import Token, AppReleaseInfo, Apps
from rest_framework.response import Response
from api.utils.auth import AdminTokenAuthentication
from api.utils.serializer import AdminAppsSerializer, AdminAppReleaseSerializer
from django.core.cache import cache
from rest_framework.views import APIView
import binascii
import os, datetime
from api.utils.utils import get_captcha, valid_captcha, get_choices_dict
from api.utils.response import BaseResponse
from fir_ser.settings import CACHE_KEY_TEMPLATE, LOGIN
from api.utils.storage.caches import login_auth_failed, del_cache_response_by_short
import logging
from api.utils.throttle import VisitRegister1Throttle, VisitRegister2Throttle
from rest_framework.pagination import PageNumberPagination
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 AppInfoView(APIView):
authentication_classes = [AdminTokenAuthentication, ]
def get(self, request):
res = BaseResponse()
filter_data = {}
filter_fields = ["id", "type", "name", "short", "bundle_id", "domain_name", "user_id", "status"]
for filed in filter_fields:
f_value = request.query_params.get(filed, None)
if f_value:
filter_data[filed] = f_value
sort = request.query_params.get("sort", "-updated_time")
page_obj = AppsPageNumber()
obj_list = Apps.objects.filter(**filter_data).order_by(sort)
page_serializer = page_obj.paginate_queryset(queryset=obj_list, request=request,
view=self)
serializer_obj = AdminAppsSerializer(page_serializer, many=True)
res.data = serializer_obj.data
res.total = obj_list.count()
return Response(res.dict)
def put(self, request):
res = BaseResponse()
data = request.data
id = data.get("id", None)
if not id:
res.code = 1003
res.msg = "参数错误"
return Response(res.dict)
app_obj = Apps.objects.filter(id=id).first()
if app_obj:
data['pk'] = id
serializer_obj = AdminAppsSerializer(app_obj, data=data, partial=True)
if serializer_obj.is_valid():
serializer_obj.save()
res.data = serializer_obj.data
del_cache_response_by_short(app_obj.app_id)
return Response(res.dict)
res.code = 1004
res.msg = "数据校验失败"
return Response(res.dict)
class AppReleaseInfoView(APIView):
authentication_classes = [AdminTokenAuthentication, ]
def get(self, request):
res = BaseResponse()
filter_data = {}
filter_fields = ["id", "release_id"]
for filed in filter_fields:
f_value = request.query_params.get(filed, None)
if f_value:
filter_data[filed] = f_value
sort = request.query_params.get("sort", "-created_time")
page_obj = AppsPageNumber()
obj_list = AppReleaseInfo.objects.filter(**filter_data).order_by(sort)
page_serializer = page_obj.paginate_queryset(queryset=obj_list, request=request,
view=self)
serializer_obj = AdminAppReleaseSerializer(page_serializer, many=True)
res.data = serializer_obj.data
res.total = obj_list.count()
return Response(res.dict)
def put(self, request):
res = BaseResponse()
data = request.data
id = data.get("id", None)
if not id:
res.code = 1003
res.msg = "参数错误"
return Response(res.dict)
app_obj = Apps.objects.filter(id=id).first()
if app_obj:
data['pk'] = id
serializer_obj = AdminAppReleaseSerializer(app_obj, data=data, partial=True)
if serializer_obj.is_valid():
serializer_obj.save()
res.data = serializer_obj.data
del_cache_response_by_short(app_obj.app_id)
return Response(res.dict)
res.code = 1004
res.msg = "数据校验失败"
return Response(res.dict)

@ -35,28 +35,15 @@ class UserInfoView(APIView):
authentication_classes = [AdminTokenAuthentication, ] authentication_classes = [AdminTokenAuthentication, ]
def get(self, request): def get(self, request):
mobile = request.query_params.get("mobile", None)
username = request.query_params.get("username", None)
email = request.query_params.get("email", None)
first_name = request.query_params.get("first_name", None)
certification = request.query_params.get("certification", None)
id = request.query_params.get("id", None)
sort = request.query_params.get("sort", "-date_joined")
act_type = request.query_params.get("act", None)
res = BaseResponse() res = BaseResponse()
filter_data = {} filter_data = {}
if mobile: filter_fileds = ["id", "mobile", "username", "email", "first_name", "certification", ]
filter_data["mobile"] = mobile for filed in filter_fileds:
if username: f_value = request.query_params.get(filed, None)
filter_data["username"] = username if f_value:
if email: filter_data[filed] = f_value
filter_data["email"] = email sort = request.query_params.get("sort", "-date_joined")
if first_name: certification = request.query_params.get("certification", None)
filter_data["email"] = first_name
if id:
filter_data["id"] = id
if certification: if certification:
if certification == "-1": if certification == "-1":
filter_data["certification__status__isnull"] = True filter_data["certification__status__isnull"] = True
@ -69,6 +56,24 @@ class UserInfoView(APIView):
users_serializer = AdminUserInfoSerializer(users_page_serializer, many=True) users_serializer = AdminUserInfoSerializer(users_page_serializer, many=True)
res.data = users_serializer.data res.data = users_serializer.data
res.total = users_obj_list.count() res.total = users_obj_list.count()
res.gender_choices = get_choices_dict(UserInfo.gender_choices) return Response(res.dict)
res.role_choices = get_choices_dict(UserInfo.role_choices)
def put(self, request):
res = BaseResponse()
data = request.data
id = data.get("id", None)
if not id:
res.code = 1003
res.msg = "参数错误"
return Response(res.dict)
user_obj = UserInfo.objects.filter(id=id).first()
if user_obj:
data['pk'] = id
users_serializer = AdminUserInfoSerializer(user_obj, data=data, partial=True)
if users_serializer.is_valid():
users_serializer.save()
res.data = users_serializer.data
return Response(res.dict)
res.code = 1004
res.msg = "数据校验失败"
return Response(res.dict) return Response(res.dict)

@ -0,0 +1,38 @@
# Generated by Django 3.0.3 on 2021-04-12 15:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0036_auto_20210409_1512'),
]
operations = [
migrations.AddField(
model_name='apps',
name='status',
field=models.SmallIntegerField(choices=[(0, '正常'), (1, '封禁'), (2, '违规')], default=0, verbose_name='应用状态'),
),
migrations.AlterField(
model_name='apps',
name='isshow',
field=models.BooleanField(default=True, verbose_name='下载页可见'),
),
migrations.AlterField(
model_name='apps',
name='issupersign',
field=models.BooleanField(default=False, verbose_name='是否超级签名包'),
),
migrations.AlterField(
model_name='apps',
name='wxeasytype',
field=models.BooleanField(default=True, verbose_name='微信内简易模式,避免微信封停'),
),
migrations.AlterField(
model_name='apps',
name='wxredirect',
field=models.BooleanField(default=True, verbose_name='微信内第三方链接自动跳转'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 3.0.3 on 2021-04-12 16:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0037_auto_20210412_1559'),
]
operations = [
migrations.AlterField(
model_name='apps',
name='status',
field=models.SmallIntegerField(choices=[(0, '封禁'), (1, '正常'), (2, '违规')], default=1, verbose_name='应用状态'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 3.0.3 on 2021-04-12 17:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0038_auto_20210412_1601'),
]
operations = [
migrations.AlterField(
model_name='apps',
name='password',
field=models.CharField(blank=True, help_text='默认 没有密码', max_length=32, verbose_name='访问密码'),
),
]

@ -103,6 +103,8 @@ class Apps(models.Model):
user_id = models.ForeignKey(to="UserInfo", verbose_name="用户ID", on_delete=models.CASCADE) user_id = models.ForeignKey(to="UserInfo", verbose_name="用户ID", on_delete=models.CASCADE)
type_choices = ((0, 'android'), (1, 'ios')) type_choices = ((0, 'android'), (1, 'ios'))
type = models.SmallIntegerField(choices=type_choices, default=0, verbose_name="类型") type = models.SmallIntegerField(choices=type_choices, default=0, verbose_name="类型")
status_choices = ((0, '封禁'), (1, '正常'), (2, '违规'))
status = models.SmallIntegerField(choices=status_choices, default=1, verbose_name="应用状态")
name = models.CharField(max_length=32, blank=True, null=True, verbose_name="应用名称") name = models.CharField(max_length=32, blank=True, null=True, verbose_name="应用名称")
short = models.CharField(max_length=16, unique=True, verbose_name="短链接", db_index=True) short = models.CharField(max_length=16, unique=True, verbose_name="短链接", db_index=True)
bundle_id = models.CharField(max_length=64, blank=True, verbose_name="bundle id") bundle_id = models.CharField(max_length=64, blank=True, verbose_name="bundle id")
@ -110,16 +112,16 @@ class Apps(models.Model):
verbose_name="关联应用", on_delete=models.SET_NULL, null=True, blank=True) verbose_name="关联应用", on_delete=models.SET_NULL, null=True, blank=True)
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
count_hits = models.BigIntegerField(verbose_name="下载次数", default=0) count_hits = models.BigIntegerField(verbose_name="下载次数", default=0)
password = models.CharField(verbose_name="访问密码", default='', help_text='默认 没有密码', max_length=32) password = models.CharField(verbose_name="访问密码", blank=True, help_text='默认 没有密码', max_length=32)
isshow = models.BigIntegerField(verbose_name="下载页可见", default=1) isshow = models.BooleanField(verbose_name="下载页可见", default=True)
issupersign = models.BigIntegerField(verbose_name="是否超级签名包", default=False) issupersign = models.BooleanField(verbose_name="是否超级签名包", default=False)
supersign_type_choices = ((0, '普通权限'), (1, '特殊权限(包含network、vpn)')) supersign_type_choices = ((0, '普通权限'), (1, '特殊权限(包含network、vpn)'))
supersign_type = models.SmallIntegerField(choices=supersign_type_choices, default=0, verbose_name="签名类型") supersign_type = models.SmallIntegerField(choices=supersign_type_choices, default=0, verbose_name="签名类型")
new_bundle_id = models.CharField(max_length=64, blank=True, null=True, verbose_name="new_bundle_id", new_bundle_id = models.CharField(max_length=64, blank=True, null=True, verbose_name="new_bundle_id",
help_text="用于超级签某些因素下需要修改包名") help_text="用于超级签某些因素下需要修改包名")
supersign_limit_number = models.IntegerField(verbose_name="签名使用限额", default=0) supersign_limit_number = models.IntegerField(verbose_name="签名使用限额", default=0)
wxredirect = models.BigIntegerField(verbose_name="微信内第三方链接自动跳转", default=True) wxredirect = models.BooleanField(verbose_name="微信内第三方链接自动跳转", default=True)
wxeasytype = models.BigIntegerField(verbose_name="微信内简易模式,避免微信封停", default=True) wxeasytype = models.BooleanField(verbose_name="微信内简易模式,避免微信封停", default=True)
domain_name = models.CharField(verbose_name="专属访问域名", blank=True, null=True, max_length=64) domain_name = models.CharField(verbose_name="专属访问域名", blank=True, null=True, max_length=64)
description = models.TextField('描述', blank=True, null=True, default=None, ) description = models.TextField('描述', blank=True, null=True, default=None, )
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

@ -13,12 +13,15 @@ logger = logging.getLogger(__file__)
token_obj = DownloadToken() token_obj = DownloadToken()
def get_download_url_from_context(self, key, url, force_new=False): def get_download_url_from_context(self, obj, key, url, force_new=False):
icon_url = "" icon_url = ""
if self.context.get("key", None) and self.context.get("key") != "undefined": if self.context.get("key", None) and self.context.get("key") != "undefined":
key = self.context.get("key", '') key = self.context.get("key", '')
if self.context.get("storage", None) and self.context.get("storage") != "undefined": if self.context.get("storage", None) and self.context.get("storage") != "undefined":
storage = self.context.get("storage", None) storage = self.context.get("storage", None)
else:
storage = Storage(obj.user_id)
if storage:
icon_url = storage.get_download_url(os.path.basename(url), 600, key, force_new) icon_url = storage.get_download_url(os.path.basename(url), 600, key, force_new)
return icon_url return icon_url
@ -35,7 +38,7 @@ def get_screenshots_from_self(self, obj, force_new=False):
screenshots_list = [] screenshots_list = []
for screenshot_obj in models.AppScreenShot.objects.filter(app_id=obj).all(): for screenshot_obj in models.AppScreenShot.objects.filter(app_id=obj).all():
key = '' key = ''
icon_url = get_download_url_from_context(self, key, screenshot_obj.screenshot_url, force_new) icon_url = get_download_url_from_context(self, obj, key, screenshot_obj.screenshot_url, force_new)
screenshots_list.append({'id': screenshot_obj.pk, 'url': icon_url}) screenshots_list.append({'id': screenshot_obj.pk, 'url': icon_url})
return screenshots_list return screenshots_list
@ -71,13 +74,34 @@ class UserInfoSerializer(serializers.ModelSerializer):
class AdminUserInfoSerializer(UserInfoSerializer): class AdminUserInfoSerializer(UserInfoSerializer):
class Meta: class Meta:
model = models.UserInfo model = models.UserInfo
exclude = ["password"] exclude = ["password", "api_token"]
read_only_fields = ["id", "head_img", "free_download_times", "certification", "last_login",
"is_superuser", "last_name", "is_staff", "uid", "storage_active", "supersign_active",
"date_joined", "download_times", "all_download_times", "storage", "groups",
"user_permissions"]
gender_choices = serializers.SerializerMethodField()
def get_gender_choices(self, obj):
return get_choices_dict(obj.gender_choices)
role_choices = serializers.SerializerMethodField()
def get_role_choices(self, obj):
return get_choices_dict(obj.role_choices)
storage_choices = serializers.SerializerMethodField()
def get_storage_choices(self, obj):
return get_choices_dict(models.AppStorage.storage_choices[1:])
def update(self, instance, validated_data):
return super(AdminUserInfoSerializer, self).update(instance, validated_data)
class AppsSerializer(serializers.ModelSerializer): class AppsSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.Apps model = models.Apps
# depth = 1
exclude = ["user_id", "id"] exclude = ["user_id", "id"]
has_combo = serializers.SerializerMethodField() has_combo = serializers.SerializerMethodField()
@ -113,7 +137,7 @@ class AppsSerializer(serializers.ModelSerializer):
master_release_obj = get_app_master_obj_from_context(self, obj) master_release_obj = get_app_master_obj_from_context(self, obj)
if master_release_obj: if master_release_obj:
key = '' key = ''
icon_url = get_download_url_from_context(self, key, master_release_obj.icon_url) icon_url = get_download_url_from_context(self, obj, key, master_release_obj.icon_url)
datainfo = { datainfo = {
"app_version": master_release_obj.app_version, "app_version": master_release_obj.app_version,
"icon_url": icon_url, "icon_url": icon_url,
@ -143,6 +167,31 @@ class AppsSerializer(serializers.ModelSerializer):
return {} return {}
class AdminAppsSerializer(AppsSerializer):
class Meta:
model = models.Apps
fields = "__all__"
read_only_fields = ["id", "app_id", "user_id", "bundle_id", "count_hits","updated_time","created_time"]
status_choices = serializers.SerializerMethodField()
def get_status_choices(self, obj):
return get_choices_dict(obj.status_choices)
type_choices = serializers.SerializerMethodField()
def get_type_choices(self, obj):
return get_choices_dict(obj.type_choices)
supersign_type_choices = serializers.SerializerMethodField()
def get_supersign_type_choices(self, obj):
return get_choices_dict(obj.supersign_type_choices)
def update(self, instance, validated_data):
print(validated_data)
return super(AdminAppsSerializer, self).update(instance, validated_data)
class AppsShortSerializer(serializers.ModelSerializer): class AppsShortSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.Apps model = models.Apps
@ -175,7 +224,7 @@ class AppsShortSerializer(serializers.ModelSerializer):
master_release_obj = get_app_master_obj_from_context(self, obj) master_release_obj = get_app_master_obj_from_context(self, obj)
if master_release_obj: if master_release_obj:
key = '' key = ''
icon_url = get_download_url_from_context(self, key, os.path.basename(master_release_obj.icon_url), True) icon_url = get_download_url_from_context(self, obj, key, os.path.basename(master_release_obj.icon_url), True)
datainfo = { datainfo = {
"app_version": master_release_obj.app_version, "app_version": master_release_obj.app_version,
"icon_url": icon_url, "icon_url": icon_url,
@ -229,7 +278,7 @@ class AppReleaseSerializer(serializers.ModelSerializer):
return token_obj.make_token(obj.release_id, 600) return token_obj.make_token(obj.release_id, 600)
def get_icon_url(self, obj): def get_icon_url(self, obj):
return get_download_url_from_context(self, '', os.path.basename(obj.icon_url), force_new=False) return get_download_url_from_context(self, obj, '', os.path.basename(obj.icon_url), force_new=False)
def get_master_color(self, obj): def get_master_color(self, obj):
if obj.is_master: if obj.is_master:
@ -245,6 +294,20 @@ class AppReleaseSerializer(serializers.ModelSerializer):
return {"changelog": False, "binary_url": False} return {"changelog": False, "binary_url": False}
class AdminAppReleaseSerializer(AppReleaseSerializer):
class Meta:
model = models.Apps
fields = "__all__"
read_only_fields = ["id", "app_id", "release_id", "is_master"]
release_choices = serializers.SerializerMethodField()
def get_release_choices(self, obj):
return get_choices_dict(obj.release_choices)
def update(self, instance, validated_data):
return super(AdminAppReleaseSerializer, self).update(instance, validated_data)
class StorageSerializer(serializers.ModelSerializer): class StorageSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.AppStorage model = models.AppStorage
@ -354,7 +417,7 @@ class CertificationSerializer(serializers.ModelSerializer):
certification_url = serializers.SerializerMethodField() certification_url = serializers.SerializerMethodField()
def get_certification_url(self, obj): def get_certification_url(self, obj):
return get_download_url_from_context(self, '', obj.certification_url) return get_download_url_from_context(self, obj, '', obj.certification_url)
class UserCertificationSerializer(serializers.ModelSerializer): class UserCertificationSerializer(serializers.ModelSerializer):

@ -176,7 +176,7 @@ def get_local_storage(clean_cache=False):
else: else:
new_storage_obj = LocalStorage(**auth) new_storage_obj = LocalStorage(**auth)
new_storage_obj.storage_type = 3 new_storage_obj.storage_type = 3
# cache.set(storage_key, new_storage_obj, 600) cache.set(storage_key, new_storage_obj, 600)
logger.info("system get local storage obj, from settings " logger.info("system get local storage obj, from settings "
"storage %s" % new_storage_obj) "storage %s" % new_storage_obj)
return new_storage_obj return new_storage_obj

@ -18,6 +18,7 @@ from captcha.models import CaptchaStore
from captcha.helpers import captcha_image_url from captcha.helpers import captcha_image_url
from django.core.validators import validate_email from django.core.validators import validate_email
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.cache import cache
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -314,12 +315,15 @@ def migrating_storage_file_data(user_obj, filename, new_storage_obj, clean_old_d
def migrating_storage_data(user_obj, new_storage_obj, clean_old_data): def migrating_storage_data(user_obj, new_storage_obj, clean_old_data):
with cache.lock("%s_%s" % ('migrating_storage_data', user_obj.uid)):
for app_release_obj in AppReleaseInfo.objects.filter(app_id__user_id=user_obj).all(): for app_release_obj in AppReleaseInfo.objects.filter(app_id__user_id=user_obj).all():
# 迁移APP数据 # 迁移APP数据
filename = get_filename_from_apptype(app_release_obj.release_id, app_release_obj.release_type) filename = get_filename_from_apptype(app_release_obj.release_id, app_release_obj.release_type)
migrating_storage_file_data(user_obj, filename, new_storage_obj, clean_old_data) migrating_storage_file_data(user_obj, filename, new_storage_obj, clean_old_data)
migrating_storage_file_data(user_obj, app_release_obj.icon_url, new_storage_obj, clean_old_data) migrating_storage_file_data(user_obj, app_release_obj.icon_url, new_storage_obj, clean_old_data)
# 迁移APP 截图
for screenshot_obj in AppScreenShot.objects.filter(app_id=app_release_obj.app_id).all():
migrating_storage_file_data(user_obj, screenshot_obj.screenshot_url, new_storage_obj, clean_old_data)
# 迁移超级签数据 # 迁移超级签数据
for apptodev_obj in APPToDeveloper.objects.filter(app_id=app_release_obj.app_id).all(): for apptodev_obj in APPToDeveloper.objects.filter(app_id=app_release_obj.app_id).all():
filename = get_filename_from_apptype(apptodev_obj.binary_file, app_release_obj.release_type) filename = get_filename_from_apptype(apptodev_obj.binary_file, app_release_obj.release_type)

@ -225,7 +225,7 @@ class LoginView(APIView):
# #
# try: # try:
# UserInfo.objects.get(username=username) # UserInfo.objects.get(username=username)
response.msg = "密码或者账户有误" response.msg = "密码账户有误或用户被禁用"
response.code = 1002 response.code = 1002
# except UserInfo.DoesNotExist: # except UserInfo.DoesNotExist:
# response.msg = "用户不存在!" # response.msg = "用户不存在!"

@ -27,7 +27,7 @@ class StorageView(APIView):
res.storage_list = [] res.storage_list = []
storage_org_list = list(AppStorage.storage_choices) storage_org_list = list(AppStorage.storage_choices)
for storage_t in storage_org_list: for storage_t in storage_org_list:
if storage_t[0] in [0, 3]: continue if storage_t[0] not in [0, 3]:
res.storage_list.append({'id': storage_t[0], 'name': storage_t[1]}) res.storage_list.append({'id': storage_t[0], 'name': storage_t[1]})
act = request.query_params.get("act", None) act = request.query_params.get("act", None)

Loading…
Cancel
Save