jcjy-components 0.0.68 → 0.0.69

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.
@@ -4,10 +4,10 @@
4
4
  fullscreen
5
5
  :show-close="false"
6
6
  body-class="!p-0"
7
- header-class="hidden"
7
+ header-class="!hidden"
8
8
  class="!p-0"
9
9
  >
10
- <header class="h-100 relative">
10
+ <header class="h-[400px] relative">
11
11
  <img
12
12
  src="./img/bind-bg.jpg"
13
13
  class="w-full h-full"
@@ -17,7 +17,7 @@
17
17
  class="absolute top-0 left-0 right-0 bottom-0 bg-black/40 w-full h-full"
18
18
  ></div>
19
19
  </header>
20
- <div class="text-center -mt-100 relative z-1 pt-20">
20
+ <div class="text-center -mt-[400px] relative z-1 pt-20">
21
21
  <div class="text-white font-bold text-2xl">验证身份信息</div>
22
22
  <div class="font-400 text-lg text-white my-2">
23
23
  我们需要验证你的身份信息,以便为你提供更好的服务
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <FloatingBall>
3
+ <template #ball>
4
+ <slot />
5
+ </template>
6
+
7
+ <div class="h-[58vh] w-[700px]">
8
+ <FileManager v-bind="$attrs" />
9
+ </div>
10
+ </FloatingBall>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import FloatingBall from "../floating-ball/index.vue";
15
+ import FileManager from "./index.vue";
16
+ </script>
@@ -1,3 +1,149 @@
1
1
  <template>
2
- <div>file-manager</div>
2
+ <div class="rounded overflow-hidden flex flex-col h-full py-2">
3
+ <header class="border border-b-0 border-gray-100 shrink-0 py-4 px-4">
4
+ <el-breadcrumb separator="/" class="mb-2 text-sm">
5
+ <el-breadcrumb-item>文件</el-breadcrumb-item>
6
+ <el-breadcrumb-item>文件夹</el-breadcrumb-item>
7
+ </el-breadcrumb>
8
+ <div class="flex items-center justify-between">
9
+ <div class="flex-1">
10
+ <el-progress
11
+ text-inside
12
+ :stroke-width="20"
13
+ :percentage="10"
14
+ :color="sizeColors"
15
+ class="!w-4/5"
16
+ >
17
+ <template #default="{ percentage }">
18
+ <span class="text-sm text-black/50"> {{ percentage }}/100 </span>
19
+ </template>
20
+ </el-progress>
21
+ </div>
22
+
23
+ <el-space>
24
+ <el-input
25
+ placeholder="请输入关键词~~"
26
+ v-model="localSearch"
27
+ :model-modifiers="{
28
+ trim: true,
29
+ }"
30
+ @keydown.enter="getData"
31
+ class="!w-52"
32
+ clearable
33
+ />
34
+ <el-button-group type="primary">
35
+ <el-button> 新建文件夹 </el-button>
36
+ <el-button> 上传 </el-button>
37
+ </el-button-group>
38
+ </el-space>
39
+ </div>
40
+ </header>
41
+ <main
42
+ class="flex-1 overflow-hidden"
43
+ v-loading="loading"
44
+ element-loading-text="正在加载中..."
45
+ >
46
+ <el-table
47
+ border
48
+ :data="list"
49
+ height="100%"
50
+ @row-dblclick="handleRowDblclick"
51
+ >
52
+ <el-table-column type="index" width="50" align="center" />
53
+ <el-table-column label="名称" align="center"> </el-table-column>
54
+ <el-table-column width="100" label="大小" sortable align="center">
55
+ </el-table-column>
56
+ <el-table-column width="100" label="文件类型" align="center">
57
+ </el-table-column>
58
+ <el-table-column width="150" label="上传时间" align="center" sortable>
59
+ </el-table-column>
60
+ <el-table-column label="操作" width="155" fixed="right" align="center">
61
+ <template #default="{ row }">
62
+ <el-button link> 查看 </el-button>
63
+ <el-button link type="primary"> 下载 </el-button>
64
+ <el-button link type="danger"> 删除 </el-button>
65
+ </template>
66
+ </el-table-column>
67
+ </el-table>
68
+ </main>
69
+ <footer class="shrink-0 flex flex-row-reverse pt-3">
70
+ <el-pagination
71
+ v-model:current-page="params.page"
72
+ v-model:page-size="params.pageSize"
73
+ :page-sizes="[30, 80, 150, 300]"
74
+ background
75
+ layout="total, sizes, prev, pager, next, jumper"
76
+ :total="total"
77
+ />
78
+ </footer>
79
+ </div>
3
80
  </template>
