@peng_kai/kit 0.3.0-beta.3 → 0.3.0-beta.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.vscode/settings.json +2 -2
  2. package/admin/components/currency/src/CurrencyIcon.vue +37 -33
  3. package/admin/components/date/PeriodPicker.vue +122 -0
  4. package/admin/components/date/TimeFieldSelectForLabel.vue +24 -0
  5. package/admin/components/date/TtaTimeZone.vue +516 -0
  6. package/admin/components/date/TtaTimeZoneSimple.vue +104 -0
  7. package/admin/components/date/helpers.ts +250 -0
  8. package/admin/components/date/index.ts +6 -0
  9. package/admin/components/date/presetProps.ts +19 -0
  10. package/admin/components/filter/src/FilterReset.vue +52 -8
  11. package/admin/components/filter/src/more/TableSetting.vue +95 -0
  12. package/admin/components/provider/Admin.vue +17 -0
  13. package/admin/components/provider/admin-permission.ts +48 -0
  14. package/admin/components/provider/admin-router.ts +361 -0
  15. package/admin/components/provider/index.ts +3 -0
  16. package/admin/components/rich-text/src/RichText.new.vue +19 -6
  17. package/admin/components/rich-text/src/editorConfig.ts +76 -1
  18. package/admin/components/settings/src/SchemaForm.vue +5 -4
  19. package/admin/components/settings/src/Settings.vue +1 -1
  20. package/admin/components/text/index.ts +2 -0
  21. package/admin/components/text/src/Amount.v2.vue +131 -0
  22. package/admin/components/text/src/Datetime.vue +17 -12
  23. package/admin/components/text/src/IP.vue +17 -3
  24. package/admin/components/text/src/Num.vue +192 -0
  25. package/admin/layout/large/Breadcrumb.vue +10 -23
  26. package/admin/layout/large/Content.vue +9 -6
  27. package/admin/layout/large/Layout.vue +129 -0
  28. package/admin/layout/large/Menu.vue +24 -17
  29. package/admin/layout/large/Notice.vue +152 -0
  30. package/admin/layout/large/Tabs.vue +183 -0
  31. package/admin/layout/large/index.ts +61 -1
  32. package/admin/layout/large/y682.mp3 +0 -0
  33. package/admin/permission/routerGuard.ts +24 -11
  34. package/admin/permission/vuePlugin.ts +5 -10
  35. package/admin/route-guards/index.ts +0 -1
  36. package/admin/stores/index.ts +1 -0
  37. package/admin/styles/classCover.scss +1 -1
  38. package/admin/styles/index.scss +2 -2
  39. package/antd/hooks/useAntdModal.ts +27 -13
  40. package/antd/hooks/useAntdTable.ts +10 -7
  41. package/antd/hooks/useAntdTheme.ts +7 -0
  42. package/antd/hooks/useTableColumns.ts +83 -0
  43. package/antd/index.ts +1 -1
  44. package/libs/bignumber.ts +1 -1
  45. package/libs/dayjs.ts +16 -2
  46. package/libs/fingerprintjs.ts +1 -0
  47. package/package.json +64 -95
  48. package/request/interceptors/getDeviceInfo.ts +14 -0
  49. package/utils/LocaleManager.ts +1 -1
  50. package/utils/index.ts +1 -4
  51. package/utils/locale/LocaleManager.ts +2 -1
  52. package/utils/locale/helpers.ts +9 -0
  53. package/utils/number.ts +8 -10
  54. package/utils/storage.ts +31 -0
  55. package/utils/string.ts +1 -2
  56. package/utils/upload/AwsS3.ts +11 -4
  57. package/admin/layout/large/PageTab.vue +0 -70
  58. package/admin/route-guards/collapseMenu.ts +0 -11
  59. package/libs/a-calc.ts +0 -1
  60. package/vue/components/test/KitTest.vue +0 -9
  61. package/vue/components/test/testStore.ts +0 -11
