@keqi.gress/plugin-ui 1.0.0

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.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # @gress/plugin-ui
2
+
3
+ Gress 插件 UI 组件和样式包 - 提供统一的设计系统和通用组件
4
+
5
+ ## 📦 安装
6
+
7
+ ```bash
8
+ npm install @gress/plugin-ui @gress/plugin-bridge
9
+ ```
10
+
11
+ ## 🚀 快速开始
12
+
13
+ ### 导入样式
14
+
15
+ 在你的插件入口文件中导入样式:
16
+
17
+ ```typescript
18
+ import '@gress/plugin-ui/dist/style.css'
19
+ ```
20
+
21
+ ### 使用 Composables
22
+
23
+ ```typescript
24
+ import { useLoadingState, useResponsive } from '@gress/plugin-ui'
25
+
26
+ // 加载状态管理
27
+ const { loading, error, isEmpty, execute } = useLoadingState()
28
+
29
+ await execute(async () => {
30
+ const data = await fetchData()
31
+ return data
32
+ }, {
33
+ checkEmpty: (data) => data.length === 0
34
+ })
35
+
36
+ // 响应式工具
37
+ const { isMobile, isTablet, isDesktop } = useResponsive()
38
+ ```
39
+
40
+ ### 使用组件
41
+
42
+ ```vue
43
+ <template>
44
+ <LoadingState v-if="loading" />
45
+ <EmptyState v-else-if="isEmpty" title="暂无数据" />
46
+ <ErrorState v-else-if="error" :error="error" />
47
+ <div v-else>
48
+ <!-- 你的内容 -->
49
+ </div>
50
+ </template>
51
+
52
+ <script setup lang="ts">
53
+ import { LoadingState, EmptyState, ErrorState } from '@gress/plugin-ui'
54
+ </script>
55
+ ```
56
+
57
+ ## 📚 API 文档
58
+
59
+ ### Composables
60
+
61
+ #### `useLoadingState(options?)`
62
+
63
+ 统一管理加载、错误和空状态。
64
+
65
+ **参数:**
66
+ - `options.initialLoading` - 初始加载状态
67
+ - `options.onError` - 错误回调
68
+
69
+ **返回值:**
70
+ - `loading` - 加载状态
71
+ - `error` - 错误对象
72
+ - `isEmpty` - 空状态
73
+ - `execute(fn, options?)` - 执行异步操作
74
+
75
+ #### `useResponsive()`
76
+
77
+ 响应式断点检测。
78
+
79
+ **返回值:**
80
+ - `isMobile` - 是否移动端 (< 768px)
81
+ - `isTablet` - 是否平板 (768px - 1280px)
82
+ - `isDesktop` - 是否桌面端 (>= 1280px)
83
+ - `width` - 窗口宽度
84
+ - `height` - 窗口高度
85
+
86
+ ### 组件
87
+
88
+ #### `<LoadingState />`
89
+
90
+ 加载状态组件。
91
+
92
+ #### `<EmptyState />`
93
+
94
+ 空状态组件。
95
+
96
+ **Props:**
97
+ - `title` - 标题
98
+ - `description` - 描述
99
+
100
+ #### `<ErrorState />`
101
+
102
+ 错误状态组件。
103
+
104
+ **Props:**
105
+ - `error` - 错误对象或字符串
106
+ - `title` - 标题
107
+
108
+ ## 🎨 CSS 变量
109
+
110
+ 包提供了完整的 CSS 变量系统,与主应用保持一致:
111
+
112
+ ```css
113
+ /* 颜色 */
114
+ --primary
115
+ --success
116
+ --warning
117
+ --error
118
+ --info
119
+
120
+ /* 文本颜色 */
121
+ --text-primary
122
+ --text-secondary
123
+ --text-tertiary
124
+
125
+ /* 背景颜色 */
126
+ --bg-primary
127
+ --bg-secondary
128
+
129
+ /* 间距 */
130
+ --spacing-xs
131
+ --spacing-sm
132
+ --spacing-md
133
+ --spacing-lg
134
+ --spacing-xl
135
+
136
+ /* 圆角 */
137
+ --radius-sm
138
+ --radius-md
139
+ --radius-lg
140
+
141
+ /* 阴影 */
142
+ --shadow-sm
143
+ --shadow-md
144
+ --shadow-lg
145
+
146
+ /* 字体 */
147
+ --font-size-xs
148
+ --font-size-sm
149
+ --font-size-md
150
+ --font-size-lg
151
+ ```
152
+
153
+ ## 📄 License
154
+
155
+ MIT
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 加载状态管理 Composable
3
+ * 统一管理加载、错误和空状态
4
+ */
5
+ export interface LoadingState {
6
+ loading: boolean;
7
+ error: Error | null;
8
+ isEmpty: boolean;
9
+ }
10
+ export interface UseLoadingStateOptions {
11
+ initialLoading?: boolean;
12
+ onError?: (error: Error) => void;
13
+ }
14
+ export declare function useLoadingState(options?: UseLoadingStateOptions): {
15
+ loading: import("vue").Ref<boolean, boolean>;
16
+ error: import("vue").Ref<Error | null, Error | null>;
17
+ isEmpty: import("vue").Ref<boolean, boolean>;
18
+ isLoading: import("vue").ComputedRef<boolean>;
19
+ hasError: import("vue").ComputedRef<boolean>;
20
+ isEmptyState: import("vue").ComputedRef<boolean>;
21
+ startLoading: () => void;
22
+ stopLoading: () => void;
23
+ setError: (err: Error | string) => void;
24
+ clearError: () => void;
25
+ setEmpty: (empty: boolean) => void;
26
+ reset: () => void;
27
+ execute: <T>(fn: () => Promise<T>, options?: {
28
+ checkEmpty?: (result: T) => boolean;
29
+ onSuccess?: (result: T) => void;
30
+ onError?: (error: Error) => void;
31
+ }) => Promise<T | null>;
32
+ };
33
+ //# sourceMappingURL=useLoadingState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLoadingState.d.ts","sourceRoot":"","sources":["../../src/composables/useLoadingState.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B;;;;;;;;;oBA8B3C,KAAK,GAAG,MAAM;;sBAmBZ,OAAO;;cAgBT,CAAC,MAClB,MAAM,OAAO,CAAC,CAAC,CAAC,YACV;QACR,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAA;QACnC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAA;QAC/B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;KACjC,KACA,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;EAmDrB"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 响应式设计 Composable
3
+ * 提供断点检测和响应式工具
4
+ */
5
+ import { type ComputedRef, type Ref } from 'vue';
6
+ export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
7
+ export interface BreakpointValues {
8
+ xs: number;
9
+ sm: number;
10
+ md: number;
11
+ lg: number;
12
+ xl: number;
13
+ '2xl': number;
14
+ }
15
+ export declare const breakpoints: BreakpointValues;
16
+ export interface UseResponsiveReturn {
17
+ windowWidth: Ref<number>;
18
+ currentBreakpoint: ComputedRef<Breakpoint>;
19
+ isMobile: ComputedRef<boolean>;
20
+ isTablet: ComputedRef<boolean>;
21
+ isDesktop: ComputedRef<boolean>;
22
+ isSmaller: (breakpoint: Breakpoint) => ComputedRef<boolean>;
23
+ isLarger: (breakpoint: Breakpoint) => ComputedRef<boolean>;
24
+ isBetween: (min: Breakpoint, max: Breakpoint) => ComputedRef<boolean>;
25
+ responsive: <T>(values: Partial<Record<Breakpoint, T>>, defaultValue: T) => T;
26
+ getResponsiveColumns: (config?: {
27
+ xs?: number;
28
+ sm?: number;
29
+ md?: number;
30
+ lg?: number;
31
+ xl?: number;
32
+ '2xl'?: number;
33
+ }) => number;
34
+ getResponsiveSpacing: (config?: {
35
+ xs?: number;
36
+ sm?: number;
37
+ md?: number;
38
+ lg?: number;
39
+ }) => number;
40
+ breakpoints: BreakpointValues;
41
+ }
42
+ export declare function useResponsive(): UseResponsiveReturn;
43
+ //# sourceMappingURL=useResponsive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useResponsive.d.ts","sourceRoot":"","sources":["../../src/composables/useResponsive.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAyC,KAAK,WAAW,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AAEvF,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAA;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,WAAW,EAAE,gBAOzB,CAAA;AAED,MAAM,WAAW,mBAAmB;IAElC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,iBAAiB,EAAE,WAAW,CAAC,UAAU,CAAC,CAAA;IAG1C,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAC9B,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAC9B,SAAS,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAG/B,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,CAAA;IAC3D,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,CAAA;IAC1D,SAAS,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,CAAA;IACrE,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,KAAK,CAAC,CAAA;IAC7E,oBAAoB,EAAE,CAAC,MAAM,CAAC,EAAE;QAC9B,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,KAAK,MAAM,CAAA;IACZ,oBAAoB,EAAE,CAAC,MAAM,CAAC,EAAE;QAC9B,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,EAAE,CAAC,EAAE,MAAM,CAAA;KACZ,KAAK,MAAM,CAAA;IAGZ,WAAW,EAAE,gBAAgB,CAAA;CAC9B;AAED,wBAAgB,aAAa,IAAI,mBAAmB,CAuInD"}
package/dist/index.cjs ADDED
@@ -0,0 +1,327 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const vue = require("vue");
4
+ function useLoadingState(options = {}) {
5
+ const { initialLoading = false, onError } = options;
6
+ const loading = vue.ref(initialLoading);
7
+ const error = vue.ref(null);
8
+ const isEmpty = vue.ref(false);
9
+ const isLoading = vue.computed(() => loading.value);
10
+ const hasError = vue.computed(() => error.value !== null);
11
+ const isEmptyState = vue.computed(() => isEmpty.value && !loading.value && !error.value);
12
+ const startLoading = () => {
13
+ loading.value = true;
14
+ error.value = null;
15
+ isEmpty.value = false;
16
+ };
17
+ const stopLoading = () => {
18
+ loading.value = false;
19
+ };
20
+ const setError = (err) => {
21
+ loading.value = false;
22
+ error.value = err instanceof Error ? err : new Error(err);
23
+ if (onError && error.value) {
24
+ onError(error.value);
25
+ }
26
+ };
27
+ const clearError = () => {
28
+ error.value = null;
29
+ };
30
+ const setEmpty = (empty) => {
31
+ isEmpty.value = empty;
32
+ };
33
+ const reset = () => {
34
+ loading.value = false;
35
+ error.value = null;
36
+ isEmpty.value = false;
37
+ };
38
+ const execute = async (fn, options2) => {
39
+ try {
40
+ startLoading();
41
+ const result = await fn();
42
+ if (options2 == null ? void 0 : options2.checkEmpty) {
43
+ isEmpty.value = options2.checkEmpty(result);
44
+ }
45
+ if (options2 == null ? void 0 : options2.onSuccess) {
46
+ options2.onSuccess(result);
47
+ }
48
+ return result;
49
+ } catch (err) {
50
+ const errorObj = err instanceof Error ? err : new Error(String(err));
51
+ setError(errorObj);
52
+ if (options2 == null ? void 0 : options2.onError) {
53
+ options2.onError(errorObj);
54
+ }
55
+ return null;
56
+ } finally {
57
+ stopLoading();
58
+ }
59
+ };
60
+ return {
61
+ // 状态
62
+ loading,
63
+ error,
64
+ isEmpty,
65
+ // 计算属性
66
+ isLoading,
67
+ hasError,
68
+ isEmptyState,
69
+ // 方法
70
+ startLoading,
71
+ stopLoading,
72
+ setError,
73
+ clearError,
74
+ setEmpty,
75
+ reset,
76
+ execute
77
+ };
78
+ }
79
+ const breakpoints = {
80
+ xs: 480,
81
+ sm: 640,
82
+ md: 768,
83
+ lg: 1024,
84
+ xl: 1280,
85
+ "2xl": 1536
86
+ };
87
+ function useResponsive() {
88
+ const windowWidth = vue.ref(typeof window !== "undefined" ? window.innerWidth : 1024);
89
+ const updateWidth = () => {
90
+ windowWidth.value = window.innerWidth;
91
+ };
92
+ vue.onMounted(() => {
93
+ window.addEventListener("resize", updateWidth);
94
+ });
95
+ vue.onUnmounted(() => {
96
+ window.removeEventListener("resize", updateWidth);
97
+ });
98
+ const currentBreakpoint = vue.computed(() => {
99
+ const width = windowWidth.value;
100
+ if (width < breakpoints.xs) return "xs";
101
+ if (width < breakpoints.sm) return "xs";
102
+ if (width < breakpoints.md) return "sm";
103
+ if (width < breakpoints.lg) return "md";
104
+ if (width < breakpoints.xl) return "lg";
105
+ if (width < breakpoints["2xl"]) return "xl";
106
+ return "2xl";
107
+ });
108
+ const isMobile = vue.computed(() => windowWidth.value < breakpoints.md);
109
+ const isTablet = vue.computed(
110
+ () => windowWidth.value >= breakpoints.md && windowWidth.value < breakpoints.lg
111
+ );
112
+ const isDesktop = vue.computed(() => windowWidth.value >= breakpoints.lg);
113
+ const isSmaller = (breakpoint) => {
114
+ return vue.computed(() => windowWidth.value < breakpoints[breakpoint]);
115
+ };
116
+ const isLarger = (breakpoint) => {
117
+ return vue.computed(() => windowWidth.value >= breakpoints[breakpoint]);
118
+ };
119
+ const isBetween = (min, max) => {
120
+ return vue.computed(
121
+ () => windowWidth.value >= breakpoints[min] && windowWidth.value < breakpoints[max]
122
+ );
123
+ };
124
+ const responsive = (values, defaultValue) => {
125
+ const bp = currentBreakpoint.value;
126
+ const breakpointOrder = ["2xl", "xl", "lg", "md", "sm", "xs"];
127
+ const currentIndex = breakpointOrder.indexOf(bp);
128
+ for (let i = currentIndex; i < breakpointOrder.length; i++) {
129
+ const key = breakpointOrder[i];
130
+ if (values[key] !== void 0) {
131
+ return values[key];
132
+ }
133
+ }
134
+ return defaultValue;
135
+ };
136
+ const getResponsiveColumns = (config) => {
137
+ const defaultConfig = {
138
+ xs: 1,
139
+ sm: 1,
140
+ md: 2,
141
+ lg: 3,
142
+ xl: 4,
143
+ "2xl": 4
144
+ };
145
+ const finalConfig = { ...defaultConfig, ...config };
146
+ return responsive(finalConfig, 1);
147
+ };
148
+ const getResponsiveSpacing = (config) => {
149
+ const defaultConfig = {
150
+ xs: 8,
151
+ sm: 12,
152
+ md: 16,
153
+ lg: 24
154
+ };
155
+ const finalConfig = { ...defaultConfig, ...config };
156
+ return responsive(finalConfig, 16);
157
+ };
158
+ return {
159
+ // 状态
160
+ windowWidth,
161
+ currentBreakpoint,
162
+ // 计算属性
163
+ isMobile,
164
+ isTablet,
165
+ isDesktop,
166
+ // 方法
167
+ isSmaller,
168
+ isLarger,
169
+ isBetween,
170
+ responsive,
171
+ getResponsiveColumns,
172
+ getResponsiveSpacing,
173
+ // 断点值
174
+ breakpoints
175
+ };
176
+ }
177
+ const _hoisted_1$2 = { class: "loading-container" };
178
+ const _hoisted_2$2 = {
179
+ key: 0,
180
+ class: "loading-message"
181
+ };
182
+ const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
183
+ __name: "LoadingState",
184
+ props: {
185
+ message: {}
186
+ },
187
+ setup(__props) {
188
+ return (_ctx, _cache) => {
189
+ return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$2, [
190
+ _cache[0] || (_cache[0] = vue.createElementVNode("div", { class: "loading-spinner" }, null, -1)),
191
+ __props.message ? (vue.openBlock(), vue.createElementBlock("p", _hoisted_2$2, vue.toDisplayString(__props.message), 1)) : vue.createCommentVNode("", true)
192
+ ]);
193
+ };
194
+ }
195
+ });
196
+ const _export_sfc = (sfc, props) => {
197
+ const target = sfc.__vccOpts || sfc;
198
+ for (const [key, val] of props) {
199
+ target[key] = val;
200
+ }
201
+ return target;
202
+ };
203
+ const LoadingState = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-88be2f1a"]]);
204
+ const _hoisted_1$1 = { class: "empty-state" };
205
+ const _hoisted_2$1 = { class: "empty-icon" };
206
+ const _hoisted_3$1 = { class: "empty-title" };
207
+ const _hoisted_4$1 = {
208
+ key: 0,
209
+ class: "empty-description"
210
+ };
211
+ const _hoisted_5$1 = {
212
+ key: 1,
213
+ class: "empty-action"
214
+ };
215
+ const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
216
+ __name: "EmptyState",
217
+ props: {
218
+ title: { default: "暂无数据" },
219
+ description: {}
220
+ },
221
+ setup(__props) {
222
+ return (_ctx, _cache) => {
223
+ return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$1, [
224
+ vue.createElementVNode("div", _hoisted_2$1, [
225
+ vue.renderSlot(_ctx.$slots, "icon", {}, () => [
226
+ _cache[0] || (_cache[0] = vue.createElementVNode("svg", {
227
+ width: "64",
228
+ height: "64",
229
+ viewBox: "0 0 64 64",
230
+ fill: "none",
231
+ xmlns: "http://www.w3.org/2000/svg"
232
+ }, [
233
+ vue.createElementVNode("path", {
234
+ d: "M32 8C18.745 8 8 18.745 8 32s10.745 24 24 24 24-10.745 24-24S45.255 8 32 8zm0 44c-11.028 0-20-8.972-20-20s8.972-20 20-20 20 8.972 20 20-8.972 20-20 20z",
235
+ fill: "currentColor",
236
+ opacity: "0.3"
237
+ }),
238
+ vue.createElementVNode("path", {
239
+ d: "M32 24c-1.105 0-2 .895-2 2v12c0 1.105.895 2 2 2s2-.895 2-2V26c0-1.105-.895-2-2-2zm0 20c-1.105 0-2 .895-2 2s.895 2 2 2 2-.895 2-2-.895-2-2-2z",
240
+ fill: "currentColor"
241
+ })
242
+ ], -1))
243
+ ], true)
244
+ ]),
245
+ vue.createElementVNode("h3", _hoisted_3$1, vue.toDisplayString(__props.title), 1),
246
+ __props.description ? (vue.openBlock(), vue.createElementBlock("p", _hoisted_4$1, vue.toDisplayString(__props.description), 1)) : vue.createCommentVNode("", true),
247
+ _ctx.$slots.action ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5$1, [
248
+ vue.renderSlot(_ctx.$slots, "action", {}, void 0, true)
249
+ ])) : vue.createCommentVNode("", true)
250
+ ]);
251
+ };
252
+ }
253
+ });
254
+ const EmptyState = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-12ee016f"]]);
255
+ const _hoisted_1 = { class: "error-container" };
256
+ const _hoisted_2 = { class: "error-icon" };
257
+ const _hoisted_3 = { class: "error-title" };
258
+ const _hoisted_4 = { class: "error-message" };
259
+ const _hoisted_5 = {
260
+ key: 0,
261
+ class: "error-action"
262
+ };
263
+ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
264
+ __name: "ErrorState",
265
+ props: {
266
+ error: {},
267
+ title: { default: "出错了" },
268
+ showRetry: { type: Boolean, default: false }
269
+ },
270
+ emits: ["retry"],
271
+ setup(__props, { emit: __emit }) {
272
+ const props = __props;
273
+ const emit = __emit;
274
+ const errorMessage = vue.computed(() => {
275
+ if (!props.error) return "发生了未知错误";
276
+ if (typeof props.error === "string") return props.error;
277
+ return props.error.message || "发生了未知错误";
278
+ });
279
+ const handleRetry = () => {
280
+ emit("retry");
281
+ };
282
+ return (_ctx, _cache) => {
283
+ return vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [
284
+ vue.createElementVNode("div", _hoisted_2, [
285
+ vue.renderSlot(_ctx.$slots, "icon", {}, () => [
286
+ _cache[0] || (_cache[0] = vue.createElementVNode("svg", {
287
+ width: "64",
288
+ height: "64",
289
+ viewBox: "0 0 64 64",
290
+ fill: "none",
291
+ xmlns: "http://www.w3.org/2000/svg"
292
+ }, [
293
+ vue.createElementVNode("path", {
294
+ d: "M32 8C18.745 8 8 18.745 8 32s10.745 24 24 24 24-10.745 24-24S45.255 8 32 8zm0 44c-11.028 0-20-8.972-20-20s8.972-20 20-20 20 8.972 20 20-8.972 20-20 20z",
295
+ fill: "currentColor",
296
+ opacity: "0.3"
297
+ }),
298
+ vue.createElementVNode("path", {
299
+ d: "M38.828 22.172a2 2 0 00-2.828 0L32 26.172l-4-4a2 2 0 10-2.828 2.828L29.172 29l-4 4a2 2 0 102.828 2.828L32 31.828l4 4a2 2 0 102.828-2.828L34.828 29l4-4a2 2 0 000-2.828z",
300
+ fill: "currentColor"
301
+ })
302
+ ], -1))
303
+ ], true)
304
+ ]),
305
+ vue.createElementVNode("h3", _hoisted_3, vue.toDisplayString(__props.title), 1),
306
+ vue.createElementVNode("p", _hoisted_4, vue.toDisplayString(errorMessage.value), 1),
307
+ _ctx.$slots.action || __props.showRetry ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5, [
308
+ vue.renderSlot(_ctx.$slots, "action", {}, () => [
309
+ __props.showRetry ? (vue.openBlock(), vue.createElementBlock("button", {
310
+ key: 0,
311
+ onClick: handleRetry,
312
+ class: "error-retry-btn"
313
+ }, " 重试 ")) : vue.createCommentVNode("", true)
314
+ ], true)
315
+ ])) : vue.createCommentVNode("", true)
316
+ ]);
317
+ };
318
+ }
319
+ });
320
+ const ErrorState = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9bbae138"]]);
321
+ exports.EmptyState = EmptyState;
322
+ exports.ErrorState = ErrorState;
323
+ exports.LoadingStateComponent = LoadingState;
324
+ exports.breakpoints = breakpoints;
325
+ exports.useLoadingState = useLoadingState;
326
+ exports.useResponsive = useResponsive;
327
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/composables/useLoadingState.ts","../src/composables/useResponsive.ts","../src/components/LoadingState.vue","../src/components/EmptyState.vue","../src/components/ErrorState.vue"],"sourcesContent":["/**\n * 加载状态管理 Composable\n * 统一管理加载、错误和空状态\n */\n\nimport { ref, computed } from 'vue'\n\nexport interface LoadingState {\n loading: boolean\n error: Error | null\n isEmpty: boolean\n}\n\nexport interface UseLoadingStateOptions {\n initialLoading?: boolean\n onError?: (error: Error) => void\n}\n\nexport function useLoadingState(options: UseLoadingStateOptions = {}) {\n const { initialLoading = false, onError } = options\n\n const loading = ref(initialLoading)\n const error = ref<Error | null>(null)\n const isEmpty = ref(false)\n\n const isLoading = computed(() => loading.value)\n const hasError = computed(() => error.value !== null)\n const isEmptyState = computed(() => isEmpty.value && !loading.value && !error.value)\n\n /**\n * 开始加载\n */\n const startLoading = () => {\n loading.value = true\n error.value = null\n isEmpty.value = false\n }\n\n /**\n * 停止加载\n */\n const stopLoading = () => {\n loading.value = false\n }\n\n /**\n * 设置错误\n */\n const setError = (err: Error | string) => {\n loading.value = false\n error.value = err instanceof Error ? err : new Error(err)\n \n if (onError && error.value) {\n onError(error.value)\n }\n }\n\n /**\n * 清除错误\n */\n const clearError = () => {\n error.value = null\n }\n\n /**\n * 设置空状态\n */\n const setEmpty = (empty: boolean) => {\n isEmpty.value = empty\n }\n\n /**\n * 重置所有状态\n */\n const reset = () => {\n loading.value = false\n error.value = null\n isEmpty.value = false\n }\n\n /**\n * 执行异步操作并自动管理状态\n */\n const execute = async <T>(\n fn: () => Promise<T>,\n options?: {\n checkEmpty?: (result: T) => boolean\n onSuccess?: (result: T) => void\n onError?: (error: Error) => void\n }\n ): Promise<T | null> => {\n try {\n startLoading()\n const result = await fn()\n \n // 检查是否为空\n if (options?.checkEmpty) {\n isEmpty.value = options.checkEmpty(result)\n }\n \n // 成功回调\n if (options?.onSuccess) {\n options.onSuccess(result)\n }\n \n return result\n } catch (err) {\n const errorObj = err instanceof Error ? err : new Error(String(err))\n setError(errorObj)\n \n // 错误回调\n if (options?.onError) {\n options.onError(errorObj)\n }\n \n return null\n } finally {\n stopLoading()\n }\n }\n\n return {\n // 状态\n loading,\n error,\n isEmpty,\n \n // 计算属性\n isLoading,\n hasError,\n isEmptyState,\n \n // 方法\n startLoading,\n stopLoading,\n setError,\n clearError,\n setEmpty,\n reset,\n execute\n }\n}\n","/**\n * 响应式设计 Composable\n * 提供断点检测和响应式工具\n */\n\nimport { ref, computed, onMounted, onUnmounted, type ComputedRef, type Ref } from 'vue'\n\nexport type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'\n\nexport interface BreakpointValues {\n xs: number\n sm: number\n md: number\n lg: number\n xl: number\n '2xl': number\n}\n\nexport const breakpoints: BreakpointValues = {\n xs: 480,\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n '2xl': 1536\n}\n\nexport interface UseResponsiveReturn {\n // 状态\n windowWidth: Ref<number>\n currentBreakpoint: ComputedRef<Breakpoint>\n\n // 计算属性\n isMobile: ComputedRef<boolean>\n isTablet: ComputedRef<boolean>\n isDesktop: ComputedRef<boolean>\n\n // 方法\n isSmaller: (breakpoint: Breakpoint) => ComputedRef<boolean>\n isLarger: (breakpoint: Breakpoint) => ComputedRef<boolean>\n isBetween: (min: Breakpoint, max: Breakpoint) => ComputedRef<boolean>\n responsive: <T>(values: Partial<Record<Breakpoint, T>>, defaultValue: T) => T\n getResponsiveColumns: (config?: {\n xs?: number\n sm?: number\n md?: number\n lg?: number\n xl?: number\n '2xl'?: number\n }) => number\n getResponsiveSpacing: (config?: {\n xs?: number\n sm?: number\n md?: number\n lg?: number\n }) => number\n\n // 断点值\n breakpoints: BreakpointValues\n}\n\nexport function useResponsive(): UseResponsiveReturn {\n const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1024)\n\n const updateWidth = () => {\n windowWidth.value = window.innerWidth\n }\n\n onMounted(() => {\n window.addEventListener('resize', updateWidth)\n })\n\n onUnmounted(() => {\n window.removeEventListener('resize', updateWidth)\n })\n\n // 当前断点\n const currentBreakpoint = computed<Breakpoint>(() => {\n const width = windowWidth.value\n \n if (width < breakpoints.xs) return 'xs'\n if (width < breakpoints.sm) return 'xs'\n if (width < breakpoints.md) return 'sm'\n if (width < breakpoints.lg) return 'md'\n if (width < breakpoints.xl) return 'lg'\n if (width < breakpoints['2xl']) return 'xl'\n return '2xl'\n })\n\n // 是否为移动设备\n const isMobile = computed(() => windowWidth.value < breakpoints.md)\n\n // 是否为平板设备\n const isTablet = computed(() => \n windowWidth.value >= breakpoints.md && windowWidth.value < breakpoints.lg\n )\n\n // 是否为桌面设备\n const isDesktop = computed(() => windowWidth.value >= breakpoints.lg)\n\n // 是否小于指定断点\n const isSmaller = (breakpoint: Breakpoint) => {\n return computed(() => windowWidth.value < breakpoints[breakpoint])\n }\n\n // 是否大于指定断点\n const isLarger = (breakpoint: Breakpoint) => {\n return computed(() => windowWidth.value >= breakpoints[breakpoint])\n }\n\n // 是否在指定断点范围内\n const isBetween = (min: Breakpoint, max: Breakpoint) => {\n return computed(() => \n windowWidth.value >= breakpoints[min] && windowWidth.value < breakpoints[max]\n )\n }\n\n // 根据断点返回不同的值\n const responsive = <T>(values: Partial<Record<Breakpoint, T>>, defaultValue: T): T => {\n const bp = currentBreakpoint.value\n \n // 从当前断点向下查找\n const breakpointOrder: Breakpoint[] = ['2xl', 'xl', 'lg', 'md', 'sm', 'xs']\n const currentIndex = breakpointOrder.indexOf(bp)\n \n for (let i = currentIndex; i < breakpointOrder.length; i++) {\n const key = breakpointOrder[i]\n if (values[key] !== undefined) {\n return values[key] as T\n }\n }\n \n return defaultValue\n }\n\n // 获取响应式列数\n const getResponsiveColumns = (config?: {\n xs?: number\n sm?: number\n md?: number\n lg?: number\n xl?: number\n '2xl'?: number\n }): number => {\n const defaultConfig = {\n xs: 1,\n sm: 1,\n md: 2,\n lg: 3,\n xl: 4,\n '2xl': 4\n }\n \n const finalConfig = { ...defaultConfig, ...config }\n return responsive(finalConfig, 1)\n }\n\n // 获取响应式间距\n const getResponsiveSpacing = (config?: {\n xs?: number\n sm?: number\n md?: number\n lg?: number\n }): number => {\n const defaultConfig = {\n xs: 8,\n sm: 12,\n md: 16,\n lg: 24\n }\n \n const finalConfig = { ...defaultConfig, ...config }\n return responsive(finalConfig, 16)\n }\n\n return {\n // 状态\n windowWidth,\n currentBreakpoint,\n \n // 计算属性\n isMobile,\n isTablet,\n isDesktop,\n \n // 方法\n isSmaller,\n isLarger,\n isBetween,\n responsive,\n getResponsiveColumns,\n getResponsiveSpacing,\n \n // 断点值\n breakpoints\n }\n}\n","<template>\n <div class=\"loading-container\">\n <div class=\"loading-spinner\"></div>\n <p v-if=\"message\" class=\"loading-message\">{{ message }}</p>\n </div>\n</template>\n\n<script setup lang=\"ts\">\ndefineProps<{\n message?: string\n}>()\n</script>\n\n<style scoped>\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n padding: var(--spacing-4xl, 40px);\n}\n\n.loading-spinner {\n width: 40px;\n height: 40px;\n border: 3px solid var(--border-secondary, #f0f2f5);\n border-top-color: var(--primary, #6366f1);\n border-radius: var(--radius-full, 9999px);\n animation: spin 0.8s linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.loading-message {\n margin-top: var(--spacing-lg, 16px);\n font-size: var(--font-size-sm, 12px);\n color: var(--text-secondary, #4e5969);\n}\n</style>\n","<template>\n <div class=\"empty-state\">\n <div class=\"empty-icon\">\n <slot name=\"icon\">\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M32 8C18.745 8 8 18.745 8 32s10.745 24 24 24 24-10.745 24-24S45.255 8 32 8zm0 44c-11.028 0-20-8.972-20-20s8.972-20 20-20 20 8.972 20 20-8.972 20-20 20z\" fill=\"currentColor\" opacity=\"0.3\"/>\n <path d=\"M32 24c-1.105 0-2 .895-2 2v12c0 1.105.895 2 2 2s2-.895 2-2V26c0-1.105-.895-2-2-2zm0 20c-1.105 0-2 .895-2 2s.895 2 2 2 2-.895 2-2-.895-2-2-2z\" fill=\"currentColor\"/>\n </svg>\n </slot>\n </div>\n <h3 class=\"empty-title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"empty-description\">{{ description }}</p>\n <div v-if=\"$slots.action\" class=\"empty-action\">\n <slot name=\"action\"></slot>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nwithDefaults(defineProps<{\n title?: string\n description?: string\n}>(), {\n title: '暂无数据'\n})\n</script>\n\n<style scoped>\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-5xl, 48px) var(--spacing-2xl, 24px);\n text-align: center;\n}\n\n.empty-icon {\n font-size: 48px;\n color: var(--text-quaternary, #c9cdd4);\n margin-bottom: var(--spacing-lg, 16px);\n}\n\n.empty-title {\n font-size: var(--font-size-lg, 16px);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-secondary, #4e5969);\n margin-bottom: var(--spacing-sm, 8px);\n}\n\n.empty-description {\n font-size: var(--font-size-sm, 12px);\n color: var(--text-tertiary, #86909c);\n max-width: 400px;\n margin: 0;\n}\n\n.empty-action {\n margin-top: var(--spacing-lg, 16px);\n}\n</style>\n","<template>\n <div class=\"error-container\">\n <div class=\"error-icon\">\n <slot name=\"icon\">\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M32 8C18.745 8 8 18.745 8 32s10.745 24 24 24 24-10.745 24-24S45.255 8 32 8zm0 44c-11.028 0-20-8.972-20-20s8.972-20 20-20 20 8.972 20 20-8.972 20-20 20z\" fill=\"currentColor\" opacity=\"0.3\"/>\n <path d=\"M38.828 22.172a2 2 0 00-2.828 0L32 26.172l-4-4a2 2 0 10-2.828 2.828L29.172 29l-4 4a2 2 0 102.828 2.828L32 31.828l4 4a2 2 0 102.828-2.828L34.828 29l4-4a2 2 0 000-2.828z\" fill=\"currentColor\"/>\n </svg>\n </slot>\n </div>\n <h3 class=\"error-title\">{{ title }}</h3>\n <p class=\"error-message\">{{ errorMessage }}</p>\n <div v-if=\"$slots.action || showRetry\" class=\"error-action\">\n <slot name=\"action\">\n <button v-if=\"showRetry\" @click=\"handleRetry\" class=\"error-retry-btn\">\n 重试\n </button>\n </slot>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst props = withDefaults(defineProps<{\n error?: Error | string\n title?: string\n showRetry?: boolean\n}>(), {\n title: '出错了',\n showRetry: false\n})\n\nconst emit = defineEmits<{\n retry: []\n}>()\n\nconst errorMessage = computed(() => {\n if (!props.error) return '发生了未知错误'\n if (typeof props.error === 'string') return props.error\n return props.error.message || '发生了未知错误'\n})\n\nconst handleRetry = () => {\n emit('retry')\n}\n</script>\n\n<style scoped>\n.error-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-4xl, 40px) var(--spacing-2xl, 24px);\n text-align: center;\n}\n\n.error-icon {\n font-size: 48px;\n color: var(--error, #ef4444);\n margin-bottom: var(--spacing-lg, 16px);\n}\n\n.error-title {\n font-size: var(--font-size-lg, 16px);\n font-weight: var(--font-weight-semibold, 600);\n color: var(--error, #ef4444);\n margin-bottom: var(--spacing-sm, 8px);\n}\n\n.error-message {\n font-size: var(--font-size-sm, 12px);\n color: var(--text-secondary, #4e5969);\n max-width: 500px;\n margin: 0 0 var(--spacing-lg, 16px);\n}\n\n.error-action {\n margin-top: var(--spacing-lg, 16px);\n}\n\n.error-retry-btn {\n padding: var(--spacing-sm, 8px) var(--spacing-lg, 16px);\n font-size: var(--font-size-sm, 12px);\n color: white;\n background-color: var(--primary, #6366f1);\n border: none;\n border-radius: var(--radius-md, 8px);\n cursor: pointer;\n transition: opacity var(--transition-fast, 0.15s) ease;\n}\n\n.error-retry-btn:hover {\n opacity: 0.8;\n}\n\n.error-retry-btn:active {\n opacity: 0.6;\n}\n</style>\n"],"names":["ref","computed","options","onMounted","onUnmounted","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_toDisplayString","_renderSlot","_hoisted_3","_hoisted_4","$slots","_hoisted_5"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @gress/plugin-ui
3
+ *
4
+ * Gress 插件 UI 组件和样式包
5
+ * 提供统一的设计系统和通用组件
6
+ */
7
+ export { useLoadingState } from './composables/useLoadingState';
8
+ export type { LoadingState, UseLoadingStateOptions } from './composables/useLoadingState';
9
+ export { useResponsive, breakpoints } from './composables/useResponsive';
10
+ export type { UseResponsiveReturn, Breakpoint, BreakpointValues } from './composables/useResponsive';
11
+ export { default as LoadingStateComponent } from './components/LoadingState.vue';
12
+ export { default as EmptyState } from './components/EmptyState.vue';
13
+ export { default as ErrorState } from './components/ErrorState.vue';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,YAAY,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAEzF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AACxE,YAAY,EAAE,mBAAmB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAKpG,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AAChF,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,6BAA6B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,327 @@
1
+ import { ref, computed, onMounted, onUnmounted, defineComponent, openBlock, createElementBlock, createElementVNode, toDisplayString, createCommentVNode, renderSlot } from "vue";
2
+ function useLoadingState(options = {}) {
3
+ const { initialLoading = false, onError } = options;
4
+ const loading = ref(initialLoading);
5
+ const error = ref(null);
6
+ const isEmpty = ref(false);
7
+ const isLoading = computed(() => loading.value);
8
+ const hasError = computed(() => error.value !== null);
9
+ const isEmptyState = computed(() => isEmpty.value && !loading.value && !error.value);
10
+ const startLoading = () => {
11
+ loading.value = true;
12
+ error.value = null;
13
+ isEmpty.value = false;
14
+ };
15
+ const stopLoading = () => {
16
+ loading.value = false;
17
+ };
18
+ const setError = (err) => {
19
+ loading.value = false;
20
+ error.value = err instanceof Error ? err : new Error(err);
21
+ if (onError && error.value) {
22
+ onError(error.value);
23
+ }
24
+ };
25
+ const clearError = () => {
26
+ error.value = null;
27
+ };
28
+ const setEmpty = (empty) => {
29
+ isEmpty.value = empty;
30
+ };
31
+ const reset = () => {
32
+ loading.value = false;
33
+ error.value = null;
34
+ isEmpty.value = false;
35
+ };
36
+ const execute = async (fn, options2) => {
37
+ try {
38
+ startLoading();
39
+ const result = await fn();
40
+ if (options2 == null ? void 0 : options2.checkEmpty) {
41
+ isEmpty.value = options2.checkEmpty(result);
42
+ }
43
+ if (options2 == null ? void 0 : options2.onSuccess) {
44
+ options2.onSuccess(result);
45
+ }
46
+ return result;
47
+ } catch (err) {
48
+ const errorObj = err instanceof Error ? err : new Error(String(err));
49
+ setError(errorObj);
50
+ if (options2 == null ? void 0 : options2.onError) {
51
+ options2.onError(errorObj);
52
+ }
53
+ return null;
54
+ } finally {
55
+ stopLoading();
56
+ }
57
+ };
58
+ return {
59
+ // 状态
60
+ loading,
61
+ error,
62
+ isEmpty,
63
+ // 计算属性
64
+ isLoading,
65
+ hasError,
66
+ isEmptyState,
67
+ // 方法
68
+ startLoading,
69
+ stopLoading,
70
+ setError,
71
+ clearError,
72
+ setEmpty,
73
+ reset,
74
+ execute
75
+ };
76
+ }
77
+ const breakpoints = {
78
+ xs: 480,
79
+ sm: 640,
80
+ md: 768,
81
+ lg: 1024,
82
+ xl: 1280,
83
+ "2xl": 1536
84
+ };
85
+ function useResponsive() {
86
+ const windowWidth = ref(typeof window !== "undefined" ? window.innerWidth : 1024);
87
+ const updateWidth = () => {
88
+ windowWidth.value = window.innerWidth;
89
+ };
90
+ onMounted(() => {
91
+ window.addEventListener("resize", updateWidth);
92
+ });
93
+ onUnmounted(() => {
94
+ window.removeEventListener("resize", updateWidth);
95
+ });
96
+ const currentBreakpoint = computed(() => {
97
+ const width = windowWidth.value;
98
+ if (width < breakpoints.xs) return "xs";
99
+ if (width < breakpoints.sm) return "xs";
100
+ if (width < breakpoints.md) return "sm";
101
+ if (width < breakpoints.lg) return "md";
102
+ if (width < breakpoints.xl) return "lg";
103
+ if (width < breakpoints["2xl"]) return "xl";
104
+ return "2xl";
105
+ });
106
+ const isMobile = computed(() => windowWidth.value < breakpoints.md);
107
+ const isTablet = computed(
108
+ () => windowWidth.value >= breakpoints.md && windowWidth.value < breakpoints.lg
109
+ );
110
+ const isDesktop = computed(() => windowWidth.value >= breakpoints.lg);
111
+ const isSmaller = (breakpoint) => {
112
+ return computed(() => windowWidth.value < breakpoints[breakpoint]);
113
+ };
114
+ const isLarger = (breakpoint) => {
115
+ return computed(() => windowWidth.value >= breakpoints[breakpoint]);
116
+ };
117
+ const isBetween = (min, max) => {
118
+ return computed(
119
+ () => windowWidth.value >= breakpoints[min] && windowWidth.value < breakpoints[max]
120
+ );
121
+ };
122
+ const responsive = (values, defaultValue) => {
123
+ const bp = currentBreakpoint.value;
124
+ const breakpointOrder = ["2xl", "xl", "lg", "md", "sm", "xs"];
125
+ const currentIndex = breakpointOrder.indexOf(bp);
126
+ for (let i = currentIndex; i < breakpointOrder.length; i++) {
127
+ const key = breakpointOrder[i];
128
+ if (values[key] !== void 0) {
129
+ return values[key];
130
+ }
131
+ }
132
+ return defaultValue;
133
+ };
134
+ const getResponsiveColumns = (config) => {
135
+ const defaultConfig = {
136
+ xs: 1,
137
+ sm: 1,
138
+ md: 2,
139
+ lg: 3,
140
+ xl: 4,
141
+ "2xl": 4
142
+ };
143
+ const finalConfig = { ...defaultConfig, ...config };
144
+ return responsive(finalConfig, 1);
145
+ };
146
+ const getResponsiveSpacing = (config) => {
147
+ const defaultConfig = {
148
+ xs: 8,
149
+ sm: 12,
150
+ md: 16,
151
+ lg: 24
152
+ };
153
+ const finalConfig = { ...defaultConfig, ...config };
154
+ return responsive(finalConfig, 16);
155
+ };
156
+ return {
157
+ // 状态
158
+ windowWidth,
159
+ currentBreakpoint,
160
+ // 计算属性
161
+ isMobile,
162
+ isTablet,
163
+ isDesktop,
164
+ // 方法
165
+ isSmaller,
166
+ isLarger,
167
+ isBetween,
168
+ responsive,
169
+ getResponsiveColumns,
170
+ getResponsiveSpacing,
171
+ // 断点值
172
+ breakpoints
173
+ };
174
+ }
175
+ const _hoisted_1$2 = { class: "loading-container" };
176
+ const _hoisted_2$2 = {
177
+ key: 0,
178
+ class: "loading-message"
179
+ };
180
+ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
181
+ __name: "LoadingState",
182
+ props: {
183
+ message: {}
184
+ },
185
+ setup(__props) {
186
+ return (_ctx, _cache) => {
187
+ return openBlock(), createElementBlock("div", _hoisted_1$2, [
188
+ _cache[0] || (_cache[0] = createElementVNode("div", { class: "loading-spinner" }, null, -1)),
189
+ __props.message ? (openBlock(), createElementBlock("p", _hoisted_2$2, toDisplayString(__props.message), 1)) : createCommentVNode("", true)
190
+ ]);
191
+ };
192
+ }
193
+ });
194
+ const _export_sfc = (sfc, props) => {
195
+ const target = sfc.__vccOpts || sfc;
196
+ for (const [key, val] of props) {
197
+ target[key] = val;
198
+ }
199
+ return target;
200
+ };
201
+ const LoadingState = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-88be2f1a"]]);
202
+ const _hoisted_1$1 = { class: "empty-state" };
203
+ const _hoisted_2$1 = { class: "empty-icon" };
204
+ const _hoisted_3$1 = { class: "empty-title" };
205
+ const _hoisted_4$1 = {
206
+ key: 0,
207
+ class: "empty-description"
208
+ };
209
+ const _hoisted_5$1 = {
210
+ key: 1,
211
+ class: "empty-action"
212
+ };
213
+ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
214
+ __name: "EmptyState",
215
+ props: {
216
+ title: { default: "暂无数据" },
217
+ description: {}
218
+ },
219
+ setup(__props) {
220
+ return (_ctx, _cache) => {
221
+ return openBlock(), createElementBlock("div", _hoisted_1$1, [
222
+ createElementVNode("div", _hoisted_2$1, [
223
+ renderSlot(_ctx.$slots, "icon", {}, () => [
224
+ _cache[0] || (_cache[0] = createElementVNode("svg", {
225
+ width: "64",
226
+ height: "64",
227
+ viewBox: "0 0 64 64",
228
+ fill: "none",
229
+ xmlns: "http://www.w3.org/2000/svg"
230
+ }, [
231
+ createElementVNode("path", {
232
+ d: "M32 8C18.745 8 8 18.745 8 32s10.745 24 24 24 24-10.745 24-24S45.255 8 32 8zm0 44c-11.028 0-20-8.972-20-20s8.972-20 20-20 20 8.972 20 20-8.972 20-20 20z",
233
+ fill: "currentColor",
234
+ opacity: "0.3"
235
+ }),
236
+ createElementVNode("path", {
237
+ d: "M32 24c-1.105 0-2 .895-2 2v12c0 1.105.895 2 2 2s2-.895 2-2V26c0-1.105-.895-2-2-2zm0 20c-1.105 0-2 .895-2 2s.895 2 2 2 2-.895 2-2-.895-2-2-2z",
238
+ fill: "currentColor"
239
+ })
240
+ ], -1))
241
+ ], true)
242
+ ]),
243
+ createElementVNode("h3", _hoisted_3$1, toDisplayString(__props.title), 1),
244
+ __props.description ? (openBlock(), createElementBlock("p", _hoisted_4$1, toDisplayString(__props.description), 1)) : createCommentVNode("", true),
245
+ _ctx.$slots.action ? (openBlock(), createElementBlock("div", _hoisted_5$1, [
246
+ renderSlot(_ctx.$slots, "action", {}, void 0, true)
247
+ ])) : createCommentVNode("", true)
248
+ ]);
249
+ };
250
+ }
251
+ });
252
+ const EmptyState = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-12ee016f"]]);
253
+ const _hoisted_1 = { class: "error-container" };
254
+ const _hoisted_2 = { class: "error-icon" };
255
+ const _hoisted_3 = { class: "error-title" };
256
+ const _hoisted_4 = { class: "error-message" };
257
+ const _hoisted_5 = {
258
+ key: 0,
259
+ class: "error-action"
260
+ };
261
+ const _sfc_main = /* @__PURE__ */ defineComponent({
262
+ __name: "ErrorState",
263
+ props: {
264
+ error: {},
265
+ title: { default: "出错了" },
266
+ showRetry: { type: Boolean, default: false }
267
+ },
268
+ emits: ["retry"],
269
+ setup(__props, { emit: __emit }) {
270
+ const props = __props;
271
+ const emit = __emit;
272
+ const errorMessage = computed(() => {
273
+ if (!props.error) return "发生了未知错误";
274
+ if (typeof props.error === "string") return props.error;
275
+ return props.error.message || "发生了未知错误";
276
+ });
277
+ const handleRetry = () => {
278
+ emit("retry");
279
+ };
280
+ return (_ctx, _cache) => {
281
+ return openBlock(), createElementBlock("div", _hoisted_1, [
282
+ createElementVNode("div", _hoisted_2, [
283
+ renderSlot(_ctx.$slots, "icon", {}, () => [
284
+ _cache[0] || (_cache[0] = createElementVNode("svg", {
285
+ width: "64",
286
+ height: "64",
287
+ viewBox: "0 0 64 64",
288
+ fill: "none",
289
+ xmlns: "http://www.w3.org/2000/svg"
290
+ }, [
291
+ createElementVNode("path", {
292
+ d: "M32 8C18.745 8 8 18.745 8 32s10.745 24 24 24 24-10.745 24-24S45.255 8 32 8zm0 44c-11.028 0-20-8.972-20-20s8.972-20 20-20 20 8.972 20 20-8.972 20-20 20z",
293
+ fill: "currentColor",
294
+ opacity: "0.3"
295
+ }),
296
+ createElementVNode("path", {
297
+ d: "M38.828 22.172a2 2 0 00-2.828 0L32 26.172l-4-4a2 2 0 10-2.828 2.828L29.172 29l-4 4a2 2 0 102.828 2.828L32 31.828l4 4a2 2 0 102.828-2.828L34.828 29l4-4a2 2 0 000-2.828z",
298
+ fill: "currentColor"
299
+ })
300
+ ], -1))
301
+ ], true)
302
+ ]),
303
+ createElementVNode("h3", _hoisted_3, toDisplayString(__props.title), 1),
304
+ createElementVNode("p", _hoisted_4, toDisplayString(errorMessage.value), 1),
305
+ _ctx.$slots.action || __props.showRetry ? (openBlock(), createElementBlock("div", _hoisted_5, [
306
+ renderSlot(_ctx.$slots, "action", {}, () => [
307
+ __props.showRetry ? (openBlock(), createElementBlock("button", {
308
+ key: 0,
309
+ onClick: handleRetry,
310
+ class: "error-retry-btn"
311
+ }, " 重试 ")) : createCommentVNode("", true)
312
+ ], true)
313
+ ])) : createCommentVNode("", true)
314
+ ]);
315
+ };
316
+ }
317
+ });
318
+ const ErrorState = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9bbae138"]]);
319
+ export {
320
+ EmptyState,
321
+ ErrorState,
322
+ LoadingState as LoadingStateComponent,
323
+ breakpoints,
324
+ useLoadingState,
325
+ useResponsive
326
+ };
327
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/composables/useLoadingState.ts","../src/composables/useResponsive.ts","../src/components/LoadingState.vue","../src/components/EmptyState.vue","../src/components/ErrorState.vue"],"sourcesContent":["/**\n * 加载状态管理 Composable\n * 统一管理加载、错误和空状态\n */\n\nimport { ref, computed } from 'vue'\n\nexport interface LoadingState {\n loading: boolean\n error: Error | null\n isEmpty: boolean\n}\n\nexport interface UseLoadingStateOptions {\n initialLoading?: boolean\n onError?: (error: Error) => void\n}\n\nexport function useLoadingState(options: UseLoadingStateOptions = {}) {\n const { initialLoading = false, onError } = options\n\n const loading = ref(initialLoading)\n const error = ref<Error | null>(null)\n const isEmpty = ref(false)\n\n const isLoading = computed(() => loading.value)\n const hasError = computed(() => error.value !== null)\n const isEmptyState = computed(() => isEmpty.value && !loading.value && !error.value)\n\n /**\n * 开始加载\n */\n const startLoading = () => {\n loading.value = true\n error.value = null\n isEmpty.value = false\n }\n\n /**\n * 停止加载\n */\n const stopLoading = () => {\n loading.value = false\n }\n\n /**\n * 设置错误\n */\n const setError = (err: Error | string) => {\n loading.value = false\n error.value = err instanceof Error ? err : new Error(err)\n \n if (onError && error.value) {\n onError(error.value)\n }\n }\n\n /**\n * 清除错误\n */\n const clearError = () => {\n error.value = null\n }\n\n /**\n * 设置空状态\n */\n const setEmpty = (empty: boolean) => {\n isEmpty.value = empty\n }\n\n /**\n * 重置所有状态\n */\n const reset = () => {\n loading.value = false\n error.value = null\n isEmpty.value = false\n }\n\n /**\n * 执行异步操作并自动管理状态\n */\n const execute = async <T>(\n fn: () => Promise<T>,\n options?: {\n checkEmpty?: (result: T) => boolean\n onSuccess?: (result: T) => void\n onError?: (error: Error) => void\n }\n ): Promise<T | null> => {\n try {\n startLoading()\n const result = await fn()\n \n // 检查是否为空\n if (options?.checkEmpty) {\n isEmpty.value = options.checkEmpty(result)\n }\n \n // 成功回调\n if (options?.onSuccess) {\n options.onSuccess(result)\n }\n \n return result\n } catch (err) {\n const errorObj = err instanceof Error ? err : new Error(String(err))\n setError(errorObj)\n \n // 错误回调\n if (options?.onError) {\n options.onError(errorObj)\n }\n \n return null\n } finally {\n stopLoading()\n }\n }\n\n return {\n // 状态\n loading,\n error,\n isEmpty,\n \n // 计算属性\n isLoading,\n hasError,\n isEmptyState,\n \n // 方法\n startLoading,\n stopLoading,\n setError,\n clearError,\n setEmpty,\n reset,\n execute\n }\n}\n","/**\n * 响应式设计 Composable\n * 提供断点检测和响应式工具\n */\n\nimport { ref, computed, onMounted, onUnmounted, type ComputedRef, type Ref } from 'vue'\n\nexport type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'\n\nexport interface BreakpointValues {\n xs: number\n sm: number\n md: number\n lg: number\n xl: number\n '2xl': number\n}\n\nexport const breakpoints: BreakpointValues = {\n xs: 480,\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n '2xl': 1536\n}\n\nexport interface UseResponsiveReturn {\n // 状态\n windowWidth: Ref<number>\n currentBreakpoint: ComputedRef<Breakpoint>\n\n // 计算属性\n isMobile: ComputedRef<boolean>\n isTablet: ComputedRef<boolean>\n isDesktop: ComputedRef<boolean>\n\n // 方法\n isSmaller: (breakpoint: Breakpoint) => ComputedRef<boolean>\n isLarger: (breakpoint: Breakpoint) => ComputedRef<boolean>\n isBetween: (min: Breakpoint, max: Breakpoint) => ComputedRef<boolean>\n responsive: <T>(values: Partial<Record<Breakpoint, T>>, defaultValue: T) => T\n getResponsiveColumns: (config?: {\n xs?: number\n sm?: number\n md?: number\n lg?: number\n xl?: number\n '2xl'?: number\n }) => number\n getResponsiveSpacing: (config?: {\n xs?: number\n sm?: number\n md?: number\n lg?: number\n }) => number\n\n // 断点值\n breakpoints: BreakpointValues\n}\n\nexport function useResponsive(): UseResponsiveReturn {\n const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1024)\n\n const updateWidth = () => {\n windowWidth.value = window.innerWidth\n }\n\n onMounted(() => {\n window.addEventListener('resize', updateWidth)\n })\n\n onUnmounted(() => {\n window.removeEventListener('resize', updateWidth)\n })\n\n // 当前断点\n const currentBreakpoint = computed<Breakpoint>(() => {\n const width = windowWidth.value\n \n if (width < breakpoints.xs) return 'xs'\n if (width < breakpoints.sm) return 'xs'\n if (width < breakpoints.md) return 'sm'\n if (width < breakpoints.lg) return 'md'\n if (width < breakpoints.xl) return 'lg'\n if (width < breakpoints['2xl']) return 'xl'\n return '2xl'\n })\n\n // 是否为移动设备\n const isMobile = computed(() => windowWidth.value < breakpoints.md)\n\n // 是否为平板设备\n const isTablet = computed(() => \n windowWidth.value >= breakpoints.md && windowWidth.value < breakpoints.lg\n )\n\n // 是否为桌面设备\n const isDesktop = computed(() => windowWidth.value >= breakpoints.lg)\n\n // 是否小于指定断点\n const isSmaller = (breakpoint: Breakpoint) => {\n return computed(() => windowWidth.value < breakpoints[breakpoint])\n }\n\n // 是否大于指定断点\n const isLarger = (breakpoint: Breakpoint) => {\n return computed(() => windowWidth.value >= breakpoints[breakpoint])\n }\n\n // 是否在指定断点范围内\n const isBetween = (min: Breakpoint, max: Breakpoint) => {\n return computed(() => \n windowWidth.value >= breakpoints[min] && windowWidth.value < breakpoints[max]\n )\n }\n\n // 根据断点返回不同的值\n const responsive = <T>(values: Partial<Record<Breakpoint, T>>, defaultValue: T): T => {\n const bp = currentBreakpoint.value\n \n // 从当前断点向下查找\n const breakpointOrder: Breakpoint[] = ['2xl', 'xl', 'lg', 'md', 'sm', 'xs']\n const currentIndex = breakpointOrder.indexOf(bp)\n \n for (let i = currentIndex; i < breakpointOrder.length; i++) {\n const key = breakpointOrder[i]\n if (values[key] !== undefined) {\n return values[key] as T\n }\n }\n \n return defaultValue\n }\n\n // 获取响应式列数\n const getResponsiveColumns = (config?: {\n xs?: number\n sm?: number\n md?: number\n lg?: number\n xl?: number\n '2xl'?: number\n }): number => {\n const defaultConfig = {\n xs: 1,\n sm: 1,\n md: 2,\n lg: 3,\n xl: 4,\n '2xl': 4\n }\n \n const finalConfig = { ...defaultConfig, ...config }\n return responsive(finalConfig, 1)\n }\n\n // 获取响应式间距\n const getResponsiveSpacing = (config?: {\n xs?: number\n sm?: number\n md?: number\n lg?: number\n }): number => {\n const defaultConfig = {\n xs: 8,\n sm: 12,\n md: 16,\n lg: 24\n }\n \n const finalConfig = { ...defaultConfig, ...config }\n return responsive(finalConfig, 16)\n }\n\n return {\n // 状态\n windowWidth,\n currentBreakpoint,\n \n // 计算属性\n isMobile,\n isTablet,\n isDesktop,\n \n // 方法\n isSmaller,\n isLarger,\n isBetween,\n responsive,\n getResponsiveColumns,\n getResponsiveSpacing,\n \n // 断点值\n breakpoints\n }\n}\n","<template>\n <div class=\"loading-container\">\n <div class=\"loading-spinner\"></div>\n <p v-if=\"message\" class=\"loading-message\">{{ message }}</p>\n </div>\n</template>\n\n<script setup lang=\"ts\">\ndefineProps<{\n message?: string\n}>()\n</script>\n\n<style scoped>\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n padding: var(--spacing-4xl, 40px);\n}\n\n.loading-spinner {\n width: 40px;\n height: 40px;\n border: 3px solid var(--border-secondary, #f0f2f5);\n border-top-color: var(--primary, #6366f1);\n border-radius: var(--radius-full, 9999px);\n animation: spin 0.8s linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.loading-message {\n margin-top: var(--spacing-lg, 16px);\n font-size: var(--font-size-sm, 12px);\n color: var(--text-secondary, #4e5969);\n}\n</style>\n","<template>\n <div class=\"empty-state\">\n <div class=\"empty-icon\">\n <slot name=\"icon\">\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M32 8C18.745 8 8 18.745 8 32s10.745 24 24 24 24-10.745 24-24S45.255 8 32 8zm0 44c-11.028 0-20-8.972-20-20s8.972-20 20-20 20 8.972 20 20-8.972 20-20 20z\" fill=\"currentColor\" opacity=\"0.3\"/>\n <path d=\"M32 24c-1.105 0-2 .895-2 2v12c0 1.105.895 2 2 2s2-.895 2-2V26c0-1.105-.895-2-2-2zm0 20c-1.105 0-2 .895-2 2s.895 2 2 2 2-.895 2-2-.895-2-2-2z\" fill=\"currentColor\"/>\n </svg>\n </slot>\n </div>\n <h3 class=\"empty-title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"empty-description\">{{ description }}</p>\n <div v-if=\"$slots.action\" class=\"empty-action\">\n <slot name=\"action\"></slot>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nwithDefaults(defineProps<{\n title?: string\n description?: string\n}>(), {\n title: '暂无数据'\n})\n</script>\n\n<style scoped>\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-5xl, 48px) var(--spacing-2xl, 24px);\n text-align: center;\n}\n\n.empty-icon {\n font-size: 48px;\n color: var(--text-quaternary, #c9cdd4);\n margin-bottom: var(--spacing-lg, 16px);\n}\n\n.empty-title {\n font-size: var(--font-size-lg, 16px);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-secondary, #4e5969);\n margin-bottom: var(--spacing-sm, 8px);\n}\n\n.empty-description {\n font-size: var(--font-size-sm, 12px);\n color: var(--text-tertiary, #86909c);\n max-width: 400px;\n margin: 0;\n}\n\n.empty-action {\n margin-top: var(--spacing-lg, 16px);\n}\n</style>\n","<template>\n <div class=\"error-container\">\n <div class=\"error-icon\">\n <slot name=\"icon\">\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M32 8C18.745 8 8 18.745 8 32s10.745 24 24 24 24-10.745 24-24S45.255 8 32 8zm0 44c-11.028 0-20-8.972-20-20s8.972-20 20-20 20 8.972 20 20-8.972 20-20 20z\" fill=\"currentColor\" opacity=\"0.3\"/>\n <path d=\"M38.828 22.172a2 2 0 00-2.828 0L32 26.172l-4-4a2 2 0 10-2.828 2.828L29.172 29l-4 4a2 2 0 102.828 2.828L32 31.828l4 4a2 2 0 102.828-2.828L34.828 29l4-4a2 2 0 000-2.828z\" fill=\"currentColor\"/>\n </svg>\n </slot>\n </div>\n <h3 class=\"error-title\">{{ title }}</h3>\n <p class=\"error-message\">{{ errorMessage }}</p>\n <div v-if=\"$slots.action || showRetry\" class=\"error-action\">\n <slot name=\"action\">\n <button v-if=\"showRetry\" @click=\"handleRetry\" class=\"error-retry-btn\">\n 重试\n </button>\n </slot>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst props = withDefaults(defineProps<{\n error?: Error | string\n title?: string\n showRetry?: boolean\n}>(), {\n title: '出错了',\n showRetry: false\n})\n\nconst emit = defineEmits<{\n retry: []\n}>()\n\nconst errorMessage = computed(() => {\n if (!props.error) return '发生了未知错误'\n if (typeof props.error === 'string') return props.error\n return props.error.message || '发生了未知错误'\n})\n\nconst handleRetry = () => {\n emit('retry')\n}\n</script>\n\n<style scoped>\n.error-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-4xl, 40px) var(--spacing-2xl, 24px);\n text-align: center;\n}\n\n.error-icon {\n font-size: 48px;\n color: var(--error, #ef4444);\n margin-bottom: var(--spacing-lg, 16px);\n}\n\n.error-title {\n font-size: var(--font-size-lg, 16px);\n font-weight: var(--font-weight-semibold, 600);\n color: var(--error, #ef4444);\n margin-bottom: var(--spacing-sm, 8px);\n}\n\n.error-message {\n font-size: var(--font-size-sm, 12px);\n color: var(--text-secondary, #4e5969);\n max-width: 500px;\n margin: 0 0 var(--spacing-lg, 16px);\n}\n\n.error-action {\n margin-top: var(--spacing-lg, 16px);\n}\n\n.error-retry-btn {\n padding: var(--spacing-sm, 8px) var(--spacing-lg, 16px);\n font-size: var(--font-size-sm, 12px);\n color: white;\n background-color: var(--primary, #6366f1);\n border: none;\n border-radius: var(--radius-md, 8px);\n cursor: pointer;\n transition: opacity var(--transition-fast, 0.15s) ease;\n}\n\n.error-retry-btn:hover {\n opacity: 0.8;\n}\n\n.error-retry-btn:active {\n opacity: 0.6;\n}\n</style>\n"],"names":["options","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_toDisplayString","_renderSlot","_hoisted_3","_hoisted_4","$slots","_hoisted_5"],"mappings":"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"}
package/dist/style.css ADDED
@@ -0,0 +1,101 @@
1
+
2
+ .loading-container[data-v-88be2f1a] {
3
+ display: flex;
4
+ flex-direction: column;
5
+ align-items: center;
6
+ justify-content: center;
7
+ min-height: 200px;
8
+ padding: var(--spacing-4xl, 40px);
9
+ }
10
+ .loading-spinner[data-v-88be2f1a] {
11
+ width: 40px;
12
+ height: 40px;
13
+ border: 3px solid var(--border-secondary, #f0f2f5);
14
+ border-top-color: var(--primary, #6366f1);
15
+ border-radius: var(--radius-full, 9999px);
16
+ animation: spin-88be2f1a 0.8s linear infinite;
17
+ }
18
+ @keyframes spin-88be2f1a {
19
+ to {
20
+ transform: rotate(360deg);
21
+ }
22
+ }
23
+ .loading-message[data-v-88be2f1a] {
24
+ margin-top: var(--spacing-lg, 16px);
25
+ font-size: var(--font-size-sm, 12px);
26
+ color: var(--text-secondary, #4e5969);
27
+ }
28
+
29
+ .empty-state[data-v-12ee016f] {
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ justify-content: center;
34
+ padding: var(--spacing-5xl, 48px) var(--spacing-2xl, 24px);
35
+ text-align: center;
36
+ }
37
+ .empty-icon[data-v-12ee016f] {
38
+ font-size: 48px;
39
+ color: var(--text-quaternary, #c9cdd4);
40
+ margin-bottom: var(--spacing-lg, 16px);
41
+ }
42
+ .empty-title[data-v-12ee016f] {
43
+ font-size: var(--font-size-lg, 16px);
44
+ font-weight: var(--font-weight-medium, 500);
45
+ color: var(--text-secondary, #4e5969);
46
+ margin-bottom: var(--spacing-sm, 8px);
47
+ }
48
+ .empty-description[data-v-12ee016f] {
49
+ font-size: var(--font-size-sm, 12px);
50
+ color: var(--text-tertiary, #86909c);
51
+ max-width: 400px;
52
+ margin: 0;
53
+ }
54
+ .empty-action[data-v-12ee016f] {
55
+ margin-top: var(--spacing-lg, 16px);
56
+ }
57
+
58
+ .error-container[data-v-9bbae138] {
59
+ display: flex;
60
+ flex-direction: column;
61
+ align-items: center;
62
+ justify-content: center;
63
+ padding: var(--spacing-4xl, 40px) var(--spacing-2xl, 24px);
64
+ text-align: center;
65
+ }
66
+ .error-icon[data-v-9bbae138] {
67
+ font-size: 48px;
68
+ color: var(--error, #ef4444);
69
+ margin-bottom: var(--spacing-lg, 16px);
70
+ }
71
+ .error-title[data-v-9bbae138] {
72
+ font-size: var(--font-size-lg, 16px);
73
+ font-weight: var(--font-weight-semibold, 600);
74
+ color: var(--error, #ef4444);
75
+ margin-bottom: var(--spacing-sm, 8px);
76
+ }
77
+ .error-message[data-v-9bbae138] {
78
+ font-size: var(--font-size-sm, 12px);
79
+ color: var(--text-secondary, #4e5969);
80
+ max-width: 500px;
81
+ margin: 0 0 var(--spacing-lg, 16px);
82
+ }
83
+ .error-action[data-v-9bbae138] {
84
+ margin-top: var(--spacing-lg, 16px);
85
+ }
86
+ .error-retry-btn[data-v-9bbae138] {
87
+ padding: var(--spacing-sm, 8px) var(--spacing-lg, 16px);
88
+ font-size: var(--font-size-sm, 12px);
89
+ color: white;
90
+ background-color: var(--primary, #6366f1);
91
+ border: none;
92
+ border-radius: var(--radius-md, 8px);
93
+ cursor: pointer;
94
+ transition: opacity var(--transition-fast, 0.15s) ease;
95
+ }
96
+ .error-retry-btn[data-v-9bbae138]:hover {
97
+ opacity: 0.8;
98
+ }
99
+ .error-retry-btn[data-v-9bbae138]:active {
100
+ opacity: 0.6;
101
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@keqi.gress/plugin-ui",
3
+ "version": "1.0.0",
4
+ "description": "Gress 插件 UI 组件和样式包 - 提供统一的设计系统和通用组件",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./style.css": "./dist/style.css"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "dev": "vite build --watch",
23
+ "build": "vite build && tsc --emitDeclarationOnly",
24
+ "type-check": "tsc --noEmit",
25
+ "clean": "rm -rf dist"
26
+ },
27
+ "keywords": [
28
+ "gress",
29
+ "plugin",
30
+ "ui",
31
+ "components",
32
+ "design-system"
33
+ ],
34
+ "author": "Gress Team",
35
+ "license": "MIT",
36
+ "peerDependencies": {
37
+ "@keqi.gress/plugin-bridge": "file:../plugin-bridge",
38
+ "vue": "^3.3.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.10.0",
42
+ "@vitejs/plugin-vue": "^5.0.0",
43
+ "sass": "^1.69.0",
44
+ "typescript": "^5.3.0",
45
+ "vite": "^5.0.0",
46
+ "vite-plugin-dts": "^3.7.0",
47
+ "vue": "^3.3.0"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/your-org/gress.git",
52
+ "directory": "packages/plugin-ui"
53
+ }
54
+ }