muzhiyu-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.
Files changed (96) hide show
  1. package/README.md +202 -0
  2. package/components/muzhiyu/mu-action-sheet/mu-action-sheet.vue +335 -0
  3. package/components/muzhiyu/mu-alert/mu-alert.vue +220 -0
  4. package/components/muzhiyu/mu-back-top/mu-back-top.vue +161 -0
  5. package/components/muzhiyu/mu-badge/mu-badge.vue +150 -0
  6. package/components/muzhiyu/mu-barcode/mu-barcode.vue +100 -0
  7. package/components/muzhiyu/mu-blank/mu-blank.vue +30 -0
  8. package/components/muzhiyu/mu-bottom-nav/mu-bottom-nav.vue +331 -0
  9. package/components/muzhiyu/mu-bubble/mu-bubble.vue +316 -0
  10. package/components/muzhiyu/mu-button/mu-button.vue +431 -0
  11. package/components/muzhiyu/mu-calendar/mu-calendar.vue +429 -0
  12. package/components/muzhiyu/mu-car-input/mu-car-input.vue +209 -0
  13. package/components/muzhiyu/mu-card/mu-card.vue +317 -0
  14. package/components/muzhiyu/mu-cascader/mu-cascader.vue +336 -0
  15. package/components/muzhiyu/mu-cell/mu-cell.vue +368 -0
  16. package/components/muzhiyu/mu-checkbox/mu-checkbox.vue +116 -0
  17. package/components/muzhiyu/mu-checkbox-group/mu-checkbox-group.vue +40 -0
  18. package/components/muzhiyu/mu-code-input/mu-code-input.vue +264 -0
  19. package/components/muzhiyu/mu-collapse/mu-collapse.vue +316 -0
  20. package/components/muzhiyu/mu-color-picker/mu-color-picker.vue +466 -0
  21. package/components/muzhiyu/mu-config/index.js +34 -0
  22. package/components/muzhiyu/mu-config/mu-config.vue +54 -0
  23. package/components/muzhiyu/mu-countdown/mu-countdown.vue +338 -0
  24. package/components/muzhiyu/mu-cropper/mu-cropper.vue +415 -0
  25. package/components/muzhiyu/mu-date-picker/mu-date-picker.vue +290 -0
  26. package/components/muzhiyu/mu-datetime/mu-datetime.vue +262 -0
  27. package/components/muzhiyu/mu-divider/mu-divider.vue +229 -0
  28. package/components/muzhiyu/mu-drawer/mu-drawer.vue +172 -0
  29. package/components/muzhiyu/mu-dropdown/mu-dropdown.vue +215 -0
  30. package/components/muzhiyu/mu-dropdown-top/mu-dropdown-top.vue +243 -0
  31. package/components/muzhiyu/mu-empty/mu-empty.vue +217 -0
  32. package/components/muzhiyu/mu-fab/mu-fab.vue +324 -0
  33. package/components/muzhiyu/mu-footer/mu-footer.vue +21 -0
  34. package/components/muzhiyu/mu-form/mu-form.vue +170 -0
  35. package/components/muzhiyu/mu-grid/mu-grid.vue +95 -0
  36. package/components/muzhiyu/mu-grid-item/mu-grid-item.vue +247 -0
  37. package/components/muzhiyu/mu-html/mu-html.vue +145 -0
  38. package/components/muzhiyu/mu-icon/mu-icon.vue +822 -0
  39. package/components/muzhiyu/mu-image-grid/mu-image-grid.vue +225 -0
  40. package/components/muzhiyu/mu-image-preview/mu-image-preview.vue +286 -0
  41. package/components/muzhiyu/mu-index-list/mu-index-list.vue +381 -0
  42. package/components/muzhiyu/mu-input/mu-input.vue +570 -0
  43. package/components/muzhiyu/mu-input-number/mu-input-number.vue +193 -0
  44. package/components/muzhiyu/mu-keyboard/mu-keyboard.vue +415 -0
  45. package/components/muzhiyu/mu-list/mu-list.vue +89 -0
  46. package/components/muzhiyu/mu-loading/mu-loading.vue +124 -0
  47. package/components/muzhiyu/mu-loading-page/mu-loading-page.vue +529 -0
  48. package/components/muzhiyu/mu-loadmore/mu-loadmore.vue +26 -0
  49. package/components/muzhiyu/mu-modal/mu-modal.vue +298 -0
  50. package/components/muzhiyu/mu-navbar/mu-navbar.vue +223 -0
  51. package/components/muzhiyu/mu-nomore/mu-nomore.vue +107 -0
  52. package/components/muzhiyu/mu-notice-bar/mu-notice-bar.vue +284 -0
  53. package/components/muzhiyu/mu-notify/mu-notify.vue +202 -0
  54. package/components/muzhiyu/mu-overlay/mu-overlay.vue +94 -0
  55. package/components/muzhiyu/mu-pagination/mu-pagination.vue +330 -0
  56. package/components/muzhiyu/mu-password-input/mu-password-input.vue +119 -0
  57. package/components/muzhiyu/mu-picker/mu-picker.vue +297 -0
  58. package/components/muzhiyu/mu-popup/mu-popup.vue +297 -0
  59. package/components/muzhiyu/mu-progress/mu-progress.vue +156 -0
  60. package/components/muzhiyu/mu-pull-refresh/mu-pull-refresh.vue +54 -0
  61. package/components/muzhiyu/mu-qrcode/mu-qrcode.vue +340 -0
  62. package/components/muzhiyu/mu-qrcode/qrcode.js +2237 -0
  63. package/components/muzhiyu/mu-radio/mu-radio.vue +125 -0
  64. package/components/muzhiyu/mu-radio-group/mu-radio-group.vue +37 -0
  65. package/components/muzhiyu/mu-rate/mu-rate.vue +103 -0
  66. package/components/muzhiyu/mu-search/mu-search.vue +320 -0
  67. package/components/muzhiyu/mu-section/mu-section.vue +206 -0
  68. package/components/muzhiyu/mu-select/mu-select.vue +440 -0
  69. package/components/muzhiyu/mu-share-sheet/mu-share-sheet.vue +269 -0
  70. package/components/muzhiyu/mu-sidebar/mu-sidebar.vue +304 -0
  71. package/components/muzhiyu/mu-signature/mu-signature.vue +449 -0
  72. package/components/muzhiyu/mu-skeleton/mu-skeleton.vue +225 -0
  73. package/components/muzhiyu/mu-slider/mu-slider.vue +228 -0
  74. package/components/muzhiyu/mu-space/mu-space.vue +84 -0
  75. package/components/muzhiyu/mu-steps/mu-steps.vue +311 -0
  76. package/components/muzhiyu/mu-sticky/mu-sticky.vue +57 -0
  77. package/components/muzhiyu/mu-swipe-cell/mu-swipe-cell.vue +281 -0
  78. package/components/muzhiyu/mu-swiper/mu-swiper.vue +107 -0
  79. package/components/muzhiyu/mu-switch/mu-switch.vue +99 -0
  80. package/components/muzhiyu/mu-tabbar/mu-tabbar.vue +382 -0
  81. package/components/muzhiyu/mu-table/mu-table.vue +420 -0
  82. package/components/muzhiyu/mu-tabs/mu-tabs.vue +339 -0
  83. package/components/muzhiyu/mu-tag/mu-tag.vue +65 -0
  84. package/components/muzhiyu/mu-text-ellipsis/mu-text-ellipsis.vue +54 -0
  85. package/components/muzhiyu/mu-timeline/mu-timeline.vue +166 -0
  86. package/components/muzhiyu/mu-tips/mu-tips.vue +36 -0
  87. package/components/muzhiyu/mu-toast/mu-toast.vue +151 -0
  88. package/components/muzhiyu/mu-transfer/mu-transfer.vue +114 -0
  89. package/components/muzhiyu/mu-tree/mu-tree.vue +263 -0
  90. package/components/muzhiyu/mu-upload/mu-upload.vue +415 -0
  91. package/components/muzhiyu/mu-waterfall/mu-waterfall.vue +321 -0
  92. package/components/muzhiyu/mu-watermark/mu-watermark.vue +60 -0
  93. package/index.js +83 -0
  94. package/package.json +38 -0
  95. package/utils/index.js +600 -0
  96. package/utils/request.js +265 -0