@@ -0,0 +1,131 @@
1
+ <!-- eslint-disable jsdoc/no-multi-asterisks -->
2
+
3
+ <script setup lang="ts">
4
+ import { computed } from 'vue';
5
+ import bignumber from 'bignumber.js';
6
+ import CurrencyIcon, {textIcons} from '../../currency/src/CurrencyIcon.vue';
7
+ import Num from './Num.vue';
8
+
9
+ // 由于 Vue 目前不支持 Props `interface AmountProps extends CurrencyIconProps, NumProps{}` 多继承写法,所以暂时这样写
10
+ interface AmountProps {
11
+ /** ****************** 与 Num 组件 Props 同步 ********************/
12
+ /**
13
+ * 数值
14
+ */
15
+ value: BigNumber.Value | undefined | null
16
+ /**
17
+ * 小数位数
18
+ */
19
+ decimals?: number | [number, number]
20
+ /**
21
+ * 舍入方式
22
+ *
23
+ * - up: 向上取整
24
+ * - down: 向下取整
25
+ * - half-up: 四舍五入
26
+ * - half-even: 银行家算法
27
+ */
28
+ round?: 'up' | 'down' | 'half-up' | 'half-even'
29
+ /**
30
+ * 格式化方式
31
+ *
32
+ * - original: 原始
33
+ * - pad-dec: 自适应补零,即整数位多1位,小数位就少1位
34
+ * - fixed-dec: 固定小数位数
35
+ * - max-dec: 最大小数位数
36
+ * - min-dec: 最小小数位数
37
+ * - clamp-dec: 限制小数位数
38
+ */
39
+ format?: 'original' | 'pad-dec' | 'fixed-dec' | 'max-dec' | 'min-dec' | 'clamp-dec'
40
+ /**
41
+ * 着色
42
+ *
43
+ * - inherit: 继承
44
+ * - neg: 负数
45
+ * - pos: 正数
46
+ * - full: 全部
47
+ */
48
+ colored?: 'inherit' | 'neg' | 'pos' | 'full'
49
+ /**
50
+ * 是否显示正数符号
51
+ */
52
+ showPos?: boolean
53
+ /**
54
+ * 是否使用分组分隔符(千分符)
55
+ */
56
+ grouping?: boolean
57
+ /**
58
+ * 是否弱化无价值的零值
59
+ */
60
+ weakPad?: boolean
61
+
62
+ /** ****************** 与 CurrencyIcon 组件 Props 同步 ********************/
63
+ symbol?: string
64
+ size?: string
65
+ cdn?: string
66
+ iconType?: 'img' | 'text'
67
+
68
+ /** ****************** 本组件的 Props ********************/
69
+ /** 图标显示位置 */
70
+ iconPos?: 'left' | 'right' | 'hidden'
71
+ /** 字符图标是否使用数字的颜色 */
72
+ useNumColor?: boolean
73
+ /** 值的精度(整数 value 除以 precision) */
74
+ precision?: number
75
+ }
76
+
77
+ const props = withDefaults(defineProps<AmountProps>(), {
78
+ // Num 组件 Props
79
+ value: () => bignumber(0),
80
+ decimals: 8,
81
+ round: 'down',
82
+ weakPad: true,
83
+ showPos: false,
84
+ colored: 'inherit',
85
+ format: 'original',
86
+ grouping: true,
87
+
88
+ // CurrencyIcon 组件 Props
89
+ size: '1.1em',
90
+ // symbol: '',
91
+ iconType: 'img',
92
+
93
+ // 本组件 Props
94
+ iconPos: 'left',
95
+ useNumColor: false,
96
+ precision: 0,
97
+ });
98
+
99
+ const isTextIcon = computed(() => {
100
+ return props.iconType === 'text' || textIcons.value.includes(String(props.symbol));
101
+ });
102
+ </script>
103
+
104
+ <template>
105
+ <span class="amount flex-inline" :class="[isTextIcon ? 'items-baseline' : 'items-center']">
106
+ <CurrencyIcon v-if="props.iconPos === 'left' && props.symbol && !props.useNumColor" v-bind="props" class="currency-icon" />
107
+ <Num v-bind="props" :value="bignumber(props.value ?? 0).dividedBy(10 ** props.precision)">
108
+ <template #signLeft>
109
+ <CurrencyIcon v-if="props.iconPos === 'left' && props.symbol && props.useNumColor" v-bind="props" class="currency-icon" />
110
+ </template>
111
+ </Num>
112
+ <CurrencyIcon v-if="props.iconPos === 'right'" v-bind="props" class="currency-icon" />
113
+ </span>
114
+ </template>
115
+
116
+ <style lang="scss" scoped>
117
+ .amount {
118
+ line-height: 1em;
119
+ }
120
+ .symbol-icon {
121
+ line-height: inherit;
122
+ font-size: inherit;
123
+ }
124
+ .amount .currency-icon:first-child {
125
+ margin-right: 0.15em;
126
+ }
127
+
128
+ .amount .currency-icon:last-child {
129
+ margin-left: 0.15em;
130
+ }
131
+ </style>
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { Tooltip as ATooltip } from 'ant-design-vue';
2
+ import { Tooltip } from 'ant-design-vue';
3
3
  import { computed } from 'vue';
