Skip to content

0x03e-体验shadcn-vue

shadcn-vue 是一个社区驱动的 Vue 版本的 shadcn/ui,使用 Radix Vue 和 Tailwind CSS 开发。它旨在提供可重用的 UI 组件,增强开发体验,但不被正式归类为组件库。此库支持高度定制和便于集成,使开发者能快速访问样式化组件来构建应用程序。不过有一定的学习成本。

支持使用 CLI 自动添加组件,或将代码复制并粘贴到您的项目中并根据您的需求进行自定义。

例如:

shell
npx shadcn-vue@latest add select
npx shadcn-vue@latest add select

UI设计非常简洁、优雅。

配置文件

json
{  
  "$schema": "https://shadcn-vue.com/schema.json",  
  "style": "default",  
  "typescript": true,  
  "tsConfigPath": "./tsconfig.json",  
  "tailwind": {  
    "config": "tailwind.config.js",  
    "css": "src/assets/index.css",  
    "baseColor": "slate",  
    "cssVariables": true,  
    "prefix": "y"   // 如果需要修改,要同步调整tailwind.config.js文件
  },  
  "framework": "vite",  
  "aliases": {  
    "components": "@/components",  
    "utils": "@/lib/utils"  
  }  
}
{  
  "$schema": "https://shadcn-vue.com/schema.json",  
  "style": "default",  
  "typescript": true,  
  "tsConfigPath": "./tsconfig.json",  
  "tailwind": {  
    "config": "tailwind.config.js",  
    "css": "src/assets/index.css",  
    "baseColor": "slate",  
    "cssVariables": true,  
    "prefix": "y"   // 如果需要修改,要同步调整tailwind.config.js文件
  },  
  "framework": "vite",  
  "aliases": {  
    "components": "@/components",  
    "utils": "@/lib/utils"  
  }  
}

radix-icons

radix-icons 提供了丰富的icons

json
"@radix-icons/vue": "^1.0.0",

// 使用
import {DotsHorizontalIcon} from '@radix-icons/vue'
"@radix-icons/vue": "^1.0.0",

// 使用
import {DotsHorizontalIcon} from '@radix-icons/vue'

zod

用于约束数据类型。支持运行时 数据校验。

js
import { z } from 'zod'  
  
// We're keeping a simple non-relational schema here.  
// IRL, you will have a schema for your data models.  
export const taskSchema = z.object({  
  income_or_expenses: z.string(),  
  pay_status: z.string(),  
  plat_form: z.string()  
})  
  
export type Task = z.infer<typeof taskSchema>

// use
const task = computed(() => taskSchema.parse(props.row.original))
import { z } from 'zod'  
  
// We're keeping a simple non-relational schema here.  
// IRL, you will have a schema for your data models.  
export const taskSchema = z.object({  
  income_or_expenses: z.string(),  
  pay_status: z.string(),  
  plat_form: z.string()  
})  
  
export type Task = z.infer<typeof taskSchema>

// use
const task = computed(() => taskSchema.parse(props.row.original))

不恰当的类型使用将抛出错误。

vueuse

Collection of Essential Vue Composition Utilities

useMouse工具集示例:

js
import { useMouse } from '@vueuse/core'

// "x" and "y" are refs
const { x, y } = useMouse()
console.log(x.value)
import { useMouse } from '@vueuse/core'

// "x" and "y" are refs
const { x, y } = useMouse()
console.log(x.value)

再比如:

js
import { useMounted } from '@vueuse/core' 
const isMounted = useMounted()

//相当于
const isMounted = ref(false) 
onMounted(() => { 
  isMounted.value = true 
})
import { useMounted } from '@vueuse/core' 
const isMounted = useMounted()

//相当于
const isMounted = ref(false) 
onMounted(() => { 
  isMounted.value = true 
})

ts双向绑定的例子:

js
import { useVModel } from '@vueuse/core'

const emits = defineEmits<{  
  'update:open': [open: boolean]  
}>()

const props = withDefaults(defineProps<{  
  defaultOpen?: boolean  
  open?: boolean  
  class?: HTMLAttributes['class']  
}>(), {  
  defaultOpen: true,  
  open: undefined,  
})