81
+ <script lang="ts" setup>
82
+ import {
83
+ ElTable,
84
+ ElTableColumn,
85
+ ElPagination,
86
+ ElButtonGroup,
87
+ ElButton,
88
+ ElInput,
89
+ ElProgress,
90
+ ElSpace,
91
+ ElBreadcrumb,
92
+ ElBreadcrumbItem,
93
+ } from "element-plus";
94
+ import { nextTick, ref, watch } from "vue";
95
+ import { useDebouncedRef } from "./useDebounceRef";
96
+ type FileItem = any;
97
+ const props = defineProps<{
98
+ load: (v: any) => Promise<{
99
+ count: number;
100
+ rows: FileItem[];
101
+ }>;
102
+ }>();
103
+ const localSearch = useDebouncedRef("");
104
+ const params = ref({
105
+ page: 1,
106
+ pageSize: 30,
107
+ path: "",
108
+ });
109
+ const total = ref(0);
110
+ const list = ref<FileItem[]>([1, 2]);
111
+ const loading = ref(false);
112
+ const sizeColors = [
113
+ { color: "#67c23a", percentage: 50 }, // 一半以内都安全
114
+ { color: "#409eff", percentage: 70 },
115
+ { color: "#e6a23c", percentage: 85 },
116
+ { color: "#f56c6c", percentage: 100 },
117
+ ];
118
+ watch(
119
+ [params, localSearch],
120
+ () => {
121
+ getData();
122
+ },
123
+ {
124
+ deep: true,
125
+ immediate: true,
126
+ }
127
+ );
128
+
129
+ async function getData() {
130
+ try {
131
+ loading.value = true;
132
+ const { count, rows } = await props.load({
133
+ ...params.value,
134
+ search: localSearch.value,
135
+ });
136
+ total.value = count;
137
+ list.value = [];
138
+ nextTick(() => {
139
+ list.value = rows;
140
+ });
141
+ } finally {
142
+ loading.value = false;
143
+ }
144
+ }
145
+
146
+ async function handleRowDblclick(row: FileItem) {
147
+ console.log(33333, row);
148
+ }
149
+ </script>
@@ -0,0 +1,21 @@
1
+ import { customRef } from "vue"
2
+
3
+ export function useDebouncedRef<T>(value: T, delay = 600) {
4
+ let timer: number
5
+
6
+ return customRef<T>((track, trigger) => {
7
+ return {
8
+ get() {
9
+ track()
10
+ return value
11
+ },
12
+ set(newValue: T) {
13
+ clearTimeout(timer)
14
+ timer = window.setTimeout(() => {
15
+ value = newValue
16
+ trigger()
17
+ }, delay)
18
+ },
19
+ }
20
+ })
21
+ }
@@ -0,0 +1,294 @@
1
+ <template>
2
+ <!-- 悬浮球 -->
3
+ <div
4
+ ref="ballRef"
5
+ class="floating-ball"
6
+ :class="{ dragging }"
7
+ :style="ballStyle"
8
+ @mousedown="onDown"
9
+ @touchstart.prevent="onDown"
10
+ >
11
+ <slot name="ball">
12
+ <div class="ball">+</div>
13
+ </slot>
14
+ </div>
15
+
16
+ <!-- 面板 -->
17
+ <teleport to="body">
18
+ <div
19
+ v-show="visible"
20
+ ref="panelRef"
21
+ class="floating-panel"
22
+ :style="panelStyle"
23
+ >
24
+ <slot />
25
+ </div>
26
+ </teleport>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from "vue";
31
+
32
+ /* ========= 状态 ========= */
33
+ const ballRef = ref<HTMLElement | null>(null);
34
+ const panelRef = ref<HTMLElement | null>(null);
35
+
36
+ const left = ref(window.innerWidth - 56 - 8);
37
+ const top = ref(window.innerHeight / 2);
38
+ const visible = ref(false);
39
+ const dragging = ref(false);
40
+
41
+ /* ========= 动态尺寸 ========= */
42
+ const ballRect = ref({ width: 0, height: 0 });
43
+ const panelRect = ref({ width: 0, height: 0 });
44
+
45
+ function updateBallRect() {
46
+ if (!ballRef.value) return;
47
+ const rect = ballRef.value.getBoundingClientRect();
48
+ ballRect.value = { width: rect.width, height: rect.height };
49
+ }
50
+
51
+ function updatePanelRect() {
52
+ if (!panelRef.value) return;
53
+ const rect = panelRef.value.getBoundingClientRect();
54
+ panelRect.value = { width: rect.width, height: rect.height };
55
+ }
56
+
57
+ /* ========= ResizeObserver ========= */
58
+ let ballObserver: ResizeObserver | null = null;
59
+ let panelObserver: ResizeObserver | null = null;
60
+
61
+ onMounted(() => {
62
+ nextTick(() => {
63
+ updateBallRect();
64
+ updatePanelRect();
65
+ });
66
+
67
+ ballObserver = new ResizeObserver(updateBallRect);
68
+ panelObserver = new ResizeObserver(updatePanelRect);
69
+ if (ballRef.value) ballObserver.observe(ballRef.value);
70
+ if (panelRef.value) panelObserver.observe(panelRef.value);
71
+
72
+ window.addEventListener("resize", onResize);
73
+ });
74
+
75
+ onBeforeUnmount(() => {
76
+ ballObserver?.disconnect();
77
+ panelObserver?.disconnect();
78
+ window.removeEventListener("resize", onResize);
79
+ });
80
+
81
+ const ballW = computed(() => ballRect.value.width);
82
+ const ballH = computed(() => ballRect.value.height);
83
+ const panelW = computed(() => panelRect.value.width);
84
+ const panelH = computed(() => panelRect.value.height);
85
+
86
+ /* ========= 窗口 resize ========= */
87
+ function onResize() {
88
+ const vw = window.innerWidth;
89
+ const vh = window.innerHeight;
90
+
91
+ // 确保悬浮球在视口内
92
+ left.value = Math.max(0, Math.min(left.value, vw - ballW.value));
93
+ top.value = Math.max(0, Math.min(top.value, vh - ballH.value));
94
+ }
95
+
96
+ /* ========= 监听 panel 显示 ========= */
97
+ watch(visible, (newVal) => {
98
+ if (newVal) {
99
+ nextTick(() => {
100
+ updatePanelRect();
101
+ });
102
+ }
103
+ });
104
+
105
+ /* ========= 拖拽 ========= */
106
+ let startX = 0;
107
+ let startY = 0;
108
+ let startLeft = 0;
109
+ let startTop = 0;
110
+ let moved = false;
111
+ let startTime = 0;
112
+ let isTouchEvent = false;
113
+
114
+ function getPoint(e: MouseEvent | TouchEvent) {
115
+ return "touches" in e ? e.touches[0]! : e;
116
+ }
117
+
118
+ function onDown(e: MouseEvent | TouchEvent) {
119
+ isTouchEvent = "touches" in e;
120
+ const p = getPoint(e);
121
+ startX = p.clientX;
122
+ startY = p.clientY;
123
+ startLeft = left.value;
124
+ startTop = top.value;
125
+ moved = false;
126
+ startTime = Date.now();
127
+ dragging.value = true;
128
+
129
+ if (isTouchEvent) {
130
+ document.addEventListener("touchmove", onMove, { passive: false });
131
+ document.addEventListener("touchend", onUp);
132
+ } else {
133
+ document.addEventListener("mousemove", onMove);
134
+ document.addEventListener("mouseup", onUp);
135
+ }
136
+ }
137
+
138
+ function onMove(e: MouseEvent | TouchEvent) {
139
+ const p = getPoint(e);
140
+ const dx = p.clientX - startX;
141
+ const dy = p.clientY - startY;
142
+
143
+ if (Math.abs(dx) > 3 || Math.abs(dy) > 3) moved = true;
144
+
145
+ left.value = startLeft + dx;
146
+ top.value = startTop + dy;
147
+
148
+ // 边界限制
149
+ left.value = Math.max(
150
+ 0,
151
+ Math.min(left.value, window.innerWidth - ballW.value)
152
+ );
153
+ top.value = Math.max(
154
+ 0,
155
+ Math.min(top.value, window.innerHeight - ballH.value)
156
+ );
157
+ }
158
+
159
+ function onUp() {
160
+ if (isTouchEvent) {
161
+ document.removeEventListener("touchmove", onMove);
162
+ document.removeEventListener("touchend", onUp);
163
+ } else {
164
+ document.removeEventListener("mousemove", onMove);
165
+ document.removeEventListener("mouseup", onUp);
166
+ }
167
+
168
+ dragging.value = false;
169
+
170
+ const duration = Date.now() - startTime;
171
+
172
+ if (!moved && duration < 200) {
173
+ visible.value = !visible.value;
174
+ return;
175
+ }
176
+
177
+ snapToEdge();
178
+ }
179
+
180
+ /* ========= 自动吸附 ========= */
181
+ function snapToEdge() {
182
+ const vw = window.innerWidth;
183
+ const vh = window.innerHeight;
184
+
185
+ const distances = {
186
+ left: left.value,
187
+ right: vw - left.value - ballW.value,
188
+ top: top.value,
189
+ bottom: vh - top.value - ballH.value,
190
+ };
191
+
192
+ const sorted = Object.entries(distances).sort((a, b) => a[1] - b[1]);
193
+ const edge = sorted[0]?.[0];
194
+
195
+ if (edge === "left") left.value = 8;
196
+ if (edge === "right") left.value = vw - ballW.value - 8;
197
+ if (edge === "top") top.value = 8;
198
+ if (edge === "bottom") top.value = vh - ballH.value - 8;
199
+ }
200
+
201
+ /* ========= ball 样式 ========= */
202
+ const ballStyle = computed(() => ({
203
+ left: left.value + "px",
204
+ top: top.value + "px",
205
+ position: "fixed",
206
+ zIndex: 9999,
207
+ }));
208
+
209
+ /* ========= placement ========= */
210
+ const placement = computed<"left" | "right" | "top" | "bottom">(() => {
211
+ const vw = window.innerWidth;
212
+ const vh = window.innerHeight;
213
+ const distances = {
214
+ left: left.value,
215
+ right: vw - left.value - ballW.value,
216
+ top: top.value,
217
+ bottom: vh - top.value - ballH.value,
218
+ };
219
+ const sorted = Object.entries(distances).sort((a, b) => a[1] - b[1]);
220
+ return (sorted[0]?.[0] ?? "right") as "left" | "right" | "top" | "bottom";
221
+ });
222
+
223
+ /* ========= panel 样式 ========= */
224
+ const panelStyle = computed(() => {
225
+ let x = 0;
226
+ let y = 0;
227
+ const gap = 12;
228
+ const margin = 8;
229
+
230
+ switch (placement.value) {
231
+ case "left":
232
+ x = left.value + ballW.value + gap;
233
+ y = top.value;
234
+ break;
235
+ case "right":
236
+ x = left.value - panelW.value - gap;
237
+ y = top.value;
238
+ break;
239
+ case "top":
240
+ x = left.value;
241
+ y = top.value + ballH.value + gap;
242
+ break;
243
+ case "bottom":
244
+ x = left.value;
245
+ y = top.value - panelH.value - gap;
246
+ break;
247
+ }
248
+
249
+ const vw = window.innerWidth;
250
+ const vh = window.innerHeight;
251
+
252
+ x = Math.max(margin, Math.min(x, vw - panelW.value - margin));
253
+ y = Math.max(margin, Math.min(y, vh - panelH.value - margin));
254
+
255
+ return {
256
+ left: x + "px",
257
+ top: y + "px",
258
+ position: "fixed",
259
+ zIndex: 9998,
260
+ };
261
+ });
262
+ </script>
263
+
264
+ <style scoped>
265
+ .floating-ball {
266
+ touch-action: none;
267
+ user-select: none;
268
+ cursor: grab;
269
+ }
270
+
271
+ .floating-ball.dragging {
272
+ cursor: grabbing;
273
+ }
274
+
275
+ .ball {
276
+ width: 56px;
277
+ height: 56px;
278
+ border-radius: 50%;
279
+ background: #409eff;
280
+ color: #fff;
281
+ font-size: 28px;
282
+ display: flex;
283
+ align-items: center;
284
+ justify-content: center;
285
+ }
286
+
287
+ .floating-panel {
288
+ background: #fff;
289
+ border-radius: 12px;
290
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
291
+ overflow: hidden;
292
+ transition: left 0.15s ease, top 0.15s ease;
293
+ }
294
+ </style>
package/dist/index.ts CHANGED
@@ -1,20 +1,23 @@
1
1
  import type { App } from "vue";
