基于vuestic-ui实战教程 - 页面篇

1. 简介

前面介绍了基本的内容比如如何获取动态数据,下面就到登录进来后的页面实现了,相信各位读者或多或少都有 element-ui+js 的实战经历,那么 vuestic-ui+ts 实现的页面又该如何写呢?带着疑问开启今天的学习(声明由于作者本人也不是搞前端的,所以不一定了解所有知识,这里只教大家如何快速上手以及自身遇到的问题)

2. 页面嵌入实现

在做的项目需要嵌入一个第三方页面,最常见的方法就是使用iframe(当然还有其他的实现,感兴趣的读者可以自行研究)我就以nacos的监控页面为例子,定义如下的 index.vue 页面

<template>
    <iframe
      id="page"
      src="http://localhost:8848/nacos" 
      style="width: 100%; height:100%"
    ></iframe>
</template>

然后成功运行到网页端测试,发现页面并不能正常显示,console中报错信息如下,hash出问题又是跨域出问题,一开始就给我搞蒙了查了各种资料都不嫩解决问题,而且在浏览器中输入网址又显示正常

在这里插入图片描述
细看网址发现当输入前缀后会自动跳转,而iframe中引用的src无法发起跨域请求,自然就会报错。

输入 http://localhost:8848/nacos/ 会自动跳转到如下链接(注意#
http://localhost:8848/nacos/#/configurationManagement?dataId=&group=&appName=&namespace=&pageSize=&pageNo=