const open = useVModel(props, 'open', emits, {  
  defaultValue: props.defaultOpen ?? false,  
  passive: (props.open === undefined) as false,  
}) as Ref<boolean>
import { useVModel } from '@vueuse/core'

const emits = defineEmits<{  
  'update:open': [open: boolean]  
}>()

const props = withDefaults(defineProps<{  
  defaultOpen?: boolean  
  open?: boolean  
  class?: HTMLAttributes['class']  
}>(), {  
  defaultOpen: true,  
  open: undefined,  
})

const open = useVModel(props, 'open', emits, {  
  defaultValue: props.defaultOpen ?? false,  
  passive: (props.open === undefined) as false,  
}) as Ref<boolean>

@tanstack/vue-table

Headless UI for building powerful tables & datagrids

Headless UI是一个术语,指的是为 UI 元素和交互提供逻辑、状态、处理和 API,但不提供markup、styles或pre-built实现的库和实用程序。

因此这个框架中,可以随意结合taiwindcss、shadcn等使用。

核心概念:

  • Data 提供数据
  • Column Ref 用于配置列及其数据模型、显示模板等的对象
  • Table Instance 包含状态和API的核心表对象
  • Row Models 如何根据您使用的功能将数据数组转换为有用的行
  • Rows 每一行镜像其各自的行数据并提供特定于行的 API
js
<script setup lang="ts">
import {
  FlexRender,
  getCoreRowModel,
  useVueTable,
  createColumnHelper,
} from '@tanstack/vue-table'
import {ref} from 'vue'

type Person = {
  firstName: string
  lastName: string
  age: number
}

const defaultData: Person[] = [
  {
    firstName: 'tandy',
    lastName: 'miller',
    age: 40,
  }
]

const columnHelper = createColumnHelper<Person>()

const columns = [
  columnHelper.accessor('firstName', {
    cell: info => info.getValue(),
  }),
  columnHelper.accessor('lastName', {
    cell: info => info.getValue(),
  }),
  columnHelper.accessor('age', {
    cell: info => info.getValue(),
  }),
]

const data = ref(defaultData)
const table = useVueTable({
  get data() {
    return data.value
  },
  columns,
  getCoreRowModel: getCoreRowModel(),
})
</script>

<template>
  <div class="p-20">
    <table>
      <tbody>
      <tr v-for="row in table.getRowModel().rows" :key="row.id">
        <td v-for="cell in row.getVisibleCells()" :key="cell.id">
          <FlexRender
              :render="cell.column.columnDef.cell"
              :props="cell.getContext()"
          />
        </td>
      </tr>
      </tbody>
    </table>
  </div>
</template>

<style scoped>
table {  
  border: 1px solid lightgray;  
}  
  
td {  
  border-right: 1px solid lightgray;  
}
</style>
<script setup lang="ts">
import {
  FlexRender,
  getCoreRowModel,
  useVueTable,
  createColumnHelper,
} from '@tanstack/vue-table'
import {ref} from 'vue'

type Person = {
  firstName: string
  lastName: string
  age: number
}

const defaultData: Person[] = [
  {
    firstName: 'tandy',
    lastName: 'miller',
    age: 40,
  }
]

const columnHelper = createColumnHelper<Person>()

const columns = [
  columnHelper.accessor('firstName', {
    cell: info => info.getValue(),
  }),
  columnHelper.accessor('lastName', {
    cell: info => info.getValue(),
  }),
  columnHelper.accessor('age', {
    cell: info => info.getValue(),
  }),
]

const data = ref(defaultData)
const table = useVueTable({
  get data() {
    return data.value
  },
  columns,
  getCoreRowModel: getCoreRowModel(),
})
</script>

<template>
  <div class="p-20">
    <table>
      <tbody>
      <tr v-for="row in table.getRowModel().rows" :key="row.id">
        <td v-for="cell in row.getVisibleCells()" :key="cell.id">
          <FlexRender
              :render="cell.column.columnDef.cell"
              :props="cell.getContext()"
          />
        </td>
      </tr>
      </tbody>
    </table>
  </div>
</template>

<style scoped>
table {  
  border: 1px solid lightgray;  
}  
  
td {  
  border-right: 1px solid lightgray;  
}
</style>

效果如图:

官方提供了复杂示例:

https://tanstack.com/table/v8/docs/framework/vue/examples/basic

vee-validate

vee-validate