chore(merge): Merge Soybean branch 'dev'

This commit is contained in:
xlsea
2026-01-28 16:20:43 +08:00
16 changed files with 1232 additions and 947 deletions
+34
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
+3 -1
View File
@@ -2,6 +2,7 @@ import type { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import progress from 'vite-plugin-progress';
import vueRootValidator from 'vite-plugin-vue-transition-root-validator';
import { setupElegantRouter } from './router';
import { setupUnocss } from './unocss';
import { setupUnplugin } from './unplugin';
@@ -17,7 +18,8 @@ export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
setupUnocss(viteEnv),
...setupUnplugin(viteEnv),
progress(),
setupHtmlPlugin(buildTime)
setupHtmlPlugin(buildTime),
vueRootValidator()
];
return plugins;
+20 -20
View File
@@ -48,7 +48,6 @@
"commit:zh": "sa git-commit -l=zh-cn",
"dev": "vite --mode dev",
"dev:prod": "vite --mode prod",
"dev:test": "vite --mode test",
"gen-route": "sa gen-route",
"lint": "eslint . --fix",
"prepare": "simple-git-hooks",
@@ -81,44 +80,45 @@
"pinia": "3.0.4",
"streamsaver": "^2.0.6",
"tailwind-merge": "3.4.0",
"vue": "3.5.26",
"vue": "3.5.27",
"vue-advanced-cropper": "^2.8.9",
"vue-draggable-plus": "0.6.0",
"vue-i18n": "11.2.7",
"vue-draggable-plus": "0.6.1",
"vue-i18n": "11.2.8",
"vue-router": "4.6.4"
},
"devDependencies": {
"@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.417",
"@iconify/json": "2.2.430",
"@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.7.4",
"@types/node": "25.0.3",
"@soybeanjs/eslint-config": "1.7.5",
"@types/node": "25.0.9",
"@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.5.10",
"@unocss/preset-icons": "66.5.10",
"@unocss/preset-uno": "66.5.10",
"@unocss/transformer-directives": "66.5.10",
"@unocss/transformer-variant-group": "66.5.10",
"@unocss/vite": "66.5.10",
"@unocss/eslint-config": "66.6.0",
"@unocss/preset-icons": "66.6.0",
"@unocss/preset-uno": "66.6.0",
"@unocss/transformer-directives": "66.6.0",
"@unocss/transformer-variant-group": "66.6.0",
"@unocss/vite": "66.6.0",
"@vitejs/plugin-vue": "6.0.3",
"@vitejs/plugin-vue-jsx": "5.1.2",
"@vitejs/plugin-vue-jsx": "5.1.3",
"consola": "3.4.2",
"eslint": "9.39.2",
"eslint-plugin-vue": "10.6.2",
"eslint-plugin-vue": "10.7.0",
"kolorist": "1.8.0",
"sass": "1.97.1",
"sass": "1.97.2",
"simple-git-hooks": "2.13.1",
"tsx": "4.21.0",
"typescript": "5.9.3",
"unplugin-icons": "22.5.0",
"unplugin-vue-components": "30.0.0",
"vite": "7.3.0",
"unplugin-icons": "23.0.1",
"unplugin-vue-components": "31.0.0",
"vite": "7.3.1",
"vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "8.0.5",
"vite-plugin-vue-transition-root-validator": "^0.0.4",
"vue-eslint-parser": "10.2.0",
"vue-tsc": "3.2.1"
"vue-tsc": "3.2.2"
},
"simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify",
+1 -1
View File
@@ -13,7 +13,7 @@
"@sa/utils": "workspace:*",
"axios": "1.13.2",
"axios-retry": "4.5.0",
"qs": "6.14.0"
"qs": "6.14.1"
},
"devDependencies": {
"@types/qs": "6.14.0"
+4 -1
View File
@@ -22,6 +22,7 @@ export type TableColumnCheck = {
title: TableColumnCheckTitle;
checked: boolean;
visible: boolean;
fixed: 'left' | 'right' | 'unFixed';
};
export interface UseTableOptions<ResponseData, ApiData, Column, Pagination extends boolean> {
@@ -77,12 +78,14 @@ export default function useTable<ResponseData, ApiData, Column, Pagination exten
function reloadColumns() {
const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked]));
const fixedMap = new Map(columnChecks.value.map(col => [col.key, col.fixed]));
const defaultChecks = getColumnChecks(columns());
columnChecks.value = defaultChecks.map(col => ({
...col,
checked: checkMap.get(col.key) ?? col.checked
checked: checkMap.get(col.key) ?? col.checked,
fixed: (fixedMap.get(col.key) !== 'unFixed' ? fixedMap.get(col.key) : undefined) ?? col.fixed
}));
}
+3 -3
View File
@@ -13,15 +13,15 @@
}
},
"devDependencies": {
"@soybeanjs/changelog": "0.3.25",
"bumpp": "10.3.2",
"@soybeanjs/changelog": "0.4.3",
"bumpp": "10.4.0",
"c12": "3.3.3",
"cac": "6.7.14",
"consola": "3.4.2",
"enquirer": "2.4.1",
"execa": "9.6.1",
"kolorist": "1.8.0",
"npm-check-updates": "19.2.0",
"npm-check-updates": "19.3.1",
"picomatch": "4.0.3",
"rimraf": "6.1.2"
}
+881 -895
View File
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,5 @@
<script setup lang="ts" generic="T extends Record<string, unknown>, K = never">
import { computed } from 'vue';
import { VueDraggable } from 'vue-draggable-plus';
import { $t } from '@/locales';
@@ -9,6 +10,53 @@ defineOptions({
const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
required: true
});
const tooltipRecord: Record<NaiveUI.TableColumnFixed, App.I18n.I18nKey> = {
left: 'datatable.fixed.right',
right: 'datatable.fixed.unFixed',
unFixed: 'datatable.fixed.left'
};
function handleFixed(column: NaiveUI.TableColumnCheck) {
const fixedOptions: NaiveUI.TableColumnFixed[] = ['left', 'right', 'unFixed'];
const index = fixedOptions.findIndex(item => item === column.fixed);
const nextIndex = index === fixedOptions.length - 1 ? 0 : index + 1;
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>
<template>
@@ -21,22 +69,49 @@ const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
{{ $t('common.columnSetting') }}
</NButton>
</template>
<VueDraggable v-model="columns" :animation="150" filter=".none_draggable" class="table-column-setting-list">
<div
v-for="item in columns"
:key="item.key"
class="h-36px flex-y-center rd-4px hover:(bg-primary bg-opacity-20)"
:class="{ hidden: !item.visible }"
>
<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>
<div>
<div class="h-36px flex-y-center rd-4px pl-26px hover:(bg-primary bg-opacity-20)">
<NCheckbox
:checked="selectAllChecked"
:indeterminate="selectAllIndeterminate"
:disabled="visibleStats.total === 0"
class="flex-1"
@update:checked="toggleSelectAll"
>
{{ $t('common.selectAll') }}
</NCheckbox>
</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"
text
: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>
</template>
+157 -6
View File
@@ -1,9 +1,160 @@
<script lang="ts" setup>
defineOptions({ name: 'SystemLogo' });
</script>
<template>
<icon-local-logo />
<div class="app-logo">
<svg
width="100%"
height="100%"
version="1.1"
viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<g>
<path
d="M 200,866 C 100,866 50,779.4 100,692.8 L 200,519.6 C 220,485 240,490 265,499.6 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
fill="url(#LinearGradient)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 420,866 C 455,861 478,846 500,827 C 614,696 615,597 500,517 C 394,444 333,374 380,207.82 L 260,415.67 C 240.22,450 254.37,465.1 275.28,481.79 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
fill="url(#LinearGradient_2)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 500,517 C 394,444 333,374 380,207.82 L 400,173.2 C 367,295 421,350 603,428 C 572,440 524,474 500,517"
fill="url(#LinearGradient_3)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 572,440 524,474 500,517 C 615,597 614,696 500,827"
fill="url(#LinearGradient_4)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 400,173.2 C 367,295 421,350 603,428 C 690,389, 750,445 788,500 L 600,173.2 C 550,86.6 450,86.6 400,173.2"
fill="url(#LinearGradient_5)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 690,389, 750,445 788,500 C 816,554 797,606 750,640 L 500,827"
fill="url(#LinearGradient_6)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
<path
d="M 788,500 C 816,554 797,606 750,640 L 500,827 C 497,851 513,862 540,866 L 800,866 C 900,866 950,779.4 900,692.8 L 788,500"
fill="url(#LinearGradient_7)"
fill-rule="nonzero"
opacity="1"
stroke="none"
/>
</g>
<defs>
<linearGradient
id="LinearGradient"
gradientTransform="matrix(104.391 -73.3432 73.3432 104.391 277.441 710.122)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-700)" />
<stop offset="1" stop-color="var(--logo-color-600)" />
</linearGradient>
<linearGradient
id="LinearGradient_2"
gradientTransform="matrix(-173.747 557.324 -557.324 -173.747 508.829 258.172)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-300)" />
<stop offset="1" stop-color="var(--logo-color-500)" />
</linearGradient>
<linearGradient
id="LinearGradient_3"
gradientTransform="matrix(157.951 295.666 -295.666 157.951 382.944 193.642)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-600)" />
<stop offset="1" stop-color="var(--logo-color-700)" />
</linearGradient>
<linearGradient
id="LinearGradient_4"
gradientTransform="matrix(-44.3023 219.578 -219.578 -44.3023 619.69 469.652)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-400)" />
<stop offset="1" stop-color="var(--logo-color-600)" />
</linearGradient>
<linearGradient
id="LinearGradient_5"
gradientTransform="matrix(125.52 334.256 -334.256 125.52 539.723 235.139)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-500)" />
<stop offset="1" stop-color="var(--logo-color-300)" />
</linearGradient>
<linearGradient
id="LinearGradient_6"
gradientTransform="matrix(-241.23 357.206 -357.206 -241.23 754.054 449.312)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-300)" />
<stop offset="1" stop-color="var(--logo-color-500)" />
</linearGradient>
<linearGradient
id="LinearGradient_7"
gradientTransform="matrix(125.978 210.065 -210.065 125.978 596.433 613.665)"
gradientUnits="userSpaceOnUse"
x1="0"
x2="1"
y1="0"
y2="0"
>
<stop offset="0" stop-color="var(--logo-color-700)" />
<stop offset="1" stop-color="var(--logo-color-600)" />
</linearGradient>
</defs>
</svg>
</div>
</template>
<style scoped></style>
<style scoped>
.app-logo {
--logo-color-300: rgb(var(--primary-300-color));
--logo-color-400: rgb(var(--primary-400-color));
--logo-color-500: rgb(var(--primary-500-color));
--logo-color-600: rgb(var(--primary-600-color));
--logo-color-700: rgb(var(--primary-700-color));
}
</style>
+9 -3
View File
@@ -428,6 +428,7 @@ function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
key: column.key as string,
title: column.title!,
checked: true,
fixed: column.fixed ?? 'unFixed',
visible: getColumnVisible?.(column) ?? true
});
} else if (column.type === 'selection') {
@@ -435,6 +436,7 @@ function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
key: SELECTION_KEY,
title: $t('common.check'),
checked: true,
fixed: column.fixed ?? 'unFixed',
visible: getColumnVisible?.(column) ?? false
});
} else if (column.type === 'expand') {
@@ -442,6 +444,7 @@ function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
key: EXPAND_KEY,
title: $t('common.expandColumn'),
checked: true,
fixed: column.fixed ?? 'unFixed',
visible: getColumnVisible?.(column) ?? false
});
}
@@ -463,11 +466,14 @@ function getColumns<Column extends NaiveUI.TableColumn<any>>(cols: Column[], che
}
});
// Filter out any checks that do not have a corresponding column (can happen when column definitions change).
const filteredColumns = checks
.filter(item => item.checked)
.map(check => columnMap.get(check.key))
.filter((col): col is Column => Boolean(col));
.map(check => {
return {
...columnMap.get(check.key),
fixed: check.fixed
} as Column;
});
return filteredColumns;
}
+4
View File
@@ -24,3 +24,7 @@ export const $t = i18n.global.t as App.I18n.$T;
export function setLocale(locale: App.I18n.LangType) {
i18n.global.locale.value = locale;
}
export function getLocale(): App.I18n.LangType {
return i18n.global.locale.value as App.I18n.LangType;
}
+7 -1
View File
@@ -29,6 +29,7 @@ const local: App.I18n.Schema = {
cancel: 'Cancel',
close: 'Close',
check: 'Check',
selectAll: 'Select All',
expandColumn: 'Expand Column',
columnSetting: 'Column Setting',
config: 'Config',
@@ -1272,7 +1273,12 @@ const local: App.I18n.Schema = {
unpin: 'Unpin'
},
datatable: {
itemCount: 'Total {total} items'
itemCount: 'Total {total} items',
fixed: {
left: 'Left Fixed',
right: 'Right Fixed',
unFixed: 'Unfixed'
}
}
};
+7 -1
View File
@@ -29,6 +29,7 @@ const local: App.I18n.Schema = {
cancel: '取消',
close: '关闭',
check: '勾选',
selectAll: '全选',
expandColumn: '展开列',
columnSetting: '列设置',
config: '配置',
@@ -1264,7 +1265,12 @@ const local: App.I18n.Schema = {
unpin: '取消固定'
},
datatable: {
itemCount: '共 {total} 条'
itemCount: '共 {total} 条',
fixed: {
left: '左固定',
right: '右固定',
unFixed: '取消固定'
}
}
};
+6 -1
View File
@@ -1,9 +1,10 @@
import { createApp } from 'vue';
import './plugins/assets';
import { setupVueRootValidator } from 'vite-plugin-vue-transition-root-validator/client';
import { setupAppVersionNotification, setupDayjs, setupIconifyOffline, setupLoading, setupNProgress } from './plugins';
import { setupStore } from './store';
import { setupRouter } from './router';
import { setupI18n } from './locales';
import { getLocale, setupI18n } from './locales';
import App from './App.vue';
async function setupApp() {
@@ -25,6 +26,10 @@ async function setupApp() {
setupAppVersionNotification();
setupVueRootValidator(app, {
lang: getLocale() === 'zh-CN' ? 'zh' : 'en'
});
app.mount('#app');
}
+6
View File
@@ -355,6 +355,7 @@ declare namespace App {
cancel: string;
close: string;
check: string;
selectAll: string;
expandColumn: string;
columnSetting: string;
config: string;
@@ -1052,6 +1053,11 @@ declare namespace App {
};
datatable: {
itemCount: string;
fixed: {
left: string;
right: string;
unFixed: string;
};
};
};
+1
View File
@@ -7,6 +7,7 @@ declare namespace NaiveUI {
type DataTableSelectionColumn<T> = import('naive-ui').DataTableSelectionColumn<T>;
type TableColumnGroup<T> = import('naive-ui/es/data-table/src/interface').TableColumnGroup<T>;
type TableColumnCheck = import('@sa/hooks').TableColumnCheck;
type TableColumnFixed = import('@sa/hooks').TableColumnCheck['fixed'];
type SetTableColumnKey<C, T> = Omit<C, 'key'> & { key: keyof T | (string & {}) };