在网址中,井号(#)后面的部分被称为URL的片段标识符(fragment identifier),常用于指定网页中的某个特定的位置或者元素。

当访问一个带有"#"后面内容的网站时,浏览器会自动滚动到与该标识符相对应的页面区域。例如,在单页应用(SPA)中,不同的页面内容往往对应不同的标识符,用户通过改变URL中的哈希值可以无需重新加载页面而直接导航至相应的内容区域。

所以做出如下改进,此时页面已经能正常显示出来了,但是显示的大小不对,如下图所示,这么大的版面只给我显示这一点,明明设置的style高宽都是100%啊

<template>
    <iframe
      id="child"
      src="http://localhost:8848/nacos/#/configurationManagement?dataId=&group=&appName=&namespace=&pageSize=&pageNo="
      style="width: 100%; height:100%"
    ></iframe>
</template>

在这里插入图片描述

经过查阅资料发现设置是有讲究的,常见的两种区别如下

  • 100vh : vh是一个相对单位,表示视口高度的1%。因此,height: 100vh意味着元素的高设置为视口高度的100%,即元素将占据整个视口的高度。无论该元素的父级元素有多大,或者该元素是否包含内容,使用height: 100vh都会使元素高度与视口高度一致,即使没有内容时也会撑开至屏幕高度。
  • 100%:百分比值%是相对于父元素的尺寸来计算的。当使用height: 100%时,元素的高将设置为直接父元素高度的100%,这意味着元素将尽可能地占据父元素的高度。如果父元素的大小没有明确定义或者小于视口大小,那么该元素的实际高度可能会小于视口高度。

在我们这个例子中因为不能确定父级元素的大小,所以推荐使用vh而不是% 为度量单位,修改过后就能正常大小显示啦

3. 页面编写展示动态数据

直接仿照他给好的页面users实例,我们可以自己实现一个类似的页面来动态获取数据,整体代码架构如下图(UserAvatar未作大修改),可以看到其实是跟给定案例的架构差不多的,右边的是之前讲到过的动态与后端交互的一些api方法

在这里插入图片描述

3.1 index页面

首先通过useUsers方法(3.2)定义好的获取UsersTable渲染所需要的一些数据,比如原始数据users,做分页的pagination , 可供模糊查询的filters , 还有每列上排序的组件sorting ,以及对数据增删改的api(这里api的命名自定义,在当前页面中唯一使用),具体的定义意义可以看代码中的注释,基本上都有提及,下面就重点讲讲自定义组件的实现

对于代码const userToEdit = ref<UserData | null>(null) 在 TypeScript 中十分常见,ref 是一个用于创建响应式引用的函数

ref函数的主要应用场景是在需要对简单类型值进行响应式处理时。例如,当你有一个基本类型的变量(如数字、字符串等),并希望它的改变能够触发视图或其他部分的自动更新时,你可以使用ref。这是因为Proxy对象只能拦截对象属性的访问,而不是直接对一个变量的改变进行拦截。通过ref,你可以将简单类型的值包装在一个形式为{ value: T }的对象中,这样在修改值时就可以通过.value属性来触发响应式更新。

在这个例子中,userToEdit 是一个响应式引用,它的类型是 UserData | null。这意味着 userToEdit 可以存储一个 UserData 类型的对象或者 null。

<script setup lang="ts">
import { ref } from 'vue'
import UsersTable from './widgets/UsersTable.vue'
import EditUserForm from './widgets/EditUserForm.vue'
import { UserData } from '@/api/system/sysUser/types'
import { useUsers } from '../sysUser/composables/useUsers'
import { useModal, useToast } from 'vuestic-ui'
import { onPageRender } from '@/utils/tokenMonitor'

const doShowEditUserModal = ref(false) 

let { users, isLoading , filters, sorting, pagination , ...api } = useUsers()
// 当前需要修改的数据,edit就是当前行,而add是null,如下两个function
const userToEdit = ref<UserData | null>(null)
// 动态刷新token
window.addEventListener('load', () => {
  onPageRender();
});
// 展示的是edit
const showEditUserModal = (user: UserData) => {
  userToEdit.value = user
  doShowEditUserModal.value = true
}
// 展示的是add
const showAddUserModal = () => {
  userToEdit.value = null
  doShowEditUserModal.value = true
}

const { init: notify } = useToast()
// 添加/修改方法
const onUserSaved = async (user: UserData) => {
  console.log(userToEdit.value)
  if (userToEdit.value) {
    console.log("edit")
    api.update(user).then(data => 
      notify({
        message: `${user.name} has been update`,
        color: 'success',
      })
    ).catch(() => 
      notify({
        message: `${user.name} updated fail`,
        color: 'dangerous',
      })
)
  } else {
    api.add(user).then(data => 
      notify({
        message: `${user.name} has been add`,
        color: 'success',
      })
    ).catch(() => 
      notify({
        message: `${user.name} add fail`,
        color: 'dangerous',
      })
)
  }
}
// 删除方法
const onUserDelete = async (user: UserData) => {
  api.remove(user.id).then(data => 
    notify({
      message: `${user.name} has been delete`,
      color: 'success',
    })
  ).catch(() => 
    notify({
      message: `${user.name} deleted fail`,
      color: 'dangerous',
    })
)
  
}
// 点击cancel按钮后弹出的友好提示
const editFormRef = ref()
const { confirm } = useModal()

const beforeEditFormModalClose = async (hide: () => unknown) => {
  if (editFormRef.value.isFormHasUnsavedChanges) {
    const agreed = await confirm({
      maxWidth: '380px',
      message: 'Form has unsaved changes. Are you sure you want to close it?',
      size: 'small',
    })
    if (agreed) {
      hide()
    }
  } else {
    hide()
  }
}
</script>

<template>
  <h1 class="page-title">Users</h1>

  <VaCard>
    <VaCardContent>
      <div class="flex flex-col md:flex-row gap-2 mb-2 justify-between">
        <div class="flex flex-col md:flex-row gap-2 justify-start">
          <VaButtonToggle
            v-model="filters.isActive "
            color="background-element"
            border-color="background-element"
            :options="[
              { label: 'Active', value: 1 },
              { label: 'Inactive', value: 0 },
            ]"
          />
          <VaInput v-model="filters.search" placeholder="Search">
            <template #prependInner>
              <VaIcon name="search" color="secondary" size="small" />
            </template>
          </VaInput>
        </div>
        
        <VaButton @click="showAddUserModal">Add User</VaButton>
      </div>

      <UsersTable
        v-model:sorting-order="sorting.sortingOrder"
        v-model:sort-by="sorting.sortBy"
        :users="users"
        :loading="isLoading"
        :pagination="pagination"
        @editUser="showEditUserModal"
        @deleteUser="onUserDelete"
      />
    </VaCardContent>
  </VaCard>

  <VaModal
    v-slot="{ cancel, ok }"
    v-model="doShowEditUserModal"
    size="small"
    mobile-fullscreen
    close-button
    hide-default-actions
    :before-cancel="beforeEditFormModalClose"
  >
    <h1 class="va-h5">{{ userToEdit ? 'Edit user' : 'Add user' }}</h1>
    <EditUserForm
      ref="editFormRef"
      :user="userToEdit"
      :save-button-label="userToEdit ? 'Save' : 'Add'"
      @close="cancel"
      @save="
        (user) => {
          onUserSaved(user)
          ok()
        }
      "
    />
  </VaModal>
