@mt2025ui/mt-design 1.0.1

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 (36) hide show
  1. package/.vscode/extensions.json +3 -0
  2. package/LICENSE +21 -0
  3. package/README.md +5 -0
  4. package/index.html +14 -0
  5. package/package.json +25 -0
  6. package/public/vite.svg +1 -0
  7. package/src/App.vue +142 -0
  8. package/src/assets/iconfont/iconfont.js +70 -0
  9. package/src/assets/svg/DragIcon.vue +14 -0
  10. package/src/assets/svg/LockIcon.vue +14 -0
  11. package/src/assets/svg/UnlockIcon.vue +14 -0
  12. package/src/assets/vue.svg +1 -0
  13. package/src/components/MtAttach/MtAttachmentDisplay.vue +240 -0
  14. package/src/components/MtAttach/MtAttachmentUpload.vue +138 -0
  15. package/src/components/MtIcon/MtIcon.vue +48 -0
  16. package/src/components/MtIcon/index.ts +4 -0
  17. package/src/components/MtLayout/MtContainer.vue +97 -0
  18. package/src/components/MtLayout/MtFloatingPanel.vue +583 -0
  19. package/src/components/MtLayout/MtLayout.vue +99 -0
  20. package/src/components/MtLayout/MtLayoutItem.vue +1049 -0
  21. package/src/components/MtLayout/icons/CloseIcon.vue +7 -0
  22. package/src/components/MtLayout/icons/LockIcon.vue +7 -0
  23. package/src/components/MtLayout/icons/MenuIcon.vue +5 -0
  24. package/src/components/MtLayout/icons/UnlockIcon.vue +7 -0
  25. package/src/components/MtLayout/index.ts +7 -0
  26. package/src/components/MtLayout/registry.ts +15 -0
  27. package/src/example/mtFloating/Demo.vue +266 -0
  28. package/src/example/mtIcon/Demo.vue +151 -0
  29. package/src/example/mtLayout/Demo.vue +105 -0
  30. package/src/index.ts +29 -0
  31. package/src/main.ts +7 -0
  32. package/src/style.css +88 -0
  33. package/tsconfig.app.json +17 -0
  34. package/tsconfig.json +7 -0
  35. package/tsconfig.node.json +26 -0
  36. package/vite.config.ts +31 -0