2
2
  import AuthGateway from "./components/auth-gateway/index.vue";
3
+ import FileManager from "./components/file-manager/index.vue";
3
4
  import { setTheme, setThemeByUrl } from "./components/auth-gateway/util";
4
5
  import { ElLoadingDirective } from "element-plus";
6
+ import FloatFileManager from "./components/file-manager/float-file-manager.vue";
7
+ import FloatingBall from './components/floating-ball/index.vue'
5
8
  export const theme = {
6
9
  setTheme,
7
10
  setThemeByUrl,
8
11
  };
9
- export { AuthGateway };
12
+ export { AuthGateway, FileManager,FloatFileManager,FloatingBall };
10
13
  // 用于在父项目中注册 Element Plus 组件的类型声明
11
- export interface AuthGatewayOptions {
12
- // Element Plus 组件通过父项目按需加载,这里不需要导入
13
- }
14
14
 
15
15
  export default {
16
16
  install(app: App) {
17
17
  app.directive("loading", ElLoadingDirective);
18
18
  app.component("AuthGateway", AuthGateway);
19
+ app.component("FileManager", FileManager);
20
+ app.component("FloatFileManager", FloatFileManager);
21
+ app.component("FloatingBall", FloatingBall);
19
22
  },
20
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jcjy-components",
3
- "version": "0.0.68",
3
+ "version": "0.0.69",
4
4
  "description": "Vue 3 组件库",
5
5
  "type": "module",
6
6
  "main": "./dist/index.ts",
@@ -30,13 +30,11 @@
30
30
  "@vitejs/plugin-vue": "^6.0.3",
31
31
  "@vue/tsconfig": "^0.8.1",
32
32
  "element-plus": "^2.13.0",
33
- "oxfmt": "^0.19.0",
34
- "oxlint": "^1.34.0",
35
- "rollup-plugin-visualizer": "^6.0.5",
33
+ "oxfmt": "^0.20.0",
34
+ "oxlint": "^1.35.0",
36
35
  "sass": "^1.97.1",
37
36
  "typescript": "~5.9.3",
38
37
  "vite": "7.3.0",
39
- "vite-plugin-dts": "^4.5.4",
40
38
  "vue": "^3.5.26",
41
39
  "vue-tsc": "^3.2.1"
42
40
  },
@@ -45,6 +43,10 @@
45
43
  "build": "vue-tsc -b && vite build",
46
44
  "preview": "vite preview",
47
45
  "build:types": "vue-tsc -b && vite build --mode build-types",
48
- "push:npm": "node build.js && pnpm publish"
46
+ "push:npm": "node build.js && pnpm publish",
47
+ "lint": "oxlint",
48
+ "lint:fix": "oxlint --fix",
49
+ "format": "oxfmt .",
50
+ "format:check": "oxfmt --check ."
49
51
  }
50
52
  }