</template>

自定义的组件在当前的vue页面中体现在 <EditUserForm> <UsersTable>也就是前面代码架构时提到的一些组件(对于自定义组件的实现需要跳转到定义出看更容易理解) 而在下图的实例中最上面的三个红色框(UsersTable之上的)定义在VaCardContent的第2个div中实现,具体需要注意的就是VaButtonToggle绑定的option value值应该是number类型(为了与数据库中定义的类型对应,当然读者想换成boolean也可以的,只需要全局上下文保持一致就好)

3.2 UsersTable组件定义

在这里插入图片描述

原有的table组件不够美观,想要自己定义的时候就可以用该模板,对于模板的实例如上图我用红色框框框出来的就是几个加入的组件,下面就分别在代码中实现

3.3 defineVaDataTableColumns 、defineProps 、 defineEmits

  • 第一个columns常量,它使用defineVaDataTableColumns函数来创建一个数据表格的列配置。这个函数接收一个包含多个对象的数组,每个对象代表一个列的配置信息。注意key要与后端封装的实体类命名保持一致(sortable 表示该列是否选择排序)

  • 第二个props常量,它使用defineProps函数来定义组件的属性。这个函数接收一个对象,对象中的每个键值对表示一个属性的定义。(在应用的时候使用 :key="value" 的格式实现,这里的value为引用对象),这里截了一张图供读者对应参考实现放在后面讲在这里插入图片描述

  • 第三个emit常量,它使用defineEmits函数来定义组件可以触发的事件。这个函数接收一个对象,对象中的每个键值对表示一个事件的定义。在这个例子中,定义了4个事件,分别是edit-user、delete-user、update:sortBy和update:sortingOrder。

3.4 VaPagination 做分页

根据其提供好的组件来实现分页功能,双向绑定的值是我们在props定义的一些pagination

<script setup lang="ts">
import { defineVaDataTableColumns, useModal } from 'vuestic-ui'
import { UserData } from '@/api/system/sysUser/types'
import UserAvatar from './UserAvatar.vue'
import { PropType, computed, toRef } from 'vue'
import { Pagination, Sorting } from '@/api/system/sysUser/types'
import { useVModel } from '@vueuse/core'

const columns = defineVaDataTableColumns([
  { label: 'Full Name', key: 'name', sortable: true },
  { label: 'Username', key: 'username', sortable: true },
  { label: 'Email', key: 'email', sortable: true },
  { label: 'Phone', key: 'phone', sortable: true },
  { label: 'Role', key: 'roleList', sortable: true },
  { label: ' ', key: 'actions', align: 'right' },
])

const props = defineProps({
  users: {
    type: Array as PropType<UserData[]>,
    required: true,
  },
  loading: { type: Boolean, default: false },
  pagination: { type: Object as PropType<Pagination>, required: true },
  sortBy: { type: String as PropType<Sorting['sortBy']>, required: true },
  sortingOrder: { type: String as PropType<Sorting['sortingOrder']>, required: true },
})

const emit = defineEmits<{
  (event: 'edit-user', user: UserData): void
  (event: 'delete-user', user: UserData): void
  (event: 'update:sortBy', sortBy: Sorting['sortBy']): void
  (event: 'update:sortingOrder', sortingOrder: Sorting['sortingOrder']): void
}>()
// 使用pros传进来的对象来渲染页面
const users = toRef(props, 'users')
const sortByVModel = useVModel(props, 'sortBy', emit)
const sortingOrderVModel = useVModel(props, 'sortingOrder', emit)
//每次引用时候使用compute计算当前的页数
const totalPages = computed(() => Math.ceil(props.pagination.total / props.pagination.perPage))

const { confirm } = useModal()
// 点击cancel取消的友好提示
const onUserDelete = async (user: UserData) => {
  const agreed = await confirm({
    title: 'Delete user',
    message: `Are you sure you want to delete ${user.username}?`,
    okText: 'Delete',
    cancelText: 'Cancel',
    size: 'small',
    maxWidth: '380px',
  })

  if (agreed) {
    emit('delete-user', user)
  }
}

