Compare commits

...

7 Commits

11 changed files with 963 additions and 874 deletions

34
.github/workflows/opencode.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: opencode
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
opencode:
if: |
contains(github.event.comment.body, ' /oc') ||
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: read
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Run opencode
uses: anomalyco/opencode/github@latest
env:
GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
with:
model: google/gemini-3-pro-preview

View File

@@ -23,9 +23,6 @@
> [!NOTE] > [!NOTE]
> The `SoybeanAdmin` quick start series videos have been uploaded to [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) Go online [click here](https://www.bilibili.com/video/BV1YKdRYXELC) Go check it out > The `SoybeanAdmin` quick start series videos have been uploaded to [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) Go online [click here](https://www.bilibili.com/video/BV1YKdRYXELC) Go check it out
> [!WARNING]
> `SoybeanAdmin` is planning to develop a `V2` version, see [plan list](https://github.com/soybeanjs/soybean-admin/issues/767)
## Introduction ## Introduction
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite7, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. `SoybeanAdmin` provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly. [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite7, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. `SoybeanAdmin` provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly.

View File

@@ -23,9 +23,6 @@
> [!NOTE] > [!NOTE]
> `SoybeanAdmin` 快速上手系列视频已在 [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) 上线 [点击这里](https://www.bilibili.com/video/BV1YKdRYXELC) 前往查看 > `SoybeanAdmin` 快速上手系列视频已在 [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) 上线 [点击这里](https://www.bilibili.com/video/BV1YKdRYXELC) 前往查看
> [!WARNING]
> `SoybeanAdmin` 正在计划开发 `V2` 版本,详情见[计划清单](https://github.com/soybeanjs/soybean-admin/issues/767)
## 简介 ## 简介
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite7, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite7, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。

View File

@@ -64,44 +64,44 @@
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "3.0.4", "pinia": "3.0.4",
"tailwind-merge": "3.4.0", "tailwind-merge": "3.4.0",
"vue": "3.5.26", "vue": "3.5.27",
"vue-draggable-plus": "0.6.0", "vue-draggable-plus": "0.6.1",
"vue-i18n": "11.2.7", "vue-i18n": "11.2.8",
"vue-router": "4.6.4" "vue-router": "4.6.4"
}, },
"devDependencies": { "devDependencies": {
"@elegant-router/vue": "0.3.8", "@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.417", "@iconify/json": "2.2.430",
"@sa/scripts": "workspace:*", "@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*", "@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.7.5", "@soybeanjs/eslint-config": "1.7.5",
"@types/node": "25.0.3", "@types/node": "25.0.9",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.5.10", "@unocss/eslint-config": "66.6.0",
"@unocss/preset-icons": "66.5.10", "@unocss/preset-icons": "66.6.0",
"@unocss/preset-uno": "66.5.10", "@unocss/preset-uno": "66.6.0",
"@unocss/transformer-directives": "66.5.10", "@unocss/transformer-directives": "66.6.0",
"@unocss/transformer-variant-group": "66.5.10", "@unocss/transformer-variant-group": "66.6.0",
"@unocss/vite": "66.5.10", "@unocss/vite": "66.6.0",
"@vitejs/plugin-vue": "6.0.3", "@vitejs/plugin-vue": "6.0.3",
"@vitejs/plugin-vue-jsx": "5.1.3", "@vitejs/plugin-vue-jsx": "5.1.3",
"consola": "3.4.2", "consola": "3.4.2",
"eslint": "9.39.2", "eslint": "9.39.2",
"eslint-plugin-vue": "10.6.2", "eslint-plugin-vue": "10.7.0",
"kolorist": "1.8.0", "kolorist": "1.8.0",
"sass": "1.97.1", "sass": "1.97.2",
"simple-git-hooks": "2.13.1", "simple-git-hooks": "2.13.1",
"tsx": "4.21.0", "tsx": "4.21.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"unplugin-icons": "22.5.0", "unplugin-icons": "23.0.1",
"unplugin-vue-components": "30.0.0", "unplugin-vue-components": "31.0.0",
"vite": "7.3.0", "vite": "7.3.1",
"vite-plugin-progress": "0.0.7", "vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1", "vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "8.0.5", "vite-plugin-vue-devtools": "8.0.5",
"vite-plugin-vue-transition-root-validator": "^0.0.4", "vite-plugin-vue-transition-root-validator": "^0.0.4",
"vue-eslint-parser": "10.2.0", "vue-eslint-parser": "10.2.0",
"vue-tsc": "3.2.1" "vue-tsc": "3.2.2"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify", "commit-msg": "pnpm sa git-commit-verify",

View File

@@ -13,7 +13,7 @@
"@sa/utils": "workspace:*", "@sa/utils": "workspace:*",
"axios": "1.13.2", "axios": "1.13.2",
"axios-retry": "4.5.0", "axios-retry": "4.5.0",
"qs": "6.14.0" "qs": "6.14.1"
}, },
"devDependencies": { "devDependencies": {
"@types/qs": "6.14.0" "@types/qs": "6.14.0"

View File

@@ -14,14 +14,14 @@
}, },
"devDependencies": { "devDependencies": {
"@soybeanjs/changelog": "0.4.3", "@soybeanjs/changelog": "0.4.3",
"bumpp": "10.3.2", "bumpp": "10.4.0",
"c12": "3.3.3", "c12": "3.3.3",
"cac": "6.7.14", "cac": "6.7.14",
"consola": "3.4.2", "consola": "3.4.2",
"enquirer": "2.4.1", "enquirer": "2.4.1",
"execa": "9.6.1", "execa": "9.6.1",
"kolorist": "1.8.0", "kolorist": "1.8.0",
"npm-check-updates": "19.2.0", "npm-check-updates": "19.3.1",
"picomatch": "4.0.3", "picomatch": "4.0.3",
"rimraf": "6.1.2" "rimraf": "6.1.2"
} }

1651
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
<script setup lang="ts" generic="T extends Record<string, unknown>, K = never"> <script setup lang="ts" generic="T extends Record<string, unknown>, K = never">
import { computed } from 'vue';
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';
import { $t } from '@/locales'; import { $t } from '@/locales';
@@ -22,6 +23,40 @@ function handleFixed(column: NaiveUI.TableColumnCheck) {
const nextIndex = index === fixedOptions.length - 1 ? 0 : index + 1; const nextIndex = index === fixedOptions.length - 1 ? 0 : index + 1;
column.fixed = fixedOptions[nextIndex]; column.fixed = fixedOptions[nextIndex];
} }
const visibleStats = computed(() => {
let total = 0;
let checked = 0;
columns.value.forEach(column => {
if (!column.visible) return;
total += 1;
if (column.checked) checked += 1;
});
return { total, checked };
});
const selectAllChecked = computed(() => {
const { total, checked } = visibleStats.value;
return total > 0 && checked === total;
});
const selectAllIndeterminate = computed(() => {
const { total, checked } = visibleStats.value;
return checked > 0 && checked < total;
});
function toggleSelectAll(checked: boolean) {
columns.value.forEach(column => {
if (!column.visible) return;
column.checked = checked;
});
}
</script> </script>
<template> <template>
@@ -34,35 +69,48 @@ function handleFixed(column: NaiveUI.TableColumnCheck) {
{{ $t('common.columnSetting') }} {{ $t('common.columnSetting') }}
</NButton> </NButton>
</template> </template>
<VueDraggable v-model="columns" :animation="150" filter=".none_draggable"> <div>
<div <div class="h-36px flex-y-center rd-4px pl-26px hover:(bg-primary bg-opacity-20)">
v-for="item in columns" <NCheckbox
:key="item.key" :checked="selectAllChecked"
class="h-36px flex-y-center justify-between gap-6px" :indeterminate="selectAllIndeterminate"
:class="{ hidden: !item.visible }" :disabled="visibleStats.total === 0"
> class="flex-1"
<div class="flex-y-center rd-4px hover:(bg-primary bg-opacity-20)"> @update:checked="toggleSelectAll"
<icon-mdi-drag class="mr-8px h-full cursor-move text-icon" />
<NCheckbox v-model:checked="item.checked" class="none_draggable flex-1">
<template v-if="typeof item.title === 'function'">
<component :is="item.title" />
</template>
<template v-else>{{ item.title }}</template>
</NCheckbox>
</div>
<ButtonIcon
:disabled="!item.checked"
text
:focusable="false"
:tooltip-content="$t(tooltipRecord[item.fixed!])"
@click="handleFixed(item)"
> >
<icon-octicon-pin-16 v-if="item.fixed === 'unFixed'" /> {{ $t('common.selectAll') }}
<icon-octicon-pin-16 v-else-if="item.fixed === 'left'" class="rotate-270" /> </NCheckbox>
<icon-octicon-pin-slash-16 v-else />
</ButtonIcon>
</div> </div>
</VueDraggable> <NDivider class="!my-4px" />
<VueDraggable v-model="columns" :animation="150" filter=".none_draggable" class="max-h-[200px] overflow-y-auto">
<div
v-for="item in columns"
:key="item.key"
class="h-36px flex-y-center justify-between gap-6px"
:class="{ hidden: !item.visible }"
>
<div class="h-full flex-y-center flex-1 rd-4px hover:(bg-primary bg-opacity-20)">
<icon-mdi-drag class="mr-8px h-full cursor-move text-icon" />
<NCheckbox v-model:checked="item.checked" class="none_draggable flex-1">
<template v-if="typeof item.title === 'function'">
<component :is="item.title" />
</template>
<template v-else>{{ item.title }}</template>
</NCheckbox>
</div>
<ButtonIcon
:disabled="!item.checked"
:focusable="false"
:tooltip-content="$t(tooltipRecord[item.fixed!])"
@click="handleFixed(item)"
>
<icon-octicon-pin-16 v-if="item.fixed === 'unFixed'" />
<icon-octicon-pin-16 v-else-if="item.fixed === 'left'" class="rotate-270" />
<icon-octicon-pin-slash-16 v-else />
</ButtonIcon>
</div>
</VueDraggable>
</div>
</NPopover> </NPopover>
</template> </template>

View File

@@ -15,6 +15,7 @@ const local: App.I18n.Schema = {
cancel: 'Cancel', cancel: 'Cancel',
close: 'Close', close: 'Close',
check: 'Check', check: 'Check',
selectAll: 'Select All',
expandColumn: 'Expand Column', expandColumn: 'Expand Column',
columnSetting: 'Column Setting', columnSetting: 'Column Setting',
config: 'Config', config: 'Config',

View File

@@ -15,6 +15,7 @@ const local: App.I18n.Schema = {
cancel: '取消', cancel: '取消',
close: '关闭', close: '关闭',
check: '勾选', check: '勾选',
selectAll: '全选',
expandColumn: '展开列', expandColumn: '展开列',
columnSetting: '列设置', columnSetting: '列设置',
config: '配置', config: '配置',

View File

@@ -196,6 +196,7 @@ declare namespace App {
type RouterPushOptions = { type RouterPushOptions = {
query?: Record<string, string>; query?: Record<string, string>;
params?: Record<string, string>; params?: Record<string, string>;
force?: boolean;
}; };
/** The global header props */ /** The global header props */
@@ -326,6 +327,7 @@ declare namespace App {
cancel: string; cancel: string;
close: string; close: string;
check: string; check: string;
selectAll: string;
expandColumn: string; expandColumn: string;
columnSetting: string; columnSetting: string;
config: string; config: string;