Vue3+TS 结合ant design实现简单列表页和详情页

  • Post author:
  • Post category:vue


最近在做项目的时候,因为接触的比较多的基本上就是列表页和详情页,列表页主要就是类似于一个表格,展示所有数据,详情页主要就是指某条数据的详细信息,包括修改、新建一条数据、查看等都是对应详情页面,因此本次主要是想做一个关于列表页和详情页的实现的小分享,用到的技术栈是Vue3+Ts,使用到的组件库是ant design和tailwindcss。具体实现的页面可以看下图所示。

该页面是列表页,主要展示了用户的基本数据 ,勾选一条数据后可以点击查看,点击删除后可删除当前数据。

点击新建数据按钮后,进入到新建数据详情页,该页面是一个表单,用户输入完信息后提交校验通过后会在列表页新增一条数据。


在列表页选中一条数据后,点击查看,跳转到该条数据的详情页,详情页中展示该条数据的全部信息。

在详情页中点击修改按钮后可对当前数据进行修改,修改完成后点击提交,会改变当前数据的信息。

整体来说还是非常简单的,就是两个页面,一个列表页,一个详情页。下面来说一下具体的实现思路:

在实际项目中,所有的数据都是从后端获取的,因此所做的所有改变都会通过接口请求发送给后端,然后再次请求数据刷新页面。这里我是弄了一些假数据,在页面初始化的时候赋值,由于在列表页和详情页中切换时,我都是把所有数据进行路由传参,因此可以不用担心刷新页面而导致数据丢失的问题。


由于新建页面和查看详情页、编辑页都是对应同一个页面,因此考虑到组件的复用性,采用表单的结合v-if的形式进行控制。

const routes = [
  {
    path: "/",
    component: () => import("../ant-table/Table.vue"),
  },
  {
    path: "/details",
    name: "details",
    component: () => import("../view/Detail.vue"),
  },
]

路由配置如图,默认进入列表页。

下面正式开始页面编写:

<template>
  <div>
    <router-view></router-view>
  </div>
</template>

<script setup lang="ts"></script>

这里是App根组件,路由切换时用来呈现的内容。

interface DataType {
  name: string;
  age: string;
  address: any;
  sex: string;
  school: string;
  hobby: any;
  birthday: any;
  phone: string;
  description: string;
  position: {
    isShow: boolean;
    place: string;
  };
}

定义数据类型,方便类型检测和书写规范。

    <a-table
      :row-selection="{
        type: 'radio',
        selectedRowKeys: rowSelection.selectedRowKeys,
        onChange: onSelectChange,
      }"
      rowKey="name"
      :columns="columns"
      :data-source="data"
    >
      <template #bodyCell="{ column, record, text, index }">
        <template v-if="column.dataIndex === 'operation'">
          <span class="text-yn-400">
            <delete-two-tone @click="deleteData(record, index)" />
          </span>
        </template>
        <template v-if="column.dataIndex === 'index'">
          <span>
            {{ index }}
          </span>
        </template>
        <template v-if="column.dataIndex === 'address'">
          <span v-for="city in record.address">
            {{ city }}
          </span>
        </template>
        <template v-if="column.dataIndex === 'hobby'">
          <span
            v-for="hobby in record.hobby"
            class="bg-green-200 rounded-lg py-1 px-1.5 mx-1.5"
          >
            {{ hobby }}
          </span>
        </template>
      </template>
    </a-table>

这部分的代码主要是定义的一些数据,包括表格的表头、初始化表格数据等。

// 跳转详情页
const toDetails = () => {
  if (rowSelection.selectedRowKeys.length !== 1) {
    message.error("请选择一条数据!");
  } else {
    let selectName = rowSelection.selectedRowKeys[0];
    let usedata: any = toRaw(data.value);
    router.push({
      path: "/details",
      query: { selectName, useData: JSON.stringify(usedata) },
    });
  }
};

跳转到查看详情页代码如图:这里的的query传参里面即传了当前选中数据的name(作为唯一标识符),也传了列表页的所有数据,注意路由传参这里,如果传的是非字符串类型的数据,需要用JSON.stringify包裹并在接收时用JSON.parse解析。

// 新建记录
const addNewData = () => {
  let usedata: any = toRaw(data.value);
  router.push({
    path: "/details",
    query: {
      useData: JSON.stringify(usedata),
    },
  });
};

