猛犸系统-用户功能2
用户功能2
Vue插槽
参考 https://cn.vuejs.org/v2/guide/components-slots.html
插槽
插槽,指的是在子组件中挖一个空(slot插槽),在父组件中使用子组件标签内的内容,来填充子组件的空。
注意:以上是原理图,不能和Vue的实际代码完全对应。
插槽测试
新建T1.vue和T2.vue,T1是父组件,T2是子组件。
<template>
<div>
父组件T1内容
<hr />
<T2>父组件中在子组件插入的内容c</T2>
</div>
</template>
<script>
import T2 from './T2.vue'
export default {
components: {
T2
}
}
</script>
<style>
</style>
<template>
<div>子组件T2内容</div>
</template>
<script>
export default {}
</script>
<style>
</style>
发现内容C并没有显示出来。这就需要在子组件中使用插槽来接受。
修改T2增加插槽,代码如下
<template>
<div>
子组件T2内容
<div style="border: 1px solid #000"><slot></slot></div>
</div>
</template>
<script>
export default {}
</script>
<style>
</style>
插槽:父组件通过子组件标签为子组件传递内容,但是子组件内部需要使用插槽接受
作用域插槽
插槽可以看作数据从父组件流向子组件,但有时如果子组件得到了数据,想在父组件中使用子组件中的这些数据,怎么办?
作用域插槽scoped Slot,在父组件中能访问到子组件的数据。
作用域插槽:父组件中为子组件传递得数据不在父组件中,数据在子组件中,只能通过作用域插槽从子组件向父组件传过来这些数据,最后这些数据可以在父组件中使用,但是显示在子组件得插槽中。
新语法
slot-scope="fromChildtoParent"
即将过期,非命名插槽,可以使用新语法#default="fromChildtoParent"
作用域插槽测试
修改子组件T2如下:
<template>
<div>
子组件T2内容
<div style="border: 1px solid #000">
通过插槽bind数据,这样父组件就可以使用这个数据了,user是属性名<br /><br />
<slot :user="u"></slot>
</div>
</div>
</template>
<script>
export default {
data() {
return {
u: { name: 'tom', age: 20 }
}
}
}
</script>
<style>
</style>
修改父组件T1如下,如何使用子组件中得user?
<template>
<div>
父组件T1内容
<hr />
<T2>
<template v-slot:default="xyz"> 从子组件中传来的数据是:{{ xyz.user.name
}} </template>
</T2>
</div>
</template>
<script>
import T2 from './T2.vue'
export default {
components: {
T2
}
}
</script>
<style>
</style>
可以简化为
<T2 #default="xyz"> 从子组件中传来的数据是:{{ xyz.user.age }} </T2>
顺便可以结构
<T2 #default="{ user }"> 从子组件中传来的数据是:{{ user.name }}, {{ user.age }} </T2>
激活功能
使用switch开关组件表示是否激活状态。
src/plugins/element.js
import Vue from 'vue'
import {
Form, FormItem, Input, Button, Message, Container,
Header, Aside, Main, Menu, MenuItem, Submenu, Breadcrumb,
BreadcrumbItem, Card, Row, Col, Table, TableColumn,
Dialog, Pagination, Switch
} from 'element-ui'
Vue.use(Switch)
采用下面得方式,不能把数据注入给switch组件
<el-table-column prop="is_active" label="激活">
<el-switch v-model="is_active"> </el-switch>
</el-table-column>
参考 https://element.eleme.cn/#/zh-CN/component/table#zi-ding-yi-lie-mo-ban
通过作用域插槽 Scoped slot 可以获取到 row, column, $index 和 store(table 内部的状态管理)的数据
这个例子可以看出,scope是来自表格子组件得数据,我们在父组件中想用这个值,template得内容,最终插入到表格得列中得插槽中。结构后得row就是取当前行数据对象
<el-table :data="dataList" border style="width: 100%">
<el-table-column type="index"> </el-table-column>
<el-table-column prop="username" label="登录名"> </el-table-column>
<el-table-column prop="is_active" label="激活">
<template #default="{ row }">
<el-switch v-model="row.is_active"> </el-switch>
</template>
</el-table-column>
<el-table-column prop="is_superuser" label="管理员"> </el-tablecolumn>
<el-table-column prop="phone" label="电话"> </el-table-column>
<el-table-column label="操作">
<el-button type="success" icon="el-icon-edit" size="mini"></elbutton>
<el-button type="danger" icon="el-icon-delete" size="mini"></elbutton>
</el-table-column>
</el-table>
上面代码顺便增加了2个按钮编辑和删除。
switch组件会发生change事件,改变值,就需要取改变数据库中某个用户得is_active得值
src/views/user/UserView.vue 完整代码如下
<template>
<div>
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
<el-breadcrumb-item>用户列表</el-breadcrumb-item>
</el-breadcrumb>
<el-card>
<el-row :gutter="20">
<el-col :span="12">
<el-input placeholder="请输入内容" v-model="search">
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
</el-col>
<el-col :span="12">
<el-button type="primary" @click="addDialogVisible = true">增加用
户</el-button>
</el-col>
</el-row>
<el-table :data="dataList" border style="width: 100%">
<el-table-column type="index"> </el-table-column>
<el-table-column prop="username" label="登录名"> </el-table-column>
<el-table-column prop="is_active" label="激活">
<template #default="{ row }">
<el-switch v-model="row.is_active" @change="handleChange(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column prop="is_superuser" label="管理员"> </el-tablecolumn>
<el-table-column prop="phone" label="电话"> </el-table-column>
<el-table-column label="操作">
<el-button type="success" icon="el-icon-edit" size="mini"></elbutton>
<el-button type="danger" icon="el-icon-delete" size="mini"></elbutton>
</el-table-column>
</el-table>
<el-pagination
@current-change="handleCurrentChange"
:current-page="pagination.page"
:page-size="pagination.size"
layout="total, prev, pager, next, jumper"
:total="pagination.total"
>
</el-pagination>
</el-card>
<!-- 添加用户 -->
<el-dialog title="增加用户" :visible.sync="addDialogVisible"
@close="resetForm('add')">
<el-form :model="addForm" :rules="addRules" ref="add" labelwidth="100px">
<el-form-item label="用户名" prop="username">
<el-input v-model="addForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="addForm.password"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input type="password" v-model="addForm.checkPass"></el-input>
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="addForm.phone"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="addForm.email"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="add">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
created() {
this.getList()
},
data() {
var validatePass = (rule, value, callback) => {
if (value !== this.addForm.password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
}
return {
search: '', // 搜索
// 表格
dataList: [],
pagination: { page: 1, size: 20, total: 0 },
// 新增
addDialogVisible: false,
addForm: { username: '', password: '', checkPass: '', phone: '',
email: '' },
addRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 4, max: 16, message: '长度在 4 到 16 个字符', trigger:
'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 4, max: 16, message: '长度在 4 到 16 个字符', trigger:
'blur' }
],
checkPass: [
{ required: true, message: '请再次输入确认密码', trigger: 'blur' },
{ validator: validatePass, trigger: 'blur' }
]
}
}
},
methods: {
resetForm(name) {
console.log('closing~~~')
this.$refs[name].resetFields()
},
handleCurrentChange(val) {
this.getList(val)
},
add() {
const name = 'add'
this.$refs[name].validate(async (valid) => {
if (valid) {
const { data: response } = await this.$http.post('users/',
this.addForm)
if (response.code) {
return this.$message.error(response.message)
}
this.addDialogVisible = false
this.getList() // 刷新列表
}
})
},
async getList(page = 1) {
if (!page) {
page = 1
}
const { data: response } = await this.$http.get('users/', {
params: { page }
})
if (response.code) {
return this.$message.error(response.message)
}
this.dataList = response.results
this.pagination = response.pagination
},
async handleChange(row) {
// 激活按钮变化,去后台部分更新字段
await this.$http.patch(`users/${row.id}/`, {
is_active: row.is_active
})
}
}
}
</script>
<style lang="less" scoped>
</style>
用户搜索功能
仔细思考一下,这个功能也是用的list方法,发送GET请求到/users/,只不过需要带上参数,也就是查询字符串。
阅读Axios文档,GET请求可以使用 axios.get(url[, config]) ,而config可以用下面的方式传参。
这种方式本质上还是查询字符串方式。
axios.get('users/', {
params: {
page: 4
}
})
所以,可以将getList(page),修改如下
async getList(page = 1) {
if (!page) {
page = 1
}
const { data: response } = await this.$http.get('users/', {
params: {
page,
username: this.search
}
})
if (response.code) {
return this.$message.error(response.message)
}
this.dataList = response.results
this.pagination = response.pagination
}
下面为了聚焦在改动的代码上,省略一些没改过的代码
<template>
<div>
<el-card class="box-card">
<el-row :gutter="20">
<el-col :span="12">
<el-input placeholder="请输入内容" v-model="keywords">
<el-button slot="append" icon="el-icon-search"
@click="getList(1)"></el-button>
</el-input>
</el-col>
<el-col :span="12">
<el-button type="primary" @click="addDialogVisible = true">增加用户
</el-button>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script>
export default {
created() {
this.getList()
},
data() {
return {
keywords: '',
// 用户列表表格
dataList: [],
pagination: { page: 1, size: 20, total: 0 }
}
},
methods: {
async getList(page = 1) {
if (!page) {
page = 1
}
const { data: response } = await this.$http.get('users/', {
params: {
page,
username: this.keywords
}
})
if (response.code) {
return this.$message.error(response.message)
}
this.dataList = response.results
this.pagination = response.pagination
}
}
}
</script>
发起请求时,username是发到后台了,但是没有用,如何解决,这个就必须要覆盖GenericAPIView得get_queryset()了
from rest_framework.viewsets import ModelViewSet
from django.contrib.auth import get_user_model
from .serializers import UserSerializer
class UserViewSet(ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
# permission_classes = [IsAdminUser] # 必须是管理员才能管理用户
def get_queryset(self):
queryset = super().get_queryset() # 调用父类的处理
username = self.request.query_params.get('username', None)
if username:
queryset = queryset.filter(username__icontains=username)
return queryset
由于这个用到了列表页得list方法,查看ListModelMixin源码中,list方法返回列表前调用了filter_queryset,就是用来过滤得,覆盖filter_queryset也行。
这是一个通用的功能,以后免不了经常查询,但是学习分页的时候,不是客户端提交什么参数,服务器都允许的,这需要配置。这里,查询参数也需要服务器允许才行。
通用查询
参考:https://www.django-rest-framework.org/api-guide/filtering/#djangofilterbackend
pip install django-filter
在settings.py中注册应用
INSTALLED_APPS = [
...
'django_filters',
...
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS':
['django_filters.rest_framework.DjangoFilterBackend'] }
在view中配置
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.viewsets import ModelViewSet
from django.contrib.auth import get_user_model
from .models import UserProfile
from .serializers import UserSerializer
from django_filters.rest_framework import DjangoFilterBackend
class UserViewSet(ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
# permission_classes = [IsAdminUser] # 必须是管理员才能管理用户
filter_backends = [DjangoFilterBackend] #指定filter
filterset_fields = ['username'] #指定要搜索的字段
请求为: http://localhost:8080/api/v1/users/?page=1&username=admin
搜索SQL语句条件为FROM auth_uer WHERE auth_user.username = 'admin'
这是等值条件,要求查询参数名必须为filterset_fields中指定的名称。
实现模糊搜索,代码如下,注意:前端发送请求时url参数中必须传入search,值为search_fields配置的username指定的值
from rest_framework import filters
class UserViewSet(ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
# permission_classes = [IsAdminUser] # 必须是管理员才能管理用户
filter_backends = [filters.SearchFilter] # 指定filter
# filterset_fields = ['username']
search_fields = ['username']
请求为 http://localhost:8080/api/v1/users/?page=1&search=ay
,注意是search=
搜索SQL语句条件为 FROM auth_user WHERE auth_user.username LIKE '%ay%'
search_fields = ['username']
中如果写多个字段将是或查询。
修改用户
前台实现,仿照增加用户,部分代码如下:
<template>
<div>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
<el-breadcrumb-item>用户列表</el-breadcrumb-item>
</el-breadcrumb>
<el-card class="box-card">
<el-row :gutter="20">
<el-col :span="12">
<el-input placeholder="请输入内容" v-model="search">
<el-button slot="append" icon="el-icon-search" @click="getList(1)"></el-button>
</el-input>
</el-col>
<el-col :span="12">
<el-button type="primary" @click="addDialogVisible = true">增加用
户</el-button>
</el-col>
</el-row>
<el-table :data="dataList" border style="width: 100%">
<el-table-column type="index" label="序号"> </el-table-column>
<el-table-column prop="username" label="用户名"> </el-table-column>
<el-table-column prop="is_active" label="激活">
<template #default="{ row }">
<el-switch v-model="row.is_active" @change="handleChange(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column prop="is_superuser" label="管理员"> </el-tablecolumn>
<el-table-column prop="phone" label="电话"> </el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button @click="handleEdit(row)" type="success" icon="elicon-edit" size="mini"></el-button>
<el-button type="danger" icon="el-icon-delete" size="mini">
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@current-change="handleCurrentChange"
:current-page="pagination.page"
:page-size="pagination.size"
layout="total, prev, pager, next, jumper"
:total="pagination.total"
>
</el-pagination>
</el-card>
<!-- 添加用户 -->
<el-dialog title="增加用户" :visible.sync="addDialogVisible"
@close="resetForm('add')">
<el-form :model="addForm" :rules="addRules" ref="add" labelwidth="100px">
<el-form-item label="用户名" prop="username">
<el-input v-model="addForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="addForm.password"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input type="password" v-model="addForm.checkPass"></el-input>
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="addForm.phone"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="addForm.email"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="add">确 定</el-button>
</span>
</el-dialog>
<!-- 修改用户 -->
<el-dialog title="修改" :visible.sync="editDialogVisible"
@close="resetForm('edit')">
<el-form :model="editForm" :rules="editRules" ref="edit" labelwidth="100px">
<el-form-item label="用户名" prop="username">{{ editForm.username }}
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="editForm.phone"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="editForm.email"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="edit">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
created() {
this.getList()
},
data() {
const validatePass = (rule, value, callback) => {
if (value !== this.addForm.password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
}
return {
search: '',
// 用户列表表格
dataList: [],
pagination: { page: 1, size: 20, total: 0 },
// 新增用户对话框
addDialogVisible: false,
addForm: {
username: '',
password: '',
checkPass: '',
phone: '',
email: ''
},
addRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 4, max: 16, message: '长度在 4 到 16 个字符', trigger:
'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 4, max: 16, message: '长度在 4 到 16 个字符', trigger:
'blur' }
],
checkPass: [
{ required: true, message: '请再次输入确认密码', trigger: 'blur' },
{ validator: validatePass, trigger: 'blur' }
]
},
// 修改用户对话框
editDialogVisible: false,
editForm: {
username: '',
phone: '',
email: ''
},
editRules: {}
}
},
methods: {
resetForm(name) {
this.$refs[name].resetFields()
},
handleCurrentChange(val) {
this.getList(val)
},
add() {
const name = 'add'
this.$refs[name].validate(async valid => {
if (valid) {
const { data: response } = await this.$http.post('users/',
this.addForm)
if (response.code) {
return this.$message.error(response.message)
}
console.log(response)
this.addDialogVisible = false
this.getList()
}
})
},
async getList(page = 1) {
if (!page) {
page = 1
}
const { data: response } = await this.$http.get('users/', {
params: { page, search: this.search }
})
if (response.code) {
return this.$message.error(response.message)
}
this.dataList = response.results
this.pagination = response.pagination
},
async handleChange(row) {
// 激活按钮变化,去后台部分更新字段
await this.$http.patch(`users/${row.id}/`, {
is_active: row.is_active
})
},
handleEdit(row) {
this.editForm = row
this.editDialogVisible = true
},
edit() {
const { id } = this.editForm
const name = 'edit'
this.$refs[name].validate(async valid => {
if (valid) {
const { data: response } = await this.$http.patch(`users/${id}/`,
this.editForm)
if (response.code) {
return this.$message.error(response.message)
}
this.editDialogVisible = false
this.getList(this.pagination.page) // 刷新用户列表
}
})
}
}
}
</script>
后台如何做到,修改用户时,有id,用的是详情页,但是有人故意构造提交数据中包含username,如果后端也更新了,就修改了用户名,按照要求不允许修改用户名,怎么办?还是用PATCH。部分更新(本质上就是先查后部分更新),在后端剔除掉request数据中的usename,这需要覆盖partial_update(self, request, *args, **kwargs)
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.viewsets import ModelViewSet
from django.contrib.auth import get_user_model
from .models import UserProfile
from .serializers import UserSerializer
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
class UserViewSet(ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
# permission_classes = [IsAdminUser] # 必须是管理员才能管理用户
filter_backends = [filters.SearchFilter] # 指定filter
# filterset_fields = ['username']
search_fields = ['username']
# 详情页禁止修改username,如果提供用户名,它就要验证用户名唯一性,且尝试修改用户名
def partial_update(self, request, *args, **kwargs):
request.data.pop('username', None) # 剔除不要更新的字段
request.data.pop('id', None)
request.data.pop('password', None)
return super().partial_update(request, *args, **kwargs)
is_active、is_superuser是需要通过patch修改的
确认消息框和提示
确认框参考 https://element.eleme.cn/#/zh-CN/component/message-box#que-ren-xiao-xi
提示文字参考 https://element.eleme.cn/#/zh-CN/component/tooltip
src/plugins/element.js
import Vue from 'vue'
import {
Form, FormItem, Input, Button, Message, Container,
Header, Aside, Main, Menu, MenuItem, Submenu, Breadcrumb,
BreadcrumbItem, Card, Row, Col, Table, TableColumn,
Dialog, Pagination, Switch, MessageBox, Tooltip
} from 'element-ui'
Vue.use(Tooltip)
// 全局导入
Vue.prototype.$message = Message
Vue.prototype.$msgbox = MessageBox
<el-table :data="dataList" border style="width: 100%">
<el-table-column type="index" label="序号"> </el-table-column>
<el-table-column prop="username" label="用户名"> </el-table-column>
<el-table-column prop="is_active" label="激活">
<template #default="{ row }">
<el-switch v-model="row.is_active" @change="handleChange(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column prop="is_superuser" label="管理员"> </el-tablecolumn>
<el-table-column prop="phone" label="电话"> </el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-tooltip v-if="row.id !== 1" content="修改" effect="light">
<el-button @click="handleEdit(row)" type="success" icon="elicon-edit" size="mini"></el-button>
</el-tooltip>
<el-tooltip v-if="row.id !== 1" content="删除" effect="light">
<el-button @click="handleDel(row.id)" type="danger" icon="elicon-delete" size="mini"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
删除用户
前端对id为1的用户,使用v-if禁止修改、删除,使用disabled禁止激活。注意,前端的禁止行为只是一种样子,依然可以绕过他,后端禁止才是关键
<script>
export default {
methods: {
handleDel(id) {
console.log(id)
this.$msgbox
.confirm('删除该用户, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'danger'
})
.then(async () => {
const { data: response } = await this.$http.delete(`users/${id}/`)
if (response.code) {
return this.$message.error(response.message)
}
this.getList()
})
.catch(() => {})
}
}
}
</script>
顺便考虑一下id为1,就是管理员,后端禁止修改和删除,这个需要什么方法?
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.viewsets import ModelViewSet
from django.contrib.auth import get_user_model
from .models import UserProfile
from .serializers import UserSerializer
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from django.http.response import Http404
class UserViewSet(ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
# permission_classes = [IsAdminUser] # 必须是管理员才能管理用户
filter_backends = [filters.SearchFilter] # 指定filter
# filterset_fields = ['username']
search_fields = ['username']
# 详情页禁止修改username,如果提供用户名,它就要验证用户名唯一性,且尝试修改用户名
def partial_update(self, request, *args, **kwargs):
request.data.pop('username', None) # 剔除不要更新的字段
request.data.pop('id', None)
request.data.pop('password', None)
return super().partial_update(request, *args, **kwargs)
def get_object(self):
if self.request.method.lower() != 'get':
pk = self.kwargs.get('pk')
if pk == 1 or pk == '1':
raise Http404
return super().get_object()