</script>

<template>
  <VaDataTable
    v-model:sort-by="sortByVModel"
    v-model:sorting-order="sortingOrderVModel"
    :columns="columns"
    :items="users"
    :loading="$props.loading"
  >
    <template #cell(name)="{ rowData }">
      <div class="flex items-center gap-2 max-w-[230px] ellipsis">
        <UserAvatar :user="rowData as UserData" size="small" />
        {{ rowData.name }}
      </div>
    </template>

    <template #cell(username)="{ rowData }">
      <div class="max-w-[120px] ellipsis">
        {{ rowData.username }}
      </div>
    </template>

    <template #cell(email)="{ rowData }">
      <div class="ellipsis max-w-[230px]">
        {{ rowData.email }}
      </div>
    </template>

    <template #cell(roleList)="{ rowData }">
      <div class="ellipsis max-w-[230px]">
        <template v-for="roleItem in rowData.roleList" :key="roleItem.id">
          <div>
            <VaBadge :text="roleItem.roleName"></VaBadge>
          </div>
        </template>
      </div>
    </template>

    <template #cell(phone)="{ rowData }">
      <div class="ellipsis max-w-[230px]">
        {{ rowData.phone }}
      </div>
    </template>
    
    <template #cell(actions)="{ rowData }">
      <div class="flex gap-2 justify-end">
        <VaButton
          preset="primary"
          size="small"
          icon="mso-edit"
          aria-label="Edit user"
          @click="$emit('edit-user', rowData as UserData)"
        />
        <VaButton
          preset="primary"
          size="small"
          icon="mso-delete"
          color="danger"
          aria-label="Delete user"
          @click="onUserDelete(rowData as UserData)"
        />
      </div>
    </template>
  </VaDataTable>

  <div class="flex flex-col-reverse md:flex-row gap-2 justify-between items-center py-2">
    <div>
      <b>{{ $props.pagination.total }} results.</b>
      Results per page
      <VaSelect v-model="$props.pagination.perPage" class="!w-20" :options="[10, 50, 100]" />
    </div>

    <div v-if="totalPages > 1" class="flex">
      <VaButton
        preset="secondary"
        icon="va-arrow-left"
        aria-label="Previous page"
        :disabled="$props.pagination.page === 1"
        @click="$props.pagination.page--"
      />
      <VaButton
        class="mr-2"
        preset="secondary"
        icon="va-arrow-right"
        aria-label="Next page"
        :disabled="$props.pagination.page === totalPages"
        @click="$props.pagination.page++"
      />
      <VaPagination
        v-model="$props.pagination.page"
        buttons-preset="secondary"
        :pages="totalPages"
        :visible-pages="5"
        :boundary-links="false"
        :direction-links="false"
      />
    </div>
  </div>
</template>

<style lang="scss" scoped>
.va-data-table {
  ::v-deep(.va-data-table__table-tr) {
    border-bottom: 1px solid var(--va-background-border);
  }
}
</style>

3.3 types定义

上面的代码中提到了特别多的类型比如UserData 、Pagination 、Sorting 等等,下面就给出这些类型的定义(如下代码定义在api/system/sysUser/types.ts 中)前面两个完全根据后端的实体类对应,而下面三个就是渲染所需要用到的一些类型定义

import { RoleData } from "../sysRole/types"

/* sysUser参数类型 */
export interface UserData {
    id: number,
    username: string,
    email: string,
    password: string,
    phone: string,
    headUrl: string,
    name: string,
    status: number,
    roleList: RoleData[]
  }
	/* sysUser参数类型 
	export interface RoleData {
    id: number,
    roleName: string,
    roleCode: string,
    description: string
  }
*/

  // Simulate API calls

export type Pagination = {
  page: number
  perPage: number
  total: number
}

export type Sorting = {
  sortBy: keyof UserData | undefined
  sortingOrder: 'asc' | 'desc' | null
}

export type Filters = {
  isActive: number //与数据库status类型保持一致
  search: string
}

3.4 EditUserForm 实现

下面就来到实现点击add 或者 edit按钮弹窗组件实现了,实现与UserTable其实差不多

注意在默认用户defaultNewUser中定义的参数与后端一一对应,如果不想定义那么多参数可以使用一个UserVo实体类,前端封装自己想要的UserVo实体(比如我就想要name和email)后端接收到该数据再new User对其进行封装,没有传递的参数就是null, 这样就解决了ts中报错类型参数不一致问题