@@ -0,0 +1,240 @@
1
+ <template>
2
+ <div class="attachment-display">
3
+ <div
4
+ v-for="(attachment, index) in attachments"
5
+ :key="attachment.id || index"
6
+ class="attachment-item"
7
+ >
8
+ <div class="attachment-icon">
9
+ <t-icon :name="getFileIcon(attachment.name)" size="16" />
10
+ </div>
11
+ <div class="attachment-info">
12
+ <div class="attachment-name">{{ attachment.name }}</div>
13
+ <div class="attachment-meta">
14
+ <span class="attachment-size">{{
15
+ formatSize(parseFloat(attachment.fileSize))
16
+ }}</span>
17
+ <span class="attachment-time">{{
18
+ formatDate(attachment.createTime)
19
+ }}</span>
20
+ </div>
21
+ </div>
22
+ <div class="attachment-actions">
23
+ <t-button
24
+ theme="default"
25
+ v-if="
26
+ browseableExtensions.some((ext) =>
27
+ attachment.name.toLowerCase().includes('.' + ext)
28
+ )
29
+ "
30
+ variant="text"
31
+ size="small"
32
+ @click="downloadAttachment(attachment)"
33
+ >
34
+ <t-icon name="browse" />
35
+ 查看
36
+ </t-button>
37
+ <t-button
38
+ theme="default"
39
+ variant="text"
40
+ size="small"
41
+ @click="downloadAttachment(attachment)"
42
+ >
43
+ <t-icon name="download" />
44
+ 下载
45
+ </t-button>
46
+ <t-popconfirm
47
+ content="确认删除吗?"
48
+ @confirm="$emit('remove', attachment)"
49
+ >
50
+ <t-button
51
+ v-if="!readonly"
52
+ theme="default"
53
+ variant="text"
54
+ size="small"
55
+ >
56
+ <t-icon name="delete" />
57
+ 删除
58
+ </t-button>
59
+ </t-popconfirm>
60
+ </div>
61
+ </div>
62
+ <div v-if="attachments.length === 0" class="empty-attachments">
63
+ 暂无附件
64
+ </div>
65
+ </div>
66
+ </template>
67
+
68
+ <script setup lang="ts">
69
+ import { ref } from "vue";
70
+
71
+ interface Attachment {
72
+ id: string;
73
+ name: string;
74
+ fileSize: string;
75
+ createTime: Date | string;
76
+ url?: string;
77
+ shortUrl?: string;
78
+ }
79
+
80
+ const props = defineProps({
81
+ attachments: {
82
+ type: Array<Attachment>,
83
+ default: () => [],
84
+ },
85
+ readonly: {
86
+ type: Boolean,
87
+ default: false,
88
+ },
89
+ host: {
90
+ type: String,
91
+ default: "",
92
+ },
93
+ browseableExtensions: {
94
+ type: Array<string>,
95
+ default: () => [
96
+ "pdf",
97
+ "doc",
98
+ "docx",
99
+ "xls",
100
+ "xlsx",
101
+ "ppt",
102
+ "pptx",
103
+ "jpg",
104
+ "jpeg",
105
+ "png",
106
+ "gif",
107
+ ],
108
+ },
109
+ });
110
+
111
+ const emit = defineEmits<{
112
+ (e: "remove", attachment: Attachment): void;
113
+ }>();
114
+
115
+ const getFileIcon = (fileName: string): string => {
116
+ const extension = fileName.split(".").pop()?.toLowerCase();
117
+ switch (extension) {
118
+ case "pdf":
119
+ return "document-pdf";
120
+ case "doc":
121
+ case "docx":
122
+ return "document-word";
123
+ case "xls":
124
+ case "xlsx":
125
+ return "document-excel";
126
+ case "ppt":
127
+ case "pptx":
128
+ return "document-ppt";
129
+ case "jpg":
130
+ case "jpeg":
131
+ case "png":
132
+ case "gif":
133
+ return "image";
134
+ case "zip":
135
+ case "rar":
136
+ case "7z":
137
+ return "compress";
138
+ default:
139
+ return "document";
140
+ }
141
+ };
142
+
143
+ const formatSize = (size: number): string => {
144
+ if (size === 0) return "0 B";
145
+ const k = 1024;
146
+ const sizes = ["B", "KB", "MB", "GB"];
147
+ const i = Math.floor(Math.log(size) / Math.log(k));
148
+ return parseFloat((size / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
149
+ };
150
+
151
+ const formatDate = (date: Date | string): string => {
152
+ const d = typeof date === "string" ? new Date(date) : date;
153
+ return d.toLocaleString("zh-CN", {
154
+ year: "numeric",
155
+ month: "2-digit",
156
+ day: "2-digit",
157
+ hour: "2-digit",
158
+ minute: "2-digit",
159
+ });
160
+ };
161
+
162
+ const downloadAttachment = (attachment: Attachment) => {
163
+ if (attachment.url) {
164
+ let url = attachment.url;
165
+ if (props.host) {
166
+ url = props.host + attachment.url;
167
+ }
168
+ window.open(url, "_blank");
169
+ } else {
170
+ // 使用后端下载接口
171
+ window.location.href = `/api/Attachment/DownloadFile?id=${attachment.id}`;
172
+ }
173
+ };
174
+ </script>
175
+
176
+ <style scoped lang="scss">
177
+ .attachment-display {
178
+ width: 100%;
179
+
180
+ .attachment-item {
181
+ display: flex;
182
+ align-items: center;
183
+ padding: 12px;
184
+ border-bottom: 1px solid #e8e8e8;
185
+ transition: background-color 0.2s;
186
+
187
+ &:hover {
188
+ background-color: #f5f7fa;
189
+ }
190
+
191
+ .attachment-icon {
192
+ margin-right: 12px;
193
+ color: #666;
194
+
195
+ .t-icon {
196
+ font-size: 20px;
197
+ }
198
+ }
199
+
200
+ .attachment-info {
201
+ flex: 1;
202
+
203
+ .attachment-name {
204
+ margin-bottom: 4px;
205
+ font-size: 14px;
206
+ font-weight: 500;
207
+ color: #333;
208
+ }
209
+
210
+ .attachment-meta {
211
+ display: flex;
212
+ gap: 16px;
213
+ font-size: 12px;
214
+ color: #999;
215
+
216
+ .attachment-size {
217
+ margin-right: 8px;
218
+ }
219
+ }
220
+ }
221
+
222
+ .attachment-actions {
223
+ display: flex;
224
+ gap: 8px;
225
+
226
+ .t-button {
227
+ margin-left: 8px;
228
+ }
229
+ }
230
+ }
231
+
232
+ .empty-attachments {
233
+ padding: 24px;
234
+ color: #999;
235
+ text-align: center;
236
+ background-color: #fafafa;
237
+ border-radius: 4px;
238
+ }
239
+ }
240
+ </style>
@@ -0,0 +1,138 @@
1
+ <template>
2
+ <t-upload
3
+ v-model="files"
4
+ :placeholder="`${props.placeholder} 最多只能上传 ${props.limit} 份文件`"
5
+ :requestMethod="requestMethod"
6
+ theme="file-flow"
7
+ multiple
8
+ :disabled="disabled"
9
+ :abridge-name="ABRIDGE_NAME"
10
+ :auto-upload="autoUpload"
11
+ :max="props.limit"
12
+ :show-thumbnail="showThumbnail"
13
+ :allow-upload-duplicate-file="allowUploadDuplicateFile"
14
+ :isBatchUpload="isBatchUpload"
15
+ :upload-all-files-in-one-request="uploadAllFilesInOneRequest"
16
+ :format-response="formatResponse"
17
+ :cancel-upload-button="null"
18
+ />
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { ref } from "vue";
23
+ import type { PropType } from "vue";
24
+ import { MessagePlugin } from "tdesign-vue-next";
25
+ import type {
26
+ UploadFile,
27
+ UploadProps,
28
+ RequestMethodResponse,
29
+ } from "tdesign-vue-next";
30
+
31
+ // 简化类型定义
32
+ type UploadProgressContext = any;
33
+ type UploadErrorContext = any;
34
+ type UploadSuccessContext = any;
35
+
36
+ const props = defineProps({
37
+ limit: {
38
+ type: Number,
39
+ default: 0,
40
+ },
41
+ maxSize: {
42
+ type: Number,
43
+ default: 0,
44
+ },
45
+ accept: {
46
+ type: String,
47
+ default: "",
48
+ },
49
+ refModel: {
50
+ type: String,
51
+ default: "",
52
+ },
53
+ refApp: {
54
+ type: String,
55
+ default: "",
56
+ },
57
+ theme: {
58
+ type: String as PropType<"easy" | "advanced">,
59
+ default: "advanced",
60
+ },
61
+ placeholder: {
62
+ type: String,
63
+ default: "支持批量上传文件,文件格式不限",
64
+ },
65
+ requestMethod: {
66
+ type: Function as PropType<UploadProps["requestMethod"]>,
67
+ default: (
68
+ files: UploadFile | UploadFile[]
69
+ ): Promise<RequestMethodResponse> =>
70
+ Promise.resolve<RequestMethodResponse>({
71
+ status: "fail",
72
+ } as RequestMethodResponse),
73
+ },
74
+ });
75
+ const files = ref<UploadProps["value"]>([]);
76
+ const ABRIDGE_NAME: UploadProps["abridgeName"] = [10, 7];
77
+ const disabled = ref(false);
78
+ const autoUpload = ref(false);
79
+ const showThumbnail = ref(true);
80
+ const allowUploadDuplicateFile = ref(false);
81
+ const isBatchUpload = ref(false);
82
+ const uploadAllFilesInOneRequest = ref(false);
83
+ const formatResponse: UploadProps["formatResponse"] = (res) => {
84
+ if (!res) {
85
+ return {
86
+ status: "fail",
87
+ error: "上传失败,原因:文件过大或网络不通",
88
+ };
89
+ }
90
+ return res;
91
+ };
92
+ const emit = defineEmits<{
93
+ (e: "upload-success", files: UploadProps["value"]): void;
94
+ (e: "upload-error", error: any): void;
95
+ }>();
96
+
97
+ const beforeUpload = (_file: File) => {
98
+ // 可以在这里添加额外的文件验证逻辑
99
+ return true;
100
+ };
101
+
102
+ const submitUpload = () => {
103
+ // 触发上传
104
+ const uploadInstance = (document.querySelector(".t-upload") as any)
105
+ .uploadInstance;
106
+ if (uploadInstance) {
107
+ uploadInstance.submit();
108
+ }
109
+ };
110
+
111
+ const clearFiles = () => {
112
+ files.value = [];
113
+ };
114
+
115
+ const handleSuccess = (_options: UploadSuccessContext) => {
116
+ MessagePlugin.success("文件上传成功");
117
+ emit("upload-success", files.value);
118
+ };
119
+
120
+ const handleError = (options: UploadErrorContext) => {
121
+ MessagePlugin.error("文件上传失败");
122
+ emit("upload-error", options);
123
+ };
124
+
125
+ const handleProgress = (options: UploadProgressContext) => {
126
+ // 可以在这里处理上传进度
127
+ console.log("上传进度:", options.percent);
128
+ };
129
+ </script>
130
+
131
+ <style scoped lang="scss">
132
+ .upload-footer {
133
+ display: flex;
134
+ gap: 8px;
135
+ justify-content: flex-end;
136
+ margin-top: 12px;
137
+ }
138
+ </style>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <svg :class="svgClass" aria-hidden="true">
3
+ <!-- xlink:href 兼容旧浏览器,href 是新标准 -->
4
+ <use :xlink:href="iconName" :href="iconName" />
5
+ </svg>
6
+ </template>
7
+
8
+ <script setup>
9
+ import { computed } from "vue";
10
+
11
+ const props = defineProps({
12
+ name: {
13
+ type: String,
14
+ required: true,
15
+ // 注意:IconFont 生成的 ID 通常带有前缀,如 #icon-home
16
+ // 这里假设传入的是 'home',组件自动拼接 '#icon-home'
17
+ },
18
+ className: {
19
+ type: String,
20
+ default: "",
21
+ },
22
+ // color: {
23
+ // type: String,
24
+ // default: '' // 如果不传,则继承父元素颜色或保持原色
25
+ // }
26
+ });
27
+
28
+ const iconName = computed(() => `#icon-${props.name}`);
29
+ const svgClass = computed(() => {
30
+ if (props.className) {
31
+ return `mt-icon ${props.className}`;
32
+ }
33
+ return "mt-icon";
34
+ });
35
+ </script>
36
+
37
+ <style scoped>
38
+ .mt-icon {
39
+ display: inline-block;
40
+ width: 1em;
41
+ height: 1em;
42
+ overflow: hidden;
43
+ vertical-align: -0.15em;
44
+ fill: currentcolor; /* 关键:默认跟随文字颜色 */
45
+ }
46
+
47
+ /* 如果 props.color 有值,可以通过 style 覆盖,或者在这里写特定逻辑 */
48
+ </style>
@@ -0,0 +1,4 @@
1
+ import MtIcon from "./MtIcon.vue";
2
+
3
+ export { MtIcon };
4
+ export default MtIcon;
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <Teleport :to="teleportTarget" v-if="teleportTarget">
3
+ <div class="mt-container" v-show="isActive">
4
+ <slot></slot>
5
+ </div>
6
+ </Teleport>
7
+ <!-- If not yet teleported (no owner), maybe don't render or render in place? -->
8
+ <div v-else class="mt-container-placeholder" style="display: none"></div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { inject, ref, onMounted, onBeforeUnmount, watch, computed } from "vue";
13
+
14
+ const props = defineProps({
15
+ title: {
16
+ type: String,
17
+ default: "Container",
18
+ },
19
+ });
20
+
21
+ const isActive = ref(false);
22
+ const id = `container-${Math.random().toString(36).substr(2, 9)}`;
23
+ const ownerContentId = ref<string | null>(null);
24
+ const isMounted = ref(false);
25
+
26
+ // Inject parent layout item context
27
+ const layoutItem = inject<any>("mt-layout-item", null);
28
+
29
+ const teleportTarget = computed(() => {
30
+ return isMounted.value && ownerContentId.value
31
+ ? `#${ownerContentId.value}`
32
+ : null;
33
+ });
34
+
35
+ const setOwner = (contentId: string, _itemId: string) => {
36
+ ownerContentId.value = contentId;
37
+ };
38
+
39
+ if (layoutItem) {
40
+ ownerContentId.value = layoutItem.contentId;
41
+
42
+ const containerData = {
43
+ id,
44
+ title: props.title,
45
+ setActive: (active: boolean) => {
46
+ isActive.value = active;
47
+ },
48
+ setOwner,
49
+ };
50
+
51
+ onMounted(() => {
52
+ layoutItem.registerContainer(containerData);
53
+ // Defer enabling teleport to ensure parent DOM exists
54
+ setTimeout(() => {
55
+ isMounted.value = true;
56
+ }, 0);
57
+ });
58
+
59
+ onBeforeUnmount(() => {
60
+ layoutItem.unregisterContainer(id);
61
+ });
62
+
63
+ watch(
64
+ () => props.title,
65
+ (newTitle) => {
66
+ layoutItem.updateContainerTitle(id, newTitle);
67
+ }
68
+ );
69
+ }
70
+ </script>
71
+
72
+ <style scoped>
73
+ .mt-container {
74
+ width: 100%;
75
+ height: 100%;
76
+ overflow: auto;
77
+ }
78
+
79
+ /* Custom Scrollbar */
80
+ .mt-container::-webkit-scrollbar {
81
+ width: 6px;
82
+ height: 6px;
83
+ }
84
+
85
+ .mt-container::-webkit-scrollbar-track {
86
+ background: transparent;
87
+ }
88
+
89
+ .mt-container::-webkit-scrollbar-thumb {
90
+ background: #ccc;
91
+ border-radius: 3px;
92
+ }
93
+
94
+ .mt-container::-webkit-scrollbar-thumb:hover {
95
+ background: #999;
96
+ }
97
+ </style>