package/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # MuzhiyuUI 极奢全端组件库
2
+
3
+ > **MuzhiyuUI** 是一套专为 **UniApp + Vue 3** 打造的全端极奢 UI 组件库。包含 **89+ 生产级高频组件**、**Axios 级拦截器 HTTP 客户端**、**格式化与正则校验工具箱** 以及 **全平台暗黑模式原生适配**。
4
+
5
+ ---
6
+
7
+ ## 🌟 核心特性 (Key Features)
8
+
9
+ - ⚡ **Vue 3 + `<script setup>` 驱动**:全量采用 Vue 3 Composition API 重构,逻辑清晰,性能卓越;
10
+ - 🎨 **极奢流光美学设计**:拒绝大众平庸调色,采用现代微渐变、毛玻璃浮岛、Bento 悬浮卡片与动效细节;
11
+ - 📱 **100% 微信小程序 + H5 + App 全端兼容**:重构虚拟节点 `virtualHost: true`,彻底消除小程序自定义组件壳导致的 Flex 错位与样式压缩问题;
12
+ - 🌙 **原生暗黑模式 (Dark Mode)**:全量组件支持 `prefers-color-scheme: dark` 自动跟从系统深色切换;
13
+ - 📦 **支持 `easycom` 按需加载**:无需手动 `import` 组件,随用随写,打包体积小巧;
14
+ - 🛠️ **完整工具链集结**:自带网络请求拦截器 (`http`)、表单正则校验 (`validate`)、脱敏/格式化 (`format`) 与颜色计算器 (`color`)。
15
+
16
+ ---
17
+
18
+ ## 📦 目录结构 (Directory Structure)
19
+
20
+ ```text
21
+ uni_modules/muzhiyu-ui/
22
+ ├── components/ <-- 89+ 核心 UI 组件
23
+ ├── utils/ <-- 统一网络请求与工具函数
24
+ │ ├── request.js <-- HTTP 客户端拦截器
25
+ │ └── index.js <-- 正则/格式化/脱敏工具箱
26
+ ├── index.js <-- Vue3 插件注册入口
27
+ ├── uni.scss <-- 全局 SCSS 主题变量
28
+ └── package.json <-- 插件市场配置文件
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 🚀 快速上手 (Quick Start)
34
+
35
+ ### 方式一:作为 `uni_modules` 使用(推荐)
36
+
37
+ 1. 在 DCloud 插件市场点击 **“导入到 HBuilderX”** 或下载解压至项目的 `uni_modules/muzhiyu-ui` 目录下;
38
+ 2. 打开项目的 `pages.json`,配置 `easycom` 按需自动解析规则:
39
+
40
+ ```json
41
+ {
42
+ "easycom": {
43
+ "autoscan": true,
44
+ "custom": {
45
+ "^mu-(.*)": "@/uni_modules/muzhiyu-ui/components/mu-$1/mu-$1.vue"
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ 配置完成后,无需在任何页面引入,直接在 `.vue` 模板中使用组件即可!
52
+
53
+ ---
54
+
55
+ ### 方式二:作为 `npm` 包使用
56
+
57
+ 1. 在项目根目录终端运行安装命令:
58
+ ```bash
59
+ npm install muzhiyu-ui
60
+ ```
61
+
62
+ 2. 在 `pages.json` 中配置 `easycom`:
63
+ ```json
64
+ {
65
+ "easycom": {
66
+ "autoscan": true,
67
+ "custom": {
68
+ "^mu-(.*)": "muzhiyu-ui/components/muzhiyu/mu-$1/mu-$1.vue"
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 🛠️ 全局挂载与工具函数 ($mu)
77
+
78
+ 在 `main.js` 中挂载全局插件:
79
+
80
+ ```javascript
81
+ import { createApp } from 'vue'
82
+ import App from './App.vue'
83
+ import muzhiyuUI from 'muzhiyu-ui'
84
+
85
+ const app = createApp(App)
86
+ app.use(muzhiyuUI)
87
+ app.mount('#app')
88
+ ```
89
+
90
+ ### 在页面中零 import 全局调用 (`uni.$mu`):
91
+
92
+ 挂载后,可以在任何页面、`<script setup>`、工具 JS 或 Pinia/Vuex 中**零 import** 直接调用原生 `uni.$mu`:
93
+
94
+ ```javascript
95
+ // 1. 发起网络请求 (支持全局拦截器)
96
+ uni.$mu.http.get('/api/user/info', { id: 1001 }).then(res => {
97
+ console.log('用户信息:', res)
98
+ })
99
+
100
+ // 2. 快捷 JS Toast 弹窗 / Notify 通知
101
+ uni.$mu.toast('保存成功', 'success')
102
+ uni.$mu.notify({ message: '预约确认成功', type: 'success' })
103
+
104
+ // 3. 手机号脱敏
105
+ console.log(uni.$mu.util.maskPhone('13812345678')) // "138****5678"
106
+
107
+ // 4. 正则校验
108
+ console.log(uni.$mu.validate.isEmail('test@example.com')) // true
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 📚 89+ 核心组件全景清单
114
+
115
+ ### 1. 基础组件 (Basic)
116
+ | 组件名 | 说明 | 示例标签 |
117
+ | :--- | :--- | :--- |
118
+ | **mu-button** | 极奢按钮(支持流光、圆角、加载、禁用与各种尺寸) | `<mu-button type="primary">确认提交</mu-button>` |
119
+ | **mu-icon** | 矢量图标(内置 100+ 高频极简图标库) | `<mu-icon name="heart" size="24" color="#F43F5E" />` |
120
+ | **mu-cell** | 单元格组(支持图标、箭头、副标题与右侧插槽) | `<mu-cell title="个人资料" is-link />` |
121
+ | **mu-tag** | 标签(支持渐变、镂空、胶囊与点状) | `<mu-tag type="success">已完成</mu-tag>` |
122
+ | **mu-badge** | 徽标角标(数字角标与小红点) | `<mu-badge :count="99"><view>消息</view></mu-badge>` |
123
+ | **mu-divider** | 分割线(支持带有文案或纯线) | `<mu-divider text="我是有底线的" />` |
124
+ | **mu-space** | 垂直/水平弹性留白距 | `<mu-space height="30" />` |
125
+ | **mu-blank** | 两侧固定留白容器 | `<mu-blank width="32">内容...</mu-blank>` |
126
+
127
+ ### 2. 表单与输入组件 (Form & Inputs)
128
+ | 组件名 | 说明 | 示例标签 |
129
+ | :--- | :--- | :--- |
130
+ | **mu-input** | 文本输入框(带清除、前后缀、密码切换) | `<mu-input v-model="form.name" placeholder="请输入姓名" />` |
131
+ | **mu-checkbox** | 复选框与复选框组 | `<mu-checkbox-group v-model="checkedList">...</mu-checkbox-group>` |
132
+ | **mu-radio** | 单选框与单选框组 | `<mu-radio-group v-model="radioVal">...</mu-radio-group>` |
133
+ | **mu-switch** | 异步开关 | `<mu-switch v-model="enabled" />` |
134
+ | **mu-rate** | 评分组件(支持半星与自定义图标) | `<mu-rate v-model="score" allow-half />` |
135
+ | **mu-slider** | 滑块选择器 | `<mu-slider v-model="volume" />` |
136
+ | **mu-upload** | 多图/视频上传(支持进度条、预览与删除) | `<mu-upload v-model:file-list="fileList" />` |
137
+ | **mu-code-input** | 验证码/密码分割输入框 | `<mu-code-input :length="6" v-model="code" />` |
138
+ | **mu-signature** | 手写电子签名板(输出 Canvas 图像) | `<mu-signature @confirm="onSignDone" />` |
139
+
140
+ ### 3. 布局与容器组件 (Layout)
141
+ | 组件名 | 说明 | 示例标签 |
142
+ | :--- | :--- | :--- |
143
+ | **mu-grid** | 宫格布局(金刚区、Bento 悬浮卡片、支持 `border` 与 `square`) | `<mu-grid :columns="4" border><mu-grid-item text="首页" /></mu-grid>` |
144
+ | **mu-card** | 奢华卡片容器(支持封面、标题与底部操作栏) | `<mu-card title="VisionOS 规范" cover="..." />` |
145
+ | **mu-waterfall** | 动态瀑布流布局(双列贪心算法自动分配高度) | `<mu-waterfall :data="goodsList" />` |
146
+ | **mu-table** | 高级表格(支持表头固定、左右固定列与斑马纹) | `<mu-table :columns="cols" :data="tableData" />` |
147
+
148
+ ### 4. 导航与选项卡 (Navigation)
149
+ | 组件名 | 说明 | 示例标签 |
150
+ | :--- | :--- | :--- |
151
+ | **mu-navbar** | 顶部导航栏(支持沉浸式、返回按键与自定义插槽) | `<mu-navbar title="订单详情" :is-back="true" />` |
152
+ | **mu-tabs** | 选项卡(支持滚动、居中、居左与 `sticky` 原生粘性吸顶) | `<mu-tabs :tabs="tabList" sticky :offset-top="0" />` |
153
+ | **mu-tabbar** | 底部导航栏(普通模式、悬浮毛玻璃浮岛模式、异形凸起模式) | `<mu-tabbar :items="tabbarItems" mode="island" />` |
154
+ | **mu-sidebar** | 联动侧边导航栏 | `<mu-sidebar :options="categories" v-model:active="activeIdx" />` |
155
+ | **mu-steps** | 步骤条 | `<mu-steps :options="stepList" :current="1" />` |
156
+ | **mu-index-list** | 拼音首字母索引列表(通讯录风格) | `<mu-index-list :data="cityData" />` |
157
+
158
+ ### 5. 反馈与弹层组件 (Feedback & Modals)
159
+ | 组件名 | 说明 | 示例标签 |
160
+ | :--- | :--- | :--- |
161
+ | **mu-modal** | 极奢对话框(支持异步按钮与插槽自定义) | `<mu-modal v-model:show="showModal" title="确认提示" />` |
162
+ | **mu-toast** | 快捷消息轻提示 | `this.$refs.toast.show({ title: '保存成功' })` |
163
+ | **mu-notify** | 顶部通知提醒栏 | `this.$refs.notify.show({ type: 'warning', message: '网络异常' })` |
164
+ | **mu-popup** | 多方向弹出层(顶部/底部/左侧/右侧) | `<mu-popup v-model:show="showPopup" position="bottom">...</mu-popup>` |
165
+ | **mu-cropper** | 高性能图片裁剪器(支持旋转、缩放与正方形/圆形导出) | `<mu-cropper :src="tempImg" @confirm="onCropped" />` |
166
+
167
+ ---
168
+
169
+ ## 💻 典型代码使用范例
170
+
171
+ ### 1. 吸顶 Tabs 与 4 列金刚区 Demo
172
+ ```html
173
+ <template>
174
+ <view class="container">
175
+ <!-- 1. 吸顶选项卡 -->
176
+ <mu-tabs :tabs="categoryTabs" :current="activeTab" sticky :offset-top="0" @change="activeTab = $event" />
177
+
178
+ <!-- 2. 经典 4 列金刚区 -->
179
+ <mu-grid :columns="4" border>
180
+ <mu-grid-item v-for="(item, i) in gridList" :key="i" :text="item.name" :icon="item.icon" :badge="item.badge" />
181
+ </mu-grid>
182
+ </view>
183
+ </template>
184
+
185
+ <script setup>
186
+ import { ref } from 'vue'
187
+ const activeTab = ref(0)
188
+ const categoryTabs = ['精选推荐', '热门排行', '最新上架', '手机数码']
189
+ const gridList = [
190
+ { name: '扫一扫', icon: 'scan', badge: 0 },
191
+ { name: '付款码', icon: 'qr_code', badge: 0 },
192
+ { name: '卡券包', icon: 'credit_card', badge: 'HOT' },
193
+ { name: '消息', icon: 'message', badge: 5 }
194
+ ]
195
+ </script>
196
+ ```
197
+
198
+ ---
199
+
200
+ ## 📄 开源协议 (License)
201
+
202
+ [MIT License](LICENSE) - 欢迎自由使用于商业与开源项目!
@@ -0,0 +1,335 @@
1
+ <template>
2
+ <view v-if="visible" class="mu-action-sheet" :class="[panelShow && 'mu-action-sheet--open']">
3
+ <!-- 遮罩层 -->
4
+ <view class="mu-action-sheet__mask" @tap="onMaskTap"></view>
5
+
6
+ <!-- 底部面板 -->
7
+ <view class="mu-action-sheet__body" :class="[panelShow && 'mu-action-sheet__body--show']" @tap.stop>
8
+ <!-- 标题与副标题 -->
9
+ <view v-if="title || description" class="mu-action-sheet__header">
10
+ <text v-if="title" class="mu-action-sheet__title">{{ title }}</text>
11
+ <text v-if="description" class="mu-action-sheet__desc">{{ description }}</text>
12
+ </view>
13
+
14
+ <!-- 操作选项列表 -->
15
+ <view class="mu-action-sheet__list">
16
+ <view
17
+ v-for="(item, index) in formattedItems"
18
+ :key="index"
19
+ class="mu-action-sheet__item mu-touchable-flat"
20
+ :class="[
21
+ item.disabled && 'mu-action-sheet__item--disabled',
22
+ item.danger && 'mu-action-sheet__item--danger'
23
+ ]"
24
+ @tap="handleClick(index, item)"
25
+ >
26
+ <!-- 选项图标 -->
27
+ <view v-if="item.icon || item.img" class="mu-action-sheet__icon-wrap">
28
+ <mu-icon v-if="item.icon" :name="item.icon" :size="20" :color="getItemColor(item)" />
29
+ <image v-else-if="item.img" class="mu-action-sheet__img" :src="item.img" mode="aspectFit" />
30
+ </view>
31
+
32
+ <!-- 文本与描述 -->
33
+ <view class="mu-action-sheet__info">
34
+ <text class="mu-action-sheet__text" :style="{ color: getItemColor(item) }">{{ item.text }}</text>
35
+ <text v-if="item.description" class="mu-action-sheet__subtext">{{ item.description }}</text>
36
+ </view>
37
+
38
+ <!-- 尾部标签 Tag / Badge -->
39
+ <view v-if="item.badge" class="mu-action-sheet__badge-box">
40
+ <mu-tag type="danger" size="small" shape="capsule">{{ item.badge }}</mu-tag>
41
+ </view>
42
+ </view>
43
+ </view>
44
+
45
+ <!-- 隔离底栏与取消按钮 -->
46
+ <view v-if="showCancel" class="mu-action-sheet__cancel-gap"></view>
47
+ <view v-if="showCancel" class="mu-action-sheet__cancel mu-touchable-flat" @tap="handleCancel">
48
+ <text class="mu-action-sheet__cancel-text">{{ cancelText }}</text>
49
+ </view>
50
+ </view>
51
+ </view>
52
+ </template>
53
+
54
+ <script setup>
55
+ /**
56
+ * mu-action-sheet 动作面板
57
+ * @description 底部弹出式操作面板,常用于微信分享、照片选择、危险操作确认
58
+ * @property {boolean} show - 是否显示面板
59
+ * @property {Array} items / actions - 操作列表 [{text, description, icon, color, danger, disabled, badge}]
60
+ * @property {string} title - 标题
61
+ * @property {string} description - 副标题说明
62
+ * @property {string} cancelText - 取消按钮文本
63
+ * @property {boolean} showCancel - 是否显示取消按钮
64
+ * @property {boolean} maskClosable - 点击遮罩层是否关闭
65
+ * @event {Function} select - 选中菜单项时触发
66
+ * @event {Function} cancel - 点击取消时触发
67
+ */
68
+ import { ref, computed, watch } from 'vue'
69
+
70
+ const props = defineProps({
71
+ show: { type: Boolean, default: false },
72
+ items: { type: Array, default: () => [] },
73
+ actions: { type: Array, default: () => [] },
74
+ title: { type: String, default: '' },
75
+ description: { type: String, default: '' },
76
+ cancelText: { type: String, default: '取消' },
77
+ showCancel: { type: Boolean, default: true },
78
+ maskClosable: { type: Boolean, default: true }
79
+ })
80
+
81
+ const emit = defineEmits(['click', 'cancel', 'select', 'update:show', 'close'])
82
+
83
+ const visible = ref(false)
84
+ const panelShow = ref(false)
85
+
86
+ const rawItems = computed(() => {
87
+ if (props.items && props.items.length) return props.items
88
+ return props.actions || []
89
+ })
90
+
91
+ const formattedItems = computed(() => {
92
+ return rawItems.value.map(item => {
93
+ if (typeof item === 'object' && item !== null) {
94
+ return {
95
+ text: item.text || item.label || item.name || '',
96
+ description: item.description || item.subText || item.desc || '',
97
+ icon: item.icon || '',
98
+ img: item.img || item.image || '',
99
+ color: item.color || '',
100
+ danger: !!item.danger,
101
+ disabled: !!item.disabled,
102
+ badge: item.badge || 0,
103
+ dot: !!item.dot
104
+ }
105
+ }
106
+ return { text: String(item), danger: false, disabled: false }
107
+ })
108
+ })
109
+
110
+ function getItemColor(item) {
111
+ if (item.disabled) return '#CBD5E1'
112
+ if (item.danger) return '#F43F5E'
113
+ if (item.color) return item.color
114
+ return '#0F172A'
115
+ }
116
+
117
+ function handleClick(index, item) {
118
+ if (item.disabled) return
119
+ emit('click', { index, item })
120
+ emit('select', { index, item })
121
+ close()
122
+ }
123
+
124
+ function handleCancel() {
125
+ emit('cancel')
126
+ close()
127
+ }
128
+
129
+ function onMaskTap() {
130
+ if (props.maskClosable) {
131
+ handleCancel()
132
+ }
133
+ }
134
+
135
+ function close() {
136
+ panelShow.value = false
137
+ emit('update:show', false)
138
+ setTimeout(() => {
139
+ visible.value = false
140
+ emit('close')
141
+ }, 250)
142
+ }
143
+
144
+ watch(() => props.show, (val) => {
145
+ if (val) {
146
+ visible.value = true
147
+ setTimeout(() => {
148
+ panelShow.value = true
149
+ }, 30)
150
+ } else {
151
+ panelShow.value = false
152
+ setTimeout(() => {
153
+ visible.value = false
154
+ }, 250)
155
+ }
156
+ }, { immediate: true })
157
+ </script>
158
+
159
+ <style lang="scss" scoped>
160
+ @import "@/uni.scss";
161
+
162
+ .mu-action-sheet {
163
+ position: fixed;
164
+ left: 0;
165
+ right: 0;
166
+ top: 0;
167
+ bottom: 0;
168
+ z-index: $mu-z-popup;
169
+
170
+ &__mask {
171
+ position: absolute;
172
+ inset: 0;
173
+ background: rgba(15, 23, 42, 0.45);
174
+ backdrop-filter: blur(8px);
175
+ opacity: 0;
176
+ transition: opacity 0.25s ease;
177
+ }
178
+
179
+ &--open &__mask {
180
+ opacity: 1;
181
+ }
182
+
183
+ &__body {
184
+ position: absolute;
185
+ left: 0;
186
+ right: 0;
187
+ bottom: 0;
188
+ background: #F8FAFC;
189
+ border-radius: 36rpx 36rpx 0 0;
190
+ overflow: hidden;
191
+ transform: translateY(100%);
192
+ transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
193
+ padding-bottom: env(safe-area-inset-bottom, 0);
194
+ box-shadow: 0 -12rpx 40rpx rgba(15, 23, 42, 0.12);
195
+
196
+ &--show {
197
+ transform: translateY(0);
198
+ }
199
+ }
200
+
201
+ &__header {
202
+ padding: 32rpx 36rpx 24rpx;
203
+ text-align: center;
204
+ background: #ffffff;
205
+ border-bottom: 1rpx solid rgba(226, 232, 240, 0.8);
206
+ }
207
+
208
+ &__title {
209
+ font-size: 28rpx;
210
+ font-weight: 800;
211
+ color: $mu-text-primary;
212
+ display: block;
213
+ letter-spacing: -0.5rpx;
214
+ }
215
+
216
+ &__desc {
217
+ font-size: 22rpx;
218
+ color: #94A3B8;
219
+ display: block;
220
+ margin-top: 8rpx;
221
+ }
222
+
223
+ &__list {
224
+ background: #ffffff;
225
+ }
226
+
227
+ &__item {
228
+ display: flex;
229
+ align-items: center;
230
+ padding: 32rpx 36rpx;
231
+ border-bottom: 1rpx solid rgba(226, 232, 240, 0.7);
232
+ cursor: pointer;
233
+ transition: background 0.15s ease;
234
+
235
+ &:last-child {
236
+ border-bottom: none;
237
+ }
238
+
239
+ &:active {
240
+ background: rgba(99, 102, 241, 0.04);
241
+ }
242
+
243
+ &--disabled {
244
+ opacity: 0.45;
245
+ cursor: not-allowed;
246
+ }
247
+ }
248
+
249
+ &__icon-wrap {
250
+ margin-right: 20rpx;
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: center;
254
+ }
255
+
256
+ &__img {
257
+ width: 40rpx;
258
+ height: 40rpx;
259
+ }
260
+
261
+ &__info {
262
+ flex: 1;
263
+ min-width: 0;
264
+ display: flex;
265
+ flex-direction: column;
266
+ }
267
+
268
+ &__text {
269
+ font-size: 28rpx;
270
+ font-weight: 700;
271
+ line-height: 1.3;
272
+ }
273
+
274
+ &__subtext {
275
+ font-size: 22rpx;
276
+ color: #94A3B8;
277
+ margin-top: 4rpx;
278
+ }
279
+
280
+ &__cancel-gap {
281
+ height: 14rpx;
282
+ background: #F1F5F9;
283
+ }
284
+
285
+ &__cancel {
286
+ display: flex;
287
+ align-items: center;
288
+ justify-content: center;
289
+ height: 108rpx;
290
+ background: #ffffff;
291
+ cursor: pointer;
292
+ transition: background 0.15s ease;
293
+
294
+ &:active {
295
+ background: rgba(99, 102, 241, 0.04);
296
+ }
297
+
298
+ &-text {
299
+ font-size: 28rpx;
300
+ font-weight: 800;
301
+ color: #64748B;
302
+ }
303
+ }
304
+ }
305
+
306
+ .mu-dark, page.mu-dark {
307
+
308
+ .mu-action-sheet {
309
+ &__mask { background: rgba(0, 0, 0, 0.65); }
310
+ &__body { background: #0F172A; }
311
+ &__header, &__list, &__cancel { background: $mu-dark-bg-base; }
312
+ &__header { border-color: rgba(255, 255, 255, 0.08); }
313
+ &__title { color: $mu-dark-text-primary; }
314
+ &__desc, &__subtext { color: $mu-dark-text-secondary; }
315
+ &__item { border-color: rgba(255, 255, 255, 0.06); }
316
+ &__cancel-gap { background: #0F172A; }
317
+ &__cancel-text { color: $mu-dark-text-secondary; }
318
+ }
319
+ }
320
+
321
+ @media (prefers-color-scheme: dark) {
322
+
323
+ .mu-action-sheet {
324
+ &__mask { background: rgba(0, 0, 0, 0.65); }
325
+ &__body { background: #0F172A; }
326
+ &__header, &__list, &__cancel { background: $mu-dark-bg-base; }
327
+ &__header { border-color: rgba(255, 255, 255, 0.08); }
328
+ &__title { color: $mu-dark-text-primary; }
329
+ &__desc, &__subtext { color: $mu-dark-text-secondary; }
330
+ &__item { border-color: rgba(255, 255, 255, 0.06); }
331
+ &__cancel-gap { background: #0F172A; }
332
+ &__cancel-text { color: $mu-dark-text-secondary; }
333
+ }
334
+ }
335
+ </style>