<script setup lang="ts">
import { PropType, computed, ref, watch } from 'vue'
import { useForm } from 'vuestic-ui'
import { UserData } from '@/api/system/sysUser/types'
import RoleApi from '@/api/system/sysRole'
import { useToast } from 'vuestic-ui'
import UserAvatar from './UserAvatar.vue'
import { validators } from '@/services/utils'

// 定义传递的对象 user即是用户数据,saveButtonLabel即是判断当前的操作类型
const props = defineProps({
  user: {
    type: Object as PropType<UserData | null>,
    default: null,
  },
  saveButtonLabel: {
    type: String,
    default: 'Save',
  },
})

const { init: notify } = useToast()
// 默认用户 
const defaultNewUser: UserData = {
  id: -1,
  username: '',
  name: '',
  password: '123456',
  phone: '',
  headUrl: '',
  email: '',
  status: 1,
  roleList: [],
}

const newUser = ref<UserData>({ ...defaultNewUser })
// 是否有修改过表单
const isFormHasUnsavedChanges = computed(() => {
  return Object.keys(newUser.value).some((key) => {
    if (key === 'avatar' ) {
      return false
    }

    return newUser.value[key as keyof UserData] !== (props.user ?? defaultNewUser)?.[key as keyof UserData]
  })
})

defineExpose({
  isFormHasUnsavedChanges,
})
// 监控修改当前用户的头像
watch(
  () => props.user,
  () => {
    if (!props.user) {
      return
    }

    newUser.value = {
      ...props.user,
      headUrl: props.user.headUrl || '',
    }
  },
  { immediate: true },
)

const avatar = ref<File>()

const makeAvatarBlobUrl = (avatar: File) => {
  return URL.createObjectURL(avatar)
}

watch(avatar, (newAvatar) => {
  newUser.value.headUrl = newAvatar ? makeAvatarBlobUrl(newAvatar) : ''
})

const form = useForm('add-user-form')

const emit = defineEmits(['close', 'save'])

const onSave = () => {
  if (form.validate()) {
    emit('save', newUser.value)
  }
}

</script>

<template>
  <VaForm v-slot="{ isValid }" ref="add-user-form" class="flex-col justify-start items-start gap-4 inline-flex w-full">
    <VaFileUpload
      v-model="avatar"
      type="single"
      hide-file-list
      class="self-stretch justify-start items-center gap-4 inline-flex"
    >
      <UserAvatar :user="newUser" size="large" />
      <VaButton preset="primary" size="small">Add image</VaButton>
      <VaButton
        v-if="avatar"
        preset="primary"
        color="danger"
        size="small"
        icon="delete"
        class="z-10"
        @click.stop="avatar = undefined"
      />
    </VaFileUpload>
    <div class="self-stretch flex-col justify-start items-start gap-4 flex">
      <div class="flex gap-4 flex-col sm:flex-row w-full">
        <VaInput
          v-model="newUser.name"
          label="Name"
          class="w-full sm:w-1/2"
          :rules="[validators.required]"
          name="name"
        />
        <VaInput
          v-model="newUser.username"
          label="Username"
          class="w-full sm:w-1/2"
          :rules="[validators.required]"
          name="username"
        />
      </div>
      <div class="flex gap-4 flex-col sm:flex-row w-full">
        <VaInput
          v-model="newUser.email"
          label="Email"
          class="w-full sm:w-1/2"
          :rules="[validators.required, validators.email]"
          name="email"
        />
      
        <VaInput
          v-model="newUser.phone"
          label="Phone"
          class="w-full sm:w-1/2"
          :rules="[validators.required]"
          name="phone"
        />

      </div>

        <div class="flex items-center w-1/2 mt-4">
          <VaCheckbox v-model.number="newUser.status" label="Active" class="w-full" name="status" />
        </div>
      </div>
      
      <div class="flex gap-2 flex-col-reverse items-stretch justify-end w-full sm:flex-row sm:items-center">
        <VaButton preset="secondary" color="secondary" @click="$emit('close')">Cancel</VaButton>
        <VaButton :disabled="!isValid" @click="onSave">{{ saveButtonLabel }}</VaButton>
      </div>
    </div>
  </VaForm>
</template>

3.5 useUser api编写

