1. 程式人生 > 實用技巧 >工單系統之使用者模組整體實現

工單系統之使用者模組整體實現

1 使用者模組管理分析

1.1 管理模組部分功能展示

1.2 前端總體業務邏輯分析

  • Vue元件思想

vue本身就是一個元件,所以我們在使用的過程中,一定要有元件思想。本專案就是利用父子元件傳值,呼叫方法來進行編寫的。

1.2.1 Home頁面
  • 其中巢狀元件Header和LeftMenu

總頁面,主要用於佈局。用於顯示左側選單和頭部資訊。其中巢狀子路由用於不同模組之間的跳轉。

  • Header用於頭部資訊展示

  • LeftMenu用於左側選單展示

  • 模組跳轉部分巢狀子路由

1.2.2 Index頁面
  • 巢狀四個元件

BreadCrumb,EditForm,Search,TableList,用於模組之間的跳轉變化。

  • BreadCrumb用於頂部路徑展示

  • EditForm用於控制輸入框資訊,修改和新增時有所不同
    • 修改(在原有資料基礎上進行修改,並且不支援密碼和使用者名稱的修改)

    • 新增

  • Search控制查詢

  • TableList控制表格展示,會隨著Search輸入的查詢資料變化

1.3 後端總體業務邏輯分析

1.3.1 ModelViewSet
  • 用於使用者資訊管理的增刪改查
  • 注意事項:
* 查詢介面
http://192.168.56.100:1594/user/user/?username='cat'
* 新增介面
http://192.168.56.100:1594/user/user/
* 刪除介面(指定使用者id刪除)
http://192.168.56.100:1594/user/user/id/
* 修改介面(指定使用者id修改)
http://192.168.56.100:1594/user/user/id/
1.3.2 APIView
  • 用於註冊使用者的業務邏輯

2 Django端

2.1 settings.py

