mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 15:53:43 +08:00 
			
		
		
		
	feat(projects): 1.0 beta
This commit is contained in:
		@@ -1,11 +0,0 @@
 | 
			
		||||
# Editor configuration, see http://editorconfig.org
 | 
			
		||||
 | 
			
		||||
root = true
 | 
			
		||||
 | 
			
		||||
[*]
 | 
			
		||||
charset = utf-8
 | 
			
		||||
indent_style = tab
 | 
			
		||||
indent_size = 2
 | 
			
		||||
end_of_line = lf
 | 
			
		||||
trim_trailing_whitespace = true
 | 
			
		||||
insert_final_newline = true
 | 
			
		||||
							
								
								
									
										27
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								.env
									
									
									
									
									
								
							@@ -1,20 +1,21 @@
 | 
			
		||||
VITE_BASE_URL=/
 | 
			
		||||
 | 
			
		||||
VITE_APP_NAME=SoybeanAdmin
 | 
			
		||||
VITE_APP_TITLE=SoybeanAdmin
 | 
			
		||||
 | 
			
		||||
VITE_APP_TITLE=Soybean管理系统
 | 
			
		||||
VITE_APP_DESC=SoybeanAdmin is a fresh and elegant admin template
 | 
			
		||||
 | 
			
		||||
VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
 | 
			
		||||
 | 
			
		||||
# 权限路由模式: static | dynamic
 | 
			
		||||
VITE_AUTH_ROUTE_MODE=static
 | 
			
		||||
 | 
			
		||||
# 路由首页(根路由重定向), 用于static模式的权限路由,dynamic模式取决于后端返回的路由首页
 | 
			
		||||
VITE_ROUTE_HOME_PATH=/dashboard/analysis
 | 
			
		||||
 | 
			
		||||
# iconify图标作为组件的前缀
 | 
			
		||||
# the prefix of the icon name
 | 
			
		||||
VITE_ICON_PREFIX=icon
 | 
			
		||||
 | 
			
		||||
# 本地SVG图标作为组件的前缀, 请注意一定要包含 VITE_ICON_PREFIX
 | 
			
		||||
# 格式 {VITE_ICON_PREFIX}-{本地图标集合名称}
 | 
			
		||||
# the prefix of the local svg icon component, must include VITE_ICON_PREFIX
 | 
			
		||||
# format {VITE_ICON_PREFIX}-{local icon name}
 | 
			
		||||
VITE_ICON_LOCAL_PREFIX=icon-local
 | 
			
		||||
 | 
			
		||||
# auth route mode: static | dynamic
 | 
			
		||||
VITE_AUTH_ROUTE_MODE=static
 | 
			
		||||
 | 
			
		||||
# static auth route home
 | 
			
		||||
VITE_ROUTE_HOME=home
 | 
			
		||||
 | 
			
		||||
# default menu icon
 | 
			
		||||
VITE_MENU_ICON=mdi:menu
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
/** 请求服务的环境配置 */
 | 
			
		||||
type ServiceEnv = Record<ServiceEnvType, ServiceEnvConfig>;
 | 
			
		||||
 | 
			
		||||
/** 不同请求服务的环境配置 */
 | 
			
		||||
