@peng_kai/kit 0.3.0-beta.4 → 0.3.0-beta.41

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 (64) 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 +55 -8
  11. package/admin/components/filter/src/more/TableSetting.vue +95 -0
  12. package/admin/components/filter/src/useFilterParams.ts +9 -7
  13. package/admin/components/provider/Admin.vue +17 -0
  14. package/admin/components/provider/admin-permission.ts +48 -0
  15. package/admin/components/provider/admin-router.ts +361 -0
  16. package/admin/components/provider/index.ts +3 -0
  17. package/admin/components/rich-text/src/RichText.new.vue +19 -6
  18. package/admin/components/rich-text/src/editorConfig.ts +76 -1
  19. package/admin/components/settings/index.ts +1 -1
  20. package/admin/components/settings/src/SchemaForm.vue +40 -6
  21. package/admin/components/settings/src/Settings.vue +1 -1
  22. package/admin/components/text/index.ts +2 -0
  23. package/admin/components/text/src/Amount.v2.vue +131 -0
  24. package/admin/components/text/src/Datetime.vue +17 -12
  25. package/admin/components/text/src/IP.vue +18 -4
  26. package/admin/components/text/src/Num.vue +192 -0
  27. package/admin/components/upload/src/PictureCardUpload.vue +56 -20
  28. package/admin/layout/large/Breadcrumb.vue +10 -23
  29. package/admin/layout/large/Content.vue +9 -6
  30. package/admin/layout/large/Layout.vue +129 -0
  31. package/admin/layout/large/Menu.vue +24 -17
  32. package/admin/layout/large/Notice.vue +168 -0
  33. package/admin/layout/large/Tabs.vue +183 -0
  34. package/admin/layout/large/index.ts +61 -1
  35. package/admin/layout/large/y682.mp3 +0 -0
  36. package/admin/permission/routerGuard.ts +24 -11
  37. package/admin/permission/vuePlugin.ts +5 -10
  38. package/admin/route-guards/index.ts +0 -1
  39. package/admin/stores/index.ts +1 -0
  40. package/admin/styles/classCover.scss +1 -1
  41. package/admin/styles/index.scss +2 -2
  42. package/antd/hooks/useAntdModal.ts +27 -12
  43. package/antd/hooks/useAntdTable.ts +10 -7
  44. package/antd/hooks/useAntdTheme.ts +7 -0
  45. package/antd/hooks/useTableColumns.ts +83 -0
  46. package/antd/index.ts +1 -1
  47. package/libs/bignumber.ts +1 -1
  48. package/libs/dayjs.ts +16 -2
  49. package/libs/fingerprintjs.ts +1 -0
  50. package/package.json +64 -95
  51. package/request/interceptors/getDeviceInfo.ts +14 -0
  52. package/utils/LocaleManager.ts +1 -1
  53. package/utils/index.ts +1 -4
  54. package/utils/locale/LocaleManager.ts +2 -1
  55. package/utils/locale/helpers.ts +9 -0
  56. package/utils/number.ts +8 -10
  57. package/utils/storage.ts +31 -0
  58. package/utils/string.ts +1 -2
  59. package/utils/upload/AwsS3.ts +12 -4
  60. package/admin/layout/large/PageTab.vue +0 -70
  61. package/admin/route-guards/collapseMenu.ts +0 -11
  62. package/libs/a-calc.ts +0 -1
  63. package/vue/components/test/KitTest.vue +0 -9
  64. 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,21 +1,35 @@
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
  >
18
- <span v-bind="$attrs" class="font-mono">{{ $props.ip }}</span>
32
+ <span v-bind="$attrs" class="font-mono">{{ ip }}</span>
19
33
  </ATypographyLink>
20
34
  <span v-else v-bind="$attrs" class="font-mono">{{ $props.empty }}</span>
21
35
  </template>
@@ -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>
@@ -2,10 +2,10 @@
2
2
  import { Image as AImage, ImagePreviewGroup as AImagePreviewGroup, Upload as AUpload, message } from 'ant-design-vue';
3
3
  import type { UploadProps } from 'ant-design-vue';
4
4
  import type { PreviewGroupPreview } from 'ant-design-vue/es/vc-image/src/PreviewGroup';
5
- import { useVModel } from '@vueuse/core';
6
- import { ref } from 'vue';
5
+ import { ref, customRef, nextTick, triggerRef } from 'vue';
7
6
  import type { AwsS3 } from '../../../../utils/upload';
8
7
  import { createAwsS3Request } from './customRequests';
8
+ import { urlToUploadFile } from './helpers';
9
9
 
10
10
  type UploadFile = Parameters<NonNullable<UploadProps['onPreview']>>['0'];
11
11
  </script>
@@ -13,8 +13,12 @@ type UploadFile = Parameters<NonNullable<UploadProps['onPreview']>>['0'];
13
13
  <script lang="ts" setup>
