@ruixinkeji/prism-ui 1.0.6 → 1.0.8

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.
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-ai-assist" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-ai-assist" :class="{ 'dark-mode': isDarkMode }">
3
3
  <view class="ai-btn" :class="{ 'loading': isLoading }" @click="handleClick">
4
4
  <text class="fa" :class="isLoading ? 'fa-spinner fa-spin' : 'fa-wand-magic-sparkles'"></text>
5
5
  <text class="ai-text">{{ isLoading ? loadingText : text }}</text>
@@ -9,7 +9,7 @@
9
9
 
10
10
  <script setup>
11
11
  import { ref } from 'vue';
12
- import { useAppStore } from '@/store/app';
12
+ import { useTheme } from '../../composables/useTheme';
13
13
 
14
14
  const props = defineProps({
15
15
  text: {
@@ -24,7 +24,7 @@ const props = defineProps({
24
24
 
25
25
  const emit = defineEmits(['click']);
26
26
 
27
- const appStore = useAppStore();
27
+ const { isDarkMode } = useTheme();
28
28
  const isLoading = ref(false);
29
29
 
30
30
  function handleClick() {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-address-input" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-address-input" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 模式1: textarea模式(默认) -->
4
4
  <view class="address-wrapper" v-if="mode === 'textarea'">
5
5
  <textarea
@@ -101,7 +101,7 @@
101
101
 
102
102
  <script setup>
103
103
  import { ref, computed, watch } from 'vue';
104
- import { useAppStore } from '@/store/app';
104
+ import { useTheme } from '../../composables/useTheme';
105
105
 
106
106
  const props = defineProps({
107
107
  modelValue: {
@@ -153,7 +153,7 @@ const props = defineProps({
153
153
 
154
154
  const emit = defineEmits(['update:modelValue', 'update:latitude', 'update:longitude', 'location-change']);
155
155
 
156
- const appStore = useAppStore();
156
+ const { isDarkMode } = useTheme();
157
157
  const addressValue = ref(props.modelValue);
158
158
  const showListPopup = ref(false);
159
159
  const showSelectPopup = ref(false);
@@ -163,7 +163,7 @@ const staticMapUrl = computed(() => {
163
163
  if (!props.latitude || !props.longitude || !props.mapKey) return '';
164
164
  const lat = props.latitude;
165
165
  const lng = props.longitude;
166
- const style = appStore.isDarkMode ? '&style=4' : '';
166
+ const style = isDarkMode.value ? '&style=4' : '';
167
167
  return `https://apis.map.qq.com/ws/staticmap/v2/?center=${lat},${lng}&zoom=16&size=600*300&maptype=roadmap&markers=size:large|color:red|${lat},${lng}&key=${props.mapKey}${style}`;
168
168
  });
169
169
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-city-cascade-select" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-city-cascade-select" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 触发器 -->
4
4
  <view class="select-trigger" @click="openSelect">
5
5
  <slot>
@@ -127,7 +127,7 @@
127
127
 
128
128
  <script setup>
129
129
  import { ref, computed, watch } from 'vue';
130
- import { useAppStore } from '@/store/app';
130
+ import { useTheme } from '../../composables/useTheme';
131
131
 
132
132
  const props = defineProps({
133
133
  modelValue: {
@@ -188,7 +188,7 @@ const props = defineProps({
188
188
 
189
189
  const emit = defineEmits(['update:modelValue', 'change', 'confirm', 'cancel']);
190
190
 
191
- const appStore = useAppStore();
191
+ const { isDarkMode } = useTheme();
192
192
  const showSelect = ref(false);
193
193
  const tempSelected = ref([]);
194
194
  const currentLevel = ref(0);
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-city-picker" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-city-picker" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 触发器 -->
4
4
  <view class="picker-trigger" @click="openPicker">
5
5
  <slot>
@@ -157,8 +157,8 @@
157
157
 
158
158
  <script setup>
159
159
  import { ref, computed, watch, onMounted } from 'vue';
160
- import { useAppStore } from '@/store/app';
161
- import { rpx2px } from '@/utils/system';
160
+ import { useTheme } from '../../composables/useTheme';
161
+ import { rpx2px } from '../../utils/system';
162
162
 
163
163
  const props = defineProps({
164
164
  modelValue: {
@@ -227,7 +227,7 @@ const props = defineProps({
227
227
 
228
228
  const emit = defineEmits(['update:modelValue', 'change', 'confirm', 'cancel']);
229
229
 
230
- const appStore = useAppStore();
230
+ const { isDarkMode } = useTheme();
231
231
  const showPicker = ref(false);
232
232
  const pickerIndex = ref([0, 0, 0]);
233
233
  const tempSelected = ref([]);
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-city-select" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-city-select" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 触发器 -->
4
4
  <view class="picker-trigger" @click="openPicker">
5
5
  <slot>
@@ -103,8 +103,8 @@
103
103
 
104
104
  <script setup>
105
105
  import { ref, computed } from 'vue';
106
- import { useAppStore } from '@/store/app';
107
- import PrismGroupSticky from '@/components/PrismGroupSticky/PrismGroupSticky.vue';
106
+ import { useTheme } from '../../composables/useTheme';
107
+ import PrismGroupSticky from '../PrismGroupSticky/PrismGroupSticky.vue';
108
108
 
109
109
  const props = defineProps({
110
110
  modelValue: {
@@ -143,7 +143,7 @@ const props = defineProps({
143
143
  });
144
144
 
145
145
  const emit = defineEmits(['update:modelValue', 'change', 'confirm']);
146
- const appStore = useAppStore();
146
+ const { isDarkMode } = useTheme();
147
147
 
148
148
  const showPicker = ref(false);
149
149
  const searchKeyword = ref('');
@@ -50,7 +50,7 @@
50
50
  * <PrismCode code="const a = 1;" language="JavaScript" :copyable="true" />
51
51
  */
52
52
  import { ref, computed } from 'vue';
53
- import { useAppStore } from '@/store/app';
53
+ import { useTheme } from '../../composables/useTheme';
54
54
 
55
55
  const props = defineProps({
56
56
  // 要展示的代码内容
@@ -83,7 +83,7 @@ const props = defineProps({
83
83
  highlight: { type: Boolean, default: true }
84
84
  });
85
85
 
86
- const appStore = useAppStore();
86
+ const { isDarkMode } = useTheme();
87
87
  const copied = ref(false);
88
88
  const isExpanded = ref(props.defaultExpanded);
89
89
  const foldedRanges = ref(new Set());
@@ -91,7 +91,7 @@ const foldedRanges = ref(new Set());
91
91
  // 计算实际主题
92
92
  const actualTheme = computed(() => {
93
93
  if (props.theme === 'auto') {
94
- return appStore.isDarkMode ? 'dark' : 'light';
94
+ return isDarkMode.value ? 'dark' : 'light';
95
95
  }
96
96
  return props.theme;
97
97
  });
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-code-input" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-code-input" :class="{ 'dark-mode': isDarkMode }">
3
3
  <view class="code-boxes">
4
4
  <view
5
5
  v-for="(item, index) in codeLength"
@@ -28,7 +28,7 @@
28
28
 
29
29
  <script setup>
30
30
  import { ref, computed, watch } from 'vue';
31
- import { useAppStore } from '@/store/app';
31
+ import { useTheme } from '../../composables/useTheme';
32
32
 
33
33
  const props = defineProps({
34
34
  length: {
@@ -43,7 +43,7 @@ const props = defineProps({
43
43
 
44
44
  const emit = defineEmits(['update:modelValue', 'complete']);
45
45
 
46
- const appStore = useAppStore();
46
+ const { isDarkMode } = useTheme();
47
47
  const codeValue = ref(props.modelValue);
48
48
  const isFocused = ref(false);
49
49
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-datetime-picker" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-datetime-picker" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 触发器 -->
4
4
  <view class="picker-trigger" @click="openPicker">
5
5
  <slot>
@@ -187,9 +187,9 @@
187
187
 
188
188
  <script setup>
189
189
  import { ref, computed, watch } from 'vue';
190
- import { useAppStore } from '@/store/app';
191
- import { rpx2px } from '@/utils/system';
192
- import { getLunarDayStr } from '@/utils/lunar';
190
+ import { useTheme } from '../../composables/useTheme';
191
+ import { rpx2px } from '../../utils/system';
192
+ import { getLunarDayStr } from '../../utils/lunar';
193
193
 
194
194
  const props = defineProps({
195
195
  modelValue: {
@@ -253,7 +253,7 @@ const props = defineProps({
253
253
 
254
254
  const emit = defineEmits(['update:modelValue', 'change', 'confirm', 'cancel']);
255
255
 
256
- const appStore = useAppStore();
256
+ const { isDarkMode } = useTheme();
257
257
  const showPicker = ref(false);
258
258
  const pickerIndex = ref([0, 0, 0, 0, 0, 0]);
259
259
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-id-card-input" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-id-card-input" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 身份证号输入框 -->
4
4
  <view class="id-card-input-wrapper">
5
5
  <input
@@ -35,7 +35,7 @@
35
35
 
36
36
  <script setup>
37
37
  import { ref, computed, watch } from 'vue';
38
- import { useAppStore } from '@/store/app';
38
+ import { useTheme } from '../../composables/useTheme';
39
39
 
40
40
  const props = defineProps({
41
41
  modelValue: {
@@ -54,7 +54,7 @@ const props = defineProps({
54
54
 
55
55
  const emit = defineEmits(['update:modelValue', 'complete', 'valid']);
56
56
 
57
- const appStore = useAppStore();
57
+ const { isDarkMode } = useTheme();
58
58
  const idValue = ref(props.modelValue);
59
59
  const lastDisplayLength = ref(0);
60
60
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-image-picker" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-image-picker" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 主图片区域 -->
4
4
  <view class="picker-main">
5
5
  <!-- 已选图片预览 -->
@@ -66,7 +66,7 @@
66
66
 
67
67
  <script setup>
68
68
  import { ref } from 'vue';
69
- import { useAppStore } from '@/store/app';
69
+ import { useTheme } from '../../composables/useTheme';
70
70
 
71
71
  const props = defineProps({
72
72
  modelValue: {
@@ -128,7 +128,7 @@ const props = defineProps({
128
128
 
129
129
  const emit = defineEmits(['update:modelValue', 'change', 'upload', 'remove']);
130
130
 
131
- const appStore = useAppStore();
131
+ const { isDarkMode } = useTheme();
132
132
  const showSourcePopup = ref(false);
133
133
 
134
134
  // 处理上传
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-license-plate-input" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-license-plate-input" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 车牌号输入框 -->
4
4
  <view class="plate-boxes">
5
5
  <!-- 第1位 -->
@@ -236,7 +236,7 @@
236
236
 
237
237
  <script setup>
238
238
  import { ref, computed, watch } from 'vue';
239
- import { useAppStore } from '@/store/app';
239
+ import { useTheme } from '../../composables/useTheme';
240
240
 
241
241
  const props = defineProps({
242
242
  modelValue: {
@@ -247,7 +247,7 @@ const props = defineProps({
247
247
 
248
248
  const emit = defineEmits(['update:modelValue', 'complete']);
249
249
 
250
- const appStore = useAppStore();
250
+ const { isDarkMode } = useTheme();
251
251
 
252
252
  const isNewEnergy = ref(false);
253
253
  const showKeyboard = ref(false);
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-nav-bar" :class="{ 'dark-mode': appStore.isDarkMode, 'glass-effect': glass }" :style="{ paddingTop: appStore.statusBarHeight + 'px' }">
2
+ <view class="prism-nav-bar" :class="{ 'dark-mode': isDarkMode, 'glass-effect': glass }" :style="{ paddingTop: statusBarHeight + 'px' }">
3
3
  <view class="nav-bar-content">
4
4
  <view class="nav-bar-left">
5
5
  <!-- 返回按钮 -->
@@ -23,7 +23,7 @@
23
23
 
24
24
  <script setup>
25
25
  import { computed } from 'vue';
26
- import { useAppStore } from '@/store/app';
26
+ import { useTheme } from '../../composables/useTheme';
27
27
 
28
28
  const props = defineProps({
29
29
  title: {
@@ -48,7 +48,7 @@ const props = defineProps({
48
48
  }
49
49
  });
50
50
 
51
- const appStore = useAppStore();
51
+ const { isDarkMode, themeMode, statusBarHeight, toggleTheme } = useTheme();
52
52
 
53
53
  // 主题图标
54
54
  const themeIcon = computed(() => {
@@ -57,11 +57,11 @@ const themeIcon = computed(() => {
57
57
  'dark': 'fa-moon',
58
58
  'system': 'fa-circle-half-stroke'
59
59
  };
60
- return icons[appStore.themeMode] || 'fa-moon';
60
+ return icons[themeMode.value] || 'fa-moon';
61
61
  });
62
62
 
63
63
  function handleThemeClick() {
64
- appStore.toggleTheme();
64
+ toggleTheme();
65
65
  }
66
66
 
67
67
  function handleBack() {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-secure-input" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-secure-input" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 密码输入框 -->
4
4
  <view class="password-boxes" @click="showKeyboardModal">
5
5
  <view
@@ -57,7 +57,7 @@
57
57
 
58
58
  <script setup>
59
59
  import { ref, computed, watch, onMounted } from 'vue';
60
- import { useAppStore } from '@/store/app';
60
+ import { useTheme } from '../../composables/useTheme';
61
61
 
62
62
  const props = defineProps({
63
63
  modelValue: {
@@ -80,7 +80,7 @@ const props = defineProps({
80
80
 
81
81
  const emit = defineEmits(['update:modelValue', 'complete']);
82
82
 
83
- const appStore = useAppStore();
83
+ const { isDarkMode } = useTheme();
84
84
  const passwordValue = ref(props.modelValue);
85
85
  const isFocused = ref(false);
86
86
  const showKeyboard = ref(false);
@@ -2,7 +2,7 @@
2
2
  <view
3
3
  class="prism-switch-component"
4
4
  :class="{
5
- 'dark-mode': appStore.isDarkMode,
5
+ 'dark-mode': isDarkMode,
6
6
  'active': modelValue,
7
7
  'square': square,
8
8
  'disabled': disabled,
@@ -20,7 +20,7 @@
20
20
 
21
21
  <script setup>
22
22
  import { computed } from 'vue';
23
- import { useAppStore } from '@/store/app';
23
+ import { useTheme } from '../../composables/useTheme';
24
24
 
25
25
  const props = defineProps({
26
26
  modelValue: {
@@ -56,7 +56,7 @@ const props = defineProps({
56
56
 
57
57
  const emit = defineEmits(['update:modelValue', 'change']);
58
58
 
59
- const appStore = useAppStore();
59
+ const { isDarkMode } = useTheme();
60
60
 
61
61
  function handleToggle() {
62
62
  if (props.disabled) return;
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-tabbar" :class="{ 'dark-mode': appStore.isDarkMode, 'glass-effect': glass, 'wechat-browser': isWechatBrowser }">
2
+ <view class="prism-tabbar" :class="{ 'dark-mode': isDarkMode, 'glass-effect': glass, 'wechat-browser': isWechatBrowser }">
3
3
  <view
4
4
  class="prism-tabbar-item"
5
5
  v-for="(item, index) in tabList"
@@ -17,7 +17,7 @@
17
17
 
18
18
  <script setup>
19
19
  import { computed, ref, onMounted } from 'vue';
20
- import { useAppStore } from '@/store/app';
20
+ import { useTheme } from '../../composables/useTheme';
21
21
 
22
22
  const props = defineProps({
23
23
  current: {
@@ -39,7 +39,7 @@ const props = defineProps({
39
39
  }
40
40
  });
41
41
 
42
- const appStore = useAppStore();
42
+ const { isDarkMode } = useTheme();
43
43
  const currentPath = computed(() => props.current);
44
44
  const tabList = computed(() => props.tabs);
45
45
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <view class="prism-voice-input" :class="{ 'dark-mode': appStore.isDarkMode }">
2
+ <view class="prism-voice-input" :class="{ 'dark-mode': isDarkMode }">
3
3
  <!-- 模式1: 按钮模式(默认) -->
4
4
  <view
5
5
  v-if="mode === 'button'"
@@ -67,7 +67,7 @@
67
67
 
68
68
  <script setup>
69
69
  import { ref, watch, onUnmounted } from 'vue';
70
- import { useAppStore } from '@/store/app';
70
+ import { useTheme } from '../../composables/useTheme';
71
71
 
72
72
  const props = defineProps({
73
73
  // 模式:button(按钮)或 modal(弹窗)
@@ -95,7 +95,7 @@ const props = defineProps({
95
95
 
96
96
  const emit = defineEmits(['update:visible', 'start', 'end', 'cancel', 'result', 'confirm']);
97
97
 
98
- const appStore = useAppStore();
98
+ const { isDarkMode } = useTheme();
99
99
  const isRecording = ref(false);
100
100
  const cancelHint = ref(false);
101
101
  const recordingTime = ref(0);
@@ -0,0 +1,111 @@
1
+ /**
2
+ * 主题检测 Composable
3
+ * 组件库内部使用,不依赖外部 store
4
+ */
5
+ import { ref, computed, onMounted, onUnmounted } from 'vue';
6
+
7
+ // 全局状态(单例模式,所有组件共享)
8
+ const isDarkMode = ref(false);
9
+ const themeMode = ref('system'); // 'light' | 'dark' | 'system'
10
+ const statusBarHeight = ref(0);
11
+ let initialized = false;
12
+
13
+ /**
14
+ * 初始化主题检测
15
+ */
16
+ function initTheme() {
17
+ if (initialized) return;
18
+ initialized = true;
19
+
20
+ // 获取状态栏高度
21
+ try {
22
+ const systemInfo = uni.getSystemInfoSync();
23
+ statusBarHeight.value = systemInfo.statusBarHeight || 0;
24
+ } catch (e) {
25
+ statusBarHeight.value = 0;
26
+ }
27
+
28
+ // 检测系统深色模式
29
+ updateSystemTheme();
30
+
31
+ // 监听系统主题变化
32
+ // #ifdef H5
33
+ if (typeof window !== 'undefined' && window.matchMedia) {
34
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
35
+ mediaQuery.addEventListener('change', updateSystemTheme);
36
+ }
37
+ // #endif
38
+
39
+ // #ifndef H5
40
+ uni.onThemeChange && uni.onThemeChange((res) => {
41
+ if (themeMode.value === 'system') {
42
+ isDarkMode.value = res.theme === 'dark';
43
+ }
44
+ });
45
+ // #endif
46
+ }
47
+
48
+ /**
49
+ * 更新系统主题
50
+ */
51
+ function updateSystemTheme() {
52
+ if (themeMode.value !== 'system') return;
53
+
54
+ // #ifdef H5
55
+ if (typeof window !== 'undefined' && window.matchMedia) {
56
+ isDarkMode.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
57
+ }
58
+ // #endif
59
+
60
+ // #ifndef H5
61
+ try {
62
+ const systemInfo = uni.getSystemInfoSync();
63
+ isDarkMode.value = systemInfo.theme === 'dark';
64
+ } catch (e) {
65
+ isDarkMode.value = false;
66
+ }
67
+ // #endif
68
+ }
69
+
70
+ /**
71
+ * 使用主题
72
+ * @returns {{ isDarkMode: Ref<boolean>, themeMode: Ref<string>, setThemeMode: Function }}
73
+ */
74
+ export function useTheme() {
75
+ onMounted(() => {
76
+ initTheme();
77
+ });
78
+
79
+ /**
80
+ * 设置主题模式
81
+ * @param {'light' | 'dark' | 'system'} mode
82
+ */
83
+ function setThemeMode(mode) {
84
+ themeMode.value = mode;
85
+ if (mode === 'system') {
86
+ updateSystemTheme();
87
+ } else {
88
+ isDarkMode.value = mode === 'dark';
89
+ }
90
+ }
91
+
92
+ /**
93
+ * 切换主题模式
94
+ */
95
+ function toggleTheme() {
96
+ const modes = ['light', 'dark', 'system'];
97
+ const currentIndex = modes.indexOf(themeMode.value);
98
+ const nextIndex = (currentIndex + 1) % modes.length;
99
+ setThemeMode(modes[nextIndex]);
100
+ }
101
+
102
+ return {
103
+ isDarkMode,
104
+ themeMode,
105
+ statusBarHeight,
106
+ setThemeMode,
107
+ toggleTheme
108
+ };
109
+ }
110
+
111
+ export default useTheme;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruixinkeji/prism-ui",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Prism UI - 现代化玻璃态设计 uni-app 组件库",
5
5
  "main": "index.js",
6
6
  "module": "index.esm.js",
@@ -9,8 +9,10 @@
9
9
  "styles",
10
10
  "theme",
11
11
  "components",
12
+ "composables",
12
13
  "fonts",
13
14
  "store",
15
+ "utils",
14
16
  "index.js",
15
17
  "index.esm.js",
16
18
  "index.d.ts",
package/utils/lunar.js ADDED
@@ -0,0 +1,188 @@
1
+ /**
2
+ * 农历工具
3
+ */
4
+
5
+ // 农历数据(1900-2100年)
6
+ const lunarInfo = [
7
+ 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
8
+ 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
9
+ 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
10
+ 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
11
+ 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
12
+ 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,
13
+ 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
14
+ 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,
15
+ 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
16
+ 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0,
17
+ 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
18
+ 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
19
+ 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
20
+ 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
21
+ 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
22
+ 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,
23
+ 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
24
+ 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
25
+ 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
26
+ 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
27
+ 0x0d520
28
+ ];
29
+
30
+ // 农历月份名称
31
+ const lunarMonths = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'];
32
+
33
+ // 农历日期名称
34
+ const lunarDays = [
35
+ '初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
36
+ '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
37
+ '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十'
38
+ ];
39
+
40
+ // 天干
41
+ const tianGan = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
42
+
43
+ // 地支
44
+ const diZhi = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
45
+
46
+ // 生肖
47
+ const animals = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
48
+
49
+ /**
50
+ * 返回农历年的总天数
51
+ */
52
+ function getLunarYearDays(year) {
53
+ let sum = 348;
54
+ for (let i = 0x8000; i > 0x8; i >>= 1) {
55
+ sum += (lunarInfo[year - 1900] & i) ? 1 : 0;
56
+ }
57
+ return sum + getLeapDays(year);
58
+ }
59
+
60
+ /**
61
+ * 返回农历年闰月的天数
62
+ */
63
+ function getLeapDays(year) {
64
+ if (getLeapMonth(year)) {
65
+ return (lunarInfo[year - 1900] & 0x10000) ? 30 : 29;
66
+ }
67
+ return 0;
68
+ }
69
+
70
+ /**
71
+ * 返回农历年闰月是哪个月(1-12),没有闰月返回0
72
+ */
73
+ function getLeapMonth(year) {
74
+ return lunarInfo[year - 1900] & 0xf;
75
+ }
76
+
77
+ /**
78
+ * 返回农历年某月的天数
79
+ */
80
+ function getLunarMonthDays(year, month) {
81
+ return (lunarInfo[year - 1900] & (0x10000 >> month)) ? 30 : 29;
82
+ }
83
+
84
+ /**
85
+ * 公历转农历
86
+ * @param {number} year - 公历年
87
+ * @param {number} month - 公历月(1-12)
88
+ * @param {number} day - 公历日
89
+ * @returns {Object} 农历信息
90
+ */
91
+ export function solarToLunar(year, month, day) {
92
+ // 参数检查
93
+ if (year < 1900 || year > 2100) {
94
+ return null;
95
+ }
96
+
97
+ let offset = 0;
98
+ const baseDate = new Date(1900, 0, 31);
99
+ const objDate = new Date(year, month - 1, day);
100
+
101
+ offset = Math.floor((objDate - baseDate) / 86400000);
102
+
103
+ let lunarYear, lunarMonth, lunarDay;
104
+ let isLeap = false;
105
+
106
+ // 计算农历年
107
+ for (lunarYear = 1900; lunarYear < 2101 && offset > 0; lunarYear++) {
108
+ const yearDays = getLunarYearDays(lunarYear);
109
+ offset -= yearDays;
110
+ }
111
+ if (offset < 0) {
112
+ offset += getLunarYearDays(--lunarYear);
113
+ }
114
+
115
+ // 计算农历月
116
+ const leapMonth = getLeapMonth(lunarYear);
117
+ let isLeapMonth = false;
118
+
119
+ for (lunarMonth = 1; lunarMonth < 13 && offset > 0; lunarMonth++) {
120
+ // 闰月
121
+ if (leapMonth > 0 && lunarMonth === (leapMonth + 1) && !isLeapMonth) {
122
+ --lunarMonth;
123
+ isLeapMonth = true;
124
+ const leapDays = getLeapDays(lunarYear);
125
+ offset -= leapDays;
126
+ } else {
127
+ offset -= getLunarMonthDays(lunarYear, lunarMonth);
128
+ }
129
+
130
+ if (isLeapMonth && lunarMonth === (leapMonth + 1)) {
131
+ isLeapMonth = false;
132
+ }
133
+ }
134
+
135
+ if (offset === 0 && leapMonth > 0 && lunarMonth === leapMonth + 1) {
136
+ if (isLeapMonth) {
137
+ isLeapMonth = false;
138
+ } else {
139
+ isLeapMonth = true;
140
+ --lunarMonth;
141
+ }
142
+ }
143
+
144
+ if (offset < 0) {
145
+ offset += getLunarMonthDays(lunarYear, --lunarMonth);
146
+ }
147
+
148
+ lunarDay = offset + 1;
149
+ isLeap = isLeapMonth;
150
+
151
+ return {
152
+ year: lunarYear,
153
+ month: lunarMonth,
154
+ day: lunarDay,
155
+ isLeap,
156
+ monthStr: (isLeap ? '闰' : '') + lunarMonths[lunarMonth - 1] + '月',
157
+ dayStr: lunarDays[lunarDay - 1],
158
+ yearGanZhi: getGanZhiYear(lunarYear),
159
+ animal: animals[(lunarYear - 4) % 12]
160
+ };
161
+ }
162
+
163
+ /**
164
+ * 获取农历年的干支
165
+ */
166
+ function getGanZhiYear(year) {
167
+ const ganIndex = (year - 4) % 10;
168
+ const zhiIndex = (year - 4) % 12;
169
+ return tianGan[ganIndex] + diZhi[zhiIndex];
170
+ }
171
+
172
+ /**
173
+ * 获取农历日期的简短显示(用于日历)
174
+ * @param {number} year - 公历年
175
+ * @param {number} month - 公历月(1-12)
176
+ * @param {number} day - 公历日
177
+ * @returns {string} 农历日期简短显示
178
+ */
179
+ export function getLunarDayStr(year, month, day) {
180
+ const lunar = solarToLunar(year, month, day);
181
+ if (!lunar) return '';
182
+
183
+ // 初一显示月份,其他显示日期
184
+ if (lunar.day === 1) {
185
+ return lunar.monthStr;
186
+ }
187
+ return lunar.dayStr;
188
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * 系统工具方法
3
+ */
4
+
5
+ // 缓存系统信息,避免重复获取
6
+ let systemInfoCache = null;
7
+
8
+ /**
9
+ * 获取系统信息(带缓存)
10
+ * @returns {Object} 系统信息
11
+ */
12
+ export function getSystemInfo() {
13
+ if (!systemInfoCache) {
14
+ systemInfoCache = uni.getSystemInfoSync();
15
+ }
16
+ return systemInfoCache;
17
+ }
18
+
19
+ /**
20
+ * 获取屏幕宽度
21
+ * @returns {number} 屏幕宽度(px)
22
+ */
23
+ export function getScreenWidth() {
24
+ return getSystemInfo().windowWidth;
25
+ }
26
+
27
+ /**
28
+ * 获取屏幕高度
29
+ * @returns {number} 屏幕高度(px)
30
+ */
31
+ export function getScreenHeight() {
32
+ return getSystemInfo().windowHeight;
33
+ }
34
+
35
+ /**
36
+ * rpx 转 px
37
+ * @param {number} rpx - rpx 值
38
+ * @returns {number} px 值(取整)
39
+ */
40
+ export function rpx2px(rpx) {
41
+ const screenWidth = getScreenWidth();
42
+ return Math.round(rpx * screenWidth / 750);
43
+ }
44
+
45
+ /**
46
+ * px 转 rpx
47
+ * @param {number} px - px 值
48
+ * @returns {number} rpx 值(取整)
49
+ */
50
+ export function px2rpx(px) {
51
+ const screenWidth = getScreenWidth();
52
+ return Math.round(px * 750 / screenWidth);
53
+ }
54
+
55
+ /**
56
+ * 获取状态栏高度
57
+ * @returns {number} 状态栏高度(px)
58
+ */
59
+ export function getStatusBarHeight() {
60
+ return getSystemInfo().statusBarHeight || 0;
61
+ }
62
+
63
+ /**
64
+ * 获取导航栏高度(状态栏 + 标题栏)
65
+ * @returns {number} 导航栏高度(px)
66
+ */
67
+ export function getNavBarHeight() {
68
+ const statusBarHeight = getStatusBarHeight();
69
+ // 标题栏高度:iOS 44px,Android 48px
70
+ const titleBarHeight = getSystemInfo().platform === 'ios' ? 44 : 48;
71
+ return statusBarHeight + titleBarHeight;
72
+ }
73
+
74
+ /**
75
+ * 获取安全区域信息
76
+ * @returns {Object} 安全区域 { top, bottom, left, right }
77
+ */
78
+ export function getSafeArea() {
79
+ const info = getSystemInfo();
80
+ return info.safeArea || {
81
+ top: info.statusBarHeight || 0,
82
+ bottom: 0,
83
+ left: 0,
84
+ right: info.windowWidth
85
+ };
86
+ }
87
+
88
+ /**
89
+ * 获取底部安全区域高度
90
+ * @returns {number} 底部安全区域高度(px)
91
+ */
92
+ export function getSafeAreaBottom() {
93
+ const info = getSystemInfo();
94
+ const safeArea = info.safeArea;
95
+ if (safeArea) {
96
+ return info.screenHeight - safeArea.bottom;
97
+ }
98
+ return 0;
99
+ }
100
+
101
+ /**
102
+ * 判断是否为 iOS 系统
103
+ * @returns {boolean}
104
+ */
105
+ export function isIOS() {
106
+ return getSystemInfo().platform === 'ios';
107
+ }
108
+
109
+ /**
110
+ * 判断是否为 Android 系统
111
+ * @returns {boolean}
112
+ */
113
+ export function isAndroid() {
114
+ return getSystemInfo().platform === 'android';
115
+ }
116
+
117
+ /**
118
+ * 判断是否为 H5 环境
119
+ * @returns {boolean}
120
+ */
121
+ export function isH5() {
122
+ // #ifdef H5
123
+ return true;
124
+ // #endif
125
+ // #ifndef H5
126
+ return false;
127
+ // #endif
128
+ }
129
+
130
+ /**
131
+ * 判断是否为小程序环境
132
+ * @returns {boolean}
133
+ */
134
+ export function isMiniProgram() {
135
+ // #ifdef MP
136
+ return true;
137
+ // #endif
138
+ // #ifndef MP
139
+ return false;
140
+ // #endif
141
+ }
142
+
143
+ /**
144
+ * 判断是否为 App 环境
145
+ * @returns {boolean}
146
+ */
147
+ export function isApp() {
148
+ // #ifdef APP-PLUS
149
+ return true;
150
+ // #endif
151
+ // #ifndef APP-PLUS
152
+ return false;
153
+ // #endif
154
+ }