const serviceEnv: ServiceEnv = {
 | 
			
		||||
  dev: {
 | 
			
		||||
    url: 'http://localhost:8080'
 | 
			
		||||
  },
 | 
			
		||||
  test: {
 | 
			
		||||
    url: 'http://localhost:8080'
 | 
			
		||||
  },
 | 
			
		||||
  prod: {
 | 
			
		||||
    url: 'http://localhost:8080'
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取当前环境模式下的请求服务的配置
 | 
			
		||||
 * @param env 环境
 | 
			
		||||
 */
 | 
			
		||||
export function getServiceEnvConfig(env: ImportMetaEnv): ServiceEnvConfigWithProxyPattern {
 | 
			
		||||
  const { VITE_SERVICE_ENV = 'dev' } = env;
 | 
			
		||||
 | 
			
		||||
  const config = serviceEnv[VITE_SERVICE_ENV];
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    ...config,
 | 
			
		||||
    proxyPattern: '/proxy-pattern'
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@@ -1,2 +1,2 @@
 | 
			
		||||
VITE_HTTP_PROXY=Y
 | 
			
		||||
VITE_SOYBEAN_ROUTE_PLUGIN=Y
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,2 @@
 | 
			
		||||
VITE_VISUALIZER=N
 | 
			
		||||
 | 
			
		||||
VITE_COMPRESS=N
 | 
			
		||||
 | 
			
		||||
# gzip | brotliCompress | deflate | deflateRaw
 | 
			
		||||
VITE_COMPRESS_TYPE=gzip
 | 
			
		||||
 | 
			
		||||
VITE_PWA=N
 | 
			
		||||
 | 
			
		||||
VITE_PROD_MOCK=Y
 | 
			
		||||
VITE_ROUTER_HISTORY_MODE=history
 | 
			
		||||
VITE_SOURCE_MAP=N
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
!.env-config.ts
 | 
			
		||||
router-page.d.ts
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								.eslintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.eslintrc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "sa/vue",
 | 
			
		||||
  "settings": {
 | 
			
		||||
    "import/core-modules": ["uno.css", "~icons/*", "virtual:svg-icons-register"]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										133
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								.eslintrc.js
									
									
									
									
									
								
							@@ -1,133 +0,0 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  extends: ['soybeanjs/vue'],
 | 
			
		||||
  overrides: [
 | 
			
		||||
    {
 | 
			
		||||
      files: ['./scripts/*.ts'],
 | 
			
		||||
      rules: {
 | 
			
		||||
        'no-unused-expressions': 'off'
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      files: ['*.vue'],
 | 
			
		||||
      rules: {
 | 
			
		||||
        'no-undef': 'off', // use tsc to check the ts code of the vue
 | 
			
		||||
        'vue/no-setup-props-destructure': 'off' // wait to fix this rule
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  settings: {
 | 
			
		||||
    'import/core-modules': ['uno.css', '~icons/*', 'virtual:svg-icons-register']
 | 
			
		||||
  },
 | 
			
		||||
  rules: {
 | 
			
		||||
    'import/order': [
 | 
			
		||||
      'error',
 | 
			
		||||
      {
 | 
			
		||||
        'newlines-between': 'never',
 | 
			
		||||
        groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
 | 
			
		||||
        pathGroups: [
 | 
			
		||||
          {
 | 
			
		||||
            pattern: 'vue',
 | 
			
		||||
            group: 'external',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: 'vue-router',
 | 
			
		||||
            group: 'external',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: 'pinia',
 | 
			
		||||
            group: 'external',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: 'naive-ui',
 | 
			
		||||
            group: 'external',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/constants',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/config',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/settings',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/plugins',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/layouts',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/views',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/components',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/router',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/service',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/store',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/context',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/composables',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/hooks',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/utils',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/assets',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            pattern: '@/**',
 | 
			
		||||
            group: 'internal',
 | 
			
		||||
            position: 'before'
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        pathGroupsExcludedImportTypes: ['vue', 'vue-router', 'pinia', 'naive-ui']
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										17
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -1,17 +0,0 @@
 | 
			
		||||
"*.vue"    eol=lf
 | 
			
		||||
"*.js"     eol=lf
 | 
			
		||||
"*.ts"     eol=lf
 | 
			
		||||
"*.jsx"    eol=lf
 | 
			
		||||
"*.tsx"    eol=lf
 | 
			
		||||
"*.cjs"    eol=lf
 | 
			
		||||
"*.cts"    eol=lf
 | 
			
		||||
"*.mjs"    eol=lf
 | 
			
		||||
"*.mts"    eol=lf
 | 
			
		||||
"*.json"   eol=lf
 | 
			
		||||
"*.html"   eol=lf
 | 
			
		||||
"*.css"    eol=lf
 | 
			
		||||
"*.less"   eol=lf
 | 
			
		||||
"*.scss"   eol=lf
 | 
			
		||||
"*.sass"   eol=lf
 | 
			
		||||
"*.styl"   eol=lf
 | 
			
		||||
"*.md"   eol=lf
 | 
			
		||||
							
								
								
									
										90
									
								
								.github/ISSUE_TEMPLATE/bug-report.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										90
									
								
								.github/ISSUE_TEMPLATE/bug-report.yaml
									
									
									
									
										vendored
									
									
								
							@@ -1,90 +0,0 @@
 | 
			
		||||
name: Bug提交
 | 
			
		||||
description: 在使用软件或功能的过程中遇到了错误
 | 
			
		||||
title: '[Bug]: '
 | 
			
		||||
labels: [ "bug?" ]
 | 
			
		||||
 | 
			
		||||
body:
 | 
			
		||||
  - type: markdown
 | 
			
		||||
    attributes:
 | 
			
		||||
      value: |
 | 
			
		||||
        ## 请按照以下要求进行提交
 | 
			
		||||
        ### 1. 提交后需要指定标签和截止时间。
 | 
			
		||||
        ---
 | 
			
		||||
 | 
			
		||||
  - type: markdown
 | 
			
		||||
    attributes:
 | 
			
		||||
      value: |
 | 
			
		||||
        ## 环境信息
 | 
			
		||||
        请根据实际使用环境修改以下信息。
 | 
			
		||||
 | 
			
		||||
  - type: input
 | 
			
		||||
    id: env-program-ver
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 软件版本
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
 | 
			
		||||
  - type: dropdown
 | 
			
		||||
    id: env-vm-ver
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 运行环境
 | 
			
		||||
      description: 选择运行软件的系统版本
 | 
			
		||||
      options:
 | 
			
		||||
        - Windows (64)
 | 
			
		||||
        - Windows (32/x84)
 | 
			
		||||
        - MacOS
 | 
			
		||||
        - Linux
 | 
			
		||||
        - Ubuntu
 | 
			
		||||
        - CentOS
 | 
			
		||||
        - ArchLinux
 | 
			
		||||
        - UNIX (Android)
 | 
			
		||||
        - 其它(请在下方说明)
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
 | 
			
		||||
  - type: dropdown
 | 
			
		||||
    id: env-vm-arch
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 运行架构
 | 
			
		||||
      description: (可选) 选择运行软件的系统架构
 | 
			
		||||
      options:
 | 
			
		||||
        - AMD64
 | 
			
		||||
        - x86
 | 
			
		||||
        - ARM [32] (别名:AArch32 / ARMv7)
 | 
			
		||||
        - ARM [64] (别名:AArch64 / ARMv8)
 | 
			
		||||
        - 其它
 | 
			
		||||
 | 
			
		||||
  - type: textarea
 | 
			
		||||
    id: reproduce-steps
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 重现步骤
 | 
			
		||||
      description: |
 | 
			
		||||
        我们需要执行哪些操作才能让 bug 出现?
 | 
			
		||||
        简洁清晰的重现步骤能够帮助我们更迅速地定位问题所在。
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
 | 
			
		||||
  - type: textarea
 | 
			
		||||
    id: expected
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 期望的结果是什么?
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
 | 
			
		||||
  - type: textarea
 | 
			
		||||
    id: actual
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 实际的结果是什么?
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
 | 
			
		||||
  - type: textarea
 | 
			
		||||
    id: logging
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 日志记录(可选)
 | 
			
		||||
      render: golang
 | 
			
		||||
 | 
			
		||||
  - type: textarea
 | 
			
		||||
    id: extra-desc
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: 补充说明(可选)
 | 
			
		||||
							
								
								
									
										11
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +0,0 @@
 | 
			
		||||
## Pull Request 详情
 | 
			
		||||
 | 
			
		||||
请根据实际使用情况修改以下信息。
 | 
			
		||||
 | 
			
		||||
## 版本信息
 | 
			
		||||
 | 
			
		||||
## 解决了哪些问题
 | 
			
		||||
 | 
			
		||||
## 是否关闭了某个 Issue
 | 
			
		||||
 | 
			
		||||
Closes #
 | 
			
		||||
							
								
								
									
										30
									
								
								.github/workflows/linter.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/linter.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,30 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: Lint Code
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: write
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches: [main]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lint:
 | 
			
		||||
    name: Lint All Code
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout Code
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
 | 
			
		||||
      - name: Lint Code Base
 | 
			
		||||
        uses: github/super-linter@v4
 | 
			
		||||
        env:
 | 
			
		||||
          VALIDATE_ALL_CODEBASE: false
 | 
			
		||||
          DEFAULT_BRANCH: main
 | 
			
		||||
          # To change branch master or main
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          FILTER_REGEX_EXCLUDE: (docs|.github)
 | 
			
		||||
          VALIDATE_MARKDOWN: false
 | 
			
		||||
							
								
								
									
										25
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,25 +0,0 @@
 | 
			
		||||
name: Release
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: write
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    tags:
 | 
			
		||||
      - "v*"
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  release:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
 | 
			
		||||
      - uses: actions/setup-node@v3
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: 16.x
 | 
			
		||||
 | 
			
		||||
      - run: npx githublogen
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
 | 
			
		||||
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -11,10 +11,8 @@ node_modules
 | 
			
		||||
.DS_Store
 | 
			
		||||
dist
 | 
			
		||||
dist-ssr
 | 
			
		||||
dist.zip
 | 
			
		||||
coverage
 | 
			
		||||
*.local
 | 
			
		||||
stats.html
 | 
			
		||||
 | 
			
		||||
/cypress/videos/
 | 
			
		||||
/cypress/screenshots/
 | 
			
		||||
@@ -22,8 +20,8 @@ stats.html
 | 
			
		||||
# Editor directories and files
 | 
			
		||||
.vscode/*
 | 
			
		||||
!.vscode/extensions.json
 | 
			
		||||
!.vscode/launch.json
 | 
			
		||||
!.vscode/settings.json
 | 
			
		||||
!.vscode/launch.json
 | 
			
		||||
.idea
 | 
			
		||||
*.suo
 | 
			
		||||
*.ntvs*
 | 
			
		||||
@@ -31,7 +29,7 @@ stats.html
 | 
			
		||||
*.sln
 | 
			
		||||
*.sw?
 | 
			
		||||
 | 
			
		||||
/src/typings/components.d.ts
 | 
			
		||||
package-lock.json
 | 
			
		||||
yarn.lock
 | 
			
		||||
pnpm-lock.yaml
 | 
			
		||||
 | 
			
		||||
.VSCodeCounter
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							@@ -8,11 +8,9 @@
 | 
			
		||||
    "formulahendry.auto-close-tag",
 | 
			
		||||
    "formulahendry.auto-rename-tag",
 | 
			
		||||
    "kisstkondoros.vscode-gutter-preview",
 | 
			
		||||
    "lokalise.i18n-ally",
 | 
			
		||||
    "mariusalchimavicius.json-to-ts",
 | 
			
		||||
    "mhutchie.git-graph",
 | 
			
		||||
    "sdras.vue-vscode-snippets",
 | 
			
		||||
    "streetsidesoftware.code-spell-checker",
 | 
			
		||||
    "vue.volar",
 | 
			
		||||
    "vue.vscode-typescript-vue-plugin"
 | 
			
		||||
  ]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -5,7 +5,7 @@
 | 
			
		||||
      "type": "chrome",
 | 
			
		||||
      "request": "launch",
 | 
			
		||||
      "name": "Vue debugger",
 | 
			
		||||
      "url": "http://localhost:3200",
 | 
			
		||||
      "url": "http://localhost:9527",
 | 
			
		||||
      "webRoot": "${workspaceFolder}"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										81
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										81
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@@ -1,51 +1,46 @@
 | 
			
		||||
{
 | 
			
		||||
  "cSpell.ignorePaths": [
 | 
			
		||||
    "package.json",
 | 
			
		||||
    "package-lock.json",
 | 
			
		||||
    "yarn.lock",
 | 
			
		||||
    "pnpm-lock.yaml",
 | 
			
		||||
    "node_modules",
 | 
			
		||||
    "vscode-extension",
 | 
			
		||||
    ".git/objects",
 | 
			
		||||
    ".vscode",
 | 
			
		||||
    ".vscode-insiders",
 | 
			
		||||
    "CHANGELOG.md",
 | 
			
		||||
    "dist",
 | 
			
		||||
    "public",
 | 
			
		||||
    "styles"
 | 
			
		||||
  ],
 | 
			
		||||
  "cSpell.words": [
 | 
			
		||||
    "AMAP",
 | 
			
		||||
    "antdesign",
 | 
			
		||||
    "antv",
 | 
			
		||||
    "apacheecharts",
 | 
			
		||||
    "areaspline",
 | 
			
		||||
    "bmapgl",
 | 
			
		||||
    "apifox",
 | 
			
		||||
    "clickoutside",
 | 
			
		||||
    "clsx",
 | 
			
		||||
    "colord",
 | 
			
		||||
    "echarts",
 | 
			
		||||
    "consola",
 | 
			
		||||
    "Destructurable",
 | 
			
		||||
    "EDITMSG",
 | 
			
		||||
    "espree",
 | 
			
		||||
    "execa",
 | 
			
		||||
    "gitee",
 | 
			
		||||
    "gridicons",
 | 
			
		||||
    "heroicons",
 | 
			
		||||
    "HEXA",
 | 
			
		||||
    "hexcode",
 | 
			
		||||
    "iconify",
 | 
			
		||||
    "jsapi",
 | 
			
		||||
    "naiveui",
 | 
			
		||||
    "Popconfirm",
 | 
			
		||||
    "Posva",
 | 
			
		||||
    "Shenzhen",
 | 
			
		||||
    "Sider",
 | 
			
		||||
    "INDEXEDDB",
 | 
			
		||||
    "jiti",
 | 
			
		||||
    "kolorist",
 | 
			
		||||
    "Laba",
 | 
			
		||||
    "localforage",
 | 
			
		||||
    "LOCALSTORAGE",
 | 
			
		||||
    "majesticons",
 | 
			
		||||
    "MEDZ",
 | 
			
		||||
    "nocheck",
 | 
			
		||||
    "nprogress",
 | 
			
		||||
    "ofetch",
 | 
			
		||||
    "pickr",
 | 
			
		||||
    "preflights",
 | 
			
		||||
    "sider",
 | 
			
		||||
    "simonwep",
 | 
			
		||||
    "simplebar",
 | 
			
		||||
    "tada",
 | 
			
		||||
    "tauri",
 | 
			
		||||
    "Uncapitalize",
 | 
			
		||||
    "unocss",
 | 
			
		||||
    "unplugin",
 | 
			
		||||
    "vditor",
 | 
			
		||||
    "VERCEL",
 | 
			
		||||
    "Vite",
 | 
			
		||||
    "vitejs",
 | 
			
		||||
    "vuedraggable",
 | 
			
		||||
    "VITE",
 | 
			
		||||
    "vitepress",
 | 
			
		||||
    "vueuse",
 | 
			
		||||
    "wangeditor",
 | 
			
		||||
    "wechat",
 | 
			
		||||
    "xgplayer",
 | 
			
		||||
    "yanbowe",
 | 
			
		||||
    "ភាសាខ្មែរ"
 | 
			
		||||
    "WEBSQL",
 | 
			
		||||
    "wechat"
 | 
			
		||||
  ],
 | 
			
		||||
  "editor.codeActionsOnSave": {
 | 
			
		||||
    "source.fixAll.eslint": true
 | 
			
		||||
@@ -56,22 +51,20 @@
 | 
			
		||||
    "strings": true
 | 
			
		||||
  },
 | 
			
		||||
  "editor.tabSize": 2,
 | 
			
		||||
  "eslint.validate": ["json"],
 | 
			
		||||
  "files.associations": {
 | 
			
		||||
    "*.env.*": "dotenv",
 | 
			
		||||
    "*.svg": "html"
 | 
			
		||||
  },
 | 
			
		||||
  "files.eol": "\n",
 | 
			
		||||
  "i18n-ally.displayLanguage": "zh-CN",
 | 
			
		||||
  "i18n-ally.displayLanguage": "zh-cn",
 | 
			
		||||
  "i18n-ally.enabledParsers": ["ts"],
 | 
			
		||||
  "i18n-ally.enabledFrameworks": ["vue"],
 | 
			
		||||
  "i18n-ally.editor.preferEditor": true,
 | 
			
		||||
  "i18n-ally.keystyle": "nested",
 | 
			
		||||
  "i18n-ally.localesPaths": ["src/locales/lang"],
 | 
			
		||||
  "material-icon-theme.activeIconPack": "vue",
 | 
			
		||||
  "[html][css][less][scss][sass][markdown][yaml][yml][jsonc]": {
 | 
			
		||||
  "unocss.root": ["./"],
 | 
			
		||||
  "[html][css][less][scss][sass][markdown][yaml][yml][json][jsonc]": {
 | 
			
		||||
    "editor.defaultFormatter": "esbenp.prettier-vscode",
 | 
			
		||||
    "editor.formatOnSave": true
 | 
			
		||||
  },
 | 
			
		||||
  "prettier": {}
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1275
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										1275
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								LICENSE
									
									
									
									
									
								
							@@ -1,21 +0,0 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2021 Soybean
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										16
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,16 +0,0 @@
 | 
			
		||||
ImageTag ?=v0.9.6
 | 
			
		||||
SoybeanAdminImg ?= soybeanjs/soybean-admin:$(ImageTag)
 | 
			
		||||
 | 
			
		||||
VERSION=$(shell git rev-parse --short HEAD)
 | 
			
		||||
 | 
			
		||||
soybean-admin: soybean-admin-build soybean-admin-push
 | 
			
		||||
 | 
			
		||||
soybean-admin-build:
 | 
			
		||||
	docker build --build-arg version=$(VERSION) -t ${SoybeanAdminImg} -f docker/Dockerfile .
 | 
			
		||||
 | 
			
		||||
soybean-admin-push:
 | 
			
		||||
	docker push ${SoybeanAdminImg}
 | 
			
		||||
 | 
			
		||||
# run tauri app:
 | 
			
		||||
run:
 | 
			
		||||
	pnpm tauri dev
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
 | 
			
		||||
/** 项目构建时间 */
 | 
			
		||||
const PROJECT_BUILD_TIME = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss'));
 | 
			
		||||
 | 
			
		||||
export const viteDefine = {
 | 
			
		||||
  PROJECT_BUILD_TIME
 | 
			
		||||
};
 | 
			
		||||
@@ -1,2 +1 @@
 | 
			
		||||
export * from './define';
 | 
			
		||||
export * from './proxy';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,38 @@
 | 
			
		||||
import type { ProxyOptions } from 'vite';
 | 
			
		||||
import { createServiceConfig, createProxyPattern } from '../../env.config';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 设置网络代理
 | 
			
		||||
 * @param isOpenProxy - 是否开启代理
 | 
			
		||||
 * @param envConfig - env环境配置
 | 
			
		||||
 * set http proxy
 | 
			
		||||
 * @param env - the current env
 | 
			
		||||
 */
 | 
			
		||||
export function createViteProxy(isOpenProxy: boolean, envConfig: ServiceEnvConfigWithProxyPattern) {
 | 
			
		||||
  if (!isOpenProxy) return undefined;
 | 
			
		||||
export function createViteProxy(env: Env.ImportMeta) {
 | 
			
		||||
  const isEnableHttpProxy = env.VITE_HTTP_PROXY === 'Y';
 | 
			
		||||
 | 
			
		||||
  const proxy: Record<string, string | ProxyOptions> = {
 | 
			
		||||
    [envConfig.proxyPattern]: {
 | 
			
		||||
      target: envConfig.url,
 | 
			
		||||
  if (!isEnableHttpProxy) return undefined;
 | 
			
		||||
 | 
			
		||||
  const { baseURL, otherBaseURL } = createServiceConfig(env);
 | 
			
		||||
 | 
			
		||||
  const defaultProxyPattern = createProxyPattern();
 | 
			
		||||
 | 
			
		||||
  const proxy: Record<string, ProxyOptions> = {
 | 
			
		||||
    [defaultProxyPattern]: {
 | 
			
		||||
      target: baseURL,
 | 
			
		||||
      changeOrigin: true,
 | 
			
		||||
      rewrite: path => path.replace(new RegExp(`^${envConfig.proxyPattern}`), '')
 | 
			
		||||
      rewrite: path => path.replace(new RegExp(`^${defaultProxyPattern}`), '')
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const otherURLEntries = Object.entries(otherBaseURL);
 | 
			
		||||
 | 
			
		||||
  for (const [key, url] of otherURLEntries) {
 | 
			
		||||
    const proxyPattern = createProxyPattern(key);
 | 
			
		||||
 | 
			
		||||
    proxy[proxyPattern] = {
 | 
			
		||||
      target: url,
 | 
			
		||||
      changeOrigin: true,
 | 
			
		||||
      rewrite: path => path.replace(new RegExp(`^${proxyPattern}`), '')
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return proxy;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
export * from './plugins';
 | 
			
		||||
export * from './config';
 | 
			
		||||
export * from './utils';
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
import ViteCompression from 'vite-plugin-compression';
 | 
			
		||||
 | 
			
		||||
export default (viteEnv: ImportMetaEnv) => {
 | 
			
		||||
  const { VITE_COMPRESS_TYPE = 'gzip' } = viteEnv;
 | 
			
		||||
  return ViteCompression({ algorithm: VITE_COMPRESS_TYPE });
 | 
			
		||||
};
 | 
			
		||||
@@ -1,22 +1,14 @@
 | 
			
		||||
import type { PluginOption } from 'vite';
 | 
			
		||||
import vue from '@vitejs/plugin-vue';
 | 
			
		||||
import vueJsx from '@vitejs/plugin-vue-jsx';
 | 
			
		||||
import unocss from '@unocss/vite';
 | 
			
		||||
import progress from 'vite-plugin-progress';
 | 
			
		||||
import VueDevtools from 'vite-plugin-vue-devtools';
 | 
			
		||||
import pageRoute from '@soybeanjs/vite-plugin-vue-page-route';
 | 
			
		||||
import unplugin from './unplugin';
 | 
			
		||||
import mock from './mock';
 | 
			
		||||
import visualizer from './visualizer';
 | 
			
		||||
import compress from './compress';
 | 
			
		||||
import pwa from './pwa';
 | 
			
		||||
import progress from 'vite-plugin-progress';
 | 
			
		||||
import { setupElegantRouter } from './router';
 | 
			
		||||
import { setupUnocss } from './unocss';
 | 
			
		||||
import { setupUnplugin } from './unplugin';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * vite插件
 | 
			
		||||
 * @param viteEnv - 环境变量配置
 | 
			
		||||
 */
 | 
			
		||||
export function setupVitePlugins(viteEnv: ImportMetaEnv): (PluginOption | PluginOption[])[] {
 | 
			
		||||
  const plugins = [
 | 
			
		||||
export function setupVitePlugins(viteEnv: Env.ImportMeta) {
 | 
			
		||||
  const plugins: PluginOption = [
 | 
			
		||||
    vue({
 | 
			
		||||
      script: {
 | 
			
		||||
        defineModel: true
 | 
			
		||||
@@ -24,24 +16,11 @@ export function setupVitePlugins(viteEnv: ImportMetaEnv): (PluginOption | Plugin
 | 
			
		||||
    }),
 | 
			
		||||
    vueJsx(),
 | 
			
		||||
    VueDevtools(),
 | 
			
		||||
    ...unplugin(viteEnv),
 | 
			
		||||
    unocss(),
 | 
			
		||||
    mock(viteEnv),
 | 
			
		||||
    setupElegantRouter(),
 | 
			
		||||
    setupUnocss(viteEnv),
 | 
			
		||||
    ...setupUnplugin(viteEnv),
 | 
			
		||||
    progress()
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  if (viteEnv.VITE_VISUALIZER === 'Y') {
 | 
			
		||||
    plugins.push(visualizer as PluginOption);
 | 
			
		||||
  }
 | 
			
		||||
  if (viteEnv.VITE_COMPRESS === 'Y') {
 | 
			
		||||
    plugins.push(compress(viteEnv));
 | 
			
		||||
  }
 | 
			
		||||
  if (viteEnv.VITE_PWA === 'Y' || viteEnv.VITE_VERCEL === 'Y') {
 | 
			
		||||
    plugins.push(pwa());
 | 
			
		||||
  }
 | 
			
		||||
  if (viteEnv.VITE_SOYBEAN_ROUTE_PLUGIN === 'Y') {
 | 
			
		||||
    plugins.push(pageRoute());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return plugins;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
import { viteMockServe } from 'vite-plugin-mock';
 | 
			
		||||
 | 
			
		||||
export default (viteEnv: ImportMetaEnv) => {
 | 
			
		||||
  const prodMock = viteEnv.VITE_PROD_MOCK === 'Y';
 | 
			
		||||
 | 
			
		||||
  return viteMockServe({
 | 
			
		||||
    mockPath: 'mock',
 | 
			
		||||
    prodEnabled: prodMock,
 | 
			
		||||
    injectCode: `
 | 
			
		||||
			import { setupMockServer } from '../mock';
 | 
			
		||||
			setupMockServer();
 | 
			
		||||
		`
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
import { VitePWA } from 'vite-plugin-pwa';
 | 
			
		||||
 | 
			
		||||
export default function setupVitePwa() {
 | 
			
		||||
  return VitePWA({
 | 
			
		||||
    registerType: 'autoUpdate',
 | 
			
		||||
    includeAssets: ['favicon.ico'],
 | 
			
		||||
    manifest: {
 | 
			
		||||
      name: 'SoybeanAdmin',
 | 
			
		||||
      short_name: 'SoybeanAdmin',
 | 
			
		||||
      theme_color: '#fff',
 | 
			
		||||
      icons: [
 | 
			
		||||
        {
 | 
			
		||||
          src: '/logo.png',
 | 
			
		||||
          sizes: '192x192',
 | 
			
		||||
          type: 'image/png'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: '/logo.png',
 | 
			
		||||
          sizes: '512x512',
 | 
			
		||||
          type: 'image/png'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: '/logo.png',
 | 
			
		||||
          sizes: '512x512',
 | 
			
		||||
          type: 'image/png',
 | 
			
		||||
          purpose: 'any maskable'
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								build/plugins/router.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								build/plugins/router.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import type { RouteMeta } from 'vue-router';
 | 
			
		||||
import ElegantVueRouter from '@elegant-router/vue/vite';
 | 
			
		||||
import type { RouteKey } from '@elegant-router/types';
 | 
			
		||||
 | 
			
		||||
export function setupElegantRouter() {
 | 
			
		||||
  return ElegantVueRouter({
 | 
			
		||||
    layouts: {
 | 
			
		||||
      base: 'src/layouts/base-layout/index.vue',
 | 
			
		||||
      blank: 'src/layouts/blank-layout/index.vue'
 | 
			
		||||
    },
 | 
			
		||||
    routePathTransformer(routeName, routePath) {
 | 
			
		||||
      const key = routeName as RouteKey;
 | 
			
		||||
 | 
			
		||||
      if (key === 'login') {
 | 
			
		||||
        const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
 | 
			
		||||
 | 
			
		||||
        const moduleReg = modules.join('|');
 | 
			
		||||
 | 
			
		||||
        return `/login/:module(${moduleReg})?`;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return routePath;
 | 
			
		||||
    },
 | 
			
		||||
    onRouteMetaGen(routeName) {
 | 
			
		||||
      const key = routeName as RouteKey;
 | 
			
		||||
 | 
			
		||||
      const constantRoutes: RouteKey[] = ['login', '403', '404', '500'];
 | 
			
		||||
 | 
			
		||||
      const meta: Partial<RouteMeta> = {
 | 
			
		||||
        title: key,
 | 
			
		||||
        i18nKey: `route.${key}` as App.I18n.I18nKey
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (constantRoutes.includes(key)) {
 | 
			
		||||
        meta.constant = true;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return meta;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								build/plugins/unocss.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								build/plugins/unocss.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import path from 'node:path';
 | 
			
		||||
import unocss from '@unocss/vite';
 | 
			
		||||
import presetIcons from '@unocss/preset-icons';
 | 
			
		||||
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders';
 | 
			
		||||
 | 
			
		||||
export function setupUnocss(viteEnv: Env.ImportMeta) {
 | 
			
		||||
  const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
 | 
			
		||||
 | 
			
		||||
  const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon');
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * the name of the local icon collection
 | 
			
		||||
   */
 | 
			
		||||
  const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, '');
 | 
			
		||||
 | 
			
		||||
  return unocss({
 | 
			
		||||
    presets: [
 | 
			
		||||
      presetIcons({
 | 
			
		||||
        prefix: `${VITE_ICON_PREFIX}-`,
 | 
			
		||||
        scale: 1,
 | 
			
		||||
        extraProperties: {
 | 
			
		||||
          display: 'inline-block'
 | 
			
		||||
        },
 | 
			
		||||
        collections: {
 | 
			
		||||
          [collectionName]: FileSystemIconLoader(localIconPath, svg =>
 | 
			
		||||
            svg.replace(/^<svg\s/, '<svg width="1em" height="1em" ')
 | 
			
		||||
          )
 | 
			
		||||
        },
 | 
			
		||||
        warn: true
 | 
			
		||||
      })
 | 
			
		||||
    ]
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +1,23 @@
 | 
			
		||||
import path from 'node:path';
 | 
			
		||||
import type { PluginOption } from 'vite';
 | 
			
		||||
import Icons from 'unplugin-icons/vite';
 | 
			
		||||
import IconsResolver from 'unplugin-icons/resolver';
 | 
			
		||||
import Components from 'unplugin-vue-components/vite';
 | 
			
		||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
 | 
			
		||||
import { AntDesignVueResolver, NaiveUiResolver } from 'unplugin-vue-components/resolvers';
 | 
			
		||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
 | 
			
		||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
 | 
			
		||||
import { getSrcPath } from '../utils';
 | 
			
		||||
 | 
			
		||||
export default function unplugin(viteEnv: ImportMetaEnv) {
 | 
			
		||||
export function setupUnplugin(viteEnv: Env.ImportMeta) {
 | 
			
		||||
  const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
 | 
			
		||||
 | 
			
		||||
  const srcPath = getSrcPath();
 | 
			
		||||
  const localIconPath = `${srcPath}/assets/svg-icon`;
 | 
			
		||||
  const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon');
 | 
			
		||||
 | 
			
		||||
  /** 本地svg图标集合名称 */
 | 
			
		||||
  /**
 | 
			
		||||
   * the name of the local icon collection
 | 
			
		||||
   */
 | 
			
		||||
  const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, '');
 | 
			
		||||
 | 
			
		||||
  return [
 | 
			
		||||
  const plugins: PluginOption[] = [
 | 
			
		||||
    Icons({
 | 
			
		||||
      compiler: 'vue3',
 | 
			
		||||
      customCollections: {
 | 
			
		||||
@@ -30,6 +32,9 @@ export default function unplugin(viteEnv: ImportMetaEnv) {
 | 
			
		||||
      dts: 'src/typings/components.d.ts',
 | 
			
		||||
      types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
 | 
			
		||||
      resolvers: [
 | 
			
		||||
        AntDesignVueResolver({
 | 
			
		||||
          importStyle: false
 | 
			
		||||
        }),
 | 
			
		||||
        NaiveUiResolver(),
 | 
			
		||||
        IconsResolver({ customCollections: [collectionName], componentPrefix: VITE_ICON_PREFIX })
 | 
			
		||||
      ]
 | 
			
		||||
@@ -41,4 +46,6 @@ export default function unplugin(viteEnv: ImportMetaEnv) {
 | 
			
		||||
      customDomId: '__SVG_ICON_LOCAL__'
 | 
			
		||||
    })
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  return plugins;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
import { visualizer } from 'rollup-plugin-visualizer';
 | 
			
		||||
 | 
			
		||||
export default visualizer({
 | 
			
		||||
  gzipSize: true,
 | 
			
		||||
  brotliSize: true,
 | 
			
		||||
  open: true
 | 
			
		||||
});
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取项目根路径
 | 
			
		||||
 * @descrition 末尾不带斜杠
 | 
			
		||||
 */
 | 
			
		||||
export function getRootPath() {
 | 
			
		||||
  return path.resolve(process.cwd());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取项目src路径
 | 
			
		||||
 * @param srcName - src目录名称(默认: "src")
 | 
			
		||||
 * @descrition 末尾不带斜杠
 | 
			
		||||
 */
 | 
			
		||||
export function getSrcPath(srcName = 'src') {
 | 
			
		||||
  const rootPath = getRootPath();
 | 
			
		||||
 | 
			
		||||
  return `${rootPath}/${srcName}`;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
node_modules
 | 
			
		||||
.DS_Store
 | 
			
		||||
dist
 | 
			
		||||
.npmrc
 | 
			
		||||
.cache
 | 
			
		||||
 | 
			
		||||
tests/server/static
 | 
			
		||||
tests/server/static/upload
 | 
			
		||||
 | 
			
		||||
.local
 | 
			
		||||
# local env files
 | 
			
		||||
.env.local
 | 
			
		||||
.env.*.local
 | 
			
		||||
.eslintcache
 | 
			
		||||
 | 
			
		||||
# Log files
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
yarn-debug.log*
 | 
			
		||||
yarn-error.log*
 | 
			
		||||
pnpm-debug.log*
 | 
			
		||||
 | 
			
		||||
# Editor directories and files
 | 
			
		||||
.idea
 | 
			
		||||
# .vscode
 | 
			
		||||
*.suo
 | 
			
		||||
*.ntvs*
 | 
			
		||||
*.njsproj
 | 
			
		||||
*.sln
 | 
			
		||||
*.sw?
 | 
			
		||||
yarn.lock
 | 
			
		||||
pnpm-lock.yaml
 | 
			
		||||
/vite-profile.cpuprofile
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
FROM node:16.17.0 as builder
 | 
			
		||||
 | 
			
		||||
ENV WORKDIR=/soybean-admin
 | 
			
		||||
 | 
			
		||||
WORKDIR $WORKDIR
 | 
			
		||||
 | 
			
		||||
COPY ./ $WORKDIR/
 | 
			
		||||
 | 
			
		||||
ARG version
 | 
			
		||||
ENV COMMITID=$version
 | 
			
		||||
 | 
			
		||||
RUN npm i -g pnpm
 | 
			
		||||
 | 
			
		||||
RUN pnpm install
 | 
			
		||||
RUN pnpm build
 | 
			
		||||
 | 
			
		||||
FROM nginx:alpine as prod
 | 
			
		||||
 | 
			
		||||
RUN mkdir /soybean
 | 
			
		||||
 | 
			
		||||
COPY --from=builder /soybean-admin/dist /soybean-admin
 | 
			
		||||
COPY --from=builder /soybean-admin/docker/nginx.conf /etc/nginx/nginx.conf
 | 
			
		||||
 | 
			
		||||
EXPOSE 80
 | 
			
		||||
@@ -1,54 +0,0 @@
 | 
			
		||||
user  nginx;
 | 
			
		||||
worker_processes  1;
 | 
			
		||||
error_log  /var/log/nginx/error.log warn;
 | 
			
		||||
pid        /var/run/nginx.pid;
 | 
			
		||||
 | 
			
		||||
events {
 | 
			
		||||
  worker_connections  1024;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
http {
 | 
			
		||||
  include       /etc/nginx/mime.types;
 | 
			
		||||
  default_type  application/octet-stream;
 | 
			
		||||
  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
 | 
			
		||||
                    '$status $body_bytes_sent "$http_referer" '
 | 
			
		||||
                    '"$http_user_agent" "$http_x_forwarded_for"';
 | 
			
		||||
  access_log  /var/log/nginx/access.log  main;
 | 
			
		||||
  sendfile        on;
 | 
			
		||||
  keepalive_timeout  65;
 | 
			
		||||
 | 
			
		||||
  server {
 | 
			
		||||
    listen       80;
 | 
			
		||||
    server_name  localhost;
 | 
			
		||||
 | 
			
		||||
    location / {
 | 
			
		||||
      # 不缓存html,防止程序更新后缓存继续生效
 | 
			
		||||
      if ($request_filename ~* .*\.(?:htm|html)$) {
 | 
			
		||||
        add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
 | 
			
		||||
        access_log on;
 | 
			
		||||
      }
 | 
			
		||||
      root   /soybean-admin/;
 | 
			
		||||
      index  index.html index.htm;
 | 
			
		||||
      try_files $uri $uri/ /index.html;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # location /soybean/soybean-webserver/v1 {
 | 
			
		||||
    #     proxy_set_header Host $host;
 | 
			
		||||
    #     proxy_set_header X-Real-IP $remote_addr;
 | 
			
		||||
    #     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
			
		||||
    #     proxy_set_header REMOTE-HOST $remote_addr;
 | 
			
		||||
 | 
			
		||||
    #     # 后台接口地址
 | 
			
		||||
    #     proxy_pass http://192.168.1.99:30597/v1;
 | 
			
		||||
    #     proxy_redirect default;
 | 
			
		||||
    #     add_header Access-Control-Allow-Origin *;
 | 
			
		||||
    #     add_header Access-Control-Allow-Headers X-Requested-With;
 | 
			
		||||
    #     add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
 | 
			
		||||
    # }
 | 
			
		||||
 | 
			
		||||
    error_page   500 502 503 504  /50x.html;
 | 
			
		||||
    location = /50x.html {
 | 
			
		||||
      root   /usr/share/nginx/html;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								env.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								env.config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
/**
 | 
			
		||||
 * create service config by current env
 | 
			
		||||
 * @param env the current env
 | 
			
		||||
 */
 | 
			
		||||
export function createServiceConfig(env: Env.ImportMeta) {
 | 
			
		||||
  const mockURL = 'https://mock.apifox.com/m1/3109515-0-default';
 | 
			
		||||
 | 
			
		||||
  const serviceConfigMap = {
 | 
			
		||||
    dev: {
 | 
			
		||||
      baseURL: mockURL,
 | 
			
		||||
      otherBaseURL: {
 | 
			
		||||
        demo: 'http://localhost:9528'
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    test: {
 | 
			
		||||
      baseURL: mockURL,
 | 
			
		||||
      otherBaseURL: {
 | 
			
		||||
        demo: 'http://localhost:9529'
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    prod: {
 | 
			
		||||
      baseURL: mockURL,
 | 
			
		||||
      otherBaseURL: {
 | 
			
		||||
        demo: 'http://localhost:9530'
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } satisfies App.Service.ServiceConfigMap;
 | 
			
		||||
 | 
			
		||||
  const { VITE_SERVICE_ENV = 'dev' } = env;
 | 
			
		||||
 | 
			
		||||
  return serviceConfigMap[VITE_SERVICE_ENV];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * get proxy pattern of service url
 | 
			
		||||
 * @param key if not set, will use the default key
 | 
			
		||||
 */
 | 
			
		||||
export function createProxyPattern(key?: string) {
 | 
			
		||||
  if (!key) {
 | 
			
		||||
    return '/proxy';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return `/proxy-${key}`;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								index.html
									
									
									
									
									
								
							@@ -1,16 +1,15 @@
 | 
			
		||||
<!-- prettier-ignore -->
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="zh-cmn-Hans">
 | 
			
		||||
	<head>
 | 
			
		||||
		<meta charset="UTF-8" />
 | 
			
		||||
		<link rel="icon" href="/favicon.svg" />
 | 
			
		||||
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
		<title>%VITE_APP_NAME%</title>
 | 
			
		||||
	</head>
 | 
			
		||||
	<body>
 | 
			
		||||
		<div id="app">
 | 
			
		||||
			<div id="appLoading"></div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<script type="module" src="/src/main.ts"></script>
 | 
			
		||||
	</body>
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="UTF-8" />
 | 
			
		||||
    <link rel="icon" href="/favicon.svg" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>%VITE_APP_TITLE%</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <div id="app">
 | 
			
		||||
      <div id="appLoading"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script type="module" src="/src/main.ts"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										128
									
								
								mock/api/auth.ts
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								mock/api/auth.ts
									
									
									
									
									
								
							@@ -1,128 +0,0 @@
 | 
			
		||||
import type { MockMethod } from 'vite-plugin-mock';
 | 
			
		||||
import { userModel } from '../model';
 | 
			
		||||
 | 
			
		||||
/** 参数错误的状态码 */
 | 
			
		||||
const ERROR_PARAM_CODE = 10000;
 | 
			
		||||
 | 
			
		||||
const ERROR_PARAM_MSG = '参数校验失败!';
 | 
			
		||||
 | 
			
		||||
const apis: MockMethod[] = [
 | 
			
		||||
  // 获取验证码
 | 
			
		||||
  {
 | 
			
		||||
    url: '/mock/getSmsCode',
 | 
			
		||||
    method: 'post',
 | 
			
		||||
    response: (): Service.MockServiceResult<boolean> => {
 | 
			
		||||
      return {
 | 
			
		||||
        code: 200,
 | 
			
		||||
        message: 'ok',
 | 
			
		||||
        data: true
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  // 用户+密码 登录
 | 
			
		||||
  {
 | 
			
		||||
    url: '/mock/login',
 | 
			
		||||
    method: 'post',
 | 
			
		||||
    response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {
 | 
			
		||||
      const { userName = undefined, password = undefined } = options.body;
 | 
			
		||||
 | 
			
		||||
      if (!userName || !password) {
 | 
			
		||||
        return {
 | 
			
		||||
          code: ERROR_PARAM_CODE,
 | 
			
		||||
          message: ERROR_PARAM_MSG,
 | 
			
		||||
          data: null
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const findItem = userModel.find(item => item.userName === userName && item.password === password);
 | 
			
		||||
 | 
			
		||||
      if (findItem) {
 | 
			
		||||
        return {
 | 
			
		||||
          code: 200,
 | 
			
		||||
          message: 'ok',
 | 
			
		||||
          data: {
 | 
			
		||||
            token: findItem.token,
 | 
			
		||||
            refreshToken: findItem.refreshToken
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        code: 1000,
 | 
			
		||||
        message: '用户名或密码错误!',
 | 
			
		||||
        data: null
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  // 获取用户信息(请求头携带token, 根据token获取用户信息)
 | 
			
		||||
  {
 | 
			
		||||
    url: '/mock/getUserInfo',
 | 
			
		||||
    method: 'get',
 | 
			
		||||
    response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.UserInfo | null> => {
 | 
			
		||||
      // 这里的mock插件得到的字段是authorization, 前端传递的是Authorization字段
 | 
			
		||||
      const { authorization = '' } = options.headers;
 | 
			
		||||
      const REFRESH_TOKEN_CODE = 66666;
 | 
			
		||||
 | 
			
		||||
      if (!authorization) {
 | 
			
		||||
        return {
 | 
			
		||||
          code: REFRESH_TOKEN_CODE,
 | 
			
		||||
          message: '用户已失效或不存在!',
 | 
			
		||||
          data: null
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
      const userInfo: Auth.UserInfo = {
 | 
			
		||||
        userId: '',
 | 
			
		||||
        userName: '',
 | 
			
		||||
        userRole: 'user'
 | 
			
		||||
      };
 | 
			
		||||
      const isInUser = userModel.some(item => {
 | 
			
		||||
        const flag = item.token === authorization;
 | 
			
		||||
        if (flag) {
 | 
			
		||||
          const { userId: itemUserId, userName, userRole } = item;
 | 
			
		||||
          Object.assign(userInfo, { userId: itemUserId, userName, userRole });
 | 
			
		||||
        }
 | 
			
		||||
        return flag;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (isInUser) {
 | 
			
		||||
        return {
 | 
			
		||||
          code: 200,
 | 
			
		||||
          message: 'ok',
 | 
			
		||||
          data: userInfo
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        code: REFRESH_TOKEN_CODE,
 | 
			
		||||
        message: '用户信息异常!',
 | 
			
		||||
        data: null
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    url: '/mock/updateToken',
 | 
			
		||||
    method: 'post',
 | 
			
		||||
    response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {
 | 
			
		||||
      const { refreshToken = '' } = options.body;
 | 
			
		||||
 | 
			
		||||
      const findItem = userModel.find(item => item.refreshToken === refreshToken);
 | 
			
		||||
 | 
			
		||||
      if (findItem) {
 | 
			
		||||
        return {
 | 
			
		||||
          code: 200,
 | 
			
		||||
          message: 'ok',
 | 
			
		||||
          data: {
 | 
			
		||||
            token: findItem.token,
 | 
			
		||||
            refreshToken: findItem.refreshToken
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        code: 3000,
 | 
			
		||||
        message: '用户已失效或不存在!',
 | 
			
		||||
        data: null
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default apis;
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
import auth from './auth';
 | 
			
		||||
import route from './route';
 | 
			
		||||
import management from './management';
 | 
			
		||||
 | 
			
		||||
export default [...auth, ...route, ...management];
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
import { mock } from 'mockjs';
 | 
			
		||||
import type { MockMethod } from 'vite-plugin-mock';
 | 
			
		||||
 | 
			
		||||
const apis: MockMethod[] = [
 | 
			
		||||
  {
 | 
			
		||||
    url: '/mock/getAllUserList',
 | 
			
		||||
    method: 'post',
 | 
			
		||||
    response: (): Service.MockServiceResult<ApiUserManagement.User[]> => {
 | 
			
		||||
      const data = mock({
 | 
			
		||||
        'list|1000': [
 | 
			
		||||
          {
 | 
			
		||||
            id: '@id',
 | 
			
		||||
            userName: '@cname',
 | 
			
		||||
            'age|18-56': 56,
 | 
			
		||||
            'gender|1': ['0', '1', null],
 | 
			
		||||
            phone:
 | 
			
		||||
              /^[1](([3][0-9])|([4][01456789])|([5][012356789])|([6][2567])|([7][0-8])|([8][0-9])|([9][012356789]))[0-9]{8}$/,
 | 
			
		||||
            'email|1': ['@email("qq.com")', null],
 | 
			
		||||
            'userStatus|1': ['1', '2', '3', '4', null]
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        code: 200,
 | 
			
		||||
        message: 'ok',
 | 
			
		||||
        data: data.list
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default apis;
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
import type { MockMethod } from 'vite-plugin-mock';
 | 
			
		||||
import { routeModel, userModel } from '../model';
 | 
			
		||||
 | 
			
		||||
const apis: MockMethod[] = [
 | 
			
		||||
  {
 | 
			
		||||
    url: '/mock/getUserRoutes',
 | 
			
		||||
    method: 'post',
 | 
			
		||||
    response: (options: Service.MockOption): Service.MockServiceResult => {
 | 
			
		||||
      const { userId = undefined } = options.body;
 | 
			
		||||
 | 
			
		||||
      const routeHomeName: AuthRoute.LastDegreeRouteKey = 'dashboard_analysis';
 | 
			
		||||
 | 
			
		||||
      const role = userModel.find(item => item.userId === userId)?.userRole || 'user';
 | 
			
		||||
 | 
			
		||||
      const filterRoutes = routeModel[role];
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        code: 200,
 | 
			
		||||
        message: 'ok',
 | 
			
		||||
        data: {
 | 
			
		||||
          routes: filterRoutes,
 | 
			
		||||
          home: routeHomeName
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default apis;
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
 | 
			
		||||
import api from './api';
 | 
			
		||||
 | 
			
		||||
export function setupMockServer() {
 | 
			
		||||
  createProdMockServer(api);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
interface UserModel extends Auth.UserInfo {
 | 
			
		||||
  token: string;
 | 
			
		||||
  refreshToken: string;
 | 
			
		||||
  password: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const userModel: UserModel[] = [
 | 
			
		||||
  {
 | 
			
		||||
    token: '__TOKEN_SOYBEAN__',
 | 
			
		||||
    refreshToken: '__REFRESH_TOKEN_SOYBEAN__',
 | 
			
		||||
    userId: '0',
 | 
			
		||||
    userName: 'Soybean',
 | 
			
		||||
    userRole: 'super',
 | 
			
		||||
    password: 'soybean123'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    token: '__TOKEN_SUPER__',
 | 
			
		||||
    refreshToken: '__REFRESH_TOKEN_SUPER__',
 | 
			
		||||
    userId: '1',
 | 
			
		||||
    userName: 'Super',
 | 
			
		||||
    userRole: 'super',
 | 
			
		||||
    password: 'super123'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    token: '__TOKEN_ADMIN__',
 | 
			
		||||
    refreshToken: '__REFRESH_TOKEN_ADMIN__',
 | 
			
		||||
    userId: '2',
 | 
			
		||||
    userName: 'Admin',
 | 
			
		||||
    userRole: 'admin',
 | 
			
		||||
    password: 'admin123'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    token: '__TOKEN_USER01__',
 | 
			
		||||
    refreshToken: '__REFRESH_TOKEN_USER01__',
 | 
			
		||||
    userId: '3',
 | 
			
		||||
    userName: 'User01',
 | 
			
		||||
    userRole: 'user',
 | 
			
		||||
    password: 'user01123'
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
export * from './auth';
 | 
			
		||||
export * from './route';
 | 
			
		||||
							
								
								
									
										1263
									
								
								mock/model/route.ts
									
									
									
									
									
								
							
							
						
						
									
										1263
									
								
								mock/model/route.ts
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										148
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								package.json
									
									
									
									
									
								
							@@ -1,134 +1,76 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "soybean-admin",
 | 
			
		||||
  "version": "0.10.4",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "A fresh and elegant admin template, based on Vue3、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
 | 
			
		||||
  "author": {
 | 
			
		||||
    "name": "Soybean",
 | 
			
		||||
    "email": "soybeanjs@outlook.com",
 | 
			
		||||
    "url": "https://github.com/soybeanjs"
 | 
			
		||||
  },
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "homepage": "https://github.com/honghuangdc/soybean-admin",
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "url": "https://github.com/honghuangdc/soybean-admin.git"
 | 
			
		||||
  },
 | 
			
		||||
  "bugs": {
 | 
			
		||||
    "url": "https://github.com/honghuangdc/soybean-admin/issues"
 | 
			
		||||
  },
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "Vue",
 | 
			
		||||
    "Vue3",
 | 
			
		||||
    "admin",
 | 
			
		||||
    "admin-template",
 | 
			
		||||
    "vue-admin",
 | 
			
		||||
    "vue-admin-template",
 | 
			
		||||
    "Vite3",
 | 
			
		||||
    "Vite",
 | 
			
		||||
    "vite-admin",
 | 
			
		||||
    "TypeScript",
 | 
			
		||||
    "TS",
 | 
			
		||||
    "NaiveUI",
 | 
			
		||||
    "naive-ui",
 | 
			
		||||
    "naive-admin",
 | 
			
		||||
    "NaiveUI-Admin",
 | 
			
		||||
    "naive-ui-admin",
 | 
			
		||||
    "UnoCSS"
 | 
			
		||||
  ],
 | 
			
		||||
  "packageManager": "pnpm@8.10.2",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "cross-env VITE_SERVICE_ENV=dev vite",
 | 
			
		||||
    "dev:test": "cross-env VITE_SERVICE_ENV=test vite",
 | 
			
		||||
    "dev:prod": "cross-env VITE_SERVICE_ENV=prod vite",
 | 
			
		||||
    "build": "npm run typecheck && cross-env VITE_SERVICE_ENV=prod vite build",
 | 
			
		||||
    "build:dev": "npm run typecheck && cross-env VITE_SERVICE_ENV=dev vite build",
 | 
			
		||||
    "build:test": "npm run typecheck && cross-env VITE_SERVICE_ENV=test vite build",
 | 
			
		||||
    "build:vercel": "cross-env VITE_HASH_ROUTE=Y VITE_VERCEL=Y vite build",
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
    "build": "run-s typecheck build-only",
 | 
			
		||||
    "preview": "vite preview",
 | 
			
		||||
    "build-only": "vite build",
 | 
			
		||||
    "typecheck": "vue-tsc --noEmit --skipLibCheck",
 | 
			
		||||
    "lint": "eslint . --fix",
 | 
			
		||||
    "format": "soy prettier-write",
 | 
			
		||||
    "commit": "soy git-commit",
 | 
			
		||||
    "cleanup": "soy cleanup",
 | 
			
		||||
    "update-pkg": "soy ncu",
 | 
			
		||||
    "release": "soy release",
 | 
			
		||||
    "tsx": "tsx",
 | 
			
		||||
    "logo": "tsx ./scripts/logo.ts",
 | 
			
		||||
    "prepare": "soy init-simple-git-hooks"
 | 
			
		||||
    "format": "sa prettier-write",
 | 
			
		||||
    "commit": "sa git-commit",
 | 
			
		||||
    "cleanup": "sa cleanup",
 | 
			
		||||
    "update-pkg": "sa update-pkg",
 | 
			
		||||
    "prepare": "simple-git-hooks"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@antv/data-set": "0.11.8",
 | 
			
		||||
    "@antv/g2": "4.2.10",
 | 
			
		||||
    "@better-scroll/core": "2.5.1",
 | 
			
		||||
    "@soybeanjs/vue-materials": "0.2.0",
 | 
			
		||||
    "@vueuse/core": "10.5.0",
 | 
			
		||||
    "axios": "1.5.1",
 | 
			
		||||
    "@iconify/vue": "4.1.1",
 | 
			
		||||
    "@sa/color-palette": "workspace:*",
 | 
			
		||||
    "@sa/hooks": "workspace:*",
 | 
			
		||||
    "@sa/materials": "workspace:*",
 | 
			
		||||
    "@sa/request": "workspace:*",
 | 
			
		||||
    "@sa/utils": "workspace:*",
 | 
			
		||||
    "@vueuse/core": "10.6.1",
 | 
			
		||||
    "clipboard": "2.0.11",
 | 
			
		||||
    "colord": "2.9.3",
 | 
			
		||||
    "crypto-js": "4.1.1",
 | 
			
		||||
    "dayjs": "1.11.10",
 | 
			
		||||
    "echarts": "5.4.3",
 | 
			
		||||
    "form-data": "4.0.0",
 | 
			
		||||
    "dayjs": "^1.11.10",
 | 
			
		||||
    "lodash-es": "4.17.21",
 | 
			
		||||
    "naive-ui": "2.35.0",
 | 
			
		||||
    "pinia": "2.1.6",
 | 
			
		||||
    "print-js": "1.6.0",
 | 
			
		||||
    "qs": "6.11.2",
 | 
			
		||||
    "socket.io-client": "4.7.2",
 | 
			
		||||
    "swiper": "10.3.1",
 | 
			
		||||
    "ua-parser-js": "1.0.36",
 | 
			
		||||
    "vditor": "3.9.6",
 | 
			
		||||
    "vue": "3.3.4",
 | 
			
		||||
    "vue-i18n": "9.5.0",
 | 
			
		||||
    "vue-router": "4.2.5",
 | 
			
		||||
    "vuedraggable": "4.1.0",
 | 
			
		||||
    "wangeditor": "4.7.15",
 | 
			
		||||
    "xgplayer": "3.0.9"
 | 
			
		||||
    "naive-ui": "^2.35.0",
 | 
			
		||||
    "nprogress": "0.2.0",
 | 
			
		||||
    "pinia": "2.1.7",
 | 
			
		||||
    "vue": "3.3.8",
 | 
			
		||||
    "vue-i18n": "9.6.5",
 | 
			
		||||
    "vue-router": "4.2.5"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@amap/amap-jsapi-types": "0.0.13",
 | 
			
		||||
    "@iconify/json": "2.2.128",
 | 
			
		||||
    "@iconify/vue": "4.1.1",
 | 
			
		||||
    "@soybeanjs/cli": "0.7.4",
 | 
			
		||||
    "@soybeanjs/vite-plugin-vue-page-route": "0.0.10",
 | 
			
		||||
    "@types/bmapgl": "0.0.7",
 | 
			
		||||
    "@types/crypto-js": "4.1.2",
 | 
			
		||||
    "@types/node": "20.8.4",
 | 
			
		||||
    "@types/qs": "6.9.8",
 | 
			
		||||
    "@types/ua-parser-js": "0.7.37",
 | 
			
		||||
    "@unocss/preset-uno": "0.56.5",
 | 
			
		||||
    "@unocss/transformer-directives": "0.56.5",
 | 
			
		||||
    "@unocss/vite": "0.56.5",
 | 
			
		||||
    "@vitejs/plugin-vue": "4.4.0",
 | 
			
		||||
    "@elegant-router/vue": "0.3.0",
 | 
			
		||||
    "@iconify/json": "2.2.142",
 | 
			
		||||
    "@sa/scripts": "workspace:*",
 | 
			
		||||
    "@sa/uno-preset": "workspace:*",
 | 
			
		||||
    "@types/lodash-es": "4.17.11",
 | 
			
		||||
    "@types/node": "20.9.0",
 | 
			
		||||
    "@types/nprogress": "0.2.3",
 | 
			
		||||
    "@unocss/preset-icons": "0.57.4",
 | 
			
		||||
    "@unocss/preset-uno": "0.57.4",
 | 
			
		||||
    "@unocss/transformer-directives": "0.57.4",
 | 
			
		||||
    "@unocss/transformer-variant-group": "0.57.4",
 | 
			
		||||
    "@unocss/vite": "0.57.4",
 | 
			
		||||
    "@vitejs/plugin-vue": "4.4.1",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "3.0.2",
 | 
			
		||||
    "cross-env": "7.0.3",
 | 
			
		||||
    "eslint": "8.51.0",
 | 
			
		||||
    "eslint-config-soybeanjs": "0.5.7",
 | 
			
		||||
    "mockjs": "1.1.0",
 | 
			
		||||
    "rollup-plugin-visualizer": "5.9.2",
 | 
			
		||||
    "sass": "1.69.3",
 | 
			
		||||
    "eslint-config-sa": "workspace:*",
 | 
			
		||||
    "npm-run-all": "4.1.5",
 | 
			
		||||
    "sass": "^1.69.5",
 | 
			
		||||
    "simple-git-hooks": "2.9.0",
 | 
			
		||||
    "tsx": "3.13.0",
 | 
			
		||||
    "typescript": "5.2.2",
 | 
			
		||||
    "unplugin-icons": "0.17.0",
 | 
			
		||||
    "unplugin-icons": "0.17.4",
 | 
			
		||||
    "unplugin-vue-components": "0.25.2",
 | 
			
		||||
    "vite": "4.4.11",
 | 
			
		||||
    "vite-plugin-compression": "0.5.1",
 | 
			
		||||
    "vite-plugin-mock": "2.9.8",
 | 
			
		||||
    "vite": "4.5.0",
 | 
			
		||||
    "vite-plugin-progress": "0.0.7",
 | 
			
		||||
    "vite-plugin-pwa": "0.16.5",
 | 
			
		||||
    "vite-plugin-svg-icons": "2.0.1",
 | 
			
		||||
    "vite-plugin-vue-devtools": "1.0.0-rc.5",
 | 
			
		||||
    "vue-tsc": "1.8.19"
 | 
			
		||||
  },
 | 
			
		||||
  "pnpm": {
 | 
			
		||||
    "patchedDependencies": {
 | 
			
		||||
      "mockjs@1.1.0": "patches/mockjs@1.1.0.patch"
 | 
			
		||||
    }
 | 
			
		||||
    "vue-tsc": "1.8.22"
 | 
			
		||||
  },
 | 
			
		||||
  "simple-git-hooks": {
 | 
			
		||||
    "commit-msg": "pnpm soy git-commit-verify",
 | 
			
		||||
    "pre-commit": "pnpm typecheck && pnpm soy lint-staged"
 | 
			
		||||
  },
 | 
			
		||||
  "soybean": {
 | 
			
		||||
    "useSoybeanToken": true
 | 
			
		||||
    "commit-msg": "pnpm sa git-commit-verify",
 | 
			
		||||
    "pre-commit": "pnpm typecheck && pnpm sa lint-staged"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								packages/color-palette/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/color-palette/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@sa/color-palette",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "exports": {
 | 
			
		||||
    ".": "./src/index.ts"
 | 
			
		||||
  },
 | 
			
		||||
  "typesVersions": {
 | 
			
		||||
    "*": {
 | 
			
		||||
      "*": [
 | 
			
		||||
        "./src/*"
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "colord": "2.9.3"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								packages/color-palette/src/color.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								packages/color-palette/src/color.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
import { colord, extend } from 'colord';
 | 
			
		||||
import type { HslColor } from 'colord';
 | 
			
		||||
import labPlugin from 'colord/plugins/lab';
 | 
			
		||||
 | 
			
		||||
extend([labPlugin]);
 | 
			
		||||
 | 
			
		||||
export function isValidColor(color: string) {
 | 
			
		||||
  return colord(color).isValid();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getHex(color: string) {
 | 
			
		||||
  return colord(color).toHex();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getRgb(color: string) {
 | 
			
		||||
  return colord(color).toRgb();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getHsl(color: string) {
 | 
			
		||||
  return colord(color).toHsl();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getDeltaE(color1: string, color2: string) {
 | 
			
		||||
  return colord(color1).delta(color2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function transformHslToHex(color: HslColor) {
 | 
			
		||||
  return colord(color).toHex();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								packages/color-palette/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								packages/color-palette/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import { getColorPaletteFamily } from './palette';
 | 
			
		||||
import { getColorName } from './name';
 | 
			
		||||
import type { ColorPalette, ColorPaletteNumber, ColorPaletteItem, ColorPaletteFamily } from './type';
 | 
			
		||||
import defaultPalettes from './json/palette.json';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * get color palette by provided color and color name
 | 
			
		||||
 * @param color the provided color
 | 
			
		||||
 * @param colorName color name
 | 
			
		||||
 */
 | 
			
		||||
export function getColorPalette(color: string, colorName: string) {
 | 
			
		||||
  const colorPaletteFamily = getColorPaletteFamily(color, colorName);
 | 
			
		||||
 | 
			
		||||
  const colorMap = new Map<ColorPaletteNumber, ColorPaletteItem>();
 | 
			
		||||
 | 
			
		||||
  colorPaletteFamily.palettes.forEach(palette => {
 | 
			
		||||
    colorMap.set(palette.number, palette);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const mainColor = colorMap.get(500) as ColorPaletteItem;
 | 
			
		||||
  const matchColor = colorPaletteFamily.palettes.find(palette => palette.hexcode === color) as ColorPaletteItem;
 | 
			
		||||
 | 
			
		||||
  const colorPalette: ColorPalette = {
 | 
			
		||||
    ...colorPaletteFamily,
 | 
			
		||||
    colorMap,
 | 
			
		||||
    main: mainColor,
 | 
			
		||||
    match: matchColor
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return colorPalette;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * get color by color palette number
 | 
			
		||||
 * @param color color
 | 
			
		||||
 * @param num color palette number
 | 
			
		||||
 * @return color hexcode
 | 
			
		||||
 */
 | 
			
		||||
export function getColorByColorPaletteNumber(color: string, num: ColorPaletteNumber) {
 | 
			
		||||
  const colorPalette = getColorPalette(color, color);
 | 
			
		||||
 | 
			
		||||
  const colorItem = colorPalette.colorMap.get(num) as ColorPaletteItem;
 | 
			
		||||
 | 
			
		||||
  return colorItem.hexcode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default getColorPalette;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the builtin color palettes
 | 
			
		||||
 */
 | 
			
		||||
const colorPalettes = defaultPalettes as ColorPaletteFamily[];
 | 
			
		||||
 | 
			
		||||
export { getColorName, colorPalettes };
 | 
			
		||||
 | 
			
		||||
export type { ColorPalette, ColorPaletteNumber, ColorPaletteItem, ColorPaletteFamily };
 | 
			
		||||
							
								
								
									
										1568
									
								
								packages/color-palette/src/json/color-name.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1568
									
								
								packages/color-palette/src/json/color-name.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										274
									
								
								packages/color-palette/src/json/palette.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								packages/color-palette/src/json/palette.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,274 @@
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
    "key": "red",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#fef2f2", "number": 50, "name": "Bridesmaid" },
 | 
			
		||||
      { "hexcode": "#fee2e2", "number": 100, "name": "Pippin" },
 | 
			
		||||
      { "hexcode": "#fecaca", "number": 200, "name": "Your Pink" },
 | 
			
		||||
      { "hexcode": "#fca5a5", "number": 300, "name": "Cornflower Lilac" },
 | 
			
		||||
      { "hexcode": "#f87171", "number": 400, "name": "Bittersweet" },
 | 
			
		||||
      { "hexcode": "#ef4444", "number": 500, "name": "Cinnabar" },
 | 
			
		||||
      { "hexcode": "#dc2626", "number": 600, "name": "Persian Red" },
 | 
			
		||||
      { "hexcode": "#b91c1c", "number": 700, "name": "Thunderbird" },
 | 
			
		||||
      { "hexcode": "#991b1b", "number": 800, "name": "Old Brick" },
 | 
			
		||||
      { "hexcode": "#7f1d1d", "number": 900, "name": "Falu Red" },
 | 
			
		||||
      { "hexcode": "#450a0a", "number": 950, "name": "Mahogany" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "orange",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#fff7ed", "number": 50, "name": "Serenade" },
 | 
			
		||||
      { "hexcode": "#ffedd5", "number": 100, "name": "Derby" },
 | 
			
		||||
      { "hexcode": "#fed7aa", "number": 200, "name": "Caramel" },
 | 
			
		||||
      { "hexcode": "#fdba74", "number": 300, "name": "Macaroni and Cheese" },
 | 
			
		||||
      { "hexcode": "#fb923c", "number": 400, "name": "Neon Carrot" },
 | 
			
		||||
      { "hexcode": "#f97316", "number": 500, "name": "Ecstasy" },
 | 
			
		||||
      { "hexcode": "#ea580c", "number": 600, "name": "Trinidad" },
 | 
			
		||||
      { "hexcode": "#c2410c", "number": 700, "name": "Tia Maria" },
 | 
			
		||||
      { "hexcode": "#9a3412", "number": 800, "name": "Tabasco" },
 | 
			
		||||
      { "hexcode": "#7c2d12", "number": 900, "name": "Pueblo" },
 | 
			
		||||
      { "hexcode": "#431407", "number": 950, "name": "Rebel" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "amber",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#fffbeb", "number": 50, "name": "Island Spice" },
 | 
			
		||||
      { "hexcode": "#fef3c7", "number": 100, "name": "Beeswax" },
 | 
			
		||||
      { "hexcode": "#fde68a", "number": 200, "name": "Sweet Corn" },
 | 
			
		||||
      { "hexcode": "#fcd34d", "number": 300, "name": "Mustard" },
 | 
			
		||||
      { "hexcode": "#fbbf24", "number": 400, "name": "Lightning Yellow" },
 | 
			
		||||
      { "hexcode": "#f59e0b", "number": 500, "name": "California" },
 | 
			
		||||
      { "hexcode": "#d97706", "number": 600, "name": "Christine" },
 | 
			
		||||
      { "hexcode": "#b45309", "number": 700, "name": "Vesuvius" },
 | 
			
		||||
      { "hexcode": "#92400e", "number": 800, "name": "Korma" },
 | 
			
		||||
      { "hexcode": "#78350f", "number": 900, "name": "Copper Canyon" },
 | 
			
		||||
      { "hexcode": "#451a03", "number": 950, "name": "Brown Pod" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "yellow",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#fefce8", "number": 50, "name": "Orange White" },
 | 
			
		||||
      { "hexcode": "#fef9c3", "number": 100, "name": "Lemon Chiffon" },
 | 
			
		||||
      { "hexcode": "#fef08a", "number": 200, "name": "Sweet Corn" },
 | 
			
		||||
      { "hexcode": "#fde047", "number": 300, "name": "Bright Sun" },
 | 
			
		||||
      { "hexcode": "#facc15", "number": 400, "name": "Candlelight" },
 | 
			
		||||
      { "hexcode": "#eab308", "number": 500, "name": "Corn" },
 | 
			
		||||
      { "hexcode": "#ca8a04", "number": 600, "name": "Pirate Gold" },
 | 
			
		||||
      { "hexcode": "#a16207", "number": 700, "name": "Mai Tai" },
 | 
			
		||||
      { "hexcode": "#854d0e", "number": 800, "name": "Korma" },
 | 
			
		||||
      { "hexcode": "#713f12", "number": 900, "name": "Sepia" },
 | 
			
		||||
      { "hexcode": "#422006", "number": 950, "name": "Dark Ebony" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "lime",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#f7fee7", "number": 50, "name": "Spring Sun" },
 | 
			
		||||
      { "hexcode": "#ecfccb", "number": 100, "name": "Chiffon" },
 | 
			
		||||
      { "hexcode": "#d9f99d", "number": 200, "name": "Gossip" },
 | 
			
		||||
      { "hexcode": "#bef264", "number": 300, "name": "Sulu" },
 | 
			
		||||
      { "hexcode": "#a3e635", "number": 400, "name": "Conifer" },
 | 
			
		||||
      { "hexcode": "#84cc16", "number": 500, "name": "Lima" },
 | 
			
		||||
      { "hexcode": "#65a30d", "number": 600, "name": "Christi" },
 | 
			
		||||
      { "hexcode": "#4d7c0f", "number": 700, "name": "Green Leaf" },
 | 
			
		||||
      { "hexcode": "#3f6212", "number": 800, "name": "Dell" },
 | 
			
		||||
      { "hexcode": "#365314", "number": 900, "name": "Clover" },
 | 
			
		||||
      { "hexcode": "#1a2e05", "number": 950, "name": "Deep Forest Green" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "green",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#f0fdf4", "number": 50, "name": "Ottoman" },
 | 
			
		||||
      { "hexcode": "#dcfce7", "number": 100, "name": "Blue Romance" },
 | 
			
		||||
      { "hexcode": "#bbf7d0", "number": 200, "name": "Magic Mint" },
 | 
			
		||||
      { "hexcode": "#86efac", "number": 300, "name": "Algae Green" },
 | 
			
		||||
      { "hexcode": "#4ade80", "number": 400, "name": "Emerald" },
 | 
			
		||||
      { "hexcode": "#22c55e", "number": 500, "name": "Malachite" },
 | 
			
		||||
      { "hexcode": "#16a34a", "number": 600, "name": "Salem" },
 | 
			
		||||
      { "hexcode": "#15803d", "number": 700, "name": "Jewel" },
 | 
			
		||||
      { "hexcode": "#166534", "number": 800, "name": "Jewel" },
 | 
			
		||||
      { "hexcode": "#14532d", "number": 900, "name": "Green Pea" },
 | 
			
		||||
      { "hexcode": "#052e16", "number": 950, "name": "English Holly" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "emerald",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#ecfdf5", "number": 50, "name": "White Ice" },
 | 
			
		||||
      { "hexcode": "#d1fae5", "number": 100, "name": "Granny Apple" },
 | 
			
		||||
      { "hexcode": "#a7f3d0", "number": 200, "name": "Magic Mint" },
 | 
			
		||||
      { "hexcode": "#6ee7b7", "number": 300, "name": "Bermuda" },
 | 
			
		||||
      { "hexcode": "#34d399", "number": 400, "name": "Shamrock" },
 | 
			
		||||
      { "hexcode": "#10b981", "number": 500, "name": "Mountain Meadow" },
 | 
			
		||||
      { "hexcode": "#059669", "number": 600, "name": "Green Haze" },
 | 
			
		||||
      { "hexcode": "#047857", "number": 700, "name": "Watercourse" },
 | 
			
		||||
      { "hexcode": "#065f46", "number": 800, "name": "Watercourse" },
 | 
			
		||||
      { "hexcode": "#064e3b", "number": 900, "name": "Evening Sea" },
 | 
			
		||||
      { "hexcode": "#022c22", "number": 950, "name": "Burnham" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "teal",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#f0fdfa", "number": 50, "name": "White Ice" },
 | 
			
		||||
      { "hexcode": "#ccfbf1", "number": 100, "name": "Scandal" },
 | 
			
		||||
      { "hexcode": "#99f6e4", "number": 200, "name": "Ice Cold" },
 | 
			
		||||
      { "hexcode": "#5eead4", "number": 300, "name": "Turquoise Blue" },
 | 
			
		||||
      { "hexcode": "#2dd4bf", "number": 400, "name": "Turquoise" },
 | 
			
		||||
      { "hexcode": "#14b8a6", "number": 500, "name": "Java" },
 | 
			
		||||
      { "hexcode": "#0d9488", "number": 600, "name": "Blue Chill" },
 | 
			
		||||
      { "hexcode": "#0f766e", "number": 700, "name": "Genoa" },
 | 
			
		||||
      { "hexcode": "#115e59", "number": 800, "name": "Eden" },
 | 
			
		||||
      { "hexcode": "#134e4a", "number": 900, "name": "Eden" },
 | 
			
		||||
      { "hexcode": "#042f2e", "number": 950, "name": "Tiber" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "cyan",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#ecfeff", "number": 50, "name": "Bubbles" },
 | 
			
		||||
      { "hexcode": "#cffafe", "number": 100, "name": "Oyster Bay" },
 | 
			
		||||
      { "hexcode": "#a5f3fc", "number": 200, "name": "Anakiwa" },
 | 
			
		||||
      { "hexcode": "#67e8f9", "number": 300, "name": "Spray" },
 | 
			
		||||
      { "hexcode": "#22d3ee", "number": 400, "name": "Bright Turquoise" },
 | 
			
		||||
      { "hexcode": "#06b6d4", "number": 500, "name": "Cerulean" },
 | 
			
		||||
      { "hexcode": "#0891b2", "number": 600, "name": "Bondi Blue" },
 | 
			
		||||
      { "hexcode": "#0e7490", "number": 700, "name": "Blue Chill" },
 | 
			
		||||
      { "hexcode": "#155e75", "number": 800, "name": "Blumine" },
 | 
			
		||||
      { "hexcode": "#164e63", "number": 900, "name": "Chathams Blue" },
 | 
			
		||||
      { "hexcode": "#083344", "number": 950, "name": "Tarawera" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "sky",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#f0f9ff", "number": 50, "name": "Alice Blue" },
 | 
			
		||||
      { "hexcode": "#e0f2fe", "number": 100, "name": "Pattens Blue" },
 | 
			
		||||
      { "hexcode": "#bae6fd", "number": 200, "name": "French Pass" },
 | 
			
		||||
      { "hexcode": "#7dd3fc", "number": 300, "name": "Malibu" },
 | 
			
		||||
      { "hexcode": "#38bdf8", "number": 400, "name": "Picton Blue" },
 | 
			
		||||
      { "hexcode": "#0ea5e9", "number": 500, "name": "Cerulean" },
 | 
			
		||||
      { "hexcode": "#0284c7", "number": 600, "name": "Lochmara" },
 | 
			
		||||
      { "hexcode": "#0369a1", "number": 700, "name": "Bahama Blue" },
 | 
			
		||||
      { "hexcode": "#075985", "number": 800, "name": "Venice Blue" },
 | 
			
		||||
      { "hexcode": "#0c4a6e", "number": 900, "name": "Chathams Blue" },
 | 
			
		||||
      { "hexcode": "#082f49", "number": 950, "name": "Blue Whale" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "blue",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#eff6ff", "number": 50, "name": "Zumthor" },
 | 
			
		||||
      { "hexcode": "#dbeafe", "number": 100, "name": "Hawkes Blue" },
 | 
			
		||||
      { "hexcode": "#bfdbfe", "number": 200, "name": "Tropical Blue" },
 | 
			
		||||
      { "hexcode": "#93c5fd", "number": 300, "name": "Malibu" },
 | 
			
		||||
      { "hexcode": "#60a5fa", "number": 400, "name": "Cornflower Blue" },
 | 
			
		||||
      { "hexcode": "#3b82f6", "number": 500, "name": "Dodger Blue" },
 | 
			
		||||
      { "hexcode": "#2563eb", "number": 600, "name": "Royal Blue" },
 | 
			
		||||
      { "hexcode": "#1d4ed8", "number": 700, "name": "Cerulean Blue" },
 | 
			
		||||
      { "hexcode": "#1e40af", "number": 800, "name": "Persian Blue" },
 | 
			
		||||
      { "hexcode": "#1e3a8a", "number": 900, "name": "Bay of Many" },
 | 
			
		||||
      { "hexcode": "#172554", "number": 950, "name": "Bunting" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "indigo",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#eef2ff", "number": 50, "name": "Zircon" },
 | 
			
		||||
      { "hexcode": "#e0e7ff", "number": 100, "name": "Hawkes Blue" },
 | 
			
		||||
      { "hexcode": "#c7d2fe", "number": 200, "name": "Periwinkle" },
 | 
			
		||||
      { "hexcode": "#a5b4fc", "number": 300, "name": "Perano" },
 | 
			
		||||
      { "hexcode": "#818cf8", "number": 400, "name": "Portage" },
 | 
			
		||||
      { "hexcode": "#6366f1", "number": 500, "name": "Royal Blue" },
 | 
			
		||||
      { "hexcode": "#4f46e5", "number": 600, "name": "Royal Blue" },
 | 
			
		||||
      { "hexcode": "#4338ca", "number": 700, "name": "Governor Bay" },
 | 
			
		||||
      { "hexcode": "#3730a3", "number": 800, "name": "Governor Bay" },
 | 
			
		||||
      { "hexcode": "#312e81", "number": 900, "name": "Minsk" },
 | 
			
		||||
      { "hexcode": "#1e1b4b", "number": 950, "name": "Port Gore" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "violet",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#f5f3ff", "number": 50, "name": "Titan White" },
 | 
			
		||||
      { "hexcode": "#ede9fe", "number": 100, "name": "Titan White" },
 | 
			
		||||
      { "hexcode": "#ddd6fe", "number": 200, "name": "Fog" },
 | 
			
		||||
      { "hexcode": "#c4b5fd", "number": 300, "name": "Melrose" },
 | 
			
		||||
      { "hexcode": "#a78bfa", "number": 400, "name": "Dull Lavender" },
 | 
			
		||||
      { "hexcode": "#8b5cf6", "number": 500, "name": "Medium Purple" },
 | 
			
		||||
      { "hexcode": "#7c3aed", "number": 600, "name": "Purple Heart" },
 | 
			
		||||
      { "hexcode": "#6d28d9", "number": 700, "name": "Purple Heart" },
 | 
			
		||||
      { "hexcode": "#5b21b6", "number": 800, "name": "Purple Heart" },
 | 
			
		||||
      { "hexcode": "#4c1d95", "number": 900, "name": "Daisy Bush" },
 | 
			
		||||
      { "hexcode": "#2e1065", "number": 950, "name": "Violent Violet" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "purple",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#faf5ff", "number": 50, "name": "Magnolia" },
 | 
			
		||||
      { "hexcode": "#f3e8ff", "number": 100, "name": "Blue Chalk" },
 | 
			
		||||
      { "hexcode": "#e9d5ff", "number": 200, "name": "Blue Chalk" },
 | 
			
		||||
      { "hexcode": "#d8b4fe", "number": 300, "name": "Mauve" },
 | 
			
		||||
      { "hexcode": "#c084fc", "number": 400, "name": "Heliotrope" },
 | 
			
		||||
      { "hexcode": "#a855f7", "number": 500, "name": "Medium Purple" },
 | 
			
		||||
      { "hexcode": "#9333ea", "number": 600, "name": "Electric Violet" },
 | 
			
		||||
      { "hexcode": "#7e22ce", "number": 700, "name": "Purple Heart" },
 | 
			
		||||
      { "hexcode": "#6b21a8", "number": 800, "name": "Seance" },
 | 
			
		||||
      { "hexcode": "#581c87", "number": 900, "name": "Daisy Bush" },
 | 
			
		||||
      { "hexcode": "#3b0764", "number": 950, "name": "Christalle" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "fuchsia",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#fdf4ff", "number": 50, "name": "White Pointer" },
 | 
			
		||||
      { "hexcode": "#fae8ff", "number": 100, "name": "White Pointer" },
 | 
			
		||||
      { "hexcode": "#f5d0fe", "number": 200, "name": "Mauve" },
 | 
			
		||||
      { "hexcode": "#f0abfc", "number": 300, "name": "Mauve" },
 | 
			
		||||
      { "hexcode": "#e879f9", "number": 400, "name": "Heliotrope" },
 | 
			
		||||
      { "hexcode": "#d946ef", "number": 500, "name": "Heliotrope" },
 | 
			
		||||
      { "hexcode": "#c026d3", "number": 600, "name": "Fuchsia Pink" },
 | 
			
		||||
      { "hexcode": "#a21caf", "number": 700, "name": "Violet Eggplant" },
 | 
			
		||||
      { "hexcode": "#86198f", "number": 800, "name": "Seance" },
 | 
			
		||||
      { "hexcode": "#701a75", "number": 900, "name": "Seance" },
 | 
			
		||||
      { "hexcode": "#4a044e", "number": 950, "name": "Clairvoyant" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "pink",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#fdf2f8", "number": 50, "name": "Wisp Pink" },
 | 
			
		||||
      { "hexcode": "#fce7f3", "number": 100, "name": "Carousel Pink" },
 | 
			
		||||
      { "hexcode": "#fbcfe8", "number": 200, "name": "Classic Rose" },
 | 
			
		||||
      { "hexcode": "#f9a8d4", "number": 300, "name": "Lavender Pink" },
 | 
			
		||||
      { "hexcode": "#f472b6", "number": 400, "name": "Persian Pink" },
 | 
			
		||||
      { "hexcode": "#ec4899", "number": 500, "name": "Brilliant Rose" },
 | 
			
		||||
      { "hexcode": "#db2777", "number": 600, "name": "Cerise" },
 | 
			
		||||
      { "hexcode": "#be185d", "number": 700, "name": "Maroon Flush" },
 | 
			
		||||
      { "hexcode": "#9d174d", "number": 800, "name": "Disco" },
 | 
			
		||||
      { "hexcode": "#831843", "number": 900, "name": "Disco" },
 | 
			
		||||
      { "hexcode": "#500724", "number": 950, "name": "Cab Sav" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "key": "rose",
 | 
			
		||||
    "palettes": [
 | 
			
		||||
      { "hexcode": "#fff1f2", "number": 50, "name": "Lavender blush" },
 | 
			
		||||
      { "hexcode": "#ffe4e6", "number": 100, "name": "Cosmos" },
 | 
			
		||||
      { "hexcode": "#fecdd3", "number": 200, "name": "Pastel Pink" },
 | 
			
		||||
      { "hexcode": "#fda4af", "number": 300, "name": "Sweet Pink" },
 | 
			
		||||
      { "hexcode": "#fb7185", "number": 400, "name": "Froly" },
 | 
			
		||||
      { "hexcode": "#f43f5e", "number": 500, "name": "Radical Red" },
 | 
			
		||||
      { "hexcode": "#e11d48", "number": 600, "name": "Amaranth" },
 | 
			
		||||
      { "hexcode": "#be123c", "number": 700, "name": "Cardinal" },
 | 
			
		||||
      { "hexcode": "#9f1239", "number": 800, "name": "Shiraz" },
 | 
			
		||||
      { "hexcode": "#881337", "number": 900, "name": "Claret" },
 | 
			
		||||
      { "hexcode": "#4c0519", "number": 950, "name": "Cab Sav" }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										46
									
								
								packages/color-palette/src/name.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								packages/color-palette/src/name.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
import { getHex, getRgb, getHsl } from './color';
 | 
			
		||||
import colorNames from './json/color-name.json';
 | 
			
		||||
 | 
			
		||||
export function getColorName(color: string) {
 | 
			
		||||
  const hex = getHex(color);
 | 
			
		||||
  const rgb = getRgb(color);
 | 
			
		||||
  const hsl = getHsl(color);
 | 
			
		||||
 | 
			
		||||
  let ndf = 0;
 | 
			
		||||
  let ndf1 = 0;
 | 
			
		||||
  let ndf2 = 0;
 | 
			
		||||
  let cl = -1;
 | 
			
		||||
  let df = -1;
 | 
			
		||||
 | 
			
		||||
  let name = '';
 | 
			
		||||
 | 
			
		||||
  colorNames.some((item, index) => {
 | 
			
		||||
    const [hexValue, colorName] = item;
 | 
			
		||||
 | 
			
		||||
    const hexcode = `#${hexValue}`;
 | 
			
		||||
 | 
			
		||||
    const match = hex === hexcode;
 | 
			
		||||
 | 
			
		||||
    if (match) {
 | 
			
		||||
      name = colorName;
 | 
			
		||||
    } else {
 | 
			
		||||
      const { r, g, b } = getRgb(hexcode);
 | 
			
		||||
      const { h, s, l } = getHsl(hexcode);
 | 
			
		||||
 | 
			
		||||
      ndf1 = (rgb.r - r) ** 2 + (rgb.g - g) ** 2 + (rgb.b - b) ** 2;
 | 
			
		||||
      ndf2 = (hsl.h - h) ** 2 + (hsl.s - s) ** 2 + (hsl.l - l) ** 2;
 | 
			
		||||
 | 
			
		||||
      ndf = ndf1 + ndf2 * 2;
 | 
			
		||||
      if (df < 0 || df > ndf) {
 | 
			
		||||
        df = ndf;
 | 
			
		||||
        cl = index;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return match;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  name = cl < 0 ? 'Invalid Color' : colorNames[cl][1];
 | 
			
		||||
 | 
			
		||||
  return name;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										95
									
								
								packages/color-palette/src/palette.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								packages/color-palette/src/palette.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
import { isValidColor, getHsl, getDeltaE, transformHslToHex } from './color';
 | 
			
		||||
import { getColorName } from './name';
 | 
			
		||||
import type { ColorPaletteFamily, ColorPaletteFamilyWithNearestPalette } from './type';
 | 
			
		||||
import defaultPalettes from './json/palette.json';
 | 
			
		||||
 | 
			
		||||
export function getNearestColorPaletteFamily(color: string, families: ColorPaletteFamily[]) {
 | 
			
		||||
  const familyWithConfig = families.map(family => {
 | 
			
		||||
    const palettes = family.palettes.map(palette => {
 | 
			
		||||
      return {
 | 
			
		||||
        ...palette,
 | 
			
		||||
        delta: getDeltaE(color, palette.hexcode)
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const nearestPalette = palettes.reduce((prev, curr) => (prev.delta < curr.delta ? prev : curr));
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      ...family,
 | 
			
		||||
      palettes,
 | 
			
		||||
      nearestPalette
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const nearestPaletteFamily = familyWithConfig.reduce((prev, curr) =>
 | 
			
		||||
    prev.nearestPalette.delta < curr.nearestPalette.delta ? prev : curr
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const { l } = getHsl(color);
 | 
			
		||||
 | 
			
		||||
  const paletteFamily: ColorPaletteFamilyWithNearestPalette = {
 | 
			
		||||
    ...nearestPaletteFamily,
 | 
			
		||||
    nearestLightnessPalette: nearestPaletteFamily.palettes.reduce((prev, curr) => {
 | 
			
		||||
      const { l: prevLightness } = getHsl(prev.hexcode);
 | 
			
		||||
      const { l: currLightness } = getHsl(curr.hexcode);
 | 
			
		||||
 | 
			
		||||
      const deltaPrev = Math.abs(prevLightness - l);
 | 
			
		||||
      const deltaCurr = Math.abs(currLightness - l);
 | 
			
		||||
 | 
			
		||||
      return deltaPrev < deltaCurr ? prev : curr;
 | 
			
		||||
    })
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return paletteFamily;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getColorPaletteFamily(color: string, colorName: string) {
 | 
			
		||||
  if (!isValidColor(color)) {
 | 
			
		||||
    throw new Error('Invalid color, please check color value!');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { h: h1, s: s1 } = getHsl(color);
 | 
			
		||||
 | 
			
		||||
  const { nearestLightnessPalette, palettes } = getNearestColorPaletteFamily(
 | 
			
		||||
    color,
 | 
			
		||||
    defaultPalettes as ColorPaletteFamily[]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const { number, hexcode } = nearestLightnessPalette;
 | 
			
		||||
 | 
			
		||||
  const { h: h2, s: s2 } = getHsl(hexcode);
 | 
			
		||||
 | 
			
		||||
  const deltaH = h1 - h2 || h2;
 | 
			
		||||
 | 
			
		||||
  const sRatio = s1 / s2;
 | 
			
		||||
 | 
			
		||||
  const colorPaletteFamily: ColorPaletteFamily = {
 | 
			
		||||
    key: colorName,
 | 
			
		||||
    palettes: palettes.map(palette => {
 | 
			
		||||
      let hexValue = color;
 | 
			
		||||
 | 
			
		||||
      const isSame = number === palette.number;
 | 
			
		||||
 | 
			
		||||
      if (!isSame) {
 | 
			
		||||
        const { h: h3, s: s3, l } = getHsl(palette.hexcode);
 | 
			
		||||
 | 
			
		||||
        const newH = deltaH < 0 ? h3 + deltaH : deltaH;
 | 
			
		||||
        const newS = s3 * sRatio;
 | 
			
		||||
 | 
			
		||||
        hexValue = transformHslToHex({
 | 
			
		||||
          h: newH,
 | 
			
		||||
          s: newS,
 | 
			
		||||
          l
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        hexcode: hexValue,
 | 
			
		||||
        number: palette.number,
 | 
			
		||||
        name: getColorName(hexValue)
 | 
			
		||||
      };
 | 
			
		||||
    })
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return colorPaletteFamily;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								packages/color-palette/src/type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								packages/color-palette/src/type.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
/**
 | 
			
		||||
 * the color palette number
 | 
			
		||||
 */
 | 
			
		||||
export type ColorPaletteNumber = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the color palette item
 | 
			
		||||
 */
 | 
			
		||||
export type ColorPaletteItem = {
 | 
			
		||||
  /**
 | 
			
		||||
   * the color hexcode
 | 
			
		||||
   */
 | 
			
		||||
  hexcode: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * the color number
 | 
			
		||||
   * @link {@link ColorPaletteNumber}
 | 
			
		||||
   */
 | 
			
		||||
  number: ColorPaletteNumber;
 | 
			
		||||
  /**
 | 
			
		||||
   * the color name
 | 
			
		||||
   */
 | 
			
		||||
  name: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ColorPaletteFamily = {
 | 
			
		||||
  /**
 | 
			
		||||
   * the color palette family key
 | 
			
		||||
   */
 | 
			
		||||
  key: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * the color palette family's palettes
 | 
			
		||||
   */
 | 
			
		||||
  palettes: ColorPaletteItem[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ColorPaletteWithDelta = ColorPaletteItem & {
 | 
			
		||||
  delta: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ColorPaletteItemWithName = ColorPaletteItem & {
 | 
			
		||||
  name: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ColorPaletteFamilyWithNearestPalette = ColorPaletteFamily & {
 | 
			
		||||
  nearestPalette: ColorPaletteWithDelta;
 | 
			
		||||
  nearestLightnessPalette: ColorPaletteWithDelta;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ColorPalette = ColorPaletteFamily & {
 | 
			
		||||
  /**
 | 
			
		||||
   * the color map of the palette
 | 
			
		||||
   */
 | 
			
		||||
  colorMap: Map<ColorPaletteNumber, ColorPaletteItem>;
 | 
			
		||||
  /**
 | 
			
		||||
   * the main color of the palette
 | 
			
		||||
   * @description which number is 500
 | 
			
		||||
   */
 | 
			
		||||
  main: ColorPaletteItemWithName;
 | 
			
		||||
  /**
 | 
			
		||||
   * the match color of the palette
 | 
			
		||||
   */
 | 
			
		||||
  match: ColorPaletteItemWithName;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										38
									
								
								packages/docs/.vitepress/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								packages/docs/.vitepress/config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
import path from 'node:path';
 | 
			
		||||
import { defineConfig } from 'vitepress';
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
  title: 'Soybean Admin',
 | 
			
		||||
  description: '一个优雅、清新、漂亮的中后台模版',
 | 
			
		||||
  head: [
 | 
			
		||||
    ['meta', { name: 'author', content: 'Soybean' }],
 | 
			
		||||
    [
 | 
			
		||||
      'meta',
 | 
			
		||||
      {
 | 
			
		||||
        name: 'keywords',
 | 
			
		||||
        content: 'soybean, soybean-admin, vite, vue, vue3, soybean-admin docs'
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }],
 | 
			
		||||
    [
 | 
			
		||||
      'meta',
 | 
			
		||||
      {
 | 
			
		||||
        name: 'viewport',
 | 
			
		||||
        content: 'width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no'
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    ['link', { rel: 'icon', href: '/favicon.ico' }]
 | 
			
		||||
  ],
 | 
			
		||||
  srcDir: path.join(process.cwd(), 'packages/docs/src'),
 | 
			
		||||
  themeConfig: {
 | 
			
		||||
    logo: '/logo.svg',
 | 
			
		||||
    socialLinks: [{ icon: 'github', link: 'https://github.com/honghuangdc/soybean-admin' }],
 | 
			
		||||
    algolia: {
 | 
			
		||||
      appId: '98WN1RY04S',
 | 
			
		||||
      apiKey: '13e9f5767b774422a5880723d9c23265',
 | 
			
		||||
      indexName: 'soybean'
 | 
			
		||||
    },
 | 
			
		||||
    nav: [],
 | 
			
		||||
    sidebar: {}
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										32
									
								
								packages/docs/.vitepress/icon.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								packages/docs/.vitepress/icon.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
export const qqSvg = `
 | 
			
		||||
<svg height="2500" viewBox="-1.94 0 124.879 145.085" width="2101" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
	<path
 | 
			
		||||
		d="m60.503 142.237c-12.533 0-24.038-4.195-31.445-10.46-3.762 1.124-8.574 2.932-11.61 5.175-2.6 1.918-2.275 3.874-1.807 4.663 2.056 3.47 35.273 2.216 44.862 1.136zm0 0c12.535 0 24.039-4.195 31.447-10.46 3.76 1.124 8.573 2.932 11.61 5.175 2.598 1.918 2.274 3.874 1.805 4.663-2.056 3.47-35.272 2.216-44.862 1.136zm0 0"
 | 
			
		||||
		fill="#faab07"
 | 
			
		||||
	/>
 | 
			
		||||
	<path
 | 
			
		||||
		d="m60.576 67.119c20.698-.14 37.286-4.147 42.907-5.683 1.34-.367 2.056-1.024 2.056-1.024.005-.189.085-3.37.085-5.01 0-27.634-13.044-55.401-45.124-55.402-32.08.001-45.125 27.769-45.125 55.401 0 1.642.08 4.822.086 5.01 0 0 .583.615 1.65.913 5.19 1.444 22.09 5.65 43.312 5.795zm56.245 23.02c-1.283-4.129-3.034-8.944-4.808-13.568 0 0-1.02-.126-1.537.023-15.913 4.623-35.202 7.57-49.9 7.392h-.153c-14.616.175-33.774-2.737-49.634-7.315-.606-.175-1.802-.1-1.802-.1-1.774 4.624-3.525 9.44-4.808 13.568-6.119 19.69-4.136 27.838-2.627 28.02 3.239.392 12.606-14.821 12.606-14.821 0 15.459 13.957 39.195 45.918 39.413h.848c31.96-.218 45.917-23.954 45.917-39.413 0 0 9.368 15.213 12.607 14.822 1.508-.183 3.491-8.332-2.627-28.021"
 | 
			
		||||
	/>
 | 
			
		||||
	<path
 | 
			
		||||
		d="m49.085 40.824c-4.352.197-8.07-4.76-8.304-11.063-.236-6.305 3.098-11.576 7.45-11.773 4.347-.195 8.064 4.76 8.3 11.065.238 6.306-3.097 11.577-7.446 11.771m31.133-11.063c-.233 6.302-3.951 11.26-8.303 11.063-4.35-.195-7.684-5.465-7.446-11.77.236-6.305 3.952-11.26 8.3-11.066 4.352.197 7.686 5.468 7.449 11.773"
 | 
			
		||||
		fill="#fff"
 | 
			
		||||
	/>
 | 
			
		||||
	<path
 | 
			
		||||
		d="m87.952 49.725c-1.162-2.575-12.875-5.445-27.374-5.445h-.156c-14.5 0-26.212 2.87-27.375 5.446a.863.863 0 0 0 -.085.367c0 .186.063.352.16.496.98 1.427 13.985 8.487 27.3 8.487h.156c13.314 0 26.319-7.058 27.299-8.487a.873.873 0 0 0 .16-.498.856.856 0 0 0 -.085-.365"
 | 
			
		||||
		fill="#faab07"
 | 
			
		||||
	/>
 | 
			
		||||
	<path
 | 
			
		||||
		d="m54.434 29.854c.199 2.49-1.167 4.702-3.046 4.943-1.883.242-3.568-1.58-3.768-4.07-.197-2.492 1.167-4.704 3.043-4.944 1.886-.244 3.574 1.58 3.771 4.07m11.956.833c.385-.689 3.004-4.312 8.427-2.993 1.425.347 2.084.857 2.223 1.057.205.296.262.718.053 1.286-.412 1.126-1.263 1.095-1.734.875-.305-.142-4.082-2.66-7.562 1.097-.24.257-.668.346-1.073.04-.407-.308-.574-.93-.334-1.362"
 | 
			
		||||
	/>
 | 
			
		||||
	<path
 | 
			
		||||
		d="m60.576 83.08h-.153c-9.996.12-22.116-1.204-33.854-3.518-1.004 5.818-1.61 13.132-1.09 21.853 1.316 22.043 14.407 35.9 34.614 36.1h.82c20.208-.2 33.298-14.057 34.616-36.1.52-8.723-.087-16.035-1.092-21.854-11.739 2.315-23.862 3.64-33.86 3.518"
 | 
			
		||||
		fill="#fff"
 | 
			
		||||
	/>
 | 
			
		||||
	<g fill="#eb1923">
 | 
			
		||||
		<path d="m32.102 81.235v21.693s9.937 2.004 19.893.616v-20.009c-6.307-.357-13.109-1.152-19.893-2.3" />
 | 
			
		||||
		<path
 | 
			
		||||
			d="m105.539 60.412s-19.33 6.102-44.963 6.275h-.153c-25.591-.172-44.896-6.255-44.962-6.275l-6.474 16.158c16.193 4.882 36.261 8.028 51.436 7.845h.153c15.175.183 35.242-2.963 51.437-7.845zm0 0"
 | 
			
		||||
		/>
 | 
			
		||||
	</g>
 | 
			
		||||
</svg>
 | 
			
		||||
`;
 | 
			
		||||
							
								
								
									
										4
									
								
								packages/docs/.vitepress/theme/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								packages/docs/.vitepress/theme/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
import Theme from 'vitepress/theme';
 | 
			
		||||
import './style.css';
 | 
			
		||||
 | 
			
		||||
export default Theme;
 | 
			
		||||
							
								
								
									
										90
									
								
								packages/docs/.vitepress/theme/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								packages/docs/.vitepress/theme/style.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Customize default theme styling by overriding CSS variables:
 | 
			
		||||
 * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Colors
 | 
			
		||||
 * -------------------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  --vp-c-brand: #646cff;
 | 
			
		||||
  --vp-c-brand-light: #747bff;
 | 
			
		||||
  --vp-c-brand-lighter: #9499ff;
 | 
			
		||||
  --vp-c-brand-lightest: #bcc0ff;
 | 
			
		||||
  --vp-c-brand-dark: #535bf2;
 | 
			
		||||
  --vp-c-brand-darker: #454ce1;
 | 
			
		||||
  --vp-c-brand-dimm: rgba(100, 108, 255, 0.08);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component: Button
 | 
			
		||||
 * -------------------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  --vp-button-brand-border: var(--vp-c-brand-light);
 | 
			
		||||
  --vp-button-brand-text: var(--vp-c-white);
 | 
			
		||||
  --vp-button-brand-bg: var(--vp-c-brand);
 | 
			
		||||
  --vp-button-brand-hover-border: var(--vp-c-brand-light);
 | 
			
		||||
  --vp-button-brand-hover-text: var(--vp-c-white);
 | 
			
		||||
  --vp-button-brand-hover-bg: var(--vp-c-brand-light);
 | 
			
		||||
  --vp-button-brand-active-border: var(--vp-c-brand-light);
 | 
			
		||||
  --vp-button-brand-active-text: var(--vp-c-white);
 | 
			
		||||
  --vp-button-brand-active-bg: var(--vp-button-brand-bg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component: Home
 | 
			
		||||
 * -------------------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  --vp-home-hero-name-color: transparent;
 | 
			
		||||
  --vp-home-hero-name-background: -webkit-linear-gradient(
 | 
			
		||||
    120deg,
 | 
			
		||||
    var(--vp-c-brand-lightest) 30%,
 | 
			
		||||
    var(--vp-c-brand-darker)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  --vp-home-hero-image-background-image: linear-gradient(
 | 
			
		||||
    -45deg,
 | 
			
		||||
    var(--vp-c-brand-lightest) 30%,
 | 
			
		||||
    var(--vp-c-brand) 50%
 | 
			
		||||
  );
 | 
			
		||||
  --vp-home-hero-image-filter: blur(40px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 640px) {
 | 
			
		||||
  :root {
 | 
			
		||||
    --vp-home-hero-image-filter: blur(56px);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 960px) {
 | 
			
		||||
  :root {
 | 
			
		||||
    --vp-home-hero-image-filter: blur(72px);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component: Custom Block
 | 
			
		||||
 * -------------------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  --vp-custom-block-tip-border: var(--vp-c-brand);
 | 
			
		||||
  --vp-custom-block-tip-text: var(--vp-c-brand-darker);
 | 
			
		||||
  --vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dark {
 | 
			
		||||
  --vp-custom-block-tip-border: var(--vp-c-brand);
 | 
			
		||||
  --vp-custom-block-tip-text: var(--vp-c-brand-lightest);
 | 
			
		||||
  --vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component: Algolia
 | 
			
		||||
 * -------------------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
.DocSearch {
 | 
			
		||||
  --docsearch-primary-color: var(--vp-c-brand) !important;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								packages/docs/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/docs/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@sa/docs",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vitepress dev",
 | 
			
		||||
    "build": "vitepress build",
 | 
			
		||||
    "serve": "vitepress serve"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "vitepress": "1.0.0-rc.25"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								packages/docs/src/index.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								packages/docs/src/index.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
---
 | 
			
		||||
layout: home
 | 
			
		||||
 | 
			
		||||
title: Soybean Admin
 | 
			
		||||
titleTemplate: 一个清新优雅的中后台模版
 | 
			
		||||
 | 
			
		||||
hero:
 | 
			
		||||
  name: Soybean Admin
 | 
			
		||||
  text: 清新优雅的中后台模版
 | 
			
		||||
  tagline: 基于 Vue3 + Vite3 + TS + NaiveUI + UnoCSS
 | 
			
		||||
  image:
 | 
			
		||||
    src: /logo.svg
 | 
			
		||||
    alt: Soybean Admin
 | 
			
		||||
  actions:
 | 
			
		||||
    - theme: brand
 | 
			
		||||
      text: 开始
 | 
			
		||||
      link: /guide/
 | 
			
		||||
    - theme: alt
 | 
			
		||||
      text: 介绍
 | 
			
		||||
      link: /guide/introduction
 | 
			
		||||
    - theme: alt
 | 
			
		||||
      text: 在 GitHub 上查看
 | 
			
		||||
      link: https://github.com/honghuangdc/soybean-admin
 | 
			
		||||
 | 
			
		||||
features:
 | 
			
		||||
  - icon: 🆕
 | 
			
		||||
    title: 最新流行技术栈
 | 
			
		||||
    details: 基于Vue3、Vite3、TS、NaiveUI和UnoCSS等最新技术栈开发
 | 
			
		||||
  - icon: 🦋
 | 
			
		||||
    title: 极高水准的代码规范
 | 
			
		||||
    details: 代码规范完善,代码结构清晰
 | 
			
		||||
  - icon: 🛠️
 | 
			
		||||
    title: 丰富的插件
 | 
			
		||||
    details: 常见的Web端插件示例实现
 | 
			
		||||
  - icon: 🔩
 | 
			
		||||
    title: 主题配置
 | 
			
		||||
    details: 丰富的主题配置及暗黑主题适配
 | 
			
		||||
  - icon: 🔗
 | 
			
		||||
    title: 基于文件的路由系统
 | 
			
		||||
    details: 自动生成路由声明、路由导入和路由模块
 | 
			
		||||
  - icon: 🔑
 | 
			
		||||
    title: 权限管理
 | 
			
		||||
    details: 完善的前后端权限管理方案
 | 
			
		||||
---
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/eslint-config/configs/base.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/eslint-config/configs/base.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @type {import('eslint').ESLint.ConfigData}
 | 
			
		||||
 */
 | 
			
		||||
module.exports = {
 | 
			
		||||
  extends: [require.resolve('./ts.js'), require.resolve('./prettier.js')]
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										44
									
								
								packages/eslint-config/configs/js.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								packages/eslint-config/configs/js.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @type {import('eslint').ESLint.ConfigData}
 | 
			
		||||
 */
 | 
			
		||||
module.exports = {
 | 
			
		||||
  root: true,
 | 
			
		||||
  env: {
 | 
			
		||||
    browser: true,
 | 
			
		||||
    node: true,
 | 
			
		||||
    commonjs: true,
 | 
			
		||||
    es2024: true
 | 
			
		||||
  },
 | 
			
		||||
  parserOptions: {
 | 
			
		||||
    ecmaVersion: 2024,
 | 
			
		||||
    ecmaFeatures: {
 | 
			
		||||
      jsx: true
 | 
			
		||||
    },
 | 
			
		||||
    sourceType: 'module'
 | 
			
		||||
  },
 | 
			
		||||
  ignorePatterns: [
 | 
			
		||||
    'node_modules',
 | 
			
		||||
    '*.min.*',
 | 
			
		||||
    'CHANGELOG.md',
 | 
			
		||||
    'dist',
 | 
			
		||||
    'LICENSE*',
 | 
			
		||||
    'output',
 | 
			
		||||
    'coverage',
 | 
			
		||||
    'public',
 | 
			
		||||
    'temp',
 | 
			
		||||
    'package-lock.json',
 | 
			
		||||
    'pnpm-lock.yaml',
 | 
			
		||||
    'yarn.lock',
 | 
			
		||||
    '__snapshots__',
 | 
			
		||||
    '!.github',
 | 
			
		||||
    '!.vitepress',
 | 
			
		||||
    '!.vscode'
 | 
			
		||||
  ],
 | 
			
		||||
  plugins: ['n', 'promise'],
 | 
			
		||||
  extends: [require.resolve('../rules/all.js'), 'plugin:import/recommended'],
 | 
			
		||||
  rules: {
 | 
			
		||||
    // import
 | 
			
		||||
    'import/no-mutable-exports': 'error',
 | 
			
		||||
    'import/no-named-as-default': 'off'
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/eslint-config/configs/prettier.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/eslint-config/configs/prettier.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
const prettierRules = require('../rules/prettier');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @type {import('eslint').ESLint.ConfigData}
 | 
			
		||||
 */
 | 
			
		||||
module.exports = {
 | 
			
		||||
  extends: ['plugin:prettier/recommended'],
 | 
			
		||||
  rules: {
 | 
			
		||||
    'prettier/prettier': ['error', prettierRules]
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										61
									
								
								packages/eslint-config/configs/ts.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								packages/eslint-config/configs/ts.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @type {import('eslint').ESLint.ConfigData}
 | 
			
		||||
 */
 | 
			
		||||
module.exports = {
 | 
			
		||||
  plugins: ['@typescript-eslint'],
 | 
			
		||||
  extends: [require.resolve('./js.js'), 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended'],
 | 
			
		||||
  settings: {
 | 
			
		||||
    'import/resolver': {
 | 
			
		||||
      typescript: {
 | 
			
		||||
        project: ['tsconfig.json', 'packages/*/tsconfig.json', 'examples/*/tsconfig.json', 'docs/*/tsconfig.json']
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  overrides: [
 | 
			
		||||
    {
 | 
			
		||||
      files: ['*.ts', '*.tsx', '*.mts', '*.cts'],
 | 
			
		||||
      parser: '@typescript-eslint/parser'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      files: ['*.js', '*.mjs', '*.cjs', '*.cts'],
 | 
			
		||||
      rules: {
 | 
			
		||||
        '@typescript-eslint/no-var-requires': 'off'
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  rules: {
 | 
			
		||||
    // TS
 | 
			
		||||
    '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports', disallowTypeAnnotations: false }],
 | 
			
		||||
    '@typescript-eslint/no-empty-interface': [
 | 
			
		||||
      'error',
 | 
			
		||||
      {
 | 
			
		||||
        allowSingleExtends: true
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    // Override JS
 | 
			
		||||
    'no-redeclare': 'off',
 | 
			
		||||
    '@typescript-eslint/no-redeclare': 'error',
 | 
			
		||||
    'no-unused-vars': 'off',
 | 
			
		||||
    '@typescript-eslint/no-unused-vars': [
 | 
			
		||||
      'error',
 | 
			
		||||
      {
 | 
			
		||||
        vars: 'all',
 | 
			
		||||
        args: 'all',
 | 
			
		||||
        ignoreRestSiblings: false,
 | 
			
		||||
        varsIgnorePattern: '^_',
 | 
			
		||||
        argsIgnorePattern: '^_'
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    'no-use-before-define': 'off',
 | 
			
		||||
    '@typescript-eslint/no-use-before-define': ['error', { functions: false, classes: false, variables: true }],
 | 
			
		||||
    'no-shadow': 'off',
 | 
			
		||||
    '@typescript-eslint/no-shadow': 'error',
 | 
			
		||||
 | 
			
		||||
    // off
 | 
			
		||||
    '@typescript-eslint/consistent-type-definitions': 'off',
 | 
			
		||||
    '@typescript-eslint/no-empty-function': 'off',
 | 
			
		||||
    '@typescript-eslint/no-explicit-any': 'off',
 | 
			
		||||
    '@typescript-eslint/no-non-null-assertion': 'off'
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										30
									
								
								packages/eslint-config/configs/vue.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								packages/eslint-config/configs/vue.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @type {import('eslint').ESLint.ConfigData}
 | 
			
		||||
 */
 | 
			
		||||
module.exports = {
 | 
			
		||||
  extends: ['plugin:vue/vue3-recommended', require.resolve('./base.js')],
 | 
			
		||||
  overrides: [
 | 
			
		||||
    {
 | 
			
		||||
      files: ['*.vue'],
 | 
			
		||||
      parser: 'vue-eslint-parser',
 | 
			
		||||
      parserOptions: {
 | 
			
		||||
        parser: {
 | 
			
		||||
          js: 'espree',
 | 
			
		||||
          jsx: 'espree',
 | 
			
		||||
          ts: '@typescript-eslint/parser',
 | 
			
		||||
          tsx: '@typescript-eslint/parser'
 | 
			
		||||
        },
 | 
			
		||||
        extraFileExtensions: ['.vue'],
 | 
			
		||||
        ecmaFeatures: {
 | 
			
		||||
          jsx: true
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      rules: {
 | 
			
		||||
        'no-undef': 'off' // TS will check un declared variables, if the script code is is in a .vue file, this rule should not disabled
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  rules: {
 | 
			
		||||
    'vue/multi-word-component-names': 'off'
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/eslint-config/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/eslint-config/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
const baseConfig = require('./configs/base');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @type {import('eslint').ESLint.ConfigData}
 | 
			
		||||
 */
 | 
			
		||||
module.exports = baseConfig;
 | 
			
		||||
							
								
								
									
										23
									
								
								packages/eslint-config/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								packages/eslint-config/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "eslint-config-sa",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "SoybeanAdmin's eslint config resets",
 | 
			
		||||
  "exports": {
 | 
			
		||||
    ".": "./index.js",
 | 
			
		||||
    "./vue": "./configs/vue.js"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/eslint": "8.44.7",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "6.11.0",
 | 
			
		||||
    "@typescript-eslint/parser": "6.11.0",
 | 
			
		||||
    "eslint": "8.53.0",
 | 
			
		||||
    "eslint-config-prettier": "9.0.0",
 | 
			
		||||
    "eslint-import-resolver-typescript": "3.6.1",
 | 
			
		||||
    "eslint-plugin-import": "2.29.0",
 | 
			
		||||
    "eslint-plugin-n": "16.3.1",
 | 
			
		||||
    "eslint-plugin-prettier": "5.0.1",
 | 
			
		||||
    "eslint-plugin-promise": "6.1.1",
 | 
			
		||||
    "eslint-plugin-vue": "9.18.1",
 | 
			
		||||
    "prettier": "3.1.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1681
									
								
								packages/eslint-config/rules/all.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1681
									
								
								packages/eslint-config/rules/all.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										26
									
								
								packages/eslint-config/rules/prettier.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								packages/eslint-config/rules/prettier.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @type {import('prettier').Options}
 | 
			
		||||
 */
 | 
			
		||||
module.exports = {
 | 
			
		||||
  printWidth: 120,
 | 
			
		||||
  tabWidth: 2,
 | 
			
		||||
  useTabs: false,
 | 
			
		||||
  semi: true,
 | 
			
		||||
  singleQuote: true,
 | 
			
		||||
  quoteProps: 'as-needed',
 | 
			
		||||
  jsxSingleQuote: false,
 | 
			
		||||
  trailingComma: 'none',
 | 
			
		||||
  bracketSpacing: true,
 | 
			
		||||
  bracketSameLine: false,
 | 
			
		||||
  arrowParens: 'avoid',
 | 
			
		||||
  rangeStart: 0,
 | 
			
		||||
  rangeEnd: Number.POSITIVE_INFINITY,
 | 
			
		||||
  requirePragma: false,
 | 
			
		||||
  insertPragma: false,
 | 
			
		||||
  proseWrap: 'preserve',
 | 
			
		||||
  htmlWhitespaceSensitivity: 'ignore',
 | 
			
		||||
  vueIndentScriptAndStyle: false,
 | 
			
		||||
  endOfLine: 'lf',
 | 
			
		||||
  embeddedLanguageFormatting: 'auto',
 | 
			
		||||
  singleAttributePerLine: false
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										14
									
								
								packages/hooks/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/hooks/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@sa/hooks",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "exports": {
 | 
			
		||||
    ".": "./src/index.ts"
 | 
			
		||||
  },
 | 
			
		||||
  "typesVersions": {
 | 
			
		||||
    "*": {
 | 
			
		||||
      "*": [
 | 
			
		||||
        "./src/*"
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/hooks/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/hooks/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
import useBoolean from './use-boolean';
 | 
			
		||||
import useLoading from './use-loading';
 | 
			
		||||
import useContext from './use-context';
 | 
			
		||||
import useSvgIconRender from './use-svg-icon-render';
 | 
			
		||||
 | 
			
		||||
export { useBoolean, useLoading, useContext, useSvgIconRender };
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * boolean组合式函数
 | 
			
		||||
 * @param initValue 初始值
 | 
			
		||||
 * boolean
 | 
			
		||||
 * @param initValue init value
 | 
			
		||||
 */
 | 
			
		||||
export default function useBoolean(initValue = false) {
 | 
			
		||||
  const bool = ref(initValue);
 | 
			
		||||
							
								
								
									
										103
									
								
								packages/hooks/src/use-context.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								packages/hooks/src/use-context.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
import { inject, provide } from 'vue';
 | 
			
		||||
import type { InjectionKey } from 'vue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * use context
 | 
			
		||||
 * @param contextName context name
 | 
			
		||||
 * @param fn context function
 | 
			
		||||
 * @example
 | 
			
		||||
 * ```ts
 | 
			
		||||
 * // there are three vue files: A.vue, B.vue, C.vue, and A.vue is the parent component of B.vue and C.vue
 | 
			
		||||
 *
 | 
			
		||||
 * // context.ts
 | 
			
		||||
 * import { ref } from 'vue';
 | 
			
		||||
 * import { useContext } from '@sa/hooks';
 | 
			
		||||
 *
 | 
			
		||||
 * export const { setupStore, useStore } = useContext('demo', () => {
 | 
			
		||||
 *   const count = ref(0);
 | 
			
		||||
 *
 | 
			
		||||
 *   function increment() {
 | 
			
		||||
 *     count.value++;
 | 
			
		||||
 *   }
 | 
			
		||||
 *
 | 
			
		||||
 *   function decrement() {
 | 
			
		||||
 *     count.value--;
 | 
			
		||||
 *   }
 | 
			
		||||
 *
 | 
			
		||||
 *   return {
 | 
			
		||||
 *     count,
 | 
			
		||||
 *     increment,
 | 
			
		||||
 *     decrement
 | 
			
		||||
 *   };
 | 
			
		||||
 * })
 | 
			
		||||
 * ```
 | 
			
		||||
 *
 | 
			
		||||
 * // A.vue
 | 
			
		||||
 * ```vue
 | 
			
		||||
 * <template>
 | 
			
		||||
 *   <div>A</div>
 | 
			
		||||
 * </template>
 | 
			
		||||
 * <script setup lang="ts">
 | 
			
		||||
 * import { setupStore } from './context';
 | 
			
		||||
 *
 | 
			
		||||
 * setupStore();
 | 
			
		||||
 * // const { increment } = setupStore(); // also can control the store in the parent component
 | 
			
		||||
 * </script>
 | 
			
		||||
 * ```
 | 
			
		||||
 * // B.vue
 | 
			
		||||
 * ```vue
 | 
			
		||||
 * <template>
 | 
			
		||||
 *  <div>B</div>
 | 
			
		||||
 * </template>
 | 
			
		||||
 * <script setup lang="ts">
 | 
			
		||||
 * import { useStore } from './context';
 | 
			
		||||
 *
 | 
			
		||||
 * const { count, increment } = useStore();
 | 
			
		||||
 * </script>
 | 
			
		||||
 * ```
 | 
			
		||||
 *
 | 
			
		||||
 * // C.vue is same as B.vue
 | 
			
		||||
 */
 | 
			
		||||
export default function useContext<T extends (...args: any[]) => any>(contextName: string, fn: T) {
 | 
			
		||||
  type Context = ReturnType<T>;
 | 
			
		||||
 | 
			
		||||
  const { useProvide, useInject: useStore } = createContext<Context>(contextName);
 | 
			
		||||
 | 
			
		||||
  function setupStore(...args: Parameters<T>) {
 | 
			
		||||
    const context: Context = fn(...args);
 | 
			
		||||
    return useProvide(context);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    /**
 | 
			
		||||
     * setup store in the parent component
 | 
			
		||||
     */
 | 
			
		||||
    setupStore,
 | 
			
		||||
    /**
 | 
			
		||||
     * use store in the child component
 | 
			
		||||
     */
 | 
			
		||||
    useStore
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * create context
 | 
			
		||||
 */
 | 
			
		||||
function createContext<T>(contextName: string) {
 | 
			
		||||
  const injectKey: InjectionKey<T> = Symbol(contextName);
 | 
			
		||||
 | 
			
		||||
  function useProvide(context: T) {
 | 
			
		||||
    provide(injectKey, context);
 | 
			
		||||
 | 
			
		||||
    return context;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function useInject() {
 | 
			
		||||
    return inject(injectKey) as T;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    useProvide,
 | 
			
		||||
    useInject
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
import useBoolean from './use-boolean';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * loading
 | 
			
		||||
 * @param initValue init value
 | 
			
		||||
 */
 | 
			
		||||
export default function useLoading(initValue = false) {
 | 
			
		||||
  const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initValue);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										56
									
								
								packages/hooks/src/use-svg-icon-render.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								packages/hooks/src/use-svg-icon-render.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import { h } from 'vue';
 | 
			
		||||
import type { Component } from 'vue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * svg icon render hook
 | 
			
		||||
 * @param SvgIcon svg icon component
 | 
			
		||||
 */
 | 
			
		||||
export default function useSvgIconRender(SvgIcon: Component) {
 | 
			
		||||
  interface IconConfig {
 | 
			
		||||
    /**
 | 
			
		||||
     * iconify icon name
 | 
			
		||||
     */
 | 
			
		||||
    icon?: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * local icon name
 | 
			
		||||
     */
 | 
			
		||||
    localIcon?: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * icon color
 | 
			
		||||
     */
 | 
			
		||||
    color?: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * icon size
 | 
			
		||||
     */
 | 
			
		||||
    fontSize?: number;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  type IconStyle = Partial<Pick<CSSStyleDeclaration, 'color' | 'fontSize'>>;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * svg icon VNode
 | 
			
		||||
   * @param config
 | 
			
		||||
   */
 | 
			
		||||
  const SvgIconVNode = (config: IconConfig) => {
 | 
			
		||||
    const { color, fontSize, icon, localIcon } = config;
 | 
			
		||||
 | 
			
		||||
    const style: IconStyle = {};
 | 
			
		||||
 | 
			
		||||
    if (color) {
 | 
			
		||||
      style.color = color;
 | 
			
		||||
    }
 | 
			
		||||
    if (fontSize) {
 | 
			
		||||
      style.fontSize = `${fontSize}px`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!icon && !localIcon) {
 | 
			
		||||
      return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return () => h(SvgIcon, { icon, localIcon, style });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    SvgIconVNode
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								packages/hooks/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/hooks/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "target": "ESNext",
 | 
			
		||||
    "lib": ["DOM", "ESNext"],
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "allowSyntheticDefaultImports": true,
 | 
			
		||||
    "jsx": "preserve",
 | 
			
		||||
    "moduleResolution": "node",
 | 
			
		||||
    "resolveJsonModule": true,
 | 
			
		||||
    "noUnusedLocals": true,
 | 
			
		||||
    "strictNullChecks": true,
 | 
			
		||||
    "forceConsistentCasingInFileNames": true,
 | 
			
		||||
    "types": ["node"]
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["src/**/*"],
 | 
			
		||||
  "exclude": ["node_modules", "dist"]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/materials/.eslintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/materials/.eslintrc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "sa/vue",
 | 
			
		||||
  "rules": {
 | 
			
		||||
    "vue/multi-word-component-names": "off"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								packages/materials/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								packages/materials/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@sa/materials",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "exports": {
 | 
			
		||||
    ".": "./src/index.ts"
 | 
			
		||||
  },
 | 
			
		||||
  "typesVersions": {
 | 
			
		||||
    "*": {
 | 
			
		||||
      "*": [
 | 
			
		||||
        "./src/*"
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@sa/utils": "workspace:*",
 | 
			
		||||
    "@simonwep/pickr": "1.9.0",
 | 
			
		||||
    "simplebar-vue": "2.3.3"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "typed-css-modules": "0.8.1"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/materials/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/materials/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import AdminLayout, { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX } from './libs/admin-layout';
 | 
			
		||||
import PageTab from './libs/page-tab';
 | 
			
		||||
import SimpleScrollbar from './libs/simple-scrollbar';
 | 
			
		||||
import ColorPicker from './libs/color-picker';
 | 
			
		||||
 | 
			
		||||
export { AdminLayout, LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX, PageTab, SimpleScrollbar, ColorPicker };
 | 
			
		||||
export * from './types';
 | 
			
		||||
							
								
								
									
										63
									
								
								packages/materials/src/libs/admin-layout/index.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								packages/materials/src/libs/admin-layout/index.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
/* @type */
 | 
			
		||||
 | 
			
		||||
.layout-header,
 | 
			
		||||
.layout-header-placement {
 | 
			
		||||
  height: var(--soy-header-height);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.layout-header {
 | 
			
		||||
  z-index: var(--soy-header-z-index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.layout-tab {
 | 
			
		||||
  top: var(--soy-header-height);
 | 
			
		||||
  height: var(--soy-tab-height);
 | 
			
		||||
  z-index: var(--soy-tab-z-index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.layout-tab-placement {
 | 
			
		||||
  height: var(--soy-tab-height);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.layout-sider {
 | 
			
		||||
  width: var(--soy-sider-width);
 | 
			
		||||
  z-index: var(--soy-sider-z-index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.layout-mobile-sider {
 | 
			
		||||
  z-index: var(--soy-sider-z-index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.layout-mobile-sider-mask {
 | 
			
		||||
  z-index: var(--soy-mobile-sider-z-index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.layout-sider_collapsed {
 | 
			
		||||
  width: var(--soy-sider-collapsed-width);
 | 
			
		||||
  z-index: var(--soy-sider-z-index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.layout-footer,
 | 
			
		||||
.layout-footer-placement {
 | 
			
		||||
  height: var(--soy-footer-height);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.layout-footer {
 | 
			
		||||
  z-index: var(--soy-footer-z-index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.left-gap {
 | 
			
		||||
  padding-left: var(--soy-sider-width);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.left-gap_collapsed {
 | 
			
		||||
  padding-left: var(--soy-sider-collapsed-width);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sider-padding-top {
 | 
			
		||||
  padding-top: var(--soy-header-height);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sider-padding-bottom {
 | 
			
		||||
  padding-bottom: var(--soy-footer-height);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								packages/materials/src/libs/admin-layout/index.module.css.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/materials/src/libs/admin-layout/index.module.css.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
declare const styles: {
 | 
			
		||||
  readonly 'layout-header': string;
 | 
			
		||||
  readonly 'layout-header-placement': string;
 | 
			
		||||
  readonly 'layout-tab': string;
 | 
			
		||||
  readonly 'layout-tab-placement': string;
 | 
			
		||||
  readonly 'layout-sider': string;
 | 
			
		||||
  readonly 'layout-mobile-sider': string;
 | 
			
		||||
  readonly 'layout-mobile-sider-mask': string;
 | 
			
		||||
  readonly 'layout-sider_collapsed': string;
 | 
			
		||||
  readonly 'layout-footer': string;
 | 
			
		||||
  readonly 'layout-footer-placement': string;
 | 
			
		||||
  readonly 'left-gap': string;
 | 
			
		||||
  readonly 'left-gap_collapsed': string;
 | 
			
		||||
  readonly 'sider-padding-top': string;
 | 
			
		||||
  readonly 'sider-padding-bottom': string;
 | 
			
		||||
};
 | 
			
		||||
export = styles;
 | 
			
		||||
							
								
								
									
										5
									
								
								packages/materials/src/libs/admin-layout/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								packages/materials/src/libs/admin-layout/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
import AdminLayout from './index.vue';
 | 
			
		||||
import { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX } from './shared';
 | 
			
		||||
 | 
			
		||||
export default AdminLayout;
 | 
			
		||||
export { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX };
 | 
			
		||||
							
								
								
									
										238
									
								
								packages/materials/src/libs/admin-layout/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								packages/materials/src/libs/admin-layout/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,238 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div :class="['relative h-full', commonClass]" :style="cssVars">
 | 
			
		||||
    <div
 | 
			
		||||
      :id="isWrapperScroll ? scrollElId : undefined"
 | 
			
		||||
      :class="['flex flex-col h-full', commonClass, scrollWrapperClass, { 'overflow-y-auto': isWrapperScroll }]"
 | 
			
		||||
    >
 | 
			
		||||
      <!-- Header -->
 | 
			
		||||
      <template v-if="showHeader">
 | 
			
		||||
        <header
 | 
			
		||||
          v-show="!fullContent"
 | 
			
		||||
          :class="[
 | 
			
		||||
            style['layout-header'],
 | 
			
		||||
            'flex-shrink-0',
 | 
			
		||||
            commonClass,
 | 
			
		||||
            headerClass,
 | 
			
		||||
            headerLeftGapClass,
 | 
			
		||||
            { 'absolute top-0 left-0 w-full': fixedHeaderAndTab }
 | 
			
		||||
          ]"
 | 
			
		||||
        >
 | 
			
		||||
          <slot name="header"></slot>
 | 
			
		||||
        </header>
 | 
			
		||||
        <div
 | 
			
		||||
          v-show="!fullContent && fixedHeaderAndTab"
 | 
			
		||||
          :class="[style['layout-header-placement'], 'flex-shrink-0 overflow-hidden']"
 | 
			
		||||
        ></div>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <!-- Tab -->
 | 
			
		||||
      <template v-if="showTab">
 | 
			
		||||
        <div
 | 
			
		||||
          :class="[
 | 
			
		||||
            style['layout-tab'],
 | 
			
		||||
            'flex-shrink-0',
 | 
			
		||||
            commonClass,
 | 
			
		||||
            tabClass,
 | 
			
		||||
            { 'top-0!': fullContent || !showHeader },
 | 
			
		||||
            leftGapClass,
 | 
			
		||||
            { 'absolute left-0 w-full': fixedHeaderAndTab }
 | 
			
		||||
          ]"
 | 
			
		||||
        >
 | 
			
		||||
          <slot name="tab"></slot>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div
 | 
			
		||||
          v-show="fullContent || fixedHeaderAndTab"
 | 
			
		||||
          :class="[style['layout-tab-placement'], 'flex-shrink-0 overflow-hidden']"
 | 
			
		||||
        ></div>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <!-- Sider -->
 | 
			
		||||
      <template v-if="showSider">
 | 
			
		||||
        <aside
 | 
			
		||||
          v-show="!fullContent"
 | 
			
		||||
          :class="[
 | 
			
		||||
            'absolute left-0 top-0 h-full',
 | 
			
		||||
            commonClass,
 | 
			
		||||
            siderClass,
 | 
			
		||||
            siderPaddingClass,
 | 
			
		||||
            siderCollapse ? style['layout-sider_collapsed'] : style['layout-sider']
 | 
			
		||||
          ]"
 | 
			
		||||
        >
 | 
			
		||||
          <slot name="sider"></slot>
 | 
			
		||||
        </aside>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <!-- Mobile Sider -->
 | 
			
		||||
      <template v-if="showMobileSider">
 | 
			
		||||
        <aside
 | 
			
		||||
          :class="[
 | 
			
		||||
            'absolute left-0 top-0 w-0 h-full bg-white',
 | 
			
		||||
            commonClass,
 | 
			
		||||
            mobileSiderClass,
 | 
			
		||||
            style['layout-mobile-sider'],
 | 
			
		||||
            siderCollapse ? 'overflow-hidden' : style['layout-sider']
 | 
			
		||||
          ]"
 | 
			
		||||
        >
 | 
			
		||||
          <slot name="sider"></slot>
 | 
			
		||||
        </aside>
 | 
			
		||||
        <div
 | 
			
		||||
          v-show="!siderCollapse"
 | 
			
		||||
          :class="['absolute left-0 top-0 w-full h-full bg-[rgba(0,0,0,0.2)]', style['layout-mobile-sider-mask']]"
 | 
			
		||||
          @click="handleClickMask"
 | 
			
		||||
        ></div>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <!-- Main Content -->
 | 
			
		||||
      <main
 | 
			
		||||
        :id="isContentScroll ? scrollElId : undefined"
 | 
			
		||||
        :class="[
 | 
			
		||||
          'flex flex-col flex-grow',
 | 
			
		||||
          commonClass,
 | 
			
		||||
          contentClass,
 | 
			
		||||
          leftGapClass,
 | 
			
		||||
          { 'overflow-y-auto': isContentScroll }
 | 
			
		||||
        ]"
 | 
			
		||||
      >
 | 
			
		||||
        <slot></slot>
 | 
			
		||||
      </main>
 | 
			
		||||
 | 
			
		||||
      <!-- Footer -->
 | 
			
		||||
      <template v-if="showFooter">
 | 
			
		||||
        <footer
 | 
			
		||||
          v-show="!fullContent"
 | 
			
		||||
          :class="[
 | 
			
		||||
            style['layout-footer'],
 | 
			
		||||
            'flex-shrink-0',
 | 
			
		||||
            commonClass,
 | 
			
		||||
            footerClass,
 | 
			
		||||
            footerLeftGapClass,
 | 
			
		||||
            { 'absolute left-0 bottom-0 w-full': fixedFooter }
 | 
			
		||||
          ]"
 | 
			
		||||
        >
 | 
			
		||||
          <slot name="footer"></slot>
 | 
			
		||||
        </footer>
 | 
			
		||||
        <div
 | 
			
		||||
          v-show="!fullContent && fixedFooter"
 | 
			
		||||
          :class="[style['layout-footer-placement'], 'flex-shrink-0 overflow-hidden']"
 | 
			
		||||
        ></div>
 | 
			
		||||
      </template>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import type { AdminLayoutProps } from '../../types';
 | 
			
		||||
import { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX, createLayoutCssVars } from './shared';
 | 
			
		||||
import style from './index.module.css';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'AdminLayout'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<AdminLayoutProps>(), {
 | 
			
		||||
  mode: 'vertical',
 | 
			
		||||
  scrollMode: 'content',
 | 
			
		||||
  scrollElId: LAYOUT_SCROLL_EL_ID,
 | 
			
		||||
  commonClass: 'transition-all-300',
 | 
			
		||||
  fixedTop: true,
 | 
			
		||||
  maxZIndex: LAYOUT_MAX_Z_INDEX,
 | 
			
		||||
  headerVisible: true,
 | 
			
		||||
  headerHeight: 56,
 | 
			
		||||
  tabVisible: true,
 | 
			
		||||
  tabHeight: 48,
 | 
			
		||||
  siderVisible: true,
 | 
			
		||||
  siderCollapse: false,
 | 
			
		||||
  siderWidth: 220,
 | 
			
		||||
  siderCollapsedWidth: 64,
 | 
			
		||||
  footerVisible: true,
 | 
			
		||||
  footerHeight: 48,
 | 
			
		||||
  rightFooter: false
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
interface Emits {
 | 
			
		||||
  /**
 | 
			
		||||
   * update siderCollapse
 | 
			
		||||
   */
 | 
			
		||||
  (e: 'update:siderCollapse', collapse: boolean): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<Emits>();
 | 
			
		||||
 | 
			
		||||
type SlotFn = (props?: Record<string, unknown>) => any;
 | 
			
		||||
 | 
			
		||||
type Slots = {
 | 
			
		||||
  /** main */
 | 
			
		||||
  default?: SlotFn;
 | 
			
		||||
  /** header */
 | 
			
		||||
  header?: SlotFn;
 | 
			
		||||
  /** tab */
 | 
			
		||||
  tab?: SlotFn;
 | 
			
		||||
  /** sider */
 | 
			
		||||
  sider?: SlotFn;
 | 
			
		||||
  /** footer */
 | 
			
		||||
  footer?: SlotFn;
 | 
			
		||||
};
 | 
			
		||||
const slots = defineSlots<Slots>();
 | 
			
		||||
 | 
			
		||||
const cssVars = computed(() => createLayoutCssVars(props));
 | 
			
		||||
 | 
			
		||||
// config visible
 | 
			
		||||
const showHeader = computed(() => Boolean(slots.header) && props.headerVisible);
 | 
			
		||||
const showTab = computed(() => Boolean(slots.tab) && props.tabVisible);
 | 
			
		||||
const showSider = computed(() => !props.isMobile && Boolean(slots.sider) && props.siderVisible);
 | 
			
		||||
const showMobileSider = computed(() => props.isMobile && Boolean(slots.sider) && props.siderVisible);
 | 
			
		||||
const showFooter = computed(() => Boolean(slots.footer) && props.footerVisible);
 | 
			
		||||
 | 
			
		||||
// scroll mode
 | 
			
		||||
const isWrapperScroll = computed(() => props.scrollMode === 'wrapper');
 | 
			
		||||
const isContentScroll = computed(() => props.scrollMode === 'content');
 | 
			
		||||
 | 
			
		||||
// layout direction
 | 
			
		||||
const isVertical = computed(() => props.mode === 'vertical');
 | 
			
		||||
const isHorizontal = computed(() => props.mode === 'horizontal');
 | 
			
		||||
 | 
			
		||||
const fixedHeaderAndTab = computed(() => props.fixedTop || (isHorizontal.value && isWrapperScroll.value));
 | 
			
		||||
 | 
			
		||||
// css
 | 
			
		||||
const leftGapClass = computed(() => {
 | 
			
		||||
  if (!props.fullContent && showSider.value) {
 | 
			
		||||
    return props.siderCollapse ? style['left-gap_collapsed'] : style['left-gap'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return '';
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const headerLeftGapClass = computed(() => (isVertical.value ? leftGapClass.value : ''));
 | 
			
		||||
 | 
			
		||||
const footerLeftGapClass = computed(() => {
 | 
			
		||||
  const condition1 = isVertical.value;
 | 
			
		||||
  const condition2 = isHorizontal.value && isWrapperScroll.value && !props.fixedFooter;
 | 
			
		||||
  const condition3 = Boolean(isHorizontal.value && props.rightFooter);
 | 
			
		||||
 | 
			
		||||
  if (condition1 || condition2 || condition3) {
 | 
			
		||||
    return leftGapClass.value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return '';
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const siderPaddingClass = computed(() => {
 | 
			
		||||
  let cls = '';
 | 
			
		||||
 | 
			
		||||
  if (showHeader.value && !headerLeftGapClass.value) {
 | 
			
		||||
    cls += style['sider-padding-top'];
 | 
			
		||||
  }
 | 
			
		||||
  if (showFooter.value && !footerLeftGapClass.value) {
 | 
			
		||||
    cls += ` ${style['sider-padding-bottom']}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return cls;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function handleClickMask() {
 | 
			
		||||
  emit('update:siderCollapse', true);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										70
									
								
								packages/materials/src/libs/admin-layout/shared.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								packages/materials/src/libs/admin-layout/shared.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
import type { AdminLayoutProps, LayoutCssVarsProps, LayoutCssVars } from '../../types';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the id of the scroll element of the layout
 | 
			
		||||
 */
 | 
			
		||||
export const LAYOUT_SCROLL_EL_ID = '__SCROLL_EL_ID__';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the max z-index of the layout
 | 
			
		||||
 */
 | 
			
		||||
export const LAYOUT_MAX_Z_INDEX = 100;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * create layout css vars by css vars props
 | 
			
		||||
 * @param props css vars props
 | 
			
		||||
 */
 | 
			
		||||
function createLayoutCssVarsByCssVarsProps(props: LayoutCssVarsProps) {
 | 
			
		||||
  const cssVars: LayoutCssVars = {
 | 
			
		||||
    '--soy-header-height': `${props.headerHeight}px`,
 | 
			
		||||
    '--soy-header-z-index': props.headerZIndex,
 | 
			
		||||
    '--soy-tab-height': `${props.tabHeight}px`,
 | 
			
		||||
    '--soy-tab-z-index': props.tabZIndex,
 | 
			
		||||
    '--soy-sider-width': `${props.siderWidth}px`,
 | 
			
		||||
    '--soy-sider-collapsed-width': `${props.siderCollapsedWidth}px`,
 | 
			
		||||
    '--soy-sider-z-index': props.siderZIndex,
 | 
			
		||||
    '--soy-mobile-sider-z-index': props.mobileSiderZIndex,
 | 
			
		||||
    '--soy-footer-height': `${props.footerHeight}px`,
 | 
			
		||||
    '--soy-footer-z-index': props.footerZIndex
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return cssVars;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * create layout css vars
 | 
			
		||||
 * @param props
 | 
			
		||||
 */
 | 
			
		||||
export function createLayoutCssVars(props: AdminLayoutProps) {
 | 
			
		||||
  const {
 | 
			
		||||
    mode,
 | 
			
		||||
    isMobile,
 | 
			
		||||
    maxZIndex = LAYOUT_MAX_Z_INDEX,
 | 
			
		||||
    headerHeight,
 | 
			
		||||
    tabHeight,
 | 
			
		||||
    siderWidth,
 | 
			
		||||
    siderCollapsedWidth,
 | 
			
		||||
    footerHeight
 | 
			
		||||
  } = props;
 | 
			
		||||
 | 
			
		||||
  const headerZIndex = maxZIndex - 3;
 | 
			
		||||
  const tabZIndex = maxZIndex - 5;
 | 
			
		||||
  const siderZIndex = mode === 'vertical' || isMobile ? maxZIndex - 1 : maxZIndex - 4;
 | 
			
		||||
  const mobileSiderZIndex = isMobile ? maxZIndex - 2 : 0;
 | 
			
		||||
  const footerZIndex = maxZIndex - 5;
 | 
			
		||||
 | 
			
		||||
  const cssProps: LayoutCssVarsProps = {
 | 
			
		||||
    headerHeight,
 | 
			
		||||
    headerZIndex,
 | 
			
		||||
    tabHeight,
 | 
			
		||||
    tabZIndex,
 | 
			
		||||
    siderWidth,
 | 
			
		||||
    siderZIndex,
 | 
			
		||||
    mobileSiderZIndex,
 | 
			
		||||
    siderCollapsedWidth,
 | 
			
		||||
    footerHeight,
 | 
			
		||||
    footerZIndex
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return createLayoutCssVarsByCssVarsProps(cssProps);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								packages/materials/src/libs/color-picker/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/materials/src/libs/color-picker/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import ColorPicker from './index.vue';
 | 
			
		||||
 | 
			
		||||
export default ColorPicker;
 | 
			
		||||
							
								
								
									
										116
									
								
								packages/materials/src/libs/color-picker/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								packages/materials/src/libs/color-picker/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, watch, onMounted } from 'vue';
 | 
			
		||||
import ColorPicker from '@simonwep/pickr';
 | 
			
		||||
import '@simonwep/pickr/dist/themes/nano.min.css';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'ColorPicker'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  color: string;
 | 
			
		||||
  palettes?: string[];
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  palettes: () => [
 | 
			
		||||
    '#3b82f6',
 | 
			
		||||
    '#6366f1',
 | 
			
		||||
    '#8b5cf6',
 | 
			
		||||
    '#a855f7',
 | 
			
		||||
    '#0ea5e9',
 | 
			
		||||
    '#06b6d4',
 | 
			
		||||
    '#f43f5e',
 | 
			
		||||
    '#ef4444',
 | 
			
		||||
    '#ec4899',
 | 
			
		||||
    '#d946ef',
 | 
			
		||||
    '#f97316',
 | 
			
		||||
    '#f59e0b',
 | 
			
		||||
    '#eab308',
 | 
			
		||||
    '#84cc16',
 | 
			
		||||
    '#22c55e',
 | 
			
		||||
    '#10b981',
 | 
			
		||||
    '#14b8a6'
 | 
			
		||||
  ]
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
interface Emits {
 | 
			
		||||
  (e: 'update:color', value: string): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<Emits>();
 | 
			
		||||
 | 
			
		||||
const domRef = ref<HTMLElement | null>(null);
 | 
			
		||||
const instance = ref<ColorPicker | null>(null);
 | 
			
		||||
 | 
			
		||||
function handleColorChange(hsva: ColorPicker.HSVaColor) {
 | 
			
		||||
  const color = hsva.toHEXA().toString();
 | 
			
		||||
  emit('update:color', color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initColorPicker() {
 | 
			
		||||
  if (!domRef.value) return;
 | 
			
		||||
 | 
			
		||||
  instance.value = ColorPicker.create({
 | 
			
		||||
    el: domRef.value,
 | 
			
		||||
    theme: 'nano',
 | 
			
		||||
    swatches: props.palettes,
 | 
			
		||||
    lockOpacity: true,
 | 
			
		||||
    default: props.color,
 | 
			
		||||
    disabled: props.disabled,
 | 
			
		||||
    components: {
 | 
			
		||||
      preview: true,
 | 
			
		||||
      opacity: false,
 | 
			
		||||
      hue: true,
 | 
			
		||||
      interaction: {
 | 
			
		||||
        hex: true,
 | 
			
		||||
        rgba: true,
 | 
			
		||||
        input: true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  instance.value.on('change', handleColorChange);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateColor(color: string) {
 | 
			
		||||
  if (!instance.value) return;
 | 
			
		||||
 | 
			
		||||
  instance.value.setColor(color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateDisabled(disabled: boolean) {
 | 
			
		||||
  if (!instance.value) return;
 | 
			
		||||
 | 
			
		||||
  if (disabled) {
 | 
			
		||||
    instance.value.disable();
 | 
			
		||||
  } else {
 | 
			
		||||
    instance.value.enable();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.color,
 | 
			
		||||
  value => {
 | 
			
		||||
    updateColor(value);
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.disabled,
 | 
			
		||||
  value => {
 | 
			
		||||
    updateDisabled(value);
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initColorPicker();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div ref="domRef"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										49
									
								
								packages/materials/src/libs/page-tab/button-tab.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								packages/materials/src/libs/page-tab/button-tab.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    :class="[
 | 
			
		||||
      ':soy: relative inline-flex justify-center items-center gap-12px px-12px py-4px border-1px border-solid rounded-4px cursor-pointer whitespace-nowrap',
 | 
			
		||||
      style['button-tab'],
 | 
			
		||||
      { [style['button-tab_dark']]: darkMode },
 | 
			
		||||
      { [style['button-tab_active']]: active },
 | 
			
		||||
      { [style['button-tab_active_dark']]: active && darkMode }
 | 
			
		||||
    ]"
 | 
			
		||||
  >
 | 
			
		||||
    <slot name="prefix"></slot>
 | 
			
		||||
    <slot></slot>
 | 
			
		||||
    <slot name="suffix"></slot>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import style from './index.module.css';
 | 
			
		||||
import type { PageTabProps } from '../../types';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'ButtonTab'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineProps<PageTabProps>();
 | 
			
		||||
 | 
			
		||||
type SlotFn = (props?: Record<string, unknown>) => any;
 | 
			
		||||
 | 
			
		||||
type Slots = {
 | 
			
		||||
  /**
 | 
			
		||||
   * slot
 | 
			
		||||
   * @description the center content of the tab
 | 
			
		||||
   */
 | 
			
		||||
  default?: SlotFn;
 | 
			
		||||
  /**
 | 
			
		||||
   * slot
 | 
			
		||||
   * @description the left content of the tab
 | 
			
		||||
   */
 | 
			
		||||
  prefix?: SlotFn;
 | 
			
		||||
  /**
 | 
			
		||||
   * slot
 | 
			
		||||
   * @description the right content of the tab
 | 
			
		||||
   */
 | 
			
		||||
  suffix?: SlotFn;
 | 
			
		||||
};
 | 
			
		||||
defineSlots<Slots>();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										31
									
								
								packages/materials/src/libs/page-tab/chrome-tab-bg.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/materials/src/libs/page-tab/chrome-tab-bg.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <svg style="width: 100%; height: 100%">
 | 
			
		||||
    <defs>
 | 
			
		||||
      <symbol id="geometry-left" viewBox="0 0 214 36">
 | 
			
		||||
        <path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z"></path>
 | 
			
		||||
      </symbol>
 | 
			
		||||
      <symbol id="geometry-right" viewBox="0 0 214 36">
 | 
			
		||||
        <use xlink:href="#geometry-left"></use>
 | 
			
		||||
      </symbol>
 | 
			
		||||
      <clipPath>
 | 
			
		||||
        <rect width="100%" height="100%" x="0"></rect>
 | 
			
		||||
      </clipPath>
 | 
			
		||||
    </defs>
 | 
			
		||||
    <svg width="51%" height="100%">
 | 
			
		||||
      <use xlink:href="#geometry-left" width="214" height="36" fill="currentColor"></use>
 | 
			
		||||
    </svg>
 | 
			
		||||
    <g transform="scale(-1, 1)">
 | 
			
		||||
      <svg width="51%" height="100%" x="-100%" y="0">
 | 
			
		||||
        <use xlink:href="#geometry-right" width="214" height="36" fill="currentColor"></use>
 | 
			
		||||
      </svg>
 | 
			
		||||
    </g>
 | 
			
		||||
  </svg>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'ChromeTabBg'
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										55
									
								
								packages/materials/src/libs/page-tab/chrome-tab.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								packages/materials/src/libs/page-tab/chrome-tab.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    :class="[
 | 
			
		||||
      ':soy: relative inline-flex justify-center items-center gap-16px -mr-18px px-24px py-6px cursor-pointer whitespace-nowrap',
 | 
			
		||||
      style['chrome-tab'],
 | 
			
		||||
      { [style['chrome-tab_dark']]: darkMode },
 | 
			
		||||
      { [style['chrome-tab_active']]: active },
 | 
			
		||||
      { [style['chrome-tab_active_dark']]: active && darkMode }
 | 
			
		||||
    ]"
 | 
			
		||||
  >
 | 
			
		||||
    <div :class="[':soy: absolute left-0 top-0 -z-1 w-full h-full pointer-events-none', style['chrome-tab__bg']]">
 | 
			
		||||
      <ChromeTabBg />
 | 
			
		||||
    </div>
 | 
			
		||||
    <slot name="prefix"></slot>
 | 
			
		||||
    <slot></slot>
 | 
			
		||||
    <slot name="suffix"></slot>
 | 
			
		||||
    <div :class="[':soy: absolute right-7px w-1px h-16px bg-#1f2225', style['chrome-tab-divider']]"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import ChromeTabBg from './chrome-tab-bg.vue';
 | 
			
		||||
import style from './index.module.css';
 | 
			
		||||
import type { PageTabProps } from '../../types';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'ChromeTab'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineProps<PageTabProps>();
 | 
			
		||||
 | 
			
		||||
type SlotFn = (props?: Record<string, unknown>) => any;
 | 
			
		||||
 | 
			
		||||
type Slots = {
 | 
			
		||||
  /**
 | 
			
		||||
   * slot
 | 
			
		||||
   * @description the center content of the tab
 | 
			
		||||
   */
 | 
			
		||||
  default?: SlotFn;
 | 
			
		||||
  /**
 | 
			
		||||
   * slot
 | 
			
		||||
   * @description the left content of the tab
 | 
			
		||||
   */
 | 
			
		||||
  prefix?: SlotFn;
 | 
			
		||||
  /**
 | 
			
		||||
   * slot
 | 
			
		||||
   * @description the right content of the tab
 | 
			
		||||
   */
 | 
			
		||||
  suffix?: SlotFn;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineSlots<Slots>();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										31
									
								
								packages/materials/src/libs/page-tab/icon-close.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/materials/src/libs/page-tab/icon-close.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    class=":soy: relative inline-flex justify-center items-center w-16px h-16px text-14px rd-50%"
 | 
			
		||||
    @click.stop="handleClick"
 | 
			
		||||
  >
 | 
			
		||||
    <svg width="1em" height="1em" viewBox="0 0 1024 1024">
 | 
			
		||||
      <path
 | 
			
		||||
        fill="currentColor"
 | 
			
		||||
        d="m563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8L295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512L196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1l216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
 | 
			
		||||
      ></path>
 | 
			
		||||
    </svg>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'IconClose'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
interface Emits {
 | 
			
		||||
  (e: 'click'): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<Emits>();
 | 
			
		||||
 | 
			
		||||
function handleClick() {
 | 
			
		||||
  emit('click');
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										97
									
								
								packages/materials/src/libs/page-tab/index.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								packages/materials/src/libs/page-tab/index.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
/* @type */
 | 
			
		||||
 | 
			
		||||
.button-tab {
 | 
			
		||||
  border-color: #e5e7eb;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-tab_dark {
 | 
			
		||||
  border-color: #ffffff3d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-tab:hover {
 | 
			
		||||
  color: var(--soy-primary-color);
 | 
			
		||||
  border-color: var(--soy-primary-color-opacity3);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-tab_active {
 | 
			
		||||
  color: var(--soy-primary-color);
 | 
			
		||||
  border-color: var(--soy-primary-color-opacity3);
 | 
			
		||||
  background-color: var(--soy-primary-color-opacity1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-tab_active_dark {
 | 
			
		||||
  background-color: var(--soy-primary-color-opacity2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-tab .icon_close:hover {
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  color: #ffffff;
 | 
			
		||||
  background-color: var(--soy-primary-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-tab_dark .icon_close:hover {
 | 
			
		||||
  color: #000000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab:hover {
 | 
			
		||||
  z-index: 9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_active {
 | 
			
		||||
  z-index: 10;
 | 
			
		||||
  color: var(--soy-primary-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab__bg {
 | 
			
		||||
  color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_active .chrome-tab__bg {
 | 
			
		||||
  color: var(--soy-primary-color1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_active_dark .chrome-tab__bg {
 | 
			
		||||
  color: var(--soy-primary-color2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab:hover .chrome-tab__bg {
 | 
			
		||||
  color: #dee1e6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_active:hover .chrome-tab__bg {
 | 
			
		||||
  color: var(--soy-primary-color1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_dark:hover .chrome-tab__bg {
 | 
			
		||||
  color: #333333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_active_dark:hover .chrome-tab__bg {
 | 
			
		||||
  color: var(--soy-primary-color2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab .icon_close:hover {
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  color: #ffffff;
 | 
			
		||||
  background-color: #9ca3af;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_active .icon_close:hover {
 | 
			
		||||
  background-color: var(--soy-primary-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_dark .icon_close:hover {
 | 
			
		||||
  color: #000000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_active .chrome-tab-divider {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab:hover .chrome-tab-divider {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chrome-tab_dark .chrome-tab-divider {
 | 
			
		||||
  background-color: rgba(255, 255, 255, 0.9);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								packages/materials/src/libs/page-tab/index.module.css.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/materials/src/libs/page-tab/index.module.css.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
declare const styles: {
 | 
			
		||||
  readonly 'button-tab': string;
 | 
			
		||||
  readonly 'button-tab_dark': string;
 | 
			
		||||
  readonly 'button-tab_active': string;
 | 
			
		||||
  readonly 'button-tab_active_dark': string;
 | 
			
		||||
  readonly icon_close: string;
 | 
			
		||||
  readonly 'chrome-tab': string;
 | 
			
		||||
  readonly 'chrome-tab_active': string;
 | 
			
		||||
  readonly 'chrome-tab__bg': string;
 | 
			
		||||
  readonly 'chrome-tab_active_dark': string;
 | 
			
		||||
  readonly 'chrome-tab_dark': string;
 | 
			
		||||
  readonly 'chrome-tab-divider': string;
 | 
			
		||||
};
 | 
			
		||||
export = styles;
 | 
			
		||||
							
								
								
									
										3
									
								
								packages/materials/src/libs/page-tab/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/materials/src/libs/page-tab/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import PageTab from './index.vue';
 | 
			
		||||
 | 
			
		||||
export default PageTab;
 | 
			
		||||
							
								
								
									
										94
									
								
								packages/materials/src/libs/page-tab/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								packages/materials/src/libs/page-tab/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <component :is="activeTabComponent.component" :class="activeTabComponent.class" :style="cssVars" v-bind="bindProps">
 | 
			
		||||
    <template #prefix>
 | 
			
		||||
      <slot name="prefix"></slot>
 | 
			
		||||
    </template>
 | 
			
		||||
    <slot></slot>
 | 
			
		||||
    <template #suffix>
 | 
			
		||||
      <slot name="suffix">
 | 
			
		||||
        <SvgIconClose v-if="closable" :class="[style['icon_close']]" @click="handleClose" />
 | 
			
		||||
      </slot>
 | 
			
		||||
    </template>
 | 
			
		||||
  </component>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import type { Component } from 'vue';
 | 
			
		||||
import { createTabCssVars, ACTIVE_COLOR } from './shared';
 | 
			
		||||
import ChromeTab from './chrome-tab.vue';
 | 
			
		||||
import ButtonTab from './button-tab.vue';
 | 
			
		||||
import SvgIconClose from './icon-close.vue';
 | 
			
		||||
import style from './index.module.css';
 | 
			
		||||
import type { PageTabProps, PageTabMode } from '../../types';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'PageTab'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<PageTabProps>(), {
 | 
			
		||||
  mode: 'chrome',
 | 
			
		||||
  commonClass: 'transition-all-300',
 | 
			
		||||
  activeColor: ACTIVE_COLOR,
 | 
			
		||||
  closable: true
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
interface Emits {
 | 
			
		||||
  (e: 'close'): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<Emits>();
 | 
			
		||||
 | 
			
		||||
type SlotFn = (props?: Record<string, unknown>) => any;
 | 
			
		||||
 | 
			
		||||
type Slots = {
 | 
			
		||||
  /**
 | 
			
		||||
   * slot
 | 
			
		||||
   * @description the center content of the tab
 | 
			
		||||
   */
 | 
			
		||||
  default?: SlotFn;
 | 
			
		||||
  /**
 | 
			
		||||
   * slot
 | 
			
		||||
   * @description the left content of the tab
 | 
			
		||||
   */
 | 
			
		||||
  prefix?: SlotFn;
 | 
			
		||||
  /**
 | 
			
		||||
   * slot
 | 
			
		||||
   * @description the right content of the tab
 | 
			
		||||
   */
 | 
			
		||||
  suffix?: SlotFn;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineSlots<Slots>();
 | 
			
		||||
 | 
			
		||||
const activeTabComponent = computed(() => {
 | 
			
		||||
  const { mode, chromeClass, buttonClass } = props;
 | 
			
		||||
 | 
			
		||||
  const tabComponentMap = {
 | 
			
		||||
    chrome: {
 | 
			
		||||
      component: ChromeTab,
 | 
			
		||||
      class: chromeClass
 | 
			
		||||
    },
 | 
			
		||||
    button: {
 | 
			
		||||
      component: ButtonTab,
 | 
			
		||||
      class: buttonClass
 | 
			
		||||
    }
 | 
			
		||||
  } satisfies Record<PageTabMode, { component: Component; class?: string }>;
 | 
			
		||||
 | 
			
		||||
  return tabComponentMap[mode];
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const cssVars = computed(() => createTabCssVars(props.activeColor));
 | 
			
		||||
 | 
			
		||||
const bindProps = computed(() => {
 | 
			
		||||
  const { chromeClass: _chromeCls, buttonClass: _btnCls, ...rest } = props;
 | 
			
		||||
 | 
			
		||||
  return rest;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function handleClose() {
 | 
			
		||||
  emit('close');
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										33
									
								
								packages/materials/src/libs/page-tab/shared.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								packages/materials/src/libs/page-tab/shared.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import { addColorAlpha, transformColorWithOpacity } from '@sa/utils';
 | 
			
		||||
import type { PageTabCssVarsProps, PageTabCssVars } from '../../types';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the active color of the tab
 | 
			
		||||
 */
 | 
			
		||||
export const ACTIVE_COLOR = '#1890ff';
 | 
			
		||||
 | 
			
		||||
function createCssVars(props: PageTabCssVarsProps) {
 | 
			
		||||
  const cssVars: PageTabCssVars = {
 | 
			
		||||
    '--soy-primary-color': props.primaryColor,
 | 
			
		||||
    '--soy-primary-color1': props.primaryColor1,
 | 
			
		||||
    '--soy-primary-color2': props.primaryColor2,
 | 
			
		||||
    '--soy-primary-color-opacity1': props.primaryColorOpacity1,
 | 
			
		||||
    '--soy-primary-color-opacity2': props.primaryColorOpacity2,
 | 
			
		||||
    '--soy-primary-color-opacity3': props.primaryColorOpacity3
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return cssVars;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createTabCssVars(primaryColor: string) {
 | 
			
		||||
  const cssProps: PageTabCssVarsProps = {
 | 
			
		||||
    primaryColor,
 | 
			
		||||
    primaryColor1: transformColorWithOpacity(primaryColor, 0.1, '#ffffff'),
 | 
			
		||||
    primaryColor2: transformColorWithOpacity(primaryColor, 0.3, '#000000'),
 | 
			
		||||
    primaryColorOpacity1: addColorAlpha(primaryColor, 0.1),
 | 
			
		||||
    primaryColorOpacity2: addColorAlpha(primaryColor, 0.15),
 | 
			
		||||
    primaryColorOpacity3: addColorAlpha(primaryColor, 0.3)
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return createCssVars(cssProps);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								packages/materials/src/libs/simple-scrollbar/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/materials/src/libs/simple-scrollbar/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import SimpleScrollbar from './index.vue';
 | 
			
		||||
 | 
			
		||||
export default SimpleScrollbar;
 | 
			
		||||
							
								
								
									
										18
									
								
								packages/materials/src/libs/simple-scrollbar/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/materials/src/libs/simple-scrollbar/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import Simplebar from 'simplebar-vue';
 | 
			
		||||
import 'simplebar-vue/dist/simplebar.min.css';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'SimpleScrollbar'
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="flex-1-hidden h-full">
 | 
			
		||||
    <Simplebar class="h-full">
 | 
			
		||||
      <slot />
 | 
			
		||||
    </Simplebar>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										282
									
								
								packages/materials/src/types/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								packages/materials/src/types/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,282 @@
 | 
			
		||||
/**
 | 
			
		||||
 * header config
 | 
			
		||||
 */
 | 
			
		||||
interface AdminLayoutHeaderConfig {
 | 
			
		||||
  /**
 | 
			
		||||
   * whether header is visible
 | 
			
		||||
   * @default true
 | 
			
		||||
   */
 | 
			
		||||
  headerVisible?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * header class
 | 
			
		||||
   * @default ''
 | 
			
		||||
   */
 | 
			
		||||
  headerClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * header height
 | 
			
		||||
   * @default 56px
 | 
			
		||||
   */
 | 
			
		||||
  headerHeight?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * tab config
 | 
			
		||||
 */
 | 
			
		||||
interface AdminLayoutTabConfig {
 | 
			
		||||
  /**
 | 
			
		||||
   * whether tab is visible
 | 
			
		||||
   * @default true
 | 
			
		||||
   */
 | 
			
		||||
  tabVisible?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * tab class
 | 
			
		||||
   * @default ''
 | 
			
		||||
   */
 | 
			
		||||
  tabClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * tab height
 | 
			
		||||
   * @default 48px
 | 
			
		||||
   */
 | 
			
		||||
  tabHeight?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * sider config
 | 
			
		||||
 */
 | 
			
		||||
interface AdminLayoutSiderConfig {
 | 
			
		||||
  /**
 | 
			
		||||
   * whether sider is visible
 | 
			
		||||
   * @default true
 | 
			
		||||
   */
 | 
			
		||||
  siderVisible?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * sider class
 | 
			
		||||
   * @default ''
 | 
			
		||||
   */
 | 
			
		||||
  siderClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * mobile sider class
 | 
			
		||||
   * @default ''
 | 
			
		||||
   */
 | 
			
		||||
  mobileSiderClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * sider collapse status
 | 
			
		||||
   * @default false
 | 
			
		||||
   */
 | 
			
		||||
  siderCollapse?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * sider width when collapse is false
 | 
			
		||||
   * @default '220px'
 | 
			
		||||
   */
 | 
			
		||||
  siderWidth?: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * sider width when collapse is true
 | 
			
		||||
   * @default '64px'
 | 
			
		||||
   */
 | 
			
		||||
  siderCollapsedWidth?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * content config
 | 
			
		||||
 */
 | 
			
		||||
export interface AdminLayoutContentConfig {
 | 
			
		||||
  /**
 | 
			
		||||
   * content class
 | 
			
		||||
   * @default ''
 | 
			
		||||
   */
 | 
			
		||||
  contentClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * whether content is full the page
 | 
			
		||||
   * @description if true, other elements will be hidden by `display: none`
 | 
			
		||||
   */
 | 
			
		||||
  fullContent?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * footer config
 | 
			
		||||
 */
 | 
			
		||||
export interface AdminLayoutFooterConfig {
 | 
			
		||||
  /**
 | 
			
		||||
   * whether footer is visible
 | 
			
		||||
   * @default true
 | 
			
		||||
   */
 | 
			
		||||
  footerVisible?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * whether footer is fixed
 | 
			
		||||
   * @default true
 | 
			
		||||
   */
 | 
			
		||||
  fixedFooter?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * footer class
 | 
			
		||||
   * @default ''
 | 
			
		||||
   */
 | 
			
		||||
  footerClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * footer height
 | 
			
		||||
   * @default 48px
 | 
			
		||||
   */
 | 
			
		||||
  footerHeight?: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * whether footer is on the right side
 | 
			
		||||
   * @description when the layout is vertical, the footer is on the right side
 | 
			
		||||
   */
 | 
			
		||||
  rightFooter?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * layout mode
 | 
			
		||||
 * - horizontal
 | 
			
		||||
 * - vertical
 | 
			
		||||
 */
 | 
			
		||||
export type LayoutMode = 'horizontal' | 'vertical';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the scroll mode when content overflow
 | 
			
		||||
 * - wrapper: the layout component's wrapper element has a scrollbar
 | 
			
		||||
 * - content: the layout component's content element has a scrollbar
 | 
			
		||||
 * @default 'wrapper'
 | 
			
		||||
 */
 | 
			
		||||
export type LayoutScrollMode = 'wrapper' | 'content';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * admin layout props
 | 
			
		||||
 */
 | 
			
		||||
export interface AdminLayoutProps
 | 
			
		||||
  extends AdminLayoutHeaderConfig,
 | 
			
		||||
    AdminLayoutTabConfig,
 | 
			
		||||
    AdminLayoutSiderConfig,
 | 
			
		||||
    AdminLayoutContentConfig,
 | 
			
		||||
    AdminLayoutFooterConfig {
 | 
			
		||||
  /**
 | 
			
		||||
   * layout mode
 | 
			
		||||
   * - {@link LayoutMode}
 | 
			
		||||
   */
 | 
			
		||||
  mode?: LayoutMode;
 | 
			
		||||
  /** is mobile layout */
 | 
			
		||||
  isMobile?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * scroll mode
 | 
			
		||||
   * - {@link ScrollMode}
 | 
			
		||||
   */
 | 
			
		||||
  scrollMode?: LayoutScrollMode;
 | 
			
		||||
  /**
 | 
			
		||||
   * the id of the scroll element of the layout
 | 
			
		||||
   * @description it can be used to get the corresponding Dom and scroll it
 | 
			
		||||
   * @default
 | 
			
		||||
   * ```ts
 | 
			
		||||
   * const adminLayoutScrollElId = '__ADMIN_LAYOUT_SCROLL_EL_ID__'
 | 
			
		||||
   * ```
 | 
			
		||||
   * @example use the default id by import
 | 
			
		||||
   * ```ts
 | 
			
		||||
   * import { adminLayoutScrollElId } from '@sa/vue-materials';
 | 
			
		||||
   * ```
 | 
			
		||||
   */
 | 
			
		||||
  scrollElId?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * the class of the scroll element
 | 
			
		||||
   */
 | 
			
		||||
  scrollElClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * the class of the scroll wrapper element
 | 
			
		||||
   */
 | 
			
		||||
  scrollWrapperClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * the common class of the layout
 | 
			
		||||
   * @description is can be used to configure the transition animation
 | 
			
		||||
   * @default 'transition-all-300'
 | 
			
		||||
   */
 | 
			
		||||
  commonClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * whether fix the header and tab
 | 
			
		||||
   * @default true
 | 
			
		||||
   */
 | 
			
		||||
  fixedTop?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * the max z-index of the layout
 | 
			
		||||
   * @description the z-index of Header,Tab,Sider and Footer will not exceed this value
 | 
			
		||||
   */
 | 
			
		||||
  maxZIndex?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Kebab<S extends string> = S extends Uncapitalize<S> ? S : `-${Uncapitalize<S>}`;
 | 
			
		||||
 | 
			
		||||
type KebabCase<S extends string> = S extends `${infer Start}${infer End}`
 | 
			
		||||
  ? `${Uncapitalize<Start>}${KebabCase<Kebab<End>>}`
 | 
			
		||||
  : S;
 | 
			
		||||
 | 
			
		||||
type Prefix = '--soy-';
 | 
			
		||||
 | 
			
		||||
export type LayoutCssVarsProps = Pick<
 | 
			
		||||
  AdminLayoutProps,
 | 
			
		||||
  'headerHeight' | 'tabHeight' | 'siderWidth' | 'siderCollapsedWidth' | 'footerHeight'
 | 
			
		||||
> & {
 | 
			
		||||
  headerZIndex?: number;
 | 
			
		||||
  tabZIndex?: number;
 | 
			
		||||
  siderZIndex?: number;
 | 
			
		||||
  mobileSiderZIndex?: number;
 | 
			
		||||
  footerZIndex?: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type LayoutCssVars = {
 | 
			
		||||
  [K in keyof LayoutCssVarsProps as `${Prefix}${KebabCase<K>}`]: string | number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the mode of the tab
 | 
			
		||||
 * - button: button style
 | 
			
		||||
 * - chrome: chrome style
 | 
			
		||||
 * @default chrome
 | 
			
		||||
 */
 | 
			
		||||
export type PageTabMode = 'button' | 'chrome';
 | 
			
		||||
 | 
			
		||||
export interface PageTabProps {
 | 
			
		||||
  /**
 | 
			
		||||
   * whether is dark mode
 | 
			
		||||
   */
 | 
			
		||||
  darkMode?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * the mode of the tab
 | 
			
		||||
   * - {@link TabMode}
 | 
			
		||||
   */
 | 
			
		||||
  mode?: PageTabMode;
 | 
			
		||||
  /**
 | 
			
		||||
   * the common class of the layout
 | 
			
		||||
   * @description is can be used to configure the transition animation
 | 
			
		||||
   * @default 'transition-all-300'
 | 
			
		||||
   */
 | 
			
		||||
  commonClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * the class of the button tab
 | 
			
		||||
   */
 | 
			
		||||
  buttonClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * the class of the chrome tab
 | 
			
		||||
   */
 | 
			
		||||
  chromeClass?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * whether the tab is active
 | 
			
		||||
   */
 | 
			
		||||
  active?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * the color of the active tab
 | 
			
		||||
   */
 | 
			
		||||
  activeColor?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * whether the tab is closable
 | 
			
		||||
   * @description show the close icon when true
 | 
			
		||||
   */
 | 
			
		||||
  closable?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type PageTabCssVarsProps = {
 | 
			
		||||
  primaryColor: string;
 | 
			
		||||
  primaryColor1: string;
 | 
			
		||||
  primaryColor2: string;
 | 
			
		||||
  primaryColorOpacity1: string;
 | 
			
		||||
  primaryColorOpacity2: string;
 | 
			
		||||
  primaryColorOpacity3: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type PageTabCssVars = {
 | 
			
		||||
  [K in keyof PageTabCssVarsProps as `${Prefix}${KebabCase<K>}`]: string | number;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										20
									
								
								packages/materials/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/materials/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "target": "ESNext",
 | 
			
		||||
    "lib": ["DOM", "ESNext"],
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "allowSyntheticDefaultImports": true,
 | 
			
		||||
    "jsx": "preserve",
 | 
			
		||||
    "moduleResolution": "node",
 | 
			
		||||
    "resolveJsonModule": true,
 | 
			
		||||
    "noUnusedLocals": true,
 | 
			
		||||
    "strictNullChecks": true,
 | 
			
		||||
    "forceConsistentCasingInFileNames": true,
 | 
			
		||||
    "types": ["node"]
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["src/**/*"],
 | 
			
		||||
  "exclude": ["node_modules", "dist"]
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user