"""
Django settings for opwf project.

Generated by 'django-admin startproject' using Django 2.0.13.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""
import datetime
import os, sys

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'uorj1ni^mnut@wo@c%)iv)%5=8dxlml4-j0!f3b%4#f*8a5)3t'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['*']


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',
    'user.apps.UserConfig',
    'workflow.apps.WorkflowConfig',
    'workerorder.apps.WorkerorderConfig',
    # 'jwt',
    # 'rest_framework_jwt',
    # 'rest_framework.authentication'

]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'opwf.urls'
CORS_ORIGIN_ALLOW_ALL = True

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'opwf.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'opwf_db',
        'USER': 'root',
        'PASSWORD': '1',
        'HOST': '127.0.0.1',
        'PORT': '3306'
    }
}


# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators

REST_FRAMEWORK = {
    # 文件報錯: AttributeError: ‘AutoSchema’ object has no attribute ‘get_link’
    # 用下面的設定可以解決
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
    # 預設設定是:
    # 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',

    # 異常處理器
    # 'EXCEPTION_HANDLER': 'user.utils.exception_handler',

    # Base API policies      預設渲染器類
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    # 預設解析器類
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ],
    # 1.認證器(全域性)
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',  # 在 DRF中配置JWT認證
        # 'rest_framework.authentication.SessionAuthentication',  # 使用session時的認證器
        # 'rest_framework.authentication.BasicAuthentication'  # 提交表單時的認證器
    ],

    # 2.許可權配置(全域性): 順序靠上的嚴格
    'DEFAULT_PERMISSION_CLASSES': [
        # 'rest_framework.permissions.IsAdminUser',  # 管理員可以訪問
        # 'rest_framework.permissions.IsAuthenticated',  # 認證使用者可以訪問
        # 'rest_framework.permissions.IsAuthenticatedOrReadOnly',  # 認證使用者可以訪問, 否則只能讀取
        'rest_framework.permissions.AllowAny',  # 所有使用者都可以訪問
    ],
    # 3.限流(防爬蟲)
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    # 3.1限流策略
    # 'DEFAULT_THROTTLE_RATES': {
    #     'user': '100/hour',  # 認證使用者每小時100次
    #     'anon': '300/day',  # 未認證使用者每天能訪問3次
    # },

    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
    'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
    'DEFAULT_VERSIONING_CLASS': None,

    # 4.分頁(全域性):全域性分頁器, 例如 省市區的資料自定義分頁器, 不需要分頁
    # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    # 每頁返回數量
    # 'PAGE_SIZE': 1
    # 5.過濾器後端
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
        # 'django_filters.rest_framework.backends.DjangoFilterBackend', 包路徑有變化
    ],

    # 5.1過濾排序(全域性):Filtering 過濾排序
    'SEARCH_PARAM': 'search',
    'ORDERING_PARAM': 'ordering',

    'NUM_PROXIES': None,

    # 6.版本控制:Versioning  介面版本控制
    'DEFAULT_VERSION': None,
    'ALLOWED_VERSIONS': None,
    'VERSION_PARAM': 'version',

    # Authentication  認證
    # 未認證使用者使用的使用者型別
    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
    # 未認證使用者使用的Token值
    'UNAUTHENTICATED_TOKEN': None,

    # View configuration
    'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
    'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',

    'NON_FIELD_ERRORS_KEY': 'non_field_errors',

    # Testing
    'TEST_REQUEST_RENDERER_CLASSES': [
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer'
    ],
    'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',

    # Hyperlink settings
    'URL_FORMAT_OVERRIDE': 'format',
    'FORMAT_SUFFIX_KWARG': 'format',
    'URL_FIELD_NAME': 'url',

    # Encoding
    'UNICODE_JSON': True,
    'COMPACT_JSON': True,
    'STRICT_JSON': True,
    'COERCE_DECIMAL_TO_STRING': True,
    'UPLOADED_FILES_USE_URL': True,

    # Browseable API
    'HTML_SELECT_CUTOFF': 1000,
    'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",

    # Schemas
    'SCHEMA_COERCE_PATH_PK': True,
    'SCHEMA_COERCE_METHOD_NAMES': {
        'retrieve': 'read',
        'destroy': 'delete'
    },

    # 'Access-Control-Allow-Origin':'http://localhost:8080',
    # 'Access-Control-Allow-Credentials': True

}

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/

STATIC_URL = '/static/'
AUTH_USER_MODEL = 'user.User'

# jwt載荷中的有效期設定
JWT_AUTH = {
    # 1.token字首:headers中 Authorization 值的字首
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
    # 2.token有效期:一天有效
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    # 3.重新整理token:允許使用舊的token換新token
    'JWT_ALLOW_REFRESH': True,
    # 4.token有效期:token在24小時內過期, 可續期token
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(hours=24),
    # 5.自定義JWT載荷資訊:自定義返回格式,需要手工建立
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'user.utils.jwt_response_payload_handler',
}

2.2 user/utils.py

# -*- coding: utf-8 -*-
def jwt_response_payload_handler(token, user=None, request=None, role=None):
    """
        自定義jwt認證成功返回資料
        :token 返回的jwt
        :user 當前登入的使用者資訊[物件]
        :request 當前本次客戶端提交過來的資料
        :role 角色
    """
    if user.first_name:
        name = user.first_name
    else:
        name = user.username
        return {
            'authenticated': 'true',
             'id': user.id,
             "role": role,
             'name': name,
             'username': user.username,
             'email': user.email,
             'token': token,
        }

2.3 user/views.py

import datetime
import random

from django.contrib.auth.hashers import make_password
from django.shortcuts import render

# Create your views here.
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.views import APIView

from user.models import User
from user.serializers import UserSerializer, UserModelSerializer


class UserViewSet(viewsets.ModelViewSet):
    # 負責使用者資訊的增刪改查
    queryset = User.objects.all()
    serializer_class = UserModelSerializer
    filter_fields = {"username"}




class RegisterView(APIView):
    # 註冊有一定業務邏輯,所以用APIView
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        password_new = request.data.get('password_new')
        if not all([username, password, password_new]):
            return Response(
                {'msg': '資訊不全', 'code': 400}
            )
        if password != password_new:
            return Response(
                {'code': 400, 'msg':'兩次登入密碼不一致'}
            )
        user_serializer = UserSerializer(data=request.data)
        if user_serializer.is_valid():
            user_serializer.save()
            user_info = User.objects.filter(username=username).first()
            return Response(
                {'msg': '註冊成功', 'code': 200, 'data':user_serializer.data}
            )
        return Response(
            {'msg': '註冊失敗', 'error': user_serializer.errors }
        )


2.4 user/serializers.py

# -*- coding: utf-8 -*-
from rest_framework import serializers
from rest_framework_jwt.serializers import jwt_payload_handler
from rest_framework_jwt.settings import api_settings

from user.models import User


class UserModelSerializer(serializers.ModelSerializer):
    # 用於ModelViewSet的增刪改查
    class Meta:
        model = User
        fields = '__all__'
    def create(self, data):
        username = data.get('username', '')
        password = data.get('password', '')
        mobile = data.get('mobile', '')
        email = data.get('email', '')
        user = User(username=username, email=email, mobile=mobile)
        user.set_password(password)
        user.save()
        return user

# 只用於登入,註冊
class UserSerializer(serializers.Serializer):
    # 用serialzier原方法進行方法重寫
    username = serializers.CharField()
    password = serializers.CharField()
    mobile = serializers.CharField()
    email = serializers.EmailField()
    token = serializers.CharField(read_only=True)

    def create(self, data):
        username = data.get('username', '')
        password = data.get('password', '')
        mobile = data.get('mobile', '')
        email = data.get('email', '')
        user = User(username=username, email=email, mobile=mobile)
        user.set_password(password)
        user.save()
        # 補充生成記錄登入狀態的token
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        user.token = token
        return user


# 重寫
# def update(self, instance, validated_data):
#     instance.mobile = validated_data.get('mobile')
#     instance.email = validated_data.get('email')
#     instance.save()
#     return instance

2.5 user/urls.py

# -*- coding: utf-8 -*-

from django.urls import path
from rest_framework.routers import DefaultRouter
from rest_framework_jwt.views import obtain_jwt_token

from user import views

router = DefaultRouter()
router.register(r'user', views.UserViewSet)

urlpatterns = [
    path('login/', obtain_jwt_token),
    path('register/', views.RegisterView.as_view()),
]

urlpatterns += router.urls

3 Vue端

  • 目錄結構

3.1 main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'

// 使用ant-design-vue
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
Vue.config.productionTip = false
Vue.use(Antd);


// router.beforeEach((to, from, next) => {
//   if (to.path =='/login' || localStorage.getItem("token")) {
//       next()
//   } else {
//       alert("尚未登入,請先登入")
//       return next("/login")
//   }
// })

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

3.2 http/api.js

//將我們http.js中封裝好的  get,post.put,delete,patch  導過來
import { axios_get, axios_post, axios_delete, axios_put, axios_patch } from './index.js'


//按照格式確定方法名
export const user_login = P => axios_post("/user/login/", P)  // 使用者登入

// 使用者模組
export const get_userlist = P => axios_get('/user/user/', P)     // 獲取使用者列表
export const add_user = P => axios_post('/user/register/', P)     // 註冊新使用者
export const search_for = P => axios_get('user/user/?username=' + P.username)   // 根據使用者名稱查詢指定使用者資訊並展示
export const delete_user = P => axios_delete('/user/user/' + P + '/')           // 根據獲取到的使用者id刪除使用者資訊
export const update_user = P => axios_put('/user/user/'+ P.id +'/', P)    // 根絕使用者id和提交來的資料修改使用者資訊
// export const get_dept_list = p => axios_get("/account/deptManage/", p)  //

3.3 http/index.js

import axios from 'axios'

// 第一步:設定axios
axios.defaults.baseURL = "http://192.168.56.100:1594/"

//全域性設定網路超時
axios.defaults.timeout = 10000;

//設定請求頭資訊
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.headers.put['Content-Type'] = 'application/json';


// 第二:設定攔截器
/**
 * 請求攔截器(當前端傳送請求給後端前進行攔截)
 * 例1:請求攔截器獲取token設定到axios請求頭中,所有請求介面都具有這個功能
 * 例2:到使用者訪問某一個頁面,但是使用者沒有登入,前端頁面自動跳轉 /login/ 頁面
 */
axios.interceptors.request.use(
    config => {
        // 每次傳送請求之前判斷是否存在token,如果存在,則統一在http請求的header都加上token,不用每次請求都手動添加了
        const token = localStorage.getItem("token")
            // console.log(token)
        if (token) {
            config.headers.Authorization = 'JWT ' + token
        }
        return config;
    },
    error => {
        return Promise.error(error);
    })

axios.interceptors.response.use(
    // 請求成功,因為 API返回的狀態碼有多個,所以一定要在這裡寫上,不然會無法訪問頁面
    res => res.status === 200 || 201 || 204 ? Promise.resolve(res) : Promise.reject(res),
    // 請求失敗
    error => {
        if (error.response) {
            // 判斷一下返回結果的status == 401?  ==401跳轉登入頁面。  !=401passs
            // console.log(error.response)
            if (error.response.status === 401) {
                // 跳轉不可以使用this.$router.push方法、
                // this.$router.push({path:'/login'})
                window.location.href = "http://127.0.0.1:8080/"
            } else {
                // errorHandle(response.status, response.data.message);
                return Promise.reject(error.response);
            }
            // 請求已發出,但是不在2xx的範圍
        } else {
            // 處理斷網的情況
            // eg:請求超時或斷網時,更新state的network狀態
            // network狀態在app.vue中控制著一個全域性的斷網提示元件的顯示隱藏
            // 關於斷網元件中的重新整理重新獲取資料,會在斷網元件中說明
            // store.commit('changeNetwork', false);
            return Promise.reject(error.response);
        }
    });


// 第三:封裝axios請求
// 3.1 封裝get請求
export function axios_get(url, params) {
    return new Promise(
        (resolve, reject) => {
            axios.get(url, {params:params})
                .then(res => {
                    // console.log("封裝資訊的的res", res)
                    resolve(res.data)
                }).catch(err => {
                    reject(err.data)
                })
        }
    )
}

// 3.2 封裝post請求
export function axios_post(url, data) {
    return new Promise(
        (resolve, reject) => {
            // console.log(data)
            axios.post(url, JSON.stringify(data))
                .then(res => {
                    // console.log("封裝資訊的的res", res)
                    resolve(res.data)
                }).catch(err => {
                    reject(err.data)
                })
        }
    )
}

// 3.3 封裝put請求
export function axios_put(url, data) {
    return new Promise(
        (resolve, reject) => {
            // console.log(data)
            axios.put(url, JSON.stringify(data))
                .then(res => {
                    // console.log("封裝資訊的的 res", res)
                    resolve(res.data)
                }).catch(err => {
                    reject(err.data)
                })
        }
    )
}

// 3.4 封裝patch請求(可用於區域性修改)

export function axios_patch(url, data) {
    return new Promise(
        (resolve, reject) => {
            // console.log(data)
            axios.patch(url, JSON.stringify(data))
                .then(res => {
                    // console.log("封裝資訊的的res", res)
                    resolve(res.data)
                }).catch(err => {
                    reject(err.data)
                })
        }
    )
}

// 3.5 封裝delete請求
export function axios_delete(url, data) {
    return new Promise(
        (resolve, reject) => {
            // console.log(data)
            axios.delete(url, { params: data })
                .then(res => {
                    // console.log("封裝資訊的的res", res)
                    resolve(res.data)
                }).catch(err => {
                    // reject(err.data)
                })
        }
    )
}

3.4 router/index.js

import Vue from 'vue'
import Router from 'vue-router'

import Home from '@/components/layout/Home'
const page = name => () => import('@/views/' + name)
Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    { path: '/login',component: page('Login'),name: '登入'},
    
    // 2.使用者管理模組
    { path: '/',component: Home,name: 'home',
      children: [
        { path: 'usermanage', component: page('user-manage/index'), name: '使用者管理' },
        { path: 'flowconf', component: page('workflow/WorkFlowConf'), name: '模板管理' },
        { path: 'flowtype', component: page('workflow/WorkFlowType'), name: '模板管理' },
        { path: 'baidu', component: page('BaiDu'), name: '跳轉百度' },
      
      ]
    },

    // 3.配置工單模板模組
    // { path: '/',component: Home,name: 'home',
    //   children: [

    //     { path: '/rolemanage', component: page('role-manage/RoleManage'), name: '角色管理' },
    //     { path: '/flowconf', component: page('flow-conf/FlowConf'), name: '模板管理' },
    //   ]
    // },

  ]
})

3.5 Home.vue

<template>
         <div id="components-layout-demo-basic">
            <a-layout>
                <a-layout-sider>
                    <LeftMenu style="margin-left:-55px"></LeftMenu>
                    <!-- 以元件發昂視匯入左側選單 -->
                </a-layout-sider>
                <a-layout>
                    <a-layout-header>
                        <Header/>
                        <!-- 匯入頭部 -->
                    </a-layout-header>
                        <div>
                            <!-- 這裡的 router-view 是繫結的路由 -->
                            <router-view></router-view>
                        </div>
                </a-layout>
            </a-layout>
        </div>
</template>

<script>
// 匯入元件
import LeftMenu from '@/components/layout/LeftMenu'
import Header from '@/components/layout/Header'
export default {
    // 註冊元件
    components:{
        LeftMenu,
        Header
    },
    data() {
        
        return {

        }
    },
    methods: {

    },
    created() {

    }
}
</script>

<style scoped>
#components-layout-demo-basic {
  text-align: center;
}
#components-layout-demo-basic .ant-layout-header,
#components-layout-demo-basic .ant-layout-footer {
  background: white;
  color: white;
}
#components-layout-demo-basic .ant-layout-footer {
  line-height: 1.5;
}
#components-layout-demo-basic .ant-layout-sider {
  background: white;
  color: white;
  line-height: 120px;
}
#components-layout-demo-basic .ant-layout-content {
  background: white;
  color: white;
  min-height: 120px;
  line-height: 120px;
}
</style>

3.6 Header.vue

<template>
        <div>
          <h3 style="margin-top:20px"><a-icon type="smile" theme="outlined" style="font-size:30px"/>&ensp;歡迎你:{{username}}</h3>
        </div>
</template>
<script>
export default{
    data(){
        return{
          username:localStorage.getItem('username')
        }
    }
}
</script>

3.7 LeftMenu.vue

<template>
  <div>
    <a-icon type="smile"/>
    <a-switch :default-checked="false" @change="changeMode"/> 
    <span className="ant-divider"/>
    <a-icon type="bulb"/>
    <a-switch :default-checked="false" @change="changeTheme"/>
    <a-menu
      style="width: 256px"
      :default-selected-keys="['1']"
      :default-open-keys="['sub1']"
      :mode="mode"
      :theme="theme"
      @click="handleClick"
    >
    <!-- 必須定義click方法,handleClick是用來跳轉路由的,內建毀掉引數e,包括傳遞上去的key路由地址 -->
    
      <a-menu-item key="usermanage">
        <!-- key是路由地址 -->
        <a-icon type="user" />
        使用者管理模組
      </a-menu-item>

      <a-menu-item key="baidu">
        <a-icon type="calendar" />
        百度翻譯
      </a-menu-item>
      <a-sub-menu key="workflow">
        <span slot="title">
          <a-icon type="appstore" />
          <span>工單管理系統</span>
        </span>
          
          <a-menu-item key="flowtype" title="工單分類">
            工單分類
          </a-menu-item>

          <a-menu-item key="flowconf" title="工單模板">
            工單模板
          </a-menu-item>

          <a-menu-item key="newflowuserroleactionconf" title="配置審批流">
              配置審批流
          </a-menu-item>

       </a-sub-menu>
    
      <a-sub-menu key="workorder">
        <span slot="title"><a-icon type="setting" /><span>申請工單</span></span>
              
          <a-menu-item key="workorder">
          例項化工單
          </a-menu-item>

          <a-menu-item key="suborder">
          例項化子工單
          </a-menu-item>

      </a-sub-menu>
    </a-menu>
  </div>
</template>
<script>
export default {
  data() {
    return {
      mode: 'inline',
      theme: 'light',
      current: '1'
    };
  },
  methods: {
    changeMode(checked) {
      this.mode = checked ? 'vertical' : 'inline';
    },
    changeTheme(checked) {
      this.theme = checked ? 'dark' : 'light';
    },
     handleClick(e) {
      this.current = e.key;
      this.$router.push({path:this.current});
      // click方法預設回撥引數中的 key,所以 e.key就是傳遞來的路由
    },
  },
};
</script>

3.8 index.vue

<template>
<div>
  <div id="components-layout-demo-basic">
     <a-layout>
        <a-layout-header>
          <BreadCrumb style="float:left"></BreadCrumb>

        </a-layout-header>
        <a-layout>
            <a-layout-content>
            <div style="margin-bottom:80px">
                <a-button type="primary" ghost style="float:left;margin-left:20px" @click="addNew">
                AddUser
                </a-button>
                <EditForm
                    :visible.sync="visible"
                    :userList='userList'
                    @add="add"
                >
                <!-- .sync控制組件是否顯示 -->
                </EditForm>
                <Search 
                  style="margin-bottom:-20px;margin-top:10px" 
                  :searchList="searchList"
                  @find="find"
                  @getUser="getUser"
                >
                </Search>
                
            </div>
                <TableList
                  :userListGet="userListGet"
                  :userList="userList"
                  @getUser="getUser"
                  @add="add"
                >
                </TableList>

                <Pagination
                  @getPage="getPage" 
                  :count="count"              
                ></Pagination>
            </a-layout-content>
        </a-layout>
     </a-layout>
  </div>
</div>
</template>

<script>
import BreadCrumb from "./components/BreadCrumb";
import TableList from "./components/TableList";
import Search from "./components/Search";
import EditForm from "./components/EditForm";
import Pagination from "./components/Pagination"

import { add_user, search_for, get_userlist, update_user } from '@/http/apis';
import { delete_user } from '../../http/apis';
export default {
    components:{
        BreadCrumb,
        TableList,
        Search,
        EditForm,
        Pagination
    },
    data() {
        return {
            visible:false,
            userList: {
              'id':'',
              'username': '',
              'passowrd': '',
              'password_new': '',
              'email':'',
              'mobile':'',
              'weixin':''
            },
            userListGet:[],
            searchList:{
                'username':'',
                'page':1,
                'page_size':4

            },
            updateUserList:[],
            // 當前頁碼
            current:1,
            // 總共的資料多少條
            count:0

        }
    },
    methods: {
        addNew(){
          this.visible = true
          this.userList = {
              'id':'',
              'username': '',
              'passowrd': '',
              'password_new': '',
              'email':'',
              'mobile':'',
              'weixin':''
            }
          // 用於控制組件顯示
        },
        add(){
          if(this.userList.id){            
            this.visible = true  
            update_user(this.userList).then(res=>{
              // alert('修改成功')
              this.getUser()
            })

          }else{
            // 新增使用者,子元件中編輯的值實際上是寫在父元件上面的
            add_user(this.userList).then(res=>{
              console.log(res)
              alert('新增新使用者成功')
              this.getUser()
              
          })   
            this.visible=false       
          }
        },
        find(){
          // 根據使用者名稱查詢使用者資訊
          search_for(this.searchList).then(res=>{
            // 闊落的辦法可以解決bug,但是不支援查詢出多條資料,因為沒辦法分頁
            // if(this.searchList.username){
            //   // 修復如果沒有搜尋資料,回車就只能顯示一個頁面的bug
            //   console.log(res)
            //   this.userListGet = res.results
            //   this.count = res.results.length
            // }else{
            //   this.getUser()
            // }
            this.getUser()

          })
        },
        getUser(){
          this.searchList.page = this.current
          // 獲取使用者資訊列表,父元件傳遞給子元件
          get_userlist(this.searchList).then(res=>{
            this.userListGet = res.results
            this.count = res.count
            console.log(this.count)
            console.log(this.userListGet)
          })
        },
        // 獲取頁碼
        getPage(currentChild){
          // 獲取到的currentChild是子元件傳遞過來是第幾頁
          this.current = currentChild
          console.log(this.current)
          this.getUser()
        }
    },
    created() {

    }
}
</script>

<style scoped>
#components-layout-demo-basic {
  text-align: center;
}
#components-layout-demo-basic .ant-layout-header,
#components-layout-demo-basic .ant-layout-footer {
  background: white;
  color: #fff;
}
#components-layout-demo-basic .ant-layout-footer {
  line-height: 1.5;
}
#components-layout-demo-basic .ant-layout-content {
  background: white;
  color: #fff;
  min-height: 120px;
  line-height: 120px;
}
#components-layout-demo-basic > .ant-layout {
  margin-bottom: 48px;
}
#components-layout-demo-basic > .ant-layout:last-child {
  margin: 0;
}
</style>

3.9 BreadCrumb.vue

<template>
    <div>
        <a-breadcrumb>
            <a-breadcrumb-item href="">
            <a-icon type="home" />
            </a-breadcrumb-item>

            <a-breadcrumb-item href="">
            <a-icon type="user" />
            <span>首頁</span>

            </a-breadcrumb-item>
            <a-breadcrumb-item>
            使用者模組
            </a-breadcrumb-item>
            <a-breadcrumb-item>
            資訊管理頁面
            </a-breadcrumb-item>
        </a-breadcrumb>
    </div>
</template>

<script>
export default {
    name:"BreadCrumb",
    data() {
        return {

        }
    },
    methods: {

    },
    created() {

    }
}
</script>

<style scoped>

</style>

3.10 EditForm.vue

<template>
    <div>
        <a-modal
        title="Please write now."
        :visible="visible"
        @ok="handleOk"
        @cancel="handleCancel"
        >
        <!-- @ok控制按鈕ok -->
        <!-- @cancel控制按鈕cancel -->
            <p v-if="userList.id">UserName:
                <a-input 
                style="width:380px;float:right" 
                placeholder="username" 
                v-model="userList.username"
                disabled="disabled"
                ></a-input>                
            </p>
            <p v-else>UserName:
                <a-input 
                style="width:380px;float:right" 
                placeholder="username" 
                v-model="userList.username"
                ></a-input>
            </p>
            <br>
            <div v-if="userList.id">

            </div>
            <div v-else>
            <p>PassWord:
                <a-input
                    v-decorator="[
                        'password',
                        { rules: [{ required: true, message: 'Please input your Password!' }] },
                    ]"
                    type="password"
                    placeholder="Password"
                    style="width:380px;float:right"
                    v-model="userList.password"
                    :disabled = 'false'
                >
                    <a-icon slot="prefix" type="lock" style="color:rgba(0,0,0,.25)" />
                </a-input>
            </p>
            <br>
            <p>PassWord Again:
                <a-input
                    v-decorator="[
                        'password',
                        { rules: [{ required: true, message: 'Please input your Password!' }] },
                    ]"
                    type="password"
                    placeholder="Password"
                    style="width:340px;float:right"
                    v-model="userList.password_new"
                >
                    <a-icon slot="prefix" type="lock" style="color:rgba(0,0,0,.25)" />
                </a-input>
            </p>
            </div>
            <br>
            <p>Email:
                <a-input style="width:410px;float:right" placeholder="Email" v-model="userList.email"></a-input>
            </p>
            <br>
            <p>Mobile:
                <a-input style="width:410px" placeholder="Mobile" v-model="userList.mobile"></a-input>
            </p>
            <br>
            <p>Weixin:
                <a-input style="width:410px" placeholder="WeiXin" v-model="userList.weixin"></a-input>
            </p>
        </a-modal>

    </div>
</template>

<script>
export default {
    props:['visible', 'userList'],
    data() {
        return {
        }
    },
    methods: {
        // 新增資料顯示username的input框,修改的時候input框內資料不能顯示
        choose(user_id){

        },
        handleOk(e) {
            this.$emit('add')
            // add方法的呼叫一定要在關閉彈窗上面,否則方法不執行完畢沒有辦法關閉彈窗
            // 呼叫父元件中 add 方法            
            this.$emit('update:visible', false)
            // 把 visible 的值更新為 false,控制組件不顯示

        },
        handleCancel(e) {
            this.$emit('update:visible', false)
            // 把 visible 的值更新為 false,控制組件不顯示
        },
    },
    created() {

    }
}
</script>

<style scoped>

</style>

3.11 Search.vue

<template>
    <div>
        <a-input-search placeholder="Input the username that you want to search for..." enter-button @search="onSearch" style="float:right;width:400px" v-model="searchList.username"/>
    </div>
</template>

<script>
export default {
    props:['searchList'],
    data() {
        return {

        } 
    },
    methods: {
        onSearch(){
            this.$emit('find')
            // 呼叫父元件中的find方法
        }
    },
    created() {

    }
}
</script>

<style scoped>

</style>

3.12 TableList.vue

<template>
  <a-table 
    :columns="columns" 
    :data-source="userListGet" 
    :rowKey="(record,index)=>{return index}"
    :pagination= 'false'
  >
  <!-- 帶:的都是屬性繫結,不可以更換名字,帶 : 就是 js 環境 -->
    <!-- 不寫:rowKey="(record,index)=>{return index}"瀏覽器會發出警告 -->
    <p slot="tags" slot-scope="text,tags,i">
      <!-- 加入操作的按鈕! -->
      <a-button @click="delUser(text,tags,i)">刪除</a-button>
      <a-button @click="updateUser(text,tags,i)">修改</a-button>
    </p>
  </a-table>
</template>

<script>
const columns = [
    {
    title: 'ID',
    dataIndex: 'id',
    key: 'id',
    // ellipsis: true,
    width: 50,
  },
  {
    title: 'UserName',
    dataIndex: 'username',
    key: 'username',
    scopedSlots: { customRender: 'username' },
    width: 80,
  },
  {
    title: 'Email',
    dataIndex: 'email',
    key: 'email',
    width: 100,
  },
  {
    title: 'Mobile',
    dataIndex: 'mobile',
    key: 'mobile',
    ellipsis: true,
    width: 100,
  },
  {
    title: 'WeiXin',
    dataIndex: 'weixin',
    key: 'weixin',
    ellipsis: true,
    width: 100,
  },
  {
    title: 'Date Joined',
    dataIndex: 'date_joined',
    key: 'date_joined',
    ellipsis: true,
    width: 100,
  },
  {
    title:'操作',
    dataIndex: 'tags',
    key : 'tags',
    width: 100,
    scopedSlots : { customRender: 'tags'}
    // scopedSlots: { customRender: 'tags' },一定不能少不然渲染不了html標籤

  }

]

import { delete_user } from '@/http/apis'
export default {
  props:[ 'userListGet', 'userList'],
  data() {
    return {
      columns,
    }
  },
  methods:{
    get(){
        this.$emit('getUser')
        // 呼叫父元件中的獲取使用者列表的方法
    },
    delUser(text,tags,i){
      // 定義變數 isDel來控制 confirm,isDel==true執行的就是對話方塊的 ok,isDel==false執行的就是對話方塊的 false
        const isDel = confirm('你確定要刪除' + tags.id)
        if(isDel==true){
          delete_user(tags.id).then(
          res=>{
            // 刪除回撥地址是  http://192.168.56.100:1594/id/
            this.get()
            alert('刪除成功啦~')
          })
        }else{
            alert('有需要再叫我哈~')
        }
             
    },
    updateUser(text,tags,i){
      this.userList.id = tags.id
      this.userList.username = tags.username
      this.userList.password = tags.password,
      this.userList.password_new = tags.password
      this.userList.email = tags.email
      this.userList.mobile = tags.mobile
      this.userList.weixin = tags.weixin      
      this.$emit('add')
    }
  },
  created(){
    this.get()
  }
};
</script>

3.13 BaiDu.vue

<template>
    <div>

    </div>
</template>

<script>
export default {
    data() {
        return {

        }
    },
    methods: {
        goBaiDu(){
            window.location.href = 'https://fanyi.baidu.com/?aldtype=16047#auto/zh'
        }
    },
    created() {
        this.goBaiDu()
    }
}
</script>

<style scoped>

</style>

3.14 Login.vue

<template>
  <div width=300>

    <center><h1>登入</h1></center>

    <a-form-item label="使用者名稱" v-bind="formlayout">

      <a-input v-model="username"/>

    </a-form-item>
    <a-form-item label="密碼" v-bind="formlayout">
      <a-input-password v-model="password" placeholder="input password" />
    </a-form-item>

    <a-form-item v-bind="buttonlayout">

      <a-button type="primary"  @click="submit">登入</a-button>

    </a-form-item>

  </div>
</template>

<script type="text/javascript">
import { user_login } from '../http/apis';

export default{

  data(){
    return{
      username:"",
      password:'',
      //表單樣式
      formlayout:{
        //標籤
        labelCol:{
          xs:{span:24},
          sm:{span:8}
        },
        //文字框
        wrapperCol:{
          xs:{span:14},
          sm:{span:6}
        }
      },
      //按鈕樣式
      buttonlayout:{
        //按鈕
        wrapperCol:{
          xs:{
            span:24,
            offset:0
          },
          sm:{span:16,offset:8}
        }
      }
    }

  },
  //自定義方法
  methods:{
    submit:function(){
      var data={'username':this.username,'password':this.password}
      user_login(data).then(resp => {
        console.log(resp)
        if(resp.token){
          // 如果返還token值,就儲存 token username uid
          localStorage.setItem('token',resp.token)
          localStorage.setItem('username',resp.username)
          localStorage.setItem('uid',resp.id)
          this.$router.push('/')
        }
      }).catch(err=>{
        console.log(err)
        alert('登入失敗')
      })

    }
  }
};
</script>

<style type="text/css">

</style>

3.15 Pagitation.vue

<template>
  <div>
    <a-pagination
      show-quick-jumper
      :default-current="2"
      :pageSize = '4'
      :total="count"
      show-less-items
      @change="onChange"
      v-model="current"

    />
  </div>
</template>

<script>
export default {
    props:[ 'count' ],
    data() {
        return {
            current:1
        }
    },
    methods: {
        
        onChange() {
            this.$emit('getPage', this.current)
    },
    },
    created() {

    }
}
</script>

<style scoped>

</style>

4 總結點

4.1 serializer重寫

class UserSerializer(serializers.Serializer):
    # 用serializer重寫,不能用ModelViewSet來新增,寫上id會報錯username不能設為key,因為User表繼承的是AbstractUser,其中的username欄位必須有unique=True屬性設定唯一約束
    username = serializers.CharField()
    password = serializers.CharField()
    mobile = serializers.CharField()
    email = serializers.EmailField()
    token = serializers.CharField(read_only=True)

    def create(self, data):
        username = data.get('username', '')
        password = data.get('password', '')
        mobile = data.get('mobile', '')
        email = data.get('email', '')
        user = User(username=username, email=email, mobile=mobile)
        user.set_password(password)
        user.save()
        # 補充生成記錄登入狀態的token
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        user.token = token
        return user

	def update(self, instance, validated_data):
        # instance 是當前物件obj
        # validated_data 是獲取的變數
		instance.mobile = validated_data.get('mobile')
		instance.email = validated_data.get('email')
		instance.save()
		return instance

4.2 要注意ModelViewSet的介面路由

  • 後端介面
* 查詢介面
http://192.168.56.100:1594/user/user/
http://192.168.56.100:1594/user/user/?username='cat'
* 新增介面
http://192.168.56.100:1594/user/user/
* 刪除介面(指定使用者id刪除)
http://192.168.56.100:1594/user/user/id/
* 修改介面(指定使用者id修改)
http://192.168.56.100:1594/user/user/id/
  • 前端訪問路徑配置
export const get_userlist = P => axios_get('/user/user/', P)     
// 查詢所有:獲取使用者列表
export const search_for = P => axios_get('user/user/?username=' + P.username)   
// 查詢指定資訊:根據使用者名稱查詢指定使用者資訊並展示
export const add_user = P => axios_post('/user/user/', P)     
// 新增使用者資訊
export const delete_user = P => axios_delete('/user/user/' + P + '/')           
// 刪除指定資訊:根據獲取到的使用者id刪除使用者資訊
export const update_user = P => axios_put('/user/user/'+ P.id +'/', P)    
// 修改指定資訊:根絕使用者id和提交來的資料修改使用者資訊

4.3 使用 ant-design-vue

// 使用ant-design-vue
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
Vue.config.productionTip = false
Vue.use(Antd);

4.4 ModelViewSet修改問題

  • put請求預設是全部修改
  • patch請求可以實現區域性修改(後端已經封裝好了方法)

4.5 Vue巢狀路由

  • index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/layout/Home'
// 用於導包路徑的配置
const page = name => () => import('@/views/' + name)
Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    // 使用者管理模組
    { path: '/',component: Home,name: 'home',
      children: [
        { path: 'usermanage', component: page('user-manage/Index'), name: '使用者管理' },
        { path: 'flowconf', component: page('workflow/WorkFlowConf'), name: '模板管理' },
        { path: 'flowtype', component: page('workflow/WorkFlowType'), name: '模板型別管理' },
        { path: 'baidu', component: page('BaiDu'), name: '跳轉百度' },
      
      ]
    },
  ]
})
  • Home頁面中寫入
<template>
    <div id="components-layout-demo-basic">
         <a-layout>
              </a-layout-header>
                  <div>
                     <!-- 這裡的 router-view 是繫結的路由 -->
                     <router-view></router-view>
                   </div>
              </a-layout-header>
          </a-layout>
    </div>
</template>

<script>
// 匯入元件
import LeftMenu from '@/components/layout/LeftMenu'
import Header from '@/components/layout/Header'
export default {
    // 註冊元件
    components:{
        LeftMenu,
        Header
    },
}
  • 繫結左側選單LeftMenu
<template>
  <div>
    <a-menu
      style="width: 256px"
      :default-selected-keys="['1']"
      :default-open-keys="['sub1']"
      :mode="mode"
      :theme="theme"
      @click="handleClick"
    >
    <!-- 必須定義click方法,handleClick是用來跳轉路由的,內建毀掉引數e,包括傳遞上去的key路由地址 -->    
      <a-menu-item key="usermanage">
        <!-- key是路由地址 -->
        使用者管理模組
      </a-menu-item>

      <a-menu-item key="baidu">
        百度翻譯
      </a-menu-item>
    </a-menu>
  </div>
</template>
<script>
export default {
  data() {
    return {
    };
  },
  methods: {
     handleClick(e) {
      this.current = e.key;
      this.$router.push({path:this.current});
      // click方法預設回撥引數中的 key,所以 e.key就是傳遞來的路由
    },
  },
};
</script>

4.6 ant-design-vue 圖示大小

  • font-size 可以調節圖示大小

4.7 v-if v-else控制input框disabled屬性

<template>
    <div>
        <a-modal
        title="Please write now."
        :visible="visible"
        @ok="handleOk"
        @cancel="handleCancel"
        >
        <!-- @ok控制按鈕ok -->
        <!-- @cancel控制按鈕cancel -->
            <p v-if="userList.id">UserName:
                <a-input 
                style="width:380px;float:right" 
                placeholder="username" 
                v-model="userList.username"
                disabled="disabled"
                ></a-input>    
                <!-- disabled可以用來控制input是否能輸入文字 -->
            </p>
            <p v-else>UserName:
                <a-input 
                style="width:380px;float:right" 
                placeholder="username" 
                v-model="userList.username"
                ></a-input>
            </p>
            <br>
            
            <div v-if="userList.id">

            </div>
            
            <div v-else>
            <p>PassWord:
                <a-input
                    v-decorator="[
                        'password',
                        { rules: [{ required: true, message: 'Please input your Password!' }] },
                    ]"
                    type="password"
                    placeholder="Password"
                    style="width:380px;float:right"
                    v-model="userList.password"
                    :disabled = 'false'
                >
                </a-input>
            </p>
        </a-modal>
    </div>
</template>

<script>
export default {
    props:['visible', 'userList'],
    methods: {
        handleOk(e) {
            this.$emit('add')
            // add方法的呼叫一定要在關閉彈窗上面,否則方法不執行完畢沒有辦法關閉彈窗        
            this.$emit('update:visible', false)
        },
        handleCancel(e) {
            this.$emit('update:visible', false)
        },
    },
}
</script>

4.8 a-table中的分頁器&按鈕

 <a-table 
    :columns="要展示的列名稱(可以自定義列表)" 
    :data-source="要展示的動態資料表名" 
    :pagination="pagination" 
    :rowKey="(record,index)=>{return index}"
  >
  <!-- 帶:的都是屬性繫結,不可以更換名字,帶 : 就是 js 環境 -->
  <!-- 不寫:rowKey="(record,index)=>{return index}"瀏覽器會發出警告 -->
     <p slot="tags" slot-scope="text,tags,i">
      <!-- 加入操作的按鈕!其中tags是某列資料,i是索引 -->
      <a-button @click="delUser(text,tags,i)">刪除</a-button>
    </p>
  </a-table>

4.9 a-table中獲取某一行值的方法

<template>
  <a-table 
    :columns="columns" 
    :data-source="userListGet" 
    :pagination="pagination" 
    :rowKey="(record,index)=>{return index}"
  >
  <!-- 帶:的都是屬性繫結,不可以更換名字,帶 : 就是 js 環境 -->
    <!-- 不寫:rowKey="(record,index)=>{return index}"瀏覽器會發出警告 -->
    <p slot="tags" slot-scope="text,tags,i">
      <!-- 加入操作的按鈕! -->
      <a-button @click="delUser(text,tags,i)">刪除</a-button>
    </p>
  </a-table>
</template>

<script>
const columns = [
  {
    title:'操作',
    dataIndex: 'tags',
    key : 'tags',
    width: 100,
    scopedSlots : { customRender: 'tags'}
    // scopedSlots: { customRender: 'tags' },一定不能少不然渲染不了html標籤
  }
]

import { delete_user } from '@/http/apis'
export default {
  data() {
    return {
      columns,//這裡要返回值哦 
      pagination:{
      pageSize: 4,
      }
    };
  },
  methods:{
    delUser(text,tags,i){
       }     
    },
  }
</script>

4.10 控制對話方塊

// 定義變數 isDel來控制 confirm,isDel==true執行的就是對話方塊的 ok,isDel==false執行的就是對話方塊的 false
const isDel = confirm('你確定要刪除' + tags.id)
if(isDel==true){
	delete_user(tags.id).then(
		res=>{
		// 刪除回撥地址是  http://192.168.56.100:1594/id/
		this.get()
			alert('刪除成功啦~')
		})
}else{
	alert('有需要再叫我哈~') 
}

4.11 自動跳轉到某一網址

window.location.href = 'https://www.baidu.com/'

4.12 前後端聯調分頁

  • 為了解決前端+後端能同時進行分頁的問題,只定義前端分頁,後端如果分頁就會影響前端資料,如果後端不定義分頁器,就會造成後端admin難以管理。

  • 後端程式碼

    • 實現區域性分頁
    from django.shortcuts import render
    from rest_framework import viewsets
    from rest_framework.pagination import PageNumberPagination
    from rest_framework.response import Response
    from rest_framework.views import APIView
    
    from user.models import User
    from user.serializers import UserSerializer, UserModelSerializer
    
    # 分頁(區域性):自定義分頁器 區域性
    class PageNum(PageNumberPagination):
        page_size = 4                           # 每頁顯示多少條
        page_size_query_param = 'page_size'     # 查詢字串中代表每頁返回資料數量的引數名, 預設值: None
        page_query_param = 'page'               # 查詢字串中代表頁碼的引數名, 有預設值: page
        max_page_size = None                    # 最大頁碼數限制
        
    
    class UserViewSet(viewsets.ModelViewSet):
        queryset = User.objects.all()
        serializer_class = UserModelSerializer
        filter_fields = {"username"}
        pagination_class = PageNum  # 注意不是列表(只能有一個分頁模式)
    
  • 前端程式碼

    • Pagination.vue
    <template>
      <div>
        <a-pagination
          show-quick-jumper
          :default-current="2"
          :pageSize = '4'
          :total="count"
          show-less-items
          @change="onChange"
          v-model="current"
        />
          <!-- 一定是change方法,不然不能跳轉 -->
      </div>
    </template>
    
    <script>
    export default {
        props:[ 'count' ],
        data() {
            return {
                current:1
            }
        },
        methods: { 
            onChange() {
                this.$emit('getPage', this.current)
        },
        },
        created() {
    
        }
    }
    </script>
    
    • Index.vue
    <template>
    <div>
      <div id="components-layout-demo-basic">
            <a-layout-content>
                <TableList
                      :userListGet="userListGet"
                      :userList="userList"
                      @getUser="getUser"
                      @add="add"
                 >
                        
                 </TableList>
                 <Pagination
                      @getPage="getPage" 
                      :count="count"              
                  >
        		  </Pagination>
            </a-layout-content>
      </div>
    </div>
    </template>
    
    <script>
    import Pagination from "./components/Pagination"
    
    import { get_userlist } from '@/http/apis';
    export default {
        components:{
            BreadCrumb,
            TableList,
            Search,
            EditForm,
            Pagination
        },
        data() {
            return {
                userListGet:[],
                updateUserList:[],
                // 當前頁碼
                current:1,
                // 總共的資料多少條
                count:0
    
            }
        },
        methods: {
            getUser(){
              // 獲取使用者資訊列表,父元件傳遞給子元件
              get_userlist(this.current).then(res=>{
                this.userListGet = res.results
                this.count = res.count
                console.log(this.count)
                console.log(this.userListGet)
              })
            },
            // 獲取頁碼
            getPage(currentChild){
              // 獲取到的currentChild是子元件傳遞過來是第幾頁
              this.current = currentChild
              console.log(this.current)
              this.getUser()
            }
        },
        created() {
    
        }
    }
    </script>
    
    <style scoped>
    #components-layout-demo-basic {
      text-align: center;
    }
    #components-layout-demo-basic .ant-layout-header,
    #components-layout-demo-basic .ant-layout-footer {
      background: white;
      color: #fff;
    }
    #components-layout-demo-basic .ant-layout-footer {
      line-height: 1.5;
    }
    #components-layout-demo-basic .ant-layout-content {
      background: white;
      color: #fff;
      min-height: 120px;
      line-height: 120px;
    }
    #components-layout-demo-basic > .ant-layout {
      margin-bottom: 48px;
    }
    #components-layout-demo-basic > .ant-layout:last-child {
      margin: 0;
    }
    </style>
    
    

4.13 利用資料解耦性完善查詢介面

export default {
    components:{
        BreadCrumb,
        TableList,
        Search,
        EditForm,
        Pagination
    },
    data() {
        return {
            roleListGet:[],
            searchList:{
                'zh_name':'',
                'page':1,
                'page_size':4,
                
            },

    },
methods: {
    find(){
              // 根據使用者名稱查詢使用者資訊
              search_for_role(this.searchList).then(res=>{
                  this.getRole()
                  // 資料解耦性!!!查詢和查詢某個其實可以呼叫同一個介面!
                  // 查詢所有:http://192.168.56.100:1594/?page=1&zh_name=
                  // 查詢某個:http://192.168.56.100:1594/?page=1&zh_name=many

              })
            },
    getRole(){
        this.searchList.page = this.current
        // 獲取使用者資訊列表,父元件傳遞給子元件
        get_rolelist(this.searchList).then(res=>{
        this.roleListGet = res.results
        this.count = res.count
        console.log(this.count)
        console.log(this.roleListGet)
    })
    },
	}