Explorar o código

feat(businessManagement): 新增比赛管理功能

- 添加比赛管理相关的 组件和页面
- 实现比赛列表、新增比赛、编辑比赛等功能
-封装通用组件-ztCustomTable,用于表单新增赛事,以及时间安排
zhangtao hai 3 semanas
pai
achega
75d8f64532

+ 6 - 2
.env.development

@@ -5,11 +5,15 @@ VITE_USE_MOCK = false
 VITE_PUBLIC_PATH = /
 
 
+
 # 跨域代理,您可以配置多个 ,请注意,没有换行符
-VITE_PROXY = [["/jeecgboot","http://192.168.1.34:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
+# VITE_PROXY = [["/jeecgboot","http://192.168.1.34:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
+VITE_PROXY = [["/jeecgboot","http://192.168.0.11:8080/jeecg-boot"],["/upload","http://192.168.0.11:8080/upload"]]
 
 #后台接口全路径地址(必填)
-VITE_GLOB_DOMAIN_URL=http://192.168.1.34:8080/jeecg-boot
+# VITE_GLOB_DOMAIN_URL=http://192.168.1.34:8080/jeecg-boot #//黄、
+VITE_GLOB_DOMAIN_URL=http://192.168.0.11:8080/jeecg-boot  #李
+
 
 #后台接口父地址(必填)
 VITE_GLOB_API_URL=/jeecgboot

+ 0 - 3
src/App.vue

@@ -6,8 +6,6 @@
   </ConfigProvider>
 </template>
 
-
-
 <script lang="ts" setup>
   import { watch, ref } from 'vue';
   import { theme } from 'ant-design-vue';
@@ -101,7 +99,6 @@
     appStore.getProjectConfig?.themeColor && changeTheme(appStore.getProjectConfig.themeColor);
   }, 300);
   // update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
-
 </script>
 <style lang="less">
   // update-begin--author:liaozhiyang---date:20230803---for:【QQYUN-5839】windi会影响到html2canvas绘制的图片样式

+ 7 - 0
src/api/businessManagement/competition.ts

@@ -0,0 +1,7 @@
+import { defHttp } from '/@/utils/http/axios';
+enum Api {
+  competition = '',
+}
+export const getcompetition = (params) => {
+  return defHttp.get({ url: Api.competition, params });
+};

+ 5 - 6
src/components/Form/src/componentMap.ts

@@ -72,11 +72,11 @@ import JAddInput from './jeecg/components/JAddInput.vue';
 import { Time } from '/@/components/Time';
 import JRangeNumber from './jeecg/components/JRangeNumber.vue';
 import UserSelect from './jeecg/components/userSelect/index.vue';
-import JRangeDate from './jeecg/components/JRangeDate.vue'
-import JRangeTime from './jeecg/components/JRangeTime.vue'
-import JInputSelect from './jeecg/components/JInputSelect.vue'
+import JRangeDate from './jeecg/components/JRangeDate.vue';
+import JRangeTime from './jeecg/components/JRangeTime.vue';
+import JInputSelect from './jeecg/components/JInputSelect.vue';
 import RoleSelectInput from './jeecg/components/roleSelect/RoleSelectInput.vue';
-import {DatePickerInFilter, CascaderPcaInFilter} from "@/components/InFilter";
+import { DatePickerInFilter, CascaderPcaInFilter } from '@/components/InFilter';
 
 const componentMap = new Map<ComponentType, Component>();
 
@@ -122,6 +122,7 @@ componentMap.set(
   'JAreaLinkage',
   createAsyncComponent(() => import('./jeecg/components/JAreaLinkage.vue'))
 );
+
 componentMap.set('JSelectPosition', JSelectPosition);
 componentMap.set('JSelectUser', JSelectUser);
 componentMap.set('JSelectRole', JSelectRole);
@@ -174,8 +175,6 @@ componentMap.set('RangeTime', JRangeTime);
 componentMap.set('RoleSelect', RoleSelectInput);
 componentMap.set('JInputSelect', JInputSelect);
 
-
-
 export function add(compName: ComponentType, component: Component) {
   componentMap.set(compName, component);
 }

+ 0 - 1
src/components/Form/src/types/index.ts

@@ -159,4 +159,3 @@ export type ComponentType =
   | 'JRangeNumber'
   | 'JLinkTableCard'
   | 'JInputSelect';
-

+ 81 - 0
src/components/ZtCustomTable/index.vue

@@ -0,0 +1,81 @@
+<template>
+  <Form :model="modelValue" class="customForm">
+    <Table :dataSource="modelValue" :columns="tableColumn" :pagination="false">
+      <template #bodyCell="{ column, index }">
+        <template v-if="column.dataIndex == 'operation'">
+          <FormItem>
+            <div class="text-18px cursor-pointer text-#eb5050" @click="handleDelete(index)">
+              <minus-circle-outlined />
+            </div>
+          </FormItem>
+        </template>
+        <template v-else>
+          <FormItem>
+            <component :is="getDom(String(column.key))" v-model:value="modelValue[index][String(column.dataIndex)]" v-bind="column.props"></component>
+          </FormItem>
+        </template>
+      </template>
+      <template #headerCell="{ title }">
+        <template v-if="title == 'operation'">
+          <FormItem>
+            <div class="text-18px cursor-pointer" @click="handleAdd">
+              <PlusCircleOutlined></PlusCircleOutlined>
+            </div>
+          </FormItem>
+        </template>
+      </template>
+    </Table>
+  </Form>
+</template>
+<script lang="ts" setup>
+  import { Table, Input, InputNumber, Select, RangePicker, Form, FormItem } from 'ant-design-vue';
+  import { PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons-vue';
+  import { ref, computed } from 'vue';
+  import { ZtTableColumnProps } from '/#/utils';
+  const props = defineProps<{ tableColumn: ZtTableColumnProps[]; value: any[] }>();
+  const emit = defineEmits(['update:value']);
+  const modelValue = computed({
+    get() {
+      console.log(props.value, 'asdas');
+
+      return props.value || [];
+    },
+    set(val) {
+      emit('update:value', val);
+    },
+  });
+  const componentMap = {
+    Input,
+    InputNumber,
+    Select,
+    RangePicker,
+  };
+  type DataRow = Record<string, any> & {
+    [key: string]: any;
+  };
+  const addEmptyRow = (columns: ZtTableColumnProps[]): DataRow => {
+    const inputFields = columns.filter((col) => col.dataIndex && col.dataIndex != 'operation').map((col) => col.dataIndex);
+    const newRow: DataRow = {};
+    inputFields.forEach((field) => {
+      newRow[String(field)] = null;
+    });
+    return newRow;
+  };
+  function handleAdd() {
+    modelValue.value.push(addEmptyRow(props.tableColumn));
+  }
+  function handleDelete(index) {
+    modelValue.value.splice(index, 1);
+  }
+  function getDom(type: string) {
+    return componentMap[type];
+  }
+</script>
+
+<style lang="less">
+  .customForm {
+    .ant-form-item {
+      margin-bottom: 0 !important;
+    }
+  }
+</style>

+ 10 - 2
src/store/modules/multipleTab.ts

@@ -127,7 +127,16 @@ export const useMultipleTabStore = defineStore({
         }
         // update-begin--author:liaozhiyang---date:20240308---for:【QQYUN-12348】online生成的菜单sql 自动带上组件名称
         if (
-          ['OnlineAutoList', 'DefaultOnlineList', 'CgformErpList', 'OnlCgformInnerTableList', 'OnlCgformTabList', 'OnlCgReportList', 'GraphreportAutoChart', 'AutoDesformDataList'].includes(item.name as string) &&
+          [
+            'OnlineAutoList',
+            'DefaultOnlineList',
+            'CgformErpList',
+            'OnlCgformInnerTableList',
+            'OnlCgformTabList',
+            'OnlCgReportList',
+            'GraphreportAutoChart',
+            'AutoDesformDataList',
+          ].includes(item.name as string) &&
           allMenus?.length
         ) {
           const route = getMatchingRoute(allMenus, item.path);
@@ -398,7 +407,6 @@ export const useMultipleTabStore = defineStore({
       this.goToPage(router);
     },
 
-
     /**
      * Close other tabs
      */

+ 317 - 0
src/views/businessManagement/competition/competition.data.ts

@@ -0,0 +1,317 @@
+import { ZtTableColumnProps } from '/#/utils';
+import { FormSchema } from '/@/components/Table';
+
+/**
+ * 列表columns
+ */
+export const columns = [
+  {
+    title: '赛事名称',
+    dataIndex: 'roleName',
+    width: 100,
+  },
+  {
+    title: '封面',
+    dataIndex: 'roleCode',
+    width: 100,
+  },
+  {
+    title: '比赛时间',
+    dataIndex: 'createTime',
+    width: 100,
+  },
+  {
+    title: '比赛项目与价格',
+    dataIndex: 'createTime',
+    width: 100,
+  },
+  {
+    title: '报名结束时间',
+    dataIndex: 'createTime',
+    width: 100,
+  },
+];
+export const searchFormSchema: FormSchema[] = [
+  {
+    field: 'name',
+    label: '赛事名称',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    field: '',
+    label: '比赛项目',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+  {
+    field: '',
+    label: '报名结束时间',
+    component: 'RangePicker',
+    colProps: { span: 8 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    field: 'title1',
+    slot: 'title1',
+    label: '',
+    component: 'Input',
+    labelWidth: 0,
+  },
+  {
+    field: 'name',
+    label: '赛事名称',
+    component: 'Input',
+    required: true,
+    labelWidth: 120,
+    colProps: {
+      span: 14,
+      xs: 24,
+    },
+  },
+  {
+    field: 'file',
+    label: '封面',
+    component: 'JImageUpload',
+    required: true,
+    labelWidth: 120,
+    colProps: {
+      span: 18,
+      xs: 24,
+    },
+  },
+  {
+    field: 'fileimg',
+    label: '背景图',
+    component: 'JImageUpload',
+    required: true,
+    labelWidth: 120,
+    colProps: {
+      span: 18,
+      xs: 24,
+    },
+  },
+  {
+    field: 'name',
+    label: '主办单位',
+    component: 'Input',
+    required: true,
+    labelWidth: 120,
+    colProps: {
+      span: 14,
+      xs: 24,
+    },
+  },
+  {
+    field: 'time',
+    label: '报名结束时间',
+    component: 'DatePicker',
+    required: true,
+    labelWidth: 120,
+    colProps: {
+      span: 14,
+      xs: 24,
+    },
+    componentProps: {
+      format: 'YYYY-MM-DD hh:mm:ss',
+      showTime: true,
+    },
+  },
+  {
+    field: 'dirver',
+    slot: 'RangePicker',
+    label: '',
+    component: 'RangePicker',
+    labelWidth: 0,
+    colProps: {
+      span: 28,
+      xs: 28,
+    },
+  },
+  {
+    field: 'RangePicker',
+    label: '比赛时间',
+    component: 'RangePicker',
+    required: true,
+    labelWidth: 120,
+    colProps: {
+      span: 14,
+      xs: 24,
+    },
+  },
+  {
+    field: 'ScheduleData1',
+    label: '赛程安排',
+    component: 'Input',
+    slot: 'ZtCustomTable1',
+    defaultValue: [],
+    required: true,
+    labelWidth: 120,
+    colProps: {
+      span: 18,
+      xs: 24,
+    },
+  },
+  {
+    field: 'title3',
+    slot: 'title3',
+    label: '',
+    component: 'RangePicker',
+    labelWidth: 0,
+    colProps: {
+      span: 28,
+      xs: 28,
+    },
+  },
+  {
+    field: 'ScheduleData2',
+    label: '比赛项目与价格',
+    component: 'Input',
+    slot: 'ZtCustomTable2',
+    defaultValue: [],
+    required: true,
+    labelWidth: 120,
+    colProps: {
+      span: 14,
+      xs: 24,
+    },
+  },
+  {
+    field: 'address',
+    label: '比赛地点',
+    component: 'RadioGroup',
+    labelWidth: 120,
+    required: true,
+    componentProps: {
+      options: [
+        { label: '平台场地', value: 1 },
+        { label: '其他场地', value: 0 },
+      ],
+    },
+    colProps: {
+      span: 24,
+      xs: 24,
+    },
+  },
+  {
+    field: 'address',
+    label: '平台场地',
+    component: 'Select',
+    labelWidth: 120,
+    required: true,
+    componentProps: {
+      options: [
+        { label: '平台场地', value: 1 },
+        { label: '其他场地', value: 0 },
+      ],
+    },
+    colProps: {
+      span: 16,
+      xs: 24,
+    },
+  },
+  {
+    field: 'address',
+    label: '图文说明',
+    component: 'JEditor',
+    labelWidth: 120,
+    required: true,
+    colProps: {
+      span: 16,
+      xs: 24,
+    },
+  },
+  {
+    field: 'title4',
+    slot: 'title4',
+    label: '',
+    component: 'RangePicker',
+    labelWidth: 0,
+    colProps: {
+      span: 28,
+      xs: 28,
+    },
+  },
+  {
+    field: 'baoxain',
+    label: '配套保险',
+    component: 'RadioGroup',
+    labelWidth: 120,
+    required: true,
+    componentProps: {
+      options: [
+        { label: '平台场地', value: 1 },
+        { label: '其他场地', value: 0 },
+      ],
+    },
+    colProps: {
+      span: 24,
+      xs: 24,
+    },
+  },
+];
+
+export const ScheduleArrangementColums: ZtTableColumnProps[] = [
+  {
+    title: '比赛项目',
+    key: 'Select',
+    dataIndex: 'project',
+    width: 220,
+    props: {
+      placeholder: '请选择比赛项目',
+    },
+  },
+  {
+    title: '时间段',
+    key: 'RangePicker',
+    dataIndex: 'time',
+    width: 450,
+    props: {
+      format: 'YYYY-MM-DD hh:mm:ss',
+      showTime: true,
+    },
+  },
+  {
+    title: '安排',
+    key: 'Input',
+    dataIndex: 'Schedule',
+    width: 220,
+    props: {
+      placeholder: '请输入',
+    },
+  },
+  {
+    key: 'op',
+    dataIndex: 'operation',
+    title: 'operation',
+    fixed: 'right',
+  },
+];
+export const SchedulePricesColums: ZtTableColumnProps[] = [
+  {
+    title: '比赛项目',
+    key: 'Select',
+    dataIndex: 'project',
+    width: 220,
+    props: {
+      placeholder: '请选择比赛项目',
+    },
+  },
+  {
+    title: '价格',
+    key: 'InputNumber',
+    dataIndex: 'price',
+    width: 250,
+    props: {
+      step: 0.01,
+    },
+  },
+  {
+    key: 'op',
+    dataIndex: 'operation',
+    title: 'operation',
+    fixed: 'right',
+  },
+];

+ 49 - 0
src/views/businessManagement/competition/competitionCommon.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="p-8px bg-white">
+    <div class="px-4">
+      <BasicForm @register="registerForm">
+        <template #title1>
+          <TypographyTitle :level="4">基础信息</TypographyTitle>
+          <Divider></Divider>
+        </template>
+        <template #RangePicker>
+          <TypographyTitle :level="4">比赛时间及赛程安排</TypographyTitle>
+          <Divider></Divider>
+        </template>
+        <template #ZtCustomTable1="{ model, field }">
+          <ZtCustomTable :tableColumn="ScheduleArrangementColums" v-model:value="model[field]"></ZtCustomTable>
+        </template>
+        <template #title3>
+          <TypographyTitle :level="4">比赛项目、价格、地点、说明</TypographyTitle>
+          <Divider></Divider>
+        </template>
+        <template #ZtCustomTable2="{ model, field }">
+          <ZtCustomTable :tableColumn="SchedulePricesColums" v-model:value="model[field]"></ZtCustomTable>
+        </template>
+        <template #title4>
+          <TypographyTitle :level="4">配套保险</TypographyTitle>
+          <Divider></Divider>
+        </template>
+        <template #formFooter>
+          <div style="margin: 0 auto">
+            <a-button type="primary" @click="save" class="mr-2"> 保存 </a-button>
+            <a-button type="error" @click="back" class="mr-2"> 关闭 </a-button>
+          </div>
+        </template>
+      </BasicForm>
+      <div class="h-20px"></div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup name="business-management-competitionCommon">
+  import { TypographyTitle, Divider } from 'ant-design-vue';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import ZtCustomTable from '/@/components/ZtCustomTable/index.vue';
+  import { formSchema, ScheduleArrangementColums, SchedulePricesColums } from './competition.data';
+  const [registerForm, { setProps, resetFields, setFieldsValue, updateSchema, validate, clearValidate }] = useForm({
+    schemas: formSchema,
+    showActionButtonGroup: false,
+  });
+  function back() {}
+  function save() {}
+</script>

+ 94 - 0
src/views/businessManagement/competition/index.vue

@@ -0,0 +1,94 @@
+<template>
+  <div class="p-4"
+    ><BasicTable @register="registerTable">
+      <template #tableTitle>
+        <a-button
+          type="primary"
+          preIcon="ant-design:plus-outlined"
+          @click="router.push({ name: 'businessManagement-competitionCommon', query: { type: 0 } })"
+        >
+          新增</a-button
+        >
+        <a-button
+          type="primary"
+          preIcon="ant-design:plus-outlined"
+          @click="router.push({ name: 'businessManagement-competitionCommon', query: { type: 1 } })"
+        >
+          编辑</a-button
+        >
+        <a-dropdown>
+          <template #overlay>
+            <a-menu>
+              <a-menu-item key="1">
+                <Icon icon="ant-design:delete-outlined"></Icon>
+                删除
+              </a-menu-item>
+            </a-menu>
+          </template>
+        </a-dropdown>
+      </template>
+    </BasicTable></div
+  >
+</template>
+
+<script setup lang="ts" name="businessManagement-competition">
+  import { BasicTable, TableAction } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  import { searchFormSchema, columns } from './competition.data';
+  import { getMenuList } from '@/api/sys/menu';
+  import { getcompetition } from '@/api/businessManagement/competition';
+  import { router } from '/@/router';
+
+  const { prefixCls, tableContext } = useListPage({
+    designScope: 'competition-template',
+    tableProps: {
+      title: '赛场列表',
+      api: getMenuList,
+      columns: columns,
+      formConfig: {
+        // update-begin--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
+        labelWidth: 100,
+        rowProps: { gutter: 24 },
+        // update-end--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
+        schemas: searchFormSchema,
+      },
+      actionColumn: {
+        width: 120,
+      },
+      // rowSelection: null,
+      //自定义默认排序
+      defSort: {
+        column: 'id',
+        order: 'desc',
+      },
+    },
+    // exportConfig: {
+    //   name: '角色列表',
+    //   url: getExportUrl,
+    // },
+    // importConfig: {
+    //   url: getImportUrl,
+    // },
+  });
+  const [registerTable, { reload, expandAll, collapseAll }] = tableContext;
+</script>
+
+<script lang="ts">
+  import { useMultipleTabStore } from '@/store/modules/multipleTab';
+  import { storeToRefs } from 'pinia';
+  const typeList = ['添加赛事', '编辑赛事', '查看赛事'];
+  export default {
+    async beforeRouteLeave(to, from, next) {
+      to.meta.title = typeList[Number(to.query.type)];
+      const { getTabList } = storeToRefs(useMultipleTabStore());
+      const closeTab = getTabList.value.filter((it) => it.name == to.name).filter((it) => it.fullPath != to.fullPath);
+      if (closeTab.length) {
+        useMultipleTabStore().closeTabByKey(closeTab[0].fullPath, router);
+      }
+      await useMultipleTabStore().updateCacheTab();
+      next();
+    },
+  };
+</script>
+
+<style scoped></style>

+ 7 - 0
src/views/businessManagement/schoolOpen/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> </div>
+</template>
+
+<script setup lang="ts" name="businessManagementschoolOpen"></script>
+
+<style scoped></style>

+ 5 - 5
src/views/system/menu/MenuDrawer.vue

@@ -10,7 +10,7 @@
   import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
   import { list, saveOrUpdateMenu } from './menu.api';
   import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
-  import { useI18n } from "/@/hooks/web/useI18n";
+  import { useI18n } from '/@/hooks/web/useI18n';
   // 声明Emits
   const emit = defineEmits(['success', 'register']);
   const { adaptiveWidth } = useDrawerAdaptiveWidth();
@@ -118,10 +118,10 @@
   }
 
   /**
-  * 2024-03-06
-  * liaozhiyang
-  * 翻译菜单名称
-  */
+   * 2024-03-06
+   * liaozhiyang
+   * 翻译菜单名称
+   */
   function translateMenu(data, key) {
     if (data?.length) {
       const { t } = useI18n();

+ 13 - 13
src/views/system/menu/index.vue

@@ -36,10 +36,10 @@
   import { useDrawer } from '/@/components/Drawer';
   import MenuDrawer from './MenuDrawer.vue';
   import DataRuleList from './DataRuleList.vue';
-  import { columns,searchFormSchema } from './menu.data';
+  import { columns, searchFormSchema } from './menu.data';
   import { list, deleteMenu, batchDeleteMenu } from './menu.api';
-  import { useDefIndexStore } from "@/store/modules/defIndex";
-  import { useI18n } from "/@/hooks/web/useI18n";
+  import { useDefIndexStore } from '@/store/modules/defIndex';
+  import { useI18n } from '/@/hooks/web/useI18n';
 
   const checkedKeys = ref<Array<string | number>>([]);
   const showFooter = ref(true);
@@ -48,18 +48,18 @@
   const { t } = useI18n();
 
   // 自定义菜单名称列渲染
-  columns[0].customRender = function ({text, record}) {
-    const isDefIndex = checkDefIndex(record)
+  columns[0].customRender = function ({ text, record }) {
+    const isDefIndex = checkDefIndex(record);
     if (isDefIndex) {
-      text += '(默认首页)'
+      text += '(默认首页)';
     }
     // update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
     if (text.includes("t('") && t) {
       return new Function('t', `return ${text}`)(t);
     }
     // update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
-    return text
-  }
+    return text;
+  };
 
   // 列表页面公共参数、方法
   const { prefixCls, tableContext } = useListPage({
@@ -189,11 +189,11 @@
 
   // --------------- begin 默认首页配置 ------------
 
-  const defIndexStore = useDefIndexStore()
+  const defIndexStore = useDefIndexStore();
 
   // 设置默认主页
   async function handleSetDefIndex(record: Recordable) {
-    defIndexStore.update(record.url, record.component, record.route)
+    defIndexStore.update(record.url, record.component, record.route);
   }
 
   /**
@@ -201,7 +201,7 @@
    * @param record
    */
   function checkDefIndex(record: Recordable) {
-    return defIndexStore.check(record.url)
+    return defIndexStore.check(record.url);
   }
 
   // 重新加载默认首页配置
@@ -209,11 +209,11 @@
     try {
       defIndexStore.query();
     } catch (e) {
-      console.error(e)
+      console.error(e);
     }
   }
 
-  reloadDefIndex()
+  reloadDefIndex();
 
   // --------------- end 默认首页配置 ------------
 

+ 4 - 0
types/utils.d.ts

@@ -1,5 +1,9 @@
 import type { ComputedRef, Ref } from 'vue';
+import { TableColumnProps } from 'ant-design-vue';
 
 export type DynamicProps<T> = {
   [P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>;
 };
+export interface ZtTableColumnProps extends TableColumnProps {
+  props?: { placeholder?: string; format?: string; showTime?: boolean; step?: number };
+}

+ 33 - 0
uno.config.ts

@@ -0,0 +1,33 @@
+import {
+  defineConfig,
+  presetAttributify,
+  presetIcons,
+  presetTypography,
+  presetUno,
+  presetWebFonts,
+  transformerDirectives,
+  transformerVariantGroup,
+} from 'unocss';
+
+export default defineConfig({
+  shortcuts: [
+    // ...
+  ],
+  theme: {
+    colors: {
+      // ...
+    },
+  },
+  presets: [
+    presetUno(),
+    presetAttributify(),
+    presetIcons(),
+    presetTypography(),
+    presetWebFonts({
+      fonts: {
+        // ...
+      },
+    }),
+  ],
+  transformers: [transformerDirectives(), transformerVariantGroup()],
+});