主要实现的就是useUser这个方法,方法中又调用了getUsers 来动态获取数据库数据await UserApi.listAll(),并根据index中传回来的props属性生成当前的Pagination Filters Sorting三个参数(每一次改变都会重新生成)

import { Ref, ref, unref, watch } from 'vue'
import { UserData , type Filters, Pagination, Sorting } from '@/api/system/sysUser/types'
import UserApi from '@/api/system/sysUser'
import { watchIgnorable } from '@vueuse/core'

const makePaginationRef = () => ref<Pagination>({ page: 1, perPage: 10, total: 0 })
const makeSortingRef = () => ref<Sorting>({ sortBy: 'name', sortingOrder: null })
const makeFiltersRef = () => ref<Partial<Filters>>({ isActive: 1, search: '' })


const getSortItem = (obj: any, sortBy: string) => {
  return obj[sortBy]
}

export const getUsers =  async (filters: Partial<Filters & Pagination & Sorting>) => {
  try {
    let users: UserData[] = []
    // Fetch the user data using UserApi.listAll()
    const data = await UserApi.listAll()
    
    console.log("-1>>>>",data)
    users = data
    let filteredUsers = users
    console.log("0>>>>",filteredUsers)
    const { isActive, search, sortBy, sortingOrder } = filters
    // Apply filter based on isActive
    filteredUsers = filteredUsers.filter((user) => user.status === isActive)

    
    console.log("1>>>>",filteredUsers)
    // Apply filter based on search
    if (search) {
      filteredUsers = filteredUsers.filter((user) => user.name.toLowerCase().includes(search.toLowerCase()))
    }
    
    console.log("2>>>>",filteredUsers)
    // Apply sorting
    if (sortBy && sortingOrder) {
      filteredUsers = filteredUsers.sort((a, b) => {
        const first = getSortItem(a, sortBy)
        const second = getSortItem(b, sortBy)
        if (first > second) {
          return sortingOrder === 'asc' ? 1 : -1
        }
        if (first < second) {
          return sortingOrder === 'asc' ? -1 : 1
        }
        return 0
      })
    }
    console.log("3>>>>",filteredUsers)
    const { page = 1, perPage = 10 } = filters || {}

    // Return the filtered and paginated data
    return {
      data: filteredUsers.slice((page - 1) * perPage, page * perPage),
      pagination: {
        page,
        perPage,
        total: filteredUsers.length,
      },
    }
  } catch (error) {
    // Handle error here
  }
}

export const useUsers = (options?: {
  pagination?: Ref<Pagination>
  sorting?: Ref<Sorting>
  filters?: Ref<Partial<Filters>>
}) => {
  const isLoading = ref(false)
  const users = ref<UserData[]>([])

  const { filters = makeFiltersRef(), sorting = makeSortingRef(), pagination = makePaginationRef() } = options || {}

  const fetch = async () => {
    isLoading.value = true
    const result = await getUsers({
      ...unref(filters),
      ...unref(sorting),
      ...unref(pagination),
    })
    if (result) {
      const { data, pagination: newPagination } = result;
      // Use 'data' and 'newPagination' here...
      users.value = data

      ignoreUpdates(() => {
        pagination.value = newPagination
      })
    } else {
      // Handle the case when 'result' is undefined
    }
    

    isLoading.value = false
  }

  const { ignoreUpdates } = watchIgnorable([pagination, sorting], fetch, { deep: true })

  watch(
    filters,
    () => {
      // Reset pagination to first page when filters changed
      pagination.value.page = 1
      fetch()
    },
    { deep: true },
  )

  fetch()

  return {
    isLoading,

    filters,
    sorting,
    pagination,

    users,

    fetch,

    async add(user: UserData) {
      isLoading.value = true
      await UserApi.addUser(user)
      await fetch()
      isLoading.value = false
    },

    async update(user: UserData) {
      isLoading.value = true
      await UserApi.updateUser(user)
      await fetch()
      isLoading.value = false
    },

    async remove(id : number) {
      isLoading.value = true
      await UserApi.deleteUser(id)
      await fetch()
      isLoading.value = false
    },
  }
}

同时最下面除了导出所需要的一些对象,还导出了三个异步方法,在这三个方法中才调用axios异步与后端交互,当成功时候自然会重新调用fetch方法渲染数据,保持当前的结果一致性(使用isLoading来阻塞table,页面显示就是转圈圈正在加载中)到此基本就编写完成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/654615.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