4
4
  import dayjs from '../../../../libs/dayjs';
5
+ import { getTtaTimeZone } from '../../date'
5
6
 
6
7
  defineOptions({
7
8
  inheritAttrs: false,
@@ -10,16 +11,18 @@ defineOptions({
10
11
  const props = withDefaults(
11
12
  defineProps<{
12
13
  timestamp?: number | string
14
+ ts?: number | string
13
15
  template?: string
14
- utc?: boolean
15
16
  }>(),
16
17
  {
17
- template: 'MM-DD HH:mm:ss',
18
- utc: false,
18
+ template: 'YY-MM-DD HH:mm:ss',
19
19
  },
20
20
  );
21
+
22
+ const tz = getTtaTimeZone();
23
+ const localTz = dayjs.tz.guess();
21
24
  const timestamp = computed(() => {
22
- let tsStr = String(props.timestamp);
25
+ let tsStr = String(props.ts || props.timestamp);
23
26
 
24
27
  if (tsStr.length === 10)
25
28
  tsStr += '000';
@@ -33,19 +36,21 @@ const timestamp = computed(() => {
33
36
  const text = computed(() => {
34
37
  if (!timestamp.value)
35
38
  return '-';
36
-
37
39
  const ts = dayjs(timestamp.value);
38
-
39
- return props.utc ? ts.utc().format(props.template) : ts.format(props.template);
40
+ return ts.tz(tz.value).format(props.template);
40
41
  });
41
42
  </script>
42
43
 
43
44
  <template>
44
- <ATooltip destroyTooltipOnHide>
45
+ <Tooltip destroyTooltipOnHide :align="{ offset: [0, 8] }">
45
46
  <template v-if="timestamp" #title>
46
- <div>{{ dayjs(timestamp).fromNow?.() }}</div>
47
- <div>
47
+ <div>{{ dayjs(timestamp).tz(tz).fromNow?.() }}</div>
48
+ <div v-if="dayjs.tz.guess() !== tz && tz !== 'UTC' ">
48
49
  {{ dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss') }}
50
+ <span class="op-50">指定</span>
51
+ </div>
52
+ <div>
53
+ {{ dayjs(timestamp).tz(localTz).format('YYYY-MM-DD HH:mm:ss') }}
49
54
  <span class="op-50">本地</span>
50
55
  </div>
51
56
  <div v-if="dayjs().utc">
@@ -54,7 +59,7 @@ const text = computed(() => {
54
59
  </div>
55
60
  </template>
56
61
  <span v-bind="$attrs" class="text">{{ text }}</span>
57
- </ATooltip>
62
+ </Tooltip>
58
63
  </template>
59
64
 
60
65
  <style scoped lang="scss">
@@ -1,17 +1,31 @@
1
1
  <script setup lang="ts">
2
2
  import { TypographyLink as ATypographyLink } from 'ant-design-vue';
3
+ import { computed } from 'vue';
3
4
 
4
- withDefaults(defineProps<{
5
+ const props = withDefaults(defineProps<{
5
6
  ip?: string
6
7
  empty?: string
8
+ fullV6?: boolean
7
9
  }>(), {
8
10
  empty: '-',
11
+ fullV6: false,
9
12
  });
10
- </script>
11
13
 
14
+ const ip = computed(() => {
15
+ const ip = props.ip;
16
+ if (!ip)
17
+ return;
18
+ const isV6 = ip.includes(':');
19
+ if (isV6 && !props.fullV6) {
20
+ const parts = ip.split(':');
21
+ return `${parts[0]}:...:${parts[parts.length - 1]}`
22
+ }
23
+ return ip;
24
+ });
25
+ </script>
12
26
  <template>
13
27
  <ATypographyLink
14
- v-if="$props.ip"
28
+ v-if="ip"
15
29
  :href="`https://www.ip138.com/iplookup.php?ip=${$props.ip}&action=2`"
16
30
  :copyable="{ text: $props.ip, tooltip: false }" target="_blank"
17
31
  >
@@ -0,0 +1,192 @@
1
+ <script lang="ts">
2
+ import bignumber, { type BigNumber } from 'bignumber.js';
3
+ import { computed } from 'vue';
4
+
5
+ export interface NumProps {
6
+ /**
7
+ * 数值
8
+ */
9
+ value: BigNumber.Value
10
+ /**
11
+ * 小数位数
12
+ */
13
+ decimals?: number | [number, number]
14
+ /**
15
+ * 舍入方式
16
+ *
17
+ * - up: 向上取整
18
+ * - down: 向下取整
19
+ * - half-up: 四舍五入
20
+ * - half-even: 银行家算法
21
+ */
22
+ round?: 'up' | 'down' | 'half-up' | 'half-even'
23
+ /**
24
+ * 格式化方式
25
+ *
26
+ * - original: 原始
27
+ * - pad-dec: 自适应补零,即整数位多1位,小数位就少1位
28
+ * - fixed-dec: 固定小数位数
29
+ * - max-dec: 最大小数位数
30
+ * - min-dec: 最小小数位数
31
+ * - clamp-dec: 限制小数位数
32
+ */
33
+ format?: 'original' | 'pad-dec' | 'fixed-dec' | 'max-dec' | 'min-dec' | 'clamp-dec'
34
+ /**
35
+ * 着色
36
+ *
37
+ * - inherit: 继承
38
+ * - neg: 负数
39
+ * - pos: 正数
40
+ * - full: 全部
41
+ */
42
+ colored?: 'inherit' | 'neg' | 'pos' | 'full'
43
+ /**
44
+ * 是否显示正数符号
45
+ */
46
+ showPos?: boolean
47
+ /**
48
+ * 是否使用分组分隔符(千分符)
49
+ */
50
+ grouping?: boolean
51
+ /**
52
+ * 是否弱化无价值填充
53
+ */
54
+ weakPad?: boolean
55
+ }
56
+
57
+ // 匹配数字中无价值的填充(仅支持有小数的), 如 1.2000 => 1.2, 1.000 => 1
58
+ const insignRE = /\.?0+$/;
59
+
60
+ function formatPadDecimals(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
61
+ const integerLen = num.integerValue().toString().length;
62
+ const showDecimalLen = Math.max(decimals - integerLen + 1, 1);
63
+ return grouping ? num.toFormat(showDecimalLen, round) : num.toFixed(showDecimalLen, round);
64
+ }
65
+
66
+ function formatFixedDecimals(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
67
+ return grouping ? num.toFormat(decimals, round) : num.toFixed(decimals, round);
68
+ }
69
+
70
+ function formatMaxDecimals(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
71
+ const showDecimalLen = Math.min(num.decimalPlaces() ?? 0, decimals);
72
+ return grouping ? num.toFormat(showDecimalLen, round) : num.toFixed(showDecimalLen, round);
73
+ }
74
+
75
+ function formatMinDecimals(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
76
+ const showDecimalLen = Math.max(num.decimalPlaces() ?? 0, decimals);
77
+ return grouping ? num.toFormat(showDecimalLen, round) : num.toFixed(showDecimalLen, round);
78
+ }
79
+
80
+ function formatClampDecimals(num: BigNumber.Instance, decimals: [number, number], round: BigNumber.RoundingMode, grouping: boolean) {
81
+ const decimalLen = num.decimalPlaces() ?? 0;
82
+ const showDecimalLen = Math.min(Math.max(decimalLen, decimals[0]), decimals[1]);
83
+ return grouping ? num.toFormat(showDecimalLen, round) : num.toFixed(showDecimalLen, round);
84
+ }
85
+
86
+ function formatOriginal(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
87
+ const decimalLen = num.decimalPlaces() ?? decimals;
88
+ return grouping ? num.toFormat(decimalLen, round) : num.toString();
89
+ }
90
+ </script>
91
+
92
+ <script setup lang="ts">
93
+ const props = withDefaults(defineProps<NumProps>(), {
94
+ value: () => bignumber(0),
95
+ showPos: false,
96
+ decimals: 2,
97
+ colored: 'inherit',
98
+ round: 'half-up',
99
+ format: 'original',
100
+ grouping: true,
101
+ weakPad: false,
102
+ });
103
+
104
+ const numParts = computed(() => {
105
+ const num = bignumber(props.value).abs();
106
+ const roundMode = ({
107
+ 'up': bignumber.ROUND_UP,
108
+ 'down': bignumber.ROUND_DOWN,
109
+ 'half-up': bignumber.ROUND_HALF_UP,
110
+ 'half-even': bignumber.ROUND_HALF_EVEN,
111
+ })[props.round];
112
+ const isRangeDecimals = ['clamp-dec'].includes(props.format);
113
+ const parts = { full: '', sign: '', value: '', pad: '' };
114
+ let text = '';
115
+
116
+ if (isRangeDecimals) {
117
+ const decLen = Array.isArray(props.decimals)
118
+ ? props.decimals
119
+ : [props.decimals, props.decimals] as [number, number];
120
+ const format = ({
121
+ 'clamp-dec': formatClampDecimals,
122
+ } as Record<string, typeof formatClampDecimals>)[props.format];
123
+
124
+ text = format(num, decLen, roundMode, props.grouping);
125
+ }
126
+ else {
127
+ const decLen = Array.isArray(props.decimals) ? props.decimals[0] : props.decimals;
128
+ const format = ({
129
+ 'pad-dec': formatPadDecimals,
130
+ 'fixed-dec': formatFixedDecimals,
131
+ 'max-dec': formatMaxDecimals,
132
+ 'min-dec': formatMinDecimals,
133
+ } as Record<string, typeof formatPadDecimals>)[props.format] ?? formatOriginal;
134
+
135
+ text = format(num, decLen, roundMode, props.grouping);
136
+ }
137
+
138
+ if (text) {
139
+ const originalNum = bignumber(props.value);
140
+
141
+ parts.sign = originalNum.lt(0) ? '-' : (originalNum.gt(0) && props.showPos) ? '+' : '';
142
+ parts.full = parts.sign + text;
143
+ parts.pad = text.match(insignRE)?.[0] ?? '';
144
+ parts.pad = originalNum.decimalPlaces() || parts.pad[0] === '.' ? parts.pad : '';
145
+ parts.value = text.slice(0, text.length - parts.pad.length);
146
+ }
147
+
148
+ return parts;
149
+ });
150
+ const classList = computed(() => {
151
+ const num = bignumber(props.value);
152
+ const classes = [`colored-${props.colored}`];
153
+
154
+ if (num.lt(0))
155
+ classes.push('sign-neg');
156
+ else if (num.gt(0))
157
+ classes.push('sign-pos');
158
+ else
159
+ classes.push('sign-zero');
160
+
161
+ if (props.weakPad)
162
+ classes.push('weak-pad');
163
+
164
+ return classes;
165
+ });
166
+ </script>
167
+
168
+ <template>
169
+ <span class="num" :class="classList">
170
+ <slot name="signLeft" />{{ numParts.sign }}<slot name="signRight" /><slot name="numLeft" />{{ numParts.value }}<span class="pad">{{ numParts.pad }}</span><slot name="numRight" />
171
+ </span>
172
+ </template>
173
+
174
+ <style lang="scss" scoped>
175
+ .num {
176
+ font-variant-numeric: tabular-nums;
177
+
178
+ &.sign-neg.colored-neg,
179
+ &.sign-neg.colored-full {
180
+ color: var(--neg-color, #ef4444);
181
+ }
182
+
183
+ &.sign-pos.colored-pos,
184
+ &.sign-pos.colored-full {
185
+ color: var(--pos-color, #22c55e);
186
+ }
187
+
188
+ &.weak-pad .pad {
189
+ opacity: 0.5;
190
+ }
191
+ }
192
+ </style>
@@ -3,15 +3,14 @@ import { computed } from 'vue';
3
3
  import type { VNode } from 'vue';
4
4
  import { Breadcrumb as ABreadcrumb } from 'ant-design-vue';
5
5
  import type { Route as AntdBreadcrumbRoute } from 'ant-design-vue/es/breadcrumb/Breadcrumb';
6
- import { injectTTAdmin } from '../../adminPlugin';
7
- import type { IBreadcrumb } from '../../stores/createUsePageStore';
6
+ import type { TBreadcrumb } from '../../components/provider/admin-router';
8
7
 
9
- interface IBreadcrumbRoute extends AntdBreadcrumbRoute {
8
+ interface TBreadcrumbRoute extends AntdBreadcrumbRoute {
10
9
  icon?: VNode | null
11
10
  trigger?: () => void
12
11
  }
13
12
 
14
- function _buildRoute(breadcrumb: IBreadcrumb): IBreadcrumbRoute {
13
+ function _buildRoute(breadcrumb: TBreadcrumb): TBreadcrumbRoute {
15
14
  return {
16
15
  breadcrumbName: breadcrumb.title,
17
16
  path: breadcrumb.title,
@@ -23,28 +22,15 @@ function _buildRoute(breadcrumb: IBreadcrumb): IBreadcrumbRoute {
23
22
  </script>
24
23
 
25
24
  <script setup lang="ts">
26
- const pageStore = injectTTAdmin()!.deps.usePageStore();
27
- const routes = computed(() => {
28
- let breadcrumbs: IBreadcrumbRoute[] = [];
29
-
30
- if (!pageStore.currentPageState)
31
- return breadcrumbs;
32
-
33
- breadcrumbs = pageStore.currentPageState.breadcrumbs.map(breadcrumb => _buildRoute(breadcrumb));
34
-
35
- breadcrumbs.push({
36
- path: pageStore.currentPageState.title,
37
- breadcrumbName: pageStore.currentPageState.title,
38
- icon: pageStore.currentPageState.icon,
39
- });
40
-
41
- return breadcrumbs;
42
- });
25
+ const props = defineProps<{
26
+ breadcrumbs: TBreadcrumb[]
27
+ }>();
28
+ const routes = computed(() => props.breadcrumbs.map(breadcrumb => _buildRoute(breadcrumb)));
43
29
  </script>
44
30
 
45
31
  <template>
46
32
  <ABreadcrumb class="breadcrumb" :routes="routes">
47
- <template #itemRender="{ route }: {route: IBreadcrumbRoute}">
33
+ <template #itemRender="{ route }: {route: TBreadcrumbRoute}">
48
34
  <div @click="route.trigger?.()">
49
35
  <component :is="route.icon" v-if="route.icon" class="mb-0.2em mr-0.2em" />
50
36
  <span>{{ route.breadcrumbName }}</span>
@@ -55,10 +41,11 @@ const routes = computed(() => {
55
41
 
56
42
  <style lang="scss" scoped>
57
43
  .breadcrumb {
58
- font-size: 1rem;
44
+ font-size: 14px;
59
45
 
60
46
  :deep(.ant-dropdown-trigger) {
61
47
  height: 1.6em;
48
+ cursor: pointer;
62
49
  }
63
50
 
64
51
  :deep(.anticon-down) {
@@ -1,16 +1,19 @@
1
1
  <script setup lang="ts">
2
- import { injectTTAdmin } from '../../adminPlugin';
2
+ import { RouterView } from 'vue-router';
3
+ import { KeepAlive, type KeepAliveProps } from 'vue';
3
4
 
4
- const pageStore = injectTTAdmin()!.deps.usePageStore();
5
+ const props = defineProps<{
6
+ cached: KeepAliveProps['include']
7
+ }>();
5
8
  </script>
6
9
 
7
10
  <template>
8
- <RouterView #default="{ Component, route }">
9
- <KeepAlive :include="pageStore.pageCacheList">
11
+ <RouterView #default="{ Component }">
12
+ <KeepAlive :include="props.cached">
10
13
  <Suspense>
11
- <component :is="pageStore.setPage(Component, route)" :key="(Component.type as any).name" />
14
+ <component :is="Component" :key="(Component as any).type?.__name" />
12
15
  <template #fallback>
13
- <div class="flex justify-center items-center h-70">
16
+ <div class="flex justify-center items-center h-full">
14
17
  <div class="flex items-center">
15
18
  <i class="i-svg-spinners:180-ring-with-bg block color-primary scale-125 mr-2" />
16
19
  <span class="text-gray">正在加载...</span>
@@ -0,0 +1,129 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue';
3
+ import { useScroll, useMutationObserver } from '@vueuse/core'
4
+
5
+ const hiddenSiderbar = ref(false);
6
+ const $content = ref<HTMLElement>();
7
+ const { arrivedState, measure } = useScroll($content);
8
+ const contentCssVars = computed(() => {
9
+ const {top: arrivedTop, bottom: arrivedBtm} = arrivedState;
10
+ return {
11
+ '--top-e': arrivedTop ? '0%' : '5px',
12
+ '--btm-s': arrivedBtm ? '100%' : '99.5%',
13
+ // 'margin-top': arrivedTop ? '0' : '-5px',
14
+ };
15
+ })
16
+
17
+ useMutationObserver($content, () => {
18
+ setTimeout(measure, 100)
19
+ }, { childList: true });
20
+ </script>
21
+
22
+ <template>
23
+ <div class="app-layout" :class="{ 'hidden-siderbar': hiddenSiderbar }">
24
+ <div class="app-logo select-none [grid-area:logo] ">
25
+ <slot name="logo" />
26
+ </div>
27
+
28
+ <div class="app-header select-none [grid-area:header]">
29
+ <!-- <div
30
+ class="text-6 flex items-center h-7 -mr-5 cursor-pointer"
31
+ @click="hiddenSiderbar = !hiddenSiderbar"
32
+ >
33
+ <i class="i-material-symbols:menu-open-rounded" :class="{ 'rotate-180': hiddenSiderbar }" />
34
+ </div> -->
35
+ <slot name="header" />
36
+ </div>
37
+
38
+ <div v-show="!hiddenSiderbar" class="app-siderbar select-none [grid-area:siderbar]">
39
+ <slot name="siderbar" />
40
+ </div>
41
+
42
+ <div ref="$content" class="app-content [grid-area:content]" :style="contentCssVars">
43
+ <slot name="content" />
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <style lang="scss" scoped>
49
+ .app-layout {
50
+ box-sizing: border-box;
51
+ display: grid;
52
+ grid-template:
53
+ "logo header" 56px
54
+ "siderbar content" 1fr
55
+ / 180px 1fr;
56
+ column-gap: 8px;
57
+ width: 100vw;
58
+ height: 100vh;
59
+ padding: 0 8px 8px;
60
+ background-color: var(--antd-colorBgLayout);
61
+
62
+ &.hidden-siderbar {
63
+ grid-template:
64
+ "logo header" 56px
65
+ "content content" 1fr
66
+ / 130px 1fr;
67
+
68
+ .app-logo {
69
+ justify-content: start;
70
+ }
71
+ }
72
+ }
73
+
74
+ .app-logo {
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ font-size: 2rem;
79
+ font-weight: 700;
80
+ line-height: 1;
81
+ color: var(--antd-colorPrimary)
82
+ }
83
+
84
+ .app-siderbar {
85
+ position: relative;
86
+ display: flex;
87
+ min-height: 100%;
88
+ flex-direction: column;
89
+ margin-right: -6px;
90
+ overflow-y: auto;
91
+ scrollbar-gutter: stable;
92
+ }
93
+
94
+ // @property --top-e {
95
+ // syntax: "<percentage>";
96
+ // initial-value: 0%;
97
+ // inherits: false;
98
+ // }
99
+ // @property --btm-s {
100
+ // syntax: "<percentage>";
101
+ // initial-value: 100%;
102
+ // inherits: false;
103
+ // }
104
+
105
+ .app-content {
106
+ --top-s: 0%;
107
+ --top-e: 0%;
108
+ --btm-s: 99%;
109
+ --btm-e: 100%;
110
+
111
+ margin-right: -6px;
112
+ overflow-y: auto;
113
+ scrollbar-gutter: stable;
114
+ mask-image: linear-gradient(
115
+ to bottom,
116
+ #0000 var(--top-s),
117
+ #000 var(--top-e),
118
+ #000 var(--btm-s),
119
+ #0000 var(--btm-e)
120
+ );
121
+ transition: --top-e 0.05s, --btm-s 0.05s;
122
+ }
123
+
124
+ .app-header {
125
+ display: flex;
126
+ align-items: center;
127
+ gap: 36px;
128
+ }
129
+ </style>