📊 列表查询页

表格展示 + 分页 + 搜索过滤

<template>
  <div>
    <!-- 搜索区域 -->
    <div class="search-bar">
      <input
        v-model="keyword"
        placeholder="搜索..."
        @input="handleSearch"
      />
    </div>

    <!-- 表格 -->
    <el-table :data="list" :loading="loading">
      <el-table-column prop="name" label="名称" />
      <el-table-column prop="status" label="状态" />
      <el-table-column label="操作">
        <template #default="{ row }">
          <el-button @click="handleEdit(row)">
            编辑
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <el-pagination
      :current-page="page"
      :page-size="pageSize"
      :total="total"
      @current-change="handlePageChange"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      keyword: '',
      list: [],
      loading: false,
      page: 1,
      pageSize: 10,
      total: 0
    }
  },
  mounted() {
    this.fetchList()
  },
  methods: {
    async fetchList() {
      this.loading = true
      const res = await api.getList({
        keyword: this.keyword,
        page: this.page,
        pageSize: this.pageSize
      })
      this.list = res.data.list
      this.total = res.data.total
      this.loading = false
    },
    handleSearch() {
      this.page = 1
      this.fetchList()
    },
    handlePageChange(page) {
      this.page = page
      this.fetchList()
    }
  }
}
</script>
// models/list.ts
import { Effect, Reducer } from 'dva'

interface ListItem {
  id: string
  name: string
  status: string
}

interface ListModelState {
  list: ListItem[]
  total: number
  page: number
  keyword: string
}

interface ListModelType {
  namespace: 'list'
  state: ListModelState
  effects: {
    fetchList: Effect
  }
  reducers: {
    setList: Reducer<ListModelState>
    setPage: Reducer<ListModelState>
    setKeyword: Reducer<ListModelState>
  }
}

const ListModel: ListModelType = {
  namespace: 'list',

  state: {
    list: [],
    total: 0,
    page: 1,
    keyword: ''
  },

  effects: {
    *fetchList({ payload }, { call, put, select }) {
      const { page, keyword } = yield select(state => state.list)
      const res = yield call(api.getList, {
        keyword: payload?.keyword ?? keyword,
        page: payload?.page ?? page,
        pageSize: 10
      })
      yield put({ type: 'setList', payload: res.data })
    }
  },

  reducers: {
    setList(state, { payload }) {
      return { ...state, list: payload.list, total: payload.total }
    },
    setPage(state, { payload }) {
      return { ...state, page: payload }
    },
    setKeyword(state, { payload }) {
      return { ...state, keyword: payload, page: 1 }
    }
  }
}

export default ListModel

// ============================================
// pages/ListPage.tsx
// ============================================
import React, { useEffect } from 'react'
import { connect } from 'dva'
import { Table, Input, Pagination } from 'antd'

interface ListPageProps {
  list: ListItem[]
  total: number
  page: number
  keyword: string
  loading: boolean
  dispatch: Dispatch
}

const ListPage: React.FC<ListPageProps> = ({
  list, total, page, keyword, loading, dispatch
}) => {
  useEffect(() => {
    dispatch({ type: 'list/fetchList' })
  }, [page, keyword])

  const handleSearch = (value: string) => {
    dispatch({ type: 'list/setKeyword', payload: value })
  }

  const handlePageChange = (newPage: number) => {
    dispatch({ type: 'list/setPage', payload: newPage })
  }

  const columns = [
    { title: '名称', dataIndex: 'name' },
    { title: '状态', dataIndex: 'status' },
    {
      title: '操作',
      render: (_: any, record: ListItem) => (
        <Button onClick={() => handleEdit(record)}>
          编辑
        </Button>
      )
    }
  ]

  return (
    <div>
      <Input.Search
        value={keyword}
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="搜索..."
      />

      <Table
        dataSource={list}
        columns={columns}
        loading={loading}
        rowKey="id"
        pagination={false}
      />

      <Pagination
        current={page}
        total={total}
        pageSize={10}
        onChange={handlePageChange}
      />
    </div>
  )
}

const mapStateToProps = (state: any) => ({
  list: state.list.list,
  total: state.list.total,
  page: state.list.page,
  keyword: state.list.keyword,
  loading: state.loading.effects['list/fetchList']
})

export default connect(mapStateToProps)(ListPage)

📝 表单提交

表单验证 + 异步提交 + 错误处理

<template>
  <el-form
    :model="form"
    :rules="rules"
    ref="formRef"
  >
    <el-form-item label="名称" prop="name">
      <el-input v-model="form.name" />
    </el-form-item>
    <el-form-item>
      <el-button
        :loading="submitting"
        @click="handleSubmit"
      >
        提交
      </el-button>
    </el-form-item>
  </el-form>
</template>

<script>
export default {
  data() {
    return {
      form: { name: '' },
      submitting: false,
      rules: {
        name: [
          { required: true, message: '请输入名称' }
        ]
      }
    }
  },
  methods: {
    async handleSubmit() {
      try {
        await this.$refs.formRef.validate()
        this.submitting = true
        await api.submit(this.form)
        this.$message.success('提交成功')
      } catch (error) {
        this.$message.error('提交失败')
      } finally {
        this.submitting = false
      }
    }
  }
}
</script>
import React, { useState } from 'react'
import { Form, Input, Button, message } from 'antd'

interface FormValues {
  name: string
}

const SubmitPage: React.FC = () => {
  const [form] = Form.useForm<FormValues>()
  const [submitting, setSubmitting] = useState(false)

  const handleSubmit = async (values: FormValues) => {
    setSubmitting(true)
    try {
      await api.submit(values)
      message.success('提交成功')
      form.resetFields()
    } catch (error) {
      message.error('提交失败')
    } finally {
      setSubmitting(false)
    }
  }

  return (
    <Form
      form={form}
      onFinish={handleSubmit}
    >
      <Form.Item
        name="name"
        rules={[{ required: true, message: '请输入名称' }]}
        label="名称"
      >
        <Input />
      </Form.Item>

      <Form.Item>
        <Button
          type="primary"
          htmlType="submit"
          loading={submitting}
        >
          提交
        </Button>
      </Form.Item>
    </Form>
  )
}

export default SubmitPage

🔐 权限控制

路由守卫、按钮权限、数据权限

hooks/usePermission.ts
import { useModel } from '@@/plugin-model/useModel'

// 权限 Hook
export function usePermission() {
  const { initialState } = useModel('@@initialState')
  const { permissions = [] } = initialState || {}

  const hasPermission = (code: string) => {
    return permissions.includes(code)
  }

  return { hasPermission, permissions }
}

// 在组件中使用
const UserList: React.FC = () => {
  const { hasPermission } = usePermission()

  return (
    <div>
      {hasPermission('user:create') && (
        <Button type="primary">
          新建用户
        </Button>
      )}
    </div>
  )
}
← Webpack 4 返回首页 →