开源VS闭源:谁将引领AI大模型的新时代?

一、引言 随着人工智能技术的飞速发展&#xff0c;AI大模型已成为推动这一浪潮的核心动力。在AI大模型的发展过程中&#xff0c;开源与闭源两种不同的发展路径一直备受关注。本文将深入探讨这两种路径的优劣势&#xff0c;分析它们对AI大模型发展的影响&#xff0c;并预测谁将…

采用java语言+B/S架构+后端SpringBoot前端Vue开发的ADR药品不良反应智能监测系统源码

采用java语言&#xff0b;B/S架构&#xff0b;后端SpringBoot前端Vue开发的ADR药品不良反应智能监测系统源码 ADR监测引擎每日主动获取检验数据、病历内容&#xff08;可拓展&#xff09;、以及其他临床数据&#xff0c;根据知识库内容自动判定患者是否有不良反应迹象&#xf…

Mesa软件框架以及重要数据结构分析

Mesa软件框架以及重要数据结构分析 引言 Mesa的实现比较复杂&#xff0c;其中还有许多的数据结构之间的关系逻辑还不是很清楚。感觉分析了又没有分析一样&#xff0c;这里我们再理一理&#xff01; 1.1 Mesa下EGL/GL核心数据结构和层级关系 MESA的核心数据结构很多很复杂&#…

【CSDN独家公开】Python解析.SchDoc格式文件转换为json文件

前情提要 因工作需求&#xff0c;需要解析.SchDoc格式文件&#xff0c;提取文本和位置关系&#xff0c;通常方式是转换为图片或PDF&#xff0c;再进行OCR&#xff0c;但是这样识别精度太低了 Github找了好些项目&#xff0c;都不支持 PyAltium不支持 https://github.com/plu…

每日一题 <leetcode--2326.螺旋矩阵>

https://leetcode.cn/problems/spiral-matrix-iv/ 函数中给出的int* returnSize和int** returnColumnSizes是需要我们返回数值的&#xff0c;这点需要注意。其中int** returnColumnSizes 是需要额外开辟一块空间。 这道题我们首先需要malloc出一快空间来把链表存放在数组中&…

元宇宙vr美术虚拟展馆激发更多文化认同和互鉴

科技引领创新潮流&#xff0c;借助前沿的Web3D技术&#xff0c;我们为用户打造了一个沉浸式的纯3D虚拟空间体验平台&#xff1a;元宇宙线上互动展厅。您只需通过网页即可轻松访问这个充满未来感的互动平台。 在这个独特的虚拟环境中&#xff0c;用户可以轻松创建个性化角色&…

Android 13 sysprop_library新增属性

前提 我们在androidP及之前的版本&#xff0c;平台侧及应用层开发习惯于通过调用&#xff08;或者反射&#xff09;SystemProperties系统API的方式进行系统属性的读写。Android R以后&#xff0c;平台侧代码采用了一种将系统属性封装成类方法的形式供开发者调用。 Android R以…

I.MX6ULL模仿 STM32 驱动开发格式实验

系列文章目录 I.MX6ULL模仿 STM32 驱动开发格式实验 I.MX6ULL模仿 STM32 驱动开发格式实验 系列文章目录一、前言二、模仿 STM32 寄存器定义2.1 STM32 寄存器定义简介2.2 I.MX6Ul 寄存器定义2.3硬件原理图2.4实验程序编写 三、编译下载验证 一、前言 使用 C 语言编写 LED 灯驱…

深度学习——自己的训练集——图像分类(CNN)

图像分类 1.导入必要的库2.指定图像和标签文件夹路径3.获取文件夹内的所有图像文件名4.获取classes.txt文件中的所有标签5.初始化一个字典来存储图片名和对应的标签6.遍历每个图片名的.txt文件7.随机选择一张图片进行展示8.构建图像的完整路径9.加载图像10.检查图像是否为空 随…

消息队列实战应用

适用场景 耗时长&#xff0c;非核心业务&#xff0c;生产者不会用到消息处理结果的情况下&#xff0c;可以将消息交给异步服务去缓存与消费 部署MQ服务 version: "3.0" services:rabbitmq:container_name: rabbitmq-15672-1image: rabbitmq:3-managementports:- &…