14
14
  const props = withDefaults(
15
15
  defineProps<{
16
- /** 上传文件的数组 */
16
+ /** 上传图片的数组 */
17
17
  modelValue?: UploadFile[]
18
+ /** 上传图片的 url 数组 */
19
+ urls?: string[]
20
+ /** 是否使用 url 数组 */
21
+ useUrl?: boolean
18
22
  /** awsS3 实例对象 */
19
23
  awsS3: AwsS3
20
24
  /** 根目录 */
@@ -27,6 +31,8 @@ const props = withDefaults(
27
31
  cardSize?: string
28
32
  /** 允许的图片后缀 */
29
33
  allowExts?: string[]
34
+ /** 允许的图片大小,单位为字节 */
35
+ maxSize?: number
30
36
  /** 禁用 */
31
37
  disabled?: boolean
32
38
  }>(),
@@ -36,14 +42,47 @@ const props = withDefaults(
36
42
  rowCount: 'auto-fill',
37
43
  cardSize: '90px',
38
44
  allowExts: () => ['jpg', 'jpeg', 'png', 'gif', 'apng', 'webp', 'svg'],
45
+ maxSize: 1024 * 1024 * 2,
39
46
  disabled: false,
47
+ useUrl: false,
40
48
  },
41
49
  );
42
50
  const emits = defineEmits<{
43
51
  (e: 'update:modelValue', value: UploadFile[]): void
52
+ (e: 'update:urls', value: string[]): void
44
53
  }>();
45
54
 
46
- const fileList = useVModel(props, 'modelValue', emits);
55
+ const fileList = customRef<any>((track, trigger) => {
56
+ let tempValue: any[] = [];
57
+ let isAllDone = true;
58
+ return {
59
+ get() {
60
+ track();
61
+ if (props.useUrl) {
62
+ return isAllDone ? urlToUploadFile(props.urls || []) : tempValue;
63
+ }
64
+ return props.modelValue || [];
65
+ },
66
+ set(v: any) {
67
+ if (props.useUrl) {
68
+ isAllDone = (v || []).every((x: any) => typeof x === 'string' || x.status === 'done');
69
+ if (isAllDone) {
70
+ const urls = (v || []).map((x: any) => typeof x === 'string' ? x : x.response?.url);
71
+ emits('update:urls', urls);
72
+ tempValue = [];
73
+ }
74
+ else {
75
+ tempValue = v;
76
+ }
77
+ }
78
+ else {
79
+ emits('update:modelValue', v);
80
+ }
81
+ trigger();
82
+ }
83
+ }
84
+ })
85
+
47
86
  const preview = ref<PreviewGroupPreview>({
48
87
  visible: false,
49
88
  onVisibleChange(value) {
@@ -54,36 +93,33 @@ const preview = ref<PreviewGroupPreview>({
54
93
  function handlePreview(file: UploadFile) {
55
94
  preview.value.src = file.url || file.thumbUrl;
56
95
  preview.value.visible = true;
57
- preview.value.current = fileList.value?.findIndex(item => preview.value.src === (item.url || item.thumbUrl)) ?? 0;
96
+ preview.value.current = fileList.value?.findIndex((x: any) => preview.value.src === (x.url || x.thumbUrl)) ?? 0;
58
97
  }
59
98
 
60
99
  function beforeUpload(file: UploadFile) {
61
100
  const isImg = props.allowExts.some(item => String(file.type).toLowerCase().includes(item));
62
-
101
+ const isSizeValid = !!(props.maxSize && file.size && file.size <= props.maxSize);
63
102
  if (!isImg)
64
103
  message.error(`${file.name} 不是图片 (${props.allowExts.join(',')})`);
65
-
66
- return isImg;
104
+ if (!isSizeValid)
105
+ message.error(`${file.name} 大小不能超过 ${props.maxSize / 1024 / 1024}MB`);
106
+
107
+ return isImg && isSizeValid;
67
108
  }
68
109
  </script>
69
110
 
70
111
  <template>
71
112
  <div :class="{ 'w-fit': typeof props.rowCount === 'number' }">
72
- <AUpload
73
- v-model:fileList="fileList" class="pic-card-upload" listType="picture-card" multiple :maxCount="$props.maxCount"
74
- :disabled="props.disabled" :beforeUpload="beforeUpload" :customRequest="createAwsS3Request($props.awsS3, $props.rootDir)"
75
- @preview="handlePreview"
76
- >
77
- <i v-if="fileList.length < props.maxCount" class="i-ant-design:plus-outlined text-26px text-$antd-colorTextTertiary" />
113
+ <AUpload v-model:fileList="fileList" class="pic-card-upload" listType="picture-card" multiple
114
+ :maxCount="$props.maxCount" :disabled="props.disabled" :beforeUpload="beforeUpload"
115
+ :customRequest="createAwsS3Request($props.awsS3, $props.rootDir)" @preview="handlePreview">
116
+ <i v-if="fileList.length < props.maxCount"
117
+ class="i-ant-design:plus-outlined text-[26px] text-$antd-colorTextTertiary" />
78
118
  </AUpload>
79
119
 
80
120
  <div style="display: none">
81
121
  <AImagePreviewGroup :preview="preview">
82
- <AImage
83
- v-for="item of fileList"
84
- :key="item.uid"
85
- :src="item.url || item.thumbUrl"
86
- />
122
+ <AImage v-for="item of fileList" :key="item.uid" :src="item.url || item.thumbUrl" />
87
123
  </AImagePreviewGroup>
88
124
  </div>
89
125
  </div>
@@ -117,7 +153,7 @@ function beforeUpload(file: UploadFile) {
117
153
  margin: 0;
118
154
  }
119
155
 
120
- :deep(.ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item::before ) {
156
+ :deep(.ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item::before) {
121
157
  width: 100%;
122
158
  height: 100%;
123
159
  }
@@ -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>