这里是点击新建数据时对应的代码。

这部分的代码就是初始化页面的操作和删除列表数据对应的代码,这里初始化数据我做了一个判断,就是页面加载时有没有数据,没有数据我就用一开始定义好的那几条数据,如果有数据(详情页传来的)就用详情页的数据。这里的妙处在于实际上只有页面一加载的时候是没有数据的,会使用默认数据,之后的每次页面路由切换,都会有数据了。

      <a-form
        :model="baseForm"
        name="basic"
        class="grid grid-cols-3"
        ref="formRef"
        :rules="baseInfoEules"
        layout="vertical"
      >
        <!-- 姓名 -->
        <div class="col-span-1 mx-3">
          <a-form-item name="name" label="姓名">
            <span v-if="!isEdit">{{ baseForm.name }}</span>
            <a-input
              :disabled="disableName"
              v-else
              v-model:value="baseForm.name"
            />
          </a-form-item>
        </div>
        <!-- 年龄 -->
        <div class="col-span-1 mx-3">
          <a-form-item name="age" label="年龄">
            <span v-if="!isEdit">{{ baseForm.age }}</span>
            <a-input-number
              class="w-full"
              id="inputNumber"
              v-else
              v-model:value="baseForm.age"
              :min="1"
              :max="99"
            />
          </a-form-item>
        </div>
        <!-- 性别 -->
        <div class="col-span-1 mx-3">
          <a-form-item name="sex" label="性别">
            <span v-if="!isEdit">{{ baseForm.sex }}</span>
            <a-select
              v-else
              v-model:value="baseForm.sex"
              placeholder="Please select sex"
            >
              <a-select-option value="男">男</a-select-option>
              <a-select-option value="女">女</a-select-option>
            </a-select>
          </a-form-item>
        </div>
        <!-- 地址 -->
        <div class="col-span-1 mx-3">
          <a-form-item name="address" label="地址">
            <span v-if="!isEdit" v-for="city in baseForm.address">{{
              city
            }}</span>
            <a-cascader
              v-else
              v-model:value="baseForm.address"
              :options="cityOptions"
              placeholder="Please select address"
            />
          </a-form-item>
        </div>
        <!-- 学校 -->
        <div class="col-span-1 mx-3">
          <a-form-item name="school" label="学校">
            <span v-if="!isEdit">{{ baseForm.school }}</span>
            <a-select
              v-else
              v-model:value="baseForm.school"
              placeholder="Please select a university"
            >
              <a-select-option value="西南大学">西南大学</a-select-option>
              <a-select-option value="中南大学">中南大学</a-select-option>
              <a-select-option value="武汉大学">武汉大学</a-select-option>
              <a-select-option value="浙江大学">浙江大学</a-select-option>
            </a-select>
          </a-form-item>
        </div>
        <!-- 爱好 -->
        <div class="col-span-1 mx-3">
          <a-form-item name="hobby" label="爱好">
            <span
              v-if="!isEdit"
              v-for="hobby in baseForm.hobby"
              class="mr-1.5 bg-green-200 py-1 px-1.5 pb-1.5 rounded-lg"
              >{{ hobby }}</span
            >
            <a-select
              :options="options"
              :fieldNames="{ label: 'label', value: 'value' }"
              v-else
              v-model:value="baseForm.hobby"
              mode="multiple"
              placeholder="Please select hobby"
            >
            </a-select>
          </a-form-item>
        </div>
        <!-- 出生日期 -->
        <div class="col-span-1 mx-3">
          <a-form-item name="birthday" label="出生日期">
            <span v-if="!isEdit">{{ baseForm.birthday }}</span>
            <a-date-picker
              placeholder="请选择时间"
              style="width: 100%"
              v-else
              v-model:value="baseForm.birthday"
              value-format="YYYY-MM-DD"
            />
          </a-form-item>
        </div>
        <!-- 手机号 -->
        <div class="col-span-1 mx-3">
          <a-form-item name="phone" label="手机号码">
            <span v-if="!isEdit">{{ baseForm.phone }}</span>
            <a-input
              v-else
              v-model:value="baseForm.phone"
              placeholder="请输入手机号码"
            />
          </a-form-item>
        </div>
        <!-- 位置信息 -->
        <a-form-item name="position" label="">
          <div class="col-span-1 mx-3 pt-8">
            <span class="mx-3">是否显示位置信息</span>
            <a-switch v-model:checked="baseForm.position!.isShow" />
            <span
              class="mx-3 bg-blue-200 px-1.5 py-1 rounded-md"
              v-if="baseForm.position!.isShow"
              >{{ baseForm.position?.place }}
            </span>
          </div>
        </a-form-item>
        <!-- 说明 -->
        <div class="col-span-3 mx-3">
          <a-form-item name="description" label="情况说明">
            <span v-if="!isEdit">{{ baseForm.description }}</span>
            <a-textarea
              v-else
              :rows="4"
              v-model:value="baseForm.description"
              placeholder="请输入说明"
            >
            </a-textarea>
          </a-form-item>
        </div>
      </a-form>

详情页的表单内容如下,通过v-if来判断当前是出于可编辑表单还是信息查看状态。

// 获取列表页数据
if (route.query.useData as any) {
  useData.value = JSON.parse(route.query.useData as any) || [];
  dataSource.value = useData.value;
}

获取列表页传递的数据

这部分的代码就不展示了,看图片上的注释应该也能知道我做的是一件什么事。

// 初始化数据
const initData = () => {
  dataSource.value = useData.value;
  // 表单置空
  getEmptydata();
  // 是否查看详情页
  if (receiveName) {
    dataSource.value.map((item) => {
      if (item.name === receiveName) {
        editData = item;
        baseForm.value.name = item.name;
        baseForm.value.age = item.age;
        baseForm.value.sex = item.sex;
        baseForm.value.school = item.school;
        baseForm.value.address = item.address;
        baseForm.value.hobby = item.hobby;
        baseForm.value.phone = item.phone;
        baseForm.value.birthday = item.birthday;
        baseForm.value.description = item.description;
        baseForm.value.position = item.position || {
          isShow: false,
          place: "湖北  武汉",
        };
      }
    });
  } else {
    // 新增数据页面
    isEdit.value = true;
    disableName.value = false;
  }
};
onMounted(() => {
  initData();
});

这部分的代码就是比较关键的了,就是初始化详情页时,首先通过传参来判断是新建页还是查看页,在列表页中,如果点击查看详情是传了所选数据的name值的,而点击新建时没有传,这里就是通过判断是否接收到了路由传参的name值来判断是新建数据还是查看详情。

// 提交
const submit = () => {
  // 修改后提交
  if (receiveName) {
    formRef.value?.validateFields().then(
      (res) => {
        isEdit.value = !isEdit.value;
        let newData: any = res;
        dataSource.value.map((item) => {
          // 把修改的数据替换掉原来的数据
          if (item.name == receiveName) {
            item.name = newData?.name;
            item.age = newData?.age;
            item.sex = newData?.sex;
            item.school = newData?.school;
            item.address = newData?.address;
            item.hobby = toRaw(newData?.hobby);
            item.birthday = newData?.birthday;
            item.phone = newData?.phone;
            item.position = newData.position;
            item.description = newData?.description;
          }
        });
        router.push({
          path: "/",
          query: { obj: JSON.stringify(dataSource.value) },
        });
      },
      (error) => {
        console.log(error);
      }
    );
  } else {
    // 新增后提交
    formRef.value?.validateFields().then(
      (res) => {
        console.log(res);
        if (res) {
          isEdit.value = !isEdit.value;
          let newData: any = res;
          dataSource.value.push(newData);
          router.push({
            path: "/",
            query: { obj: JSON.stringify(dataSource.value) },
          });
        }
      },
      (error) => {
        console.log(error);
      }
    );
  }
};

这部分的代码也尤为重要,就是修改或者新增后,点击提交所触发的方法。

先判断是修改还是新建数据,然后走表单提交得到流程,如果是修改提交,那么将修改的数据替换掉原来的那条数据,如果是新增数据,那么直接push到数据里面。最后将修改完成后路由跳转到列表页,并将全部数据传参过去。

至此,一个简单的列表页和详情页便实现了。有些部分确实冗余度比较高,可以进一步优化改善,还有一些细节部分也可以优化,比如手机号的正则匹配等。

果然,还是调接口,直接从后端请求数据简单一些哈哈,不然每次都弄那么多数据,太麻烦了。

还有其他问题欢迎大家批评指正!

源码我后面会放到码云仓库里面



版权声明:本文为weixin_48914380原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。