短视频再度重逢:四川京之华锦信息技术公司

短视频再度重逢 在数字化时代的浪潮中&#xff0c;短视频以其独特的魅力迅速崛起&#xff0c;成为现代人生活中不可或缺的一部分。而当我们谈论起短视频&#xff0c;我们不仅仅是在谈论一种娱乐方式&#xff0c;更是在谈论一种情感的载体&#xff0c;一种回忆的媒介。今天&…

【ai】LiveKit Agent 的example及python本地开发模式工程实例

title: ‘LiveKit Agent Playground’ playgroundLiveKit Community playground的环境变量&#xff1a;LiveKit API # LiveKit API Configuration LIVEKIT_API_KEYYOUR_API_KEY LIVEKIT_API_SECRETYOUR_API_SECRET# Public configuration NEXT_PUBLIC_LIVEKIT_URLwss://YOUR_…

pytorch比较操作

文章目录 常用的比较操作1.torch.allclose()2.torch.argsort()3.torch.eq()4.torch.equal()5.torch.greater_equal()6.torch.gt()7.torch.isclose()8.torch.isfinite()9.torch.isif()10.torch.isposinf()11.torch.isneginf()12.torch.isnan()13.torch.kthvalue()14.torch.less_…

【从零开始学习RabbitMQ | 第二篇】如何确保MQ的可靠性和消费者可靠性

目录 前言&#xff1a; MQ可靠性&#xff1a; 数据持久化&#xff1a; Lazy Queue&#xff1a; 消费者可靠性&#xff1a; 消费者确认机制&#xff1a; 消费失败处理&#xff1a; MQ保证幂等性&#xff1a; 方法一&#xff1a; 总结&#xff1a; 前言&#xff1a; …

以梦为马,不负韶华(3)-AGI在企业服务的应用

AGI在企业服务中&#xff0c;各应⽤已覆盖企业全流程&#xff0c;包含⼈⼒、法务、财税、流程⾃动化、知识管理和软件开发各领域。 由于⼤语⾔模型对⽂本处理类场景有着天然且直接的适配性&#xff0c;⽂本总结、⽂本内容⽣成、服务指引等发展起步早且应⽤成熟度更⾼。 在数据…

Captura完全免费的电脑录屏软件

一、简介 1、Captura 是一款免费开源的电脑录屏软件&#xff0c;允许用户捕捉电脑屏幕上的任意区域、窗口、甚至是全屏画面&#xff0c;并将这些画面录制为视频文件。这款软件具有多种功能&#xff0c;例如可以设置是否显示鼠标、记录鼠标点击、键盘按键、计时器以及声音等。此…

LeetCode题练习与总结:有序链表转换二叉搜索树--109

一、题目描述 给定一个单链表的头节点 head &#xff0c;其中的元素 按升序排序 &#xff0c;将其转换为平衡二叉搜索树。 示例 1: 输入: head [-10,-3,0,5,9] 输出: [0,-3,9,-10,null,5] 解释: 一个可能的答案是[0&#xff0c;-3,9&#xff0c;-10,null,5]&#xff0c;它表…

医疗图像处理2023:Transformers in medical imaging: A survey

医学成像中的transformer:综述 目录 一、介绍 贡献与安排 二、CNN和Transformer 1.CNN 2.ViT 三、Transformer应用于各个领域 1.图像分割 1&#xff09;器官特异性 ①2D ②3D 2&#xff09;多器官类别 ①纯transformer ②混合架构 单尺度 多尺度 3&#xff09;…

Kubernetes——监听机制与调度约束

目录 前言 一、监听机制 1.Pod启动创建过程 2.调度过程 1.指定调度节点 1.1强制匹配 1.2强制约束 二、硬策略和软策略 1.键值运算关系 1.硬策略——requiredDuringSchedulingIgnoredDuringExecution 2.软策略——preferredDuringSchedulingIgnoredDuringExecution …

Varjo XR-4功能详解:由凝视驱动的XR自动对焦相机系统

Varjo是XR市场中拥有领先技术的虚拟现实设备供应商&#xff0c;其将可变焦距摄像机直通系统带入到虚拟和混合现实场景中。在本篇文章中&#xff0c;Varjo的技术工程师维尔蒂莫宁详细介绍了这项在Varjo XR-4焦点版中投入应用的技术。 对可变焦距光学系统的需求 目前所有其他XR头…