//项目结构
- .
- ├── build # 电商商城定制开发打包脚本相关
- │ ├── config # 配置文件
- │ ├── generate # 生成器
- │ ├── script # 脚本
- │ └── vite # vite配置
- ├── mock # mock文件夹
- ├── public # 电商商城定制开发公共静态资源目录
- ├── src # 主目录
- │ ├── api # 接口文件
- │ ├── assets # 资源文件
- │ │ ├── icons # icon sprite 电商商城定制开发图标文件夹
- │ │ ├── images # 电商商城定制开发项目存放电商商城定制开发图片的文件夹
- │ │ └── svg # 项目存放svg图片的文件夹
- │ ├── components # 公共组件
- │ ├── design # 样式文件
- │ ├── directives # 指令
- │ ├── enums # 枚举/常量
- │ ├── hooks # hook
- │ │ ├── component # 组件相关hook
- │ │ ├── core # 基础hook
- │ │ ├── event # 事件相关hook
- │ │ ├── setting # 配置相关hook
- │ │ └── web # web相关hook
- │ ├── layouts # 布局文件
- │ │ ├── default # 默认布局
- │ │ ├── iframe # iframe布局
- │ │ └── page # 页面布局
- │ ├── locales # 多语言
- │ ├── logics # 逻辑
- │ ├── main.ts # 主入口
- │ ├── router # 路由配置
- │ ├── settings # 项目配置
- │ │ ├── componentSetting.ts # 组件配置
- │ │ ├── designSetting.ts # 样式配置
- │ │ ├── encryptionSetting.ts # 加密配置
- │ │ ├── localeSetting.ts # 电商商城定制开发多语言配置
- │ │ ├── projectSetting.ts # 项目配置
- │ │ └── siteSetting.ts # 站点配置
- │ ├── store # 数据仓库
- │ ├── utils # 工具类
- │ └── views # 页面
- ├── test # 测试
- │ └── server # 电商商城定制开发测试用到的服务
- │ ├── api # 电商商城定制开发测试服务器
- │ ├── upload # 电商商城定制开发测试上传服务器
- │ └── websocket # 测试ws服务器
- ├── types # 类型文件
- ├── vite.config.ts # vite配置文件
- └── windi.config.ts # windcss配置文件
//路由
电商商城定制开发项目存放于 下面。 电商商城定制开发用于存放路由模块,电商商城定制开发在该目录下的文件会自动注册。
//
在浏览器支持 ES 模块之前,JavaScript 并没有提供的原生机制让开发者以模块化的方式进行开发。这也正是我们对 “打包” 这个概念熟悉的原因:使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。
时过境迁,我们见证了诸如 、 和 等工具的变迁,它们极大地改善了前端开发者的开发体验。
然而,当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。我们开始遇到性能瓶颈 —— 使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。
Vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。
// .env
- # port 端口
- VITE_PORT = 3100
-
- # spa-title 名字
- VITE_GLOB_APP_TITLE = Vben Admin
-
- # spa shortname 轻应用
- VITE_GLOB_APP_SHORT_NAME = vue_vben_admin
// .env.development 开发环境下的配置文件
- # Whether to open mock 是否打开mock
- VITE_USE_MOCK = true
-
- # public path 公共通道
- VITE_PUBLIC_PATH = /
-
- # Cross-domain proxy, you can configure multiple 跨域代理,可以配置多个
- # Please note that no line breaks 请注意不要换行
- VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
- # VITE_PROXY=[["/api","https://vvbin.cn/test"]]
-
- # Delete console 删除控制台
- VITE_DROP_CONSOLE = false
-
- # Basic interface address SPA 基本接口地址
- VITE_GLOB_API_URL=/basic-api
-
- # File upload address, optional 文件上传地址,可选
- VITE_GLOB_UPLOAD_URL=/upload
-
- # Interface prefix 接口前缀
- VITE_GLOB_API_URL_PREFIX=
// .env.production 生产环境下的配置文件
- # Whether to open mock 是否开启mock
- VITE_USE_MOCK = true
-
- # public path 公共路径
-
- VITE_PUBLIC_PATH = /
-
- # Delete console 删除控制台
- VITE_DROP_CONSOLE = true
-
- # Whether to enable gzip or brotli compression 是否启用gzip或brotli压缩
- # Optional: gzip | brotli | none 可选:gzip | brotli | none
- # If you need multiple forms, you can use `,` to separate 如果需要多个表单,可以使用“,”分隔
- VITE_BUILD_COMPRESS = 'none'
-
- # Whether to delete origin files when using compress, default false 使用压缩时是否删除源文件,默认为false
- VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
-
- # Basic interface address SPA 基本接口地址
- VITE_GLOB_API_URL=/basic-api
-
- # File upload address, optional 文件上传地址,可选
- # It can be forwarded by nginx or write the actual address directly 它可以通过nginx转发,也可以直接写入实际地址
- VITE_GLOB_UPLOAD_URL=/upload
-
- # Interface prefix 接口前缀
- VITE_GLOB_API_URL_PREFIX=
-
- # Whether to enable image compression 是否启用图像压缩
- VITE_USE_IMAGEMIN= true
-
- # use pwa 使用pwa
- VITE_USE_PWA = false
-
- # Is it compatible with older browsers 它与旧浏览器兼容吗
- VITE_LEGACY = false
// .env.test 测试环境
- # 通常这个变量用来区分开发与生产环境,加载不同的配置。
- NODE_ENV=production
- # Whether to open mock 是否打开模拟
- VITE_USE_MOCK = true
-
- # public path
- VITE_PUBLIC_PATH = /
-
- # Delete console 公共通道
- VITE_DROP_CONSOLE = true
-
- # Whether to enable gzip or brotli compression 是否启用gzip或brotli压缩
- # Optional: gzip | brotli | none 可选:gzip | brotli | none
- # If you need multiple forms, you can use `,` to separate 如果需要多个表单,可以使用“,”分隔
- VITE_BUILD_COMPRESS = 'none'
-
- # Whether to delete origin files when using compress, default false 使用压缩时是否删除源文件,默认为false
- VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
-
- # Basic interface address SPA 基本接口地址
- VITE_GLOB_API_URL=/basic-api
-
- # File upload address, optional 文件上传地址,可选
- # It can be forwarded by nginx or write the actual address directly 它可以通过nginx转发,也可以直接写入实际地址
- VITE_GLOB_UPLOAD_URL=/upload
-
- # Interface prefix 接口前缀
- VITE_GLOB_API_URL_PREFIX=
-
- # Whether to enable image compression 是否启用图像压缩
- VITE_USE_IMAGEMIN= true
-
- # use pwa 使用pwa
- VITE_USE_PWA = false
-
- # Is it compatible with older browsers 它与旧浏览器兼容吗
- VITE_LEGACY = false
//部分权限 路径Vben Admin\vue-vben-admin\src\settings\projectSetting.ts
- import type { ProjectConfig } from '/#/config';
- import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum';
- import { CacheTypeEnum } from '/@/enums/cacheEnum';
- import {
- ContentEnum,
- PermissionModeEnum,
- ThemeEnum,
- RouterTransitionEnum,
- SettingButtonPositionEnum,
- SessionTimeoutProcessingEnum,
- } from '/@/enums/appEnum';
- import { SIDE_BAR_BG_COLOR_LIST, HEADER_PRESET_BG_COLOR_LIST } from './designSetting';
- import { primaryColor } from '../../build/config/themeConfig';
-
- // ! You need to clear the browser cache after the change
- const setting: ProjectConfig = {
- // Whether to show the configuration button 是否显示配置按钮
- showSettingButton: true,
-
- // Whether to show the theme switch button 是否显示主题切换按钮
- showDarkModeToggle: true,
-
- // `Settings` button position` 设置按钮位置
- settingButtonPosition: SettingButtonPositionEnum.AUTO,
-
- // Permission mode 权限模式
- permissionMode: PermissionModeEnum.ROUTE_MAPPING,
-
- // Permission-related cache is stored in sessionStorage or localStorage
- //与权限相关的缓存存储在sessionStorage或localStorage中
- permissionCacheType: CacheTypeEnum.LOCAL,
-
- // Session timeout processing 会话超时处理
- sessionTimeoutProcessing: SessionTimeoutProcessingEnum.ROUTE_JUMP,
-
- // color颜色
- themeColor: primaryColor,
-
- // Website gray mode, open for possible mourning dates 网站灰色模式,为可能的哀悼日开放
- grayMode: false,
-
- // Color Weakness Mode 弱色模式
- colorWeak: false,
-
- // Whether to cancel the menu, the top, the multi-tab page display, for possible embedded in other systems
- //是否取消菜单、顶部、多选项卡页面显示,以便可能嵌入其他系统
- fullContent: false,
-
- // content mode内容模式
- contentMode: ContentEnum.FULL,
-
- // Whether to display the logo 是否显示logo
- showLogo: true,
-
- // Whether to show footer 是否显示页脚
- showFooter: false,
-
- // Header configuration 标题配置
- headerSetting: {
- // header bg color 标题背景颜色
- bgColor: HEADER_PRESET_BG_COLOR_LIST[0],
- // Fixed at the top 固定在顶部
- fixed: true,
- // Whether to show top 是否显示顶部
- show: true,
- // theme主题
- theme: ThemeEnum.LIGHT,
- // Whether to enable the lock screen function 是否启用锁屏功能
- useLockPage: true,
- // Whether to show the full screen button 是否显示全屏按钮
- showFullScreen: true,
- // Whether to show the document button 是否显示文档按钮
- showDoc: true,
- // Whether to show the notification button 是否显示通知按钮
- showNotice: true,
- // Whether to display the menu search 是否显示菜单搜索
- showSearch: true,
- },
-
- // Menu configuration菜单配置
- menuSetting: {
- // sidebar menu bg color边栏菜单bg颜色
- bgColor: SIDE_BAR_BG_COLOR_LIST[0],
- // Whether to fix the left menu 是否修复左侧菜单
- fixed: true,
- // Menu collapse菜单折叠
- collapsed: false,
- // Whether to display the menu name when folding the menu 折叠菜单时是否显示菜单名称
- collapsedShowTitle: false,
- // Whether it can be dragged 是否可以拖动
- // Only limited to the opening of the left menu, the mouse has a drag bar on the right side of the menu
- //鼠标仅限于打开左侧菜单,在菜单右侧有一个拖动条
- canDrag: false,
- // Whether to show no dom 是否显示没有dom
- show: true,
- // Whether to show dom 是否显示dom
- hidden: false,
- // Menu width 菜单宽度
- menuWidth: 210,
- // Menu mode 菜单模式
- mode: MenuModeEnum.INLINE,
- // Menu type 菜单类型
- type: MenuTypeEnum.SIDEBAR,
- // Menu theme 菜单主题
- theme: ThemeEnum.DARK,
- // Split menu 分割菜单
- split: false,
- // Top menu layout 顶部菜单布局
- topMenuAlign: 'center',
- // Fold trigger position 折叠触发位置
- trigger: TriggerEnum.HEADER,
- // Turn on accordion mode, only show a menu 打开手风琴模式,只显示菜单
- accordion: true,
- // Switch page to close menu 将页面切换到关闭菜单
- closeMixSidebarOnChange: false,
- // Module opening method ‘click’ |'hover' 模块打开方法“点击”|“悬停”
- mixSideTrigger: MixSidebarTriggerEnum.CLICK,
- // Fixed expanded menu 修复了扩展菜单
- mixSideFixed: false,
- },
-
- // Multi-label 多标签
- multiTabsSetting: {
- cache: false,
- // Turn on 打开
- show: true,
- // Is it possible to drag and drop sorting tabs 可以拖放排序选项卡吗
- canDrag: true,
- // Turn on quick actions 开启快速行动
- showQuick: true,
- // Whether to show the refresh button 是否显示刷新按钮
- showRedo: true,
- // Whether to show the collapse button 是否显示折叠按钮
- showFold: true,
- },
-
- // Transition Setting
- transitionSetting: {
- // Whether to open the page switching animation 是否打开页面切换动画
- // The disabled state will also disable pageLoading 禁用状态也将禁用页面加载
- enable: true,
-
- // Route basic switching animation 路由基本切换动画
- basicTransition: RouterTransitionEnum.FADE_SIDE,
-
- // Whether to open page switching loading 是否打开页面切换加载
- // Only open when enable=true 仅当enable=true时打开
- openPageLoading: true,
-
- // Whether to open the top progress bar 是否打开顶部进度条
- openNProgress: false,
- },
-
- // Whether to enable KeepAlive cache is best to close during development, otherwise the cache needs to be cleared every time
- //是否启用KeepAlive缓存最好在开发期间关闭,否则每次都需要清除缓存
- openKeepAlive: true,
-
- // Automatic screen lock time, 0 does not lock the screen. Unit minute default 0
- //自动屏幕锁定时间,0不锁定屏幕。单位分钟默认值0
- lockTime: 0,
-
- // Whether to show breadcrumbs 是否显示面包屑
- showBreadCrumb: true,
-
- // Whether to show the breadcrumb icon 是否显示面包屑图标
- showBreadCrumbIcon: false,
-
- // Use error-handler-plugin 使用错误处理插件
- useErrorHandle: false,
-
- // Whether to open back to top 是否要从头开始
- useOpenBackTop: true,
-
- // Is it possible to embed iframe pages 是否可以嵌入iframe页面
- canEmbedIFramePage: true,
-
- // Whether to delete unclosed messages and notify when switching the interface
- //切换界面时是否删除未关闭的消息并通知
- closeMessageOnSwitch: true,
-
- // Whether to cancel the http request that has been sent but not responded when switching the interface. 切换接口时是否取消已发送但未响应的http请求。
- // If it is enabled, I want to overwrite a single interface. Can be set in a separate interface 如果启用了,我想覆盖一个接口。可以在单独的界面中设置
- removeAllHttpPending: false,
- };
-
- export default setting;
// 模拟登录密码判断 路径:Vben Admin\vue-vben-admin\mock\sys\user.ts
- import { MockMethod } from 'vite-plugin-mock';
- import { resultError, resultSuccess, getRequestToken, requestParams } from '../_util';
-
- export function createFakeUserList() {
- return [
- {
- userId: '1',
- username: 'vben',
- realName: 'Vben Admin',
- avatar: 'https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640',
- desc: 'manager',
- password: '123456',
- token: 'fakeToken1',
- homePath: '/dashboard/analysis',
- roles: [
- {
- roleName: 'Super Admin',
- value: 'super',
- },
- ],
- },
- {
- userId: '2',
- username: 'test',
- password: '123456',
- realName: 'test user',
- avatar: 'https://q1.qlogo.cn/g?b=qq&nk=339449197&s=640',
- desc: 'tester',
- token: 'fakeToken2',
- homePath: '/dashboard/workbench',
- roles: [
- {
- roleName: 'Tester',
- value: 'test',
- },
- ],
- },
- ];
- }
-
- const fakeCodeList: any = {
- '1': ['1000', '3000', '5000'],
-
- '2': ['2000', '4000', '6000'],
- };
- export default [
- // mock user login 模拟登录
- {
- url: '/basic-api/login',
- timeout: 200,
- method: 'post',
- response: ({ body }) => {
- const { username, password } = body;
- const checkUser = createFakeUserList().find(
- (item) => item.username === username && password === item.password,
- );
- if (!checkUser) {
- return resultError('Incorrect account or password!');
- }
- const { userId, username: _username, token, realName, desc, roles } = checkUser;
- return resultSuccess({
- roles,
- userId,
- username: _username,
- token,
- realName,
- desc,
- });
- },
- },
- {
- url: '/basic-api/getUserInfo',
- method: 'get',
- response: (request: requestParams) => {
- const token = getRequestToken(request);
- if (!token) return resultError('Invalid token');
- const checkUser = createFakeUserList().find((item) => item.token === token);
- if (!checkUser) {
- return resultError('The corresponding user information was not obtained!');
- }
- return resultSuccess(checkUser);
- },
- },
- {
- url: '/basic-api/getPermCode',
- timeout: 200,
- method: 'get',
- response: (request: requestParams) => {
- const token = getRequestToken(request);
- if (!token) return resultError('Invalid token');
- const checkUser = createFakeUserList().find((item) => item.token === token);
- if (!checkUser) {
- return resultError('Invalid token!');
- }
- const codeList = fakeCodeList[checkUser.userId];
-
- return resultSuccess(codeList);
- },
- },
- {
- url: '/basic-api/logout',
- timeout: 200,
- method: 'get',
- response: (request: requestParams) => {
- const token = getRequestToken(request);
- if (!token) return resultError('Invalid token');
- const checkUser = createFakeUserList().find((item) => item.token === token);
- if (!checkUser) {
- return resultError('Invalid token!');
- }
- return resultSuccess(undefined, { message: 'Token has been destroyed' });
- },
- },
- {
- url: '/basic-api/testRetry',
- statusCode: 405,
- method: 'get',
- response: () => {
- return resultError('Error!');
- },
- },
- ] as MockMethod[];
//登录表单
- <template>
- <LoginFormTitle v-show="getShow" class="enter-x" />
- <Form
- class="p-4 enter-x"
- :model="formData"
- :rules="getFormRules"
- ref="formRef"
- v-show="getShow"
- @keypress.enter="handleLogin"
- >
- <FormItem name="account" class="enter-x">
- <Input
- size="large"
- v-model:value="formData.account"
- :placeholder="t('sys.login.userName')"
- class="fix-auto-fill"
- />
- </FormItem>
- <FormItem name="password" class="enter-x">
- <InputPassword
- size="large"
- visibilityToggle
- v-model:value="formData.password"
- :placeholder="t('sys.login.password')"
- />
- </FormItem>
-
- <ARow class="enter-x">
- <ACol :span="12">
- <FormItem>
- <!-- No logic, you need to deal with it yourself -->
- <Checkbox v-model:checked="rememberMe" size="small">
- {{ t('sys.login.rememberMe') }}
- </Checkbox>
- </FormItem>
- </ACol>
- <ACol :span="12">
- <FormItem :style="{ 'text-align': 'right' }">
- <!-- No logic, you need to deal with it yourself -->
- <Button type="link" size="small" @click="setLoginState(LoginStateEnum.RESET_PASSWORD)">
- {{ t('sys.login.forgetPassword') }}
- </Button>
- </FormItem>
- </ACol>
- </ARow>
-
- <FormItem class="enter-x">
- <Button type="primary" size="large" block @click="handleLogin" :loading="loading">
- {{ t('sys.login.loginButton') }}
- </Button>
- <!-- <Button size="large" class="mt-4 enter-x" block @click="handleRegister">
- {{ t('sys.login.registerButton') }}
- </Button> -->
- </FormItem>
- <ARow class="enter-x">
- <ACol :md="8" :xs="24">
- <Button block @click="setLoginState(LoginStateEnum.MOBILE)">
- {{ t('sys.login.mobileSignInFormTitle') }}
- </Button>
- </ACol>
- <ACol :md="8" :xs="24" class="!my-2 !md:my-0 xs:mx-0 md:mx-2">
- <Button block @click="setLoginState(LoginStateEnum.QR_CODE)">
- {{ t('sys.login.qrSignInFormTitle') }}
- </Button>
- </ACol>
- <ACol :md="7" :xs="24">
- <Button block @click="setLoginState(LoginStateEnum.REGISTER)">
- {{ t('sys.login.registerButton') }}
- </Button>
- </ACol>
- </ARow>
-
- <Divider class="enter-x">{{ t('sys.login.otherSignIn') }}</Divider>
-
- <div class="flex justify-evenly enter-x" :class="`${prefixCls}-sign-in-way`">
- <GithubFilled />
- <WechatFilled />
- <AlipayCircleFilled />
- <GoogleCircleFilled />
- <TwitterCircleFilled />
- </div>
- </Form>
- </template>
- <script lang="ts" setup>
- import { reactive, ref, unref, computed } from 'vue';
-
- import { Checkbox, Form, Input, Row, Col, Button, Divider } from 'ant-design-vue';
- import {
- GithubFilled,
- WechatFilled,
- AlipayCircleFilled,
- GoogleCircleFilled,
- TwitterCircleFilled,
- } from '@ant-design/icons-vue';
- import LoginFormTitle from './LoginFormTitle.vue';
-
- import { useI18n } from '/@/hooks/web/useI18n';
- import { useMessage } from '/@/hooks/web/useMessage';
-
- import { useUserStore } from '/@/store/modules/user';
- import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin';
- import { useDesign } from '/@/hooks/web/useDesign';
- //import { onKeyStroke } from '@vueuse/core';
- const ACol = Col;
- const ARow = Row;
- const FormItem = Form.Item;
- const InputPassword = Input.Password;
- const { t } = useI18n();
- const { notification, createErrorModal } = useMessage();
- const { prefixCls } = useDesign('login');
-
- const userStore = useUserStore();
-
- //登录方式
- const { setLoginState, getLoginState } = useLoginState();
-
-
- //注册的内容
- const { getFormRules } = useFormRules();
-
-
- //ref函数仅能监听基本类型的变化,不能监听复杂类型的变化(比如对象、数组)监听复杂类型的变化可以使用reactive函数
- //坑:ref和shallowRef不能一起用这样会影响视图
- const formRef = ref();
- const loading = ref(false);
-
- const rememberMe = ref(false);
-
- //模拟账号
- const formData = reactive({
- account: 'vben',
- password: '123456',
- });
-
- const { validForm } = useFormValid(formRef);
-
- //onKeyStroke('Enter', handleLogin);
-
- const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN);
-
-
-
- //登录的条件判断
- async function handleLogin() {
- const data = await validForm();
- if (!data) return;
- //try | catch | finally 语句的用法try包裹的是密码判断的内容如果try里的密码错误就会抛出catch里的错误内容
- try {
- loading.value = true;
- const userInfo = await userStore.login({
- password: data.password,
- username: data.account,
- mode: 'none', //不要默认的错误提示
- });
- if (userInfo) {
- notification.success({
- message: t('sys.login.loginSuccessTitle'),
- description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`,
- duration: 3,
- });
- }
- } catch (error) {
- createErrorModal({
- title: t('sys.api.errorTip'),
- content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'),
- getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body,
- });
- } finally {
- loading.value = false;
- }
- }
- </script>
//关于主页的一些简单的配置
- interface GroupItem {
- title: string;
- icon: string;
- color: string;
- desc: string;
- date: string;
- group: string;
- }
-
- interface NavItem {
- title: string;
- icon: string;
- color: string;
- }
-
- interface DynamicInfoItem {
- avatar: string;
- name: string;
- date: string;
- desc: string;
- }
-
- export const navItems: NavItem[] = [
- {
- title: '首页',
- icon: 'ion:home-outline',
- color: '#1fdaca',
- },
- // {
- // title: '仪表盘',
- // icon: 'ion:grid-outline',
- // color: '#bf0c2c',
- // },
- // {
- // title: '组件',
- // icon: 'ion:layers-outline',
- // color: '#e18525',
- // },
- // {
- // title: '系统管理',
- // icon: 'ion:settings-outline',
- // color: '#3fb27f',
- // },
- // {
- // title: '权限管理',
- // icon: 'ion:key-outline',
- // color: '#4daf1bc9',
- // },
- // {
- // title: '图表',
- // icon: 'ion:bar-chart-outline',
- // color: '#00d8ff',
- // },
- ];
-
- export const dynamicInfoItems: DynamicInfoItem[] = [
- {
- avatar: 'dynamic-avatar-1|svg',
- name: '威廉',
- date: '刚刚',
- desc: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,
- },
- {
- avatar: 'dynamic-avatar-2|svg',
- name: '艾文',
- date: '1个小时前',
- desc: `关注了 <a>威廉</a> `,
- },
- {
- avatar: 'dynamic-avatar-3|svg',
- name: '克里斯',
- date: '1天前',
- desc: `发布了 <a>个人动态</a> `,
- },
- {
- avatar: 'dynamic-avatar-4|svg',
- name: 'Vben',
- date: '2天前',
- desc: `发表文章 <a>如何编写一个Vite插件</a> `,
- },
- {
- avatar: 'dynamic-avatar-5|svg',
- name: '皮特',
- date: '3天前',
- desc: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
- },
- {
- avatar: 'dynamic-avatar-6|svg',
- name: '杰克',
- date: '1周前',
- desc: `关闭了问题 <a>如何运行项目</a> `,
- },
- {
- avatar: 'dynamic-avatar-1|svg',
- name: '威廉',
- date: '1周前',
- desc: `发布了 <a>个人动态</a> `,
- },
- {
- avatar: 'dynamic-avatar-1|svg',
- name: '威廉',
- date: '2021-04-01 20:00',
- desc: `推送了代码到 <a>Github</a>`,
- },
- ];
-
- export const groupItems: GroupItem[] = [
- {
- title: 'Github',
- icon: 'carbon:logo-github',
- color: '',
- desc: '不要等待机会,而要创造机会。',
- group: '开源组',
- date: '2021-04-01',
- },
- {
- title: 'Vue',
- icon: 'ion:logo-vue',
- color: '#3fb27f',
- desc: '现在的你决定将来的你。',
- group: '算法组',
- date: '2021-04-01',
- },
- {
- title: 'Html5',
- icon: 'ion:logo-html5',
- color: '#e18525',
- desc: '没有什么才能比努力更重要。',
- group: '上班摸鱼',
- date: '2021-04-01',
- },
- {
- title: 'Angular',
- icon: 'ion:logo-angular',
- color: '#bf0c2c',
- desc: '热情和欲望可以突破一切难关。',
- group: 'UI',
- date: '2021-04-01',
- },
- {
- title: 'React',
- icon: 'bx:bxl-react',
- color: '#00d8ff',
- desc: '健康的身体是实目标的基石。',
- group: '技术牛',
- date: '2021-04-01',
- },
- {
- title: 'Js',
- icon: 'ion:logo-javascript',
- color: '#4daf1bc9',
- desc: '路是走出来的,而不是空想出来的。',
- group: '架构组',
- date: '2021-04-01',
- },
- ];
//关于抽奖导航
- <template>
- <Card title="快捷导航" v-bind="$attrs">
- <CardGrid v-for="item in navItems" :key="item" >
- <span class="flex flex-col items-center">
-
- <!--图标-->
- <Icon :icon="item.icon" :color="item.color" size="20" />
-
- <span class="text-md mt-2">{{ item.title }}</span>
- </span>
- </CardGrid>
- </Card>
- </template>
- <script lang="ts" setup>
- import { Card } from 'ant-design-vue';
- import { navItems } from './data';
- import { Icon } from '/@/components/Icon';
- //navItems是上面data里的。
-
- const CardGrid = Card.